From 911cab85b02de34f4469324a75da8fb4ad34ece7 Mon Sep 17 00:00:00 2001 From: Satish Date: Fri, 11 Nov 2016 15:49:23 +0530 Subject: [PATCH 1/2] SMS Campaign feature implementation --- api-docs/apiLive.htm | 327 ++++++++- .../service/CommandWrapperBuilder.java | 47 ++ .../campaigns/constants/CampaignType.java | 76 ++ .../campaigns/helper/SmsConfigUtils.java | 76 ++ .../sms/api/SmsCampaignApiResource.java | 195 +++++ .../sms/constants/SmsCampaignConstants.java | 43 ++ .../constants/SmsCampaignEnumerations.java | 92 +++ .../sms/constants/SmsCampaignStatus.java | 72 ++ .../sms/constants/SmsCampaignTriggerType.java | 98 +++ .../sms/data/CampaignPreviewData.java | 39 + .../sms/data/CampaignTriggerWithSubTypes.java | 298 ++++++++ .../data/MessageGatewayConfigurationData.java | 81 +++ .../sms/data/SmsBusinessRulesData.java | 92 +++ .../campaigns/sms/data/SmsCampaignData.java | 206 ++++++ .../sms/data/SmsCampaignTimeLine.java | 44 ++ .../campaigns/sms/data/SmsProviderData.java | 72 ++ .../sms/data/TriggerTypeWithSubTypesData.java | 43 ++ .../campaigns/sms/domain/SmsCampaign.java | 561 ++++++++++++++ .../sms/domain/SmsCampaignRepository.java | 35 + .../domain/SmsCampaignStatusEnumerations.java | 55 ++ .../exception/ConnectionFailureException.java | 30 + ...atewayConnectionConfigurationNotFound.java | 30 + ...paignMustBeClosedToBeDeletedException.java | 28 + ...msCampaignMustBeClosedToEditException.java | 29 + .../sms/exception/SmsCampaignNotFound.java | 28 + .../ActivateSmsCampaignCommandHandler.java | 47 ++ .../CloseSmsCampaignCommandHandler.java | 43 ++ .../CreateSmsCampaignCommandHandler.java | 46 ++ .../DeleteSmsCampaignCommandHandler.java | 45 ++ .../ReactivateSmsCampaignCommandHandler.java | 45 ++ .../UpdateSmsCampaignCommandHandler.java | 46 ++ .../serialization/SmsCampaignValidator.java | 302 ++++++++ .../sms/service/SmsCampaignDomainService.java | 25 + .../service/SmsCampaignDomainServiceImpl.java | 537 ++++++++++++++ ...msCampaignDropdownReadPlatformService.java | 43 ++ ...mpaignDropdownReadPlatformServiceImpl.java | 125 ++++ .../SmsCampaignReadPlatformService.java | 36 + .../SmsCampaignReadPlatformServiceImpl.java | 260 +++++++ .../SmsCampaignWritePlatformService.java | 59 ++ ...msCampaignWritePlatformServiceJpaImpl.java | 687 ++++++++++++++++++ ...pertiesCommandFromApiJsonDeserializer.java | 6 + .../service/ExternalServicesConstants.java | 37 + ...ServicesPropertiesReadPlatformService.java | 3 + ...icesPropertiesReadPlatformServiceImpl.java | 39 + ...ternalServicesReadPlatformServiceImpl.java | 4 + .../core/serialization/FromJsonHelper.java | 4 + .../core/serialization/JsonParserHelper.java | 13 + .../core/service/PaginationHelper.java | 10 + .../core/service/SearchParameters.java | 16 + .../service/ReadReportingService.java | 5 + .../service/ReadReportingServiceImpl.java | 31 + .../infrastructure/jobs/service/JobName.java | 5 +- .../infrastructure/sms/SmsApiConstants.java | 5 +- .../sms/api/SmsApiResource.java | 34 + .../infrastructure/sms/data/SmsData.java | 54 +- .../data/SmsMessageApiQueueResourceData.java | 125 ++++ .../data/SmsMessageApiReportResourceData.java | 75 ++ .../sms/data/SmsMessageApiResponseData.java | 67 ++ .../data/SmsMessageDeliveryReportData.java | 113 +++ .../infrastructure/sms/domain/SmsMessage.java | 104 ++- .../sms/domain/SmsMessageAssembler.java | 18 +- .../sms/domain/SmsMessageRepository.java | 5 +- .../sms/domain/SmsMessageStatusType.java | 4 + .../SmsCountryCodeNotFoundException.java | 28 + .../SmsMessageScheduledJobService.java | 47 ++ .../SmsMessageScheduledJobServiceImpl.java | 295 ++++++++ .../sms/service/SmsReadPlatformService.java | 20 + .../service/SmsReadPlatformServiceImpl.java | 145 +++- .../portfolio/client/domain/Client.java | 8 +- .../client/domain/ClientTransaction.java | 4 + ...WritePlatformServiceJpaRepositoryImpl.java | 70 +- .../BusinessEventNotificationConstants.java | 31 +- .../loanaccount/api/LoansApiResource.java | 12 +- .../portfolio/loanaccount/domain/Loan.java | 6 +- .../loanaccount/domain/LoanTransaction.java | 8 + .../exception/InvalidLoanTypeException.java | 32 + ...WritePlatformServiceJpaRepositoryImpl.java | 2 +- .../service/LoanReadPlatformServiceImpl.java | 1 - .../paymentdetail/domain/PaymentDetail.java | 1 + .../SavingsAccountDomainServiceJpa.java | 28 +- .../domain/SavingsAccountTransaction.java | 8 +- ...WritePlatformServiceJpaRepositoryImpl.java | 75 +- ...WritePlatformServiceJpaRepositoryImpl.java | 20 +- ...WritePlatformServiceJpaRepositoryImpl.java | 37 +- .../resources/META-INF/spring/appContext.xml | 1 + .../migrations/core_db/V322__sms_campaign.sql | 277 +++++++ 86 files changed, 6882 insertions(+), 94 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/helper/SmsConfigUtils.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignPreviewData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/MessageGatewayConfigurationData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsBusinessRulesData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignTimeLine.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsProviderData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignStatusEnumerations.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/ConnectionFailureException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/GatewayConnectionConfigurationNotFound.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToBeDeletedException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToEditException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNotFound.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ActivateSmsCampaignCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CloseSmsCampaignCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CreateSmsCampaignCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/DeleteSmsCampaignCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ReactivateSmsCampaignCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/UpdateSmsCampaignCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V322__sms_campaign.sql diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index abd6867f4..c2a06be6b 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -2064,7 +2064,46 @@ Retrieve a Share product Update a Share product - + + SMS Campaigns + smscampaigns + Create a SMS Campaign + List SMS Campaigns + + + + tr> + + smscampaigns/{campaignId} + + Retrieve a SMS Campaign + Update a SMS Campaign + Delete a SMS Campaign + + + + smscampaigns/{campaignId}command=activate + + Activate a SMS Campaign + + + + + + smscampaigns/{campaignId}command=close + + Close a SMS Campaign + + + + + + smscampaigns/{campaignId}command=reactivate + + Reactivate a SMS Campaign + + + Currency currencies @@ -21038,7 +21077,291 @@ Content-Type: application/json -   +   +
+
+

Create a SMS Campaign

+ + + + + +
Mandatory Fields
campaignName, campaignType, triggerType, providerId, runReportId, message
+
+ + + + + +
Mandatory Fields for Cash based on selected report id
paramValue in json format
+
+
+ + POST https://Domain Name/api/v1/smscampaigns + POST smscampaigns +Content-Type: application/json +Request Body: +{ +"campaignName": "Savings Deposit Campaign", +"campaignType": "SMS", +"triggerType": 3, +"providerId": 2, +"runReportId": 180, +"paramValue": "{"officeId":1,"loanOfficerId":-1,"transactionId":1,"reportName":"Savings Deposit"}", +"message": "Hello {fullName} your account is credited with {depositAmount} on {transactionDate}", +} + + + +{ + "resourceId": 1 +} + + +
+
+ +  +
+
+

List SMS Campaigns

+ + + + + +
Mandatory Fields
+

Example Requests:

+
smscampaigns
+
+
+ + GET https://Domain Name/api/v1/smscampaigns + + +[{ + "id": 1, + "campaignName": "Savings Deposit Campaign" + "campaignType": "SMS", + "triggerType": 3, + "providerId": 2, + "runReportId": 180, + "reportName" : "Savings Deposit", + "paramValue": "{"officeId":1,"loanOfficerId":-1,"transactionId":1,"reportName":"Savings Deposit"}", + "message": "Hello {fullName} your account is credited with {depositAmount} on {transactionDate}", + "status" : { "id":"","code":"","value":""} + +}] + +
+
+ +  +
+
+

Retrieve a Share Product

+

Example Requests:

+
smscampaigns/1
+

+
smscampaigns/1?template=true
+
+
+ GET https://Domain Name/api/v1/smscampaigns/1 + +{ + "id": 1, + "campaignName": "Savings Deposit Campaign" + "campaignType": "SMS", + "triggerType": 3, + "providerId": 2, + "runReportId": 180, + "reportName" : "Savings Deposit", + "paramValue": "{"officeId":1,"loanOfficerId":-1,"transactionId":1,"reportName":"Savings Deposit"}", + "message": "Hello {fullName} your account is credited with {depositAmount} on {transactionDate}", + "status" : { "id":"","code":"","value":""} +} +In case of Scheduled Campaign +{ + "id": 1, + "campaignName": "Savings Deposit Campaign" + "campaignType": "SMS", + "triggerType": 3, + "providerId": 2, + "runReportId": 180, + "reportName" : "Savings Deposit", + "paramValue": "{"officeId":1,"loanOfficerId":-1,"transactionId":1,"reportName":"Savings Deposit"}", + "message": "Hello {fullName} your account is credited with {depositAmount} on {transactionDate}", + "status" : { "id":"","code":"","value":""}, + "nextTriggerDate": "22 Jan 2017", + "lastTriggerDate": "22 Jan 2016", + "recurrenceStartDate":"22 Jan 2016", + "recurrence": "YEARLY" +} + +
+
+ +  +
+
+

Update a Campaign

+
+
+ PUT https://Domain Name/api/v1/smscampaigns/{campaignId} + POST smscampaigns/1 +Content-Type: application/json +Request Body: +{ + "message": "Hello your savings account {savingsAccountNo} debited with {withdrawAmount}" + +} + + +{ + "resourceId": 1, + "changes": { + "message": "Hello your savings account {savingsAccountNo} debited with {withdrawAmount}" + } +} + +
+
+ +  +
+
+

Delete a SMS Campaign

+

+ Note: Only closed SMS Campaigns can be + deleted +

+
+
+ +DELETE https://DomainName/api/v1/smscampaigns/{campaignId} + + +DELETE smscampaigns/1 +Content-Type: application/json +No Request Body: + + +{ + "resourceId": 1, + "changes": {} +} + +
+
+ +  +
+
+

Activates SMS Campaign

+ +
+
+ POST https://Domain Name/api/v1/smscampaigns/{campaignId}?command=activate + POST smscampaigns/1?command=activate +Content-Type: application/json +Request Body: +{ +"activationDate":"01 May 2016", +"locale":"en", +"dateFormat":"dd MMMM yyyy" +} + + +{ + + "resourceId": 1, + "changes": { + "status": { + "id": 300, + "code": "smsCampaignStatus.active", + "value": "Activated", + }, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "activationDate": "01 May 2016" + } +} + +
+
+ +  +
+
+

Deactivates SMS Campaign

+ +
+
+ POST https://Domain Name/api/v1/smscampaigns/{campaignId}?command=close + POST smscampaigns/1?command=close +Content-Type: application/json +Request Body: +{ +"closureDate":"01 May 2016", +"locale":"en", +"dateFormat":"dd MMMM yyyy" +} + + +{ + "resourceId": 1, + "changes": { + "status": { + "id": 600, + "code": "smsCampaignStatus.closed", + "value": "Activated", + }, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "closureDate": "01 May 2016" + } +} + +
+
+ +  +
+
+

Reactivates SMS Campaign

+ +
+
+ POST https://Domain Name/api/v1/smscampaigns/{campaignId}?command=reactivate + POST smscampaigns/1?command=reactivate +Content-Type: application/json +Request Body: +{ +"activationDate":"01 May 2016", +"locale":"en", +"dateFormat":"dd MMMM yyyy" +} + + +{ + + "resourceId": 1, + "changes": { + "status": { + "id": 300, + "code": "smsCampaignStatus.active", + "value": "Activated", + }, + "locale": "en", + "dateFormat": "dd MMMM yyyy", + "activationDate": "01 May 2016" + } +} + +
+
+ + 

Create a Share Product

diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index fb497c04c..ed92a3879 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -2803,4 +2803,51 @@ public class CommandWrapperBuilder { this.href = "/reportmailingjobs/" + entityId; return this; } + + public CommandWrapperBuilder createSmsCampaign() { + this.actionName = "CREATE"; + this.entityName = "SMSCAMPAIGN"; + this.entityId = null; + this.href = "/smscampaign"; + return this; + } + + public CommandWrapperBuilder updateSmsCampaign(final Long resourceId) { + this.actionName = "UPDATE"; + this.entityName = "SMSCAMPAIGN"; + this.entityId = resourceId; + this.href = "/smscampaign/"+resourceId; + return this; + } + + public CommandWrapperBuilder activateSmsCampaign(final Long resourceId) { + this.actionName = "ACTIVATE"; + this.entityName = "SMSCAMPAIGN"; + this.entityId = resourceId; + this.href = "/smscampaign/"+resourceId + "?command=activate"; + return this; + } + + public CommandWrapperBuilder closeSmsCampaign(final Long resourceId) { + this.actionName = "CLOSE"; + this.entityName = "SMSCAMPAIGN"; + this.entityId = resourceId; + this.href = "/smscampaign/"+resourceId + "?command=close"; + return this; + } + public CommandWrapperBuilder reactivateSmsCampaign(final Long resourceId) { + this.actionName = "REACTIVATE"; + this.entityName = "SMSCAMPAIGN"; + this.entityId = resourceId; + this.href = "/smscampaign/"+resourceId + "?command=reactivate"; + return this; + } + + public CommandWrapperBuilder deleteSmsCampaign(final Long resourceId) { + this.actionName = "DELETE"; + this.entityName = "SMSCAMPAIGN"; + this.entityId = resourceId; + this.href = "/smscampaign/"+resourceId; + return this; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java new file mode 100644 index 000000000..b6a0aaf7c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/constants/CampaignType.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.constants; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +public enum CampaignType { + INVALID(0, "campaignType.invalid"), SMS(1, "campaignType.sms"); + + private Integer value; + private String code; + + private CampaignType(Integer value, String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return value; + } + + public String getCode() { + return code; + } + + public static CampaignType fromInt(final Integer typeValue) { + CampaignType type = null; + switch (typeValue) { + case 0: + type = INVALID; + break; + case 1: + type = SMS; + break; + } + return type; + } + + public static EnumOptionData campaignType(final Integer campaignTypeId) { + return campaignType(CampaignType.fromInt(campaignTypeId)); + } + + public static EnumOptionData campaignType(final CampaignType campaignType) { + EnumOptionData optionData = new EnumOptionData(CampaignType.INVALID.getValue().longValue(), CampaignType.INVALID.getCode(), + "Invalid"); + switch (campaignType) { + case INVALID: + optionData = new EnumOptionData(CampaignType.INVALID.getValue().longValue(), CampaignType.INVALID.getCode(), "Invalid"); + break; + case SMS: + optionData = new EnumOptionData(CampaignType.SMS.getValue().longValue(), CampaignType.SMS.getCode(), "SMS"); + break; + } + return optionData; + } + + public boolean isSms() { + return this.value.equals(CampaignType.SMS.getValue()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/helper/SmsConfigUtils.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/helper/SmsConfigUtils.java new file mode 100644 index 000000000..a7fc94264 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/helper/SmsConfigUtils.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.helper; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.core.UriBuilder; + +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants; +import org.apache.fineract.infrastructure.campaigns.sms.data.MessageGatewayConfigurationData; +import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +@Component +public class SmsConfigUtils { + + @Autowired + private ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService; + + //This method will return uri and HttpEntry objects with keys as uri and entity + public Map getMessageGateWayRequestURI(final String apiEndPoint, String apiQueueResourceDatas) { + Map httpRequestdetails = new HashMap<>(); + MessageGatewayConfigurationData messageGatewayConfigurationData = this.propertiesReadPlatformService.getSMSGateway(); + final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.add(SmsCampaignConstants.FINERACT_PLATFORM_TENANT_ID, tenant.getTenantIdentifier()); + headers.add(SmsCampaignConstants.FINERACT_TENANT_APP_KEY, messageGatewayConfigurationData.getTenantAppKey()); + StringBuilder pathBuilder = new StringBuilder(); + String endPoint = (messageGatewayConfigurationData.getEndPoint() == null || messageGatewayConfigurationData.getEndPoint().equals( + "/")) ? "" : messageGatewayConfigurationData.getEndPoint(); + pathBuilder = (messageGatewayConfigurationData.getEndPoint() == null || messageGatewayConfigurationData.getEndPoint().equals("/")) ? pathBuilder + .append("{apiEndPoint}") : pathBuilder.append("{endPoint}/{apiEndPoint}"); + // pathBuilder.append("{endPoint}/{apiEndPoint}") ; + UriBuilder builder = UriBuilder.fromPath(pathBuilder.toString()).host(messageGatewayConfigurationData.getHostName()).scheme("http") + .port(messageGatewayConfigurationData.getPortNumber()); + URI uri = (messageGatewayConfigurationData.getEndPoint() == null || messageGatewayConfigurationData.getEndPoint().equals("/")) ? builder + .build(apiEndPoint) : builder.build(endPoint, apiEndPoint); + HttpEntity entity = null; + if (apiQueueResourceDatas != null) { + entity = new HttpEntity<>(apiQueueResourceDatas, headers); + } else { + entity = new HttpEntity<>(headers); + } + + httpRequestdetails.put("uri", uri); + httpRequestdetails.put("entity", entity); + + return httpRequestdetails; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java new file mode 100644 index 000000000..5c7dd7b15 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/api/SmsCampaignApiResource.java @@ -0,0 +1,195 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.api; + +import java.util.Collection; +import java.util.HashSet; + +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.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.campaigns.constants.CampaignType; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants; +import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignPreviewData; +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsCampaignData; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignReadPlatformService; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.api.JsonQuery; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; + +@Path("/smscampaigns") +@Component +@Scope("singleton") +public class SmsCampaignApiResource { + + private final PlatformSecurityContext platformSecurityContext; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final SmsCampaignReadPlatformService smsCampaignReadPlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final FromJsonHelper fromJsonHelper; + private final DefaultToApiJsonSerializer previewCampaignMessageDefaultToApiJsonSerializer; + private final SmsCampaignWritePlatformService smsCampaignWritePlatformService; + + private final String resourceNameForPermissions = "SMS_CAMPAIGN"; + private final PlatformSecurityContext context; + + @Autowired + public SmsCampaignApiResource(final PlatformSecurityContext platformSecurityContext, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final SmsCampaignReadPlatformService smsCampaignReadPlatformService, final ApiRequestParameterHelper apiRequestParameterHelper, + final FromJsonHelper fromJsonHelper, + final DefaultToApiJsonSerializer previewCampaignMessageDefaultToApiJsonSerializer, + final PlatformSecurityContext context, final SmsCampaignWritePlatformService smsCampaignWritePlatformService) { + this.platformSecurityContext = platformSecurityContext; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.smsCampaignReadPlatformService = smsCampaignReadPlatformService; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.fromJsonHelper = fromJsonHelper; + this.previewCampaignMessageDefaultToApiJsonSerializer = previewCampaignMessageDefaultToApiJsonSerializer; + this.context = context; + this.smsCampaignWritePlatformService = smsCampaignWritePlatformService; + } + + @GET + @Path("template") + public String template(@Context final UriInfo uriInfo) { + this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME); + final SmsCampaignData smsCampaignData = this.smsCampaignReadPlatformService.retrieveTemplate(CampaignType.SMS.name()); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, smsCampaignData); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String createCampaign(final String apiRequestBodyAsJson) { + this.platformSecurityContext.authenticatedUser(); + final CommandWrapper commandRequest = new CommandWrapperBuilder().createSmsCampaign().withJson(apiRequestBodyAsJson).build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{resourceId}") + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveCampaign(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) { + this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME); + SmsCampaignData smsCampaignData = this.smsCampaignReadPlatformService.retrieveOne(resourceId); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, smsCampaignData); + + } + + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveAllCampaigns(@Context final UriInfo uriInfo) { + this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME); + Collection smsCampaignDataCollection = this.smsCampaignReadPlatformService.retrieveAll(); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, smsCampaignDataCollection); + } + + @PUT + @Path("{campaignId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String updateCampaign(@PathParam("campaignId") final Long campaignId, final String apiRequestBodyAsJson) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSmsCampaign(campaignId).withJson(apiRequestBodyAsJson) + .build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } + + @POST + @Path("{campaignId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String handleCommands(@PathParam("campaignId") final Long campaignId, @QueryParam("command") final String commandParam, + final String apiRequestBodyAsJson) { + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); + CommandProcessingResult result = null; + CommandWrapper commandRequest = null; + if (is(commandParam, "activate")) { + commandRequest = builder.activateSmsCampaign(campaignId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "close")) { + commandRequest = builder.closeSmsCampaign(campaignId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "reactivate")) { + commandRequest = builder.reactivateSmsCampaign(campaignId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + return this.toApiJsonSerializer.serialize(result); + } + + @POST + @Path("preview") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String preview(final String apiRequestBodyAsJson, @Context final UriInfo uriInfo) { + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + CampaignPreviewData campaignMessage = null; + final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); + final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); + campaignMessage = this.smsCampaignWritePlatformService.previewMessage(query); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet()); + + } + + @DELETE + @Path("{campaignId}") + public String delete(@PathParam("campaignId") final Long campaignId) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSmsCampaign(campaignId).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); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java new file mode 100644 index 000000000..104f4c73e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.constants; + +public interface SmsCampaignConstants { + + String SMS_CAMPAIGN_NAME = "sms"; + + String RESOURCE_NAME = "SMSCAMPAIGN"; + + String SMS_BRIDGE = "sms_bridge"; + + String SMS = "sms"; + + String FINERACT_PLATFORM_TENANT_ID = "Fineract-Platform-TenantId"; + + String FINERACT_TENANT_APP_KEY = "Fineract-Tenant-App-Key"; + + //Supported Triggered Campaigns + + String LOAN_REJECT = "Loan Rejected" ; + + String LOAN_APPROVED = "Loan Approved" ; + + String LOAN_REPAYMENT = "Loan Repayment" ; + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java new file mode 100644 index 000000000..16ae3cd24 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.constants; + +import java.time.Month; +import java.util.ArrayList; +import java.util.List; + +import org.apache.fineract.infrastructure.campaigns.constants.CampaignType; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; + + +public class SmsCampaignEnumerations { + + + public static EnumOptionData smscampaignTriggerType(final SmsCampaignTriggerType type) { + EnumOptionData optionData = new EnumOptionData(SmsCampaignTriggerType.INVALID.getValue().longValue(), + SmsCampaignTriggerType.INVALID.getCode(), "Invalid"); + switch (type) { + case INVALID: + break; + case DIRECT: + optionData = new EnumOptionData(SmsCampaignTriggerType.DIRECT.getValue().longValue(), + SmsCampaignTriggerType.DIRECT.getCode(), "Direct"); + break; + case SCHEDULE: + optionData = new EnumOptionData(SmsCampaignTriggerType.SCHEDULE.getValue().longValue(), + SmsCampaignTriggerType.SCHEDULE.getCode(), "Scheduled"); + break; + case TRIGGERED: + optionData = new EnumOptionData(SmsCampaignTriggerType.TRIGGERED.getValue().longValue(), + SmsCampaignTriggerType.TRIGGERED.getCode(), "Triggered"); + break; + } + return optionData; + } + + public static EnumOptionData smscampaignType(final CampaignType type) { + EnumOptionData optionData = new EnumOptionData(CampaignType.INVALID.getValue().longValue(), CampaignType.INVALID.getCode(), + "Invalid"); + switch (type) { + case INVALID: + break; + case SMS: + optionData = new EnumOptionData(CampaignType.SMS.getValue().longValue(), CampaignType.SMS.getCode(), "SMS"); + break; + } + return optionData; + } + + public static EnumOptionData calendarMonthType(final Month entityType) { + final EnumOptionData optionData = new EnumOptionData(new Long(entityType.getValue()), entityType.name(), entityType.name()); + return optionData; + } + + public static List calendarMonthType() { + final List optionDatas = new ArrayList<>(); + for (final Month monthType : Month.values()) { + if (Month.DECEMBER.compareTo(monthType) != 0) { + optionDatas.add(calendarMonthType(monthType)); + } + } + return optionDatas; + } + + public static List calendarPeriodFrequencyTypes(final PeriodFrequencyType[] periodFrequencyTypes) { + final List optionDatas = new ArrayList<>(); + for (final PeriodFrequencyType periodFrequencyType : periodFrequencyTypes) { + final EnumOptionData optionData = new EnumOptionData(periodFrequencyType.getValue().longValue(), periodFrequencyType.getCode(), + periodFrequencyType.toString()); + optionDatas.add(optionData); + } + return optionDatas; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java new file mode 100644 index 000000000..43d3c27d7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignStatus.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.constants; + +public enum SmsCampaignStatus { + + INVALID(-1, "smsCampaignStatus.invalid"), // + PENDING(100, "smsCampaignStatus.pending"), // + ACTIVE(300, "smsCampaignStatus.active"), // + CLOSED(600, "smsCampaignStatus.closed"); + + private final Integer value; + private final String code; + + private SmsCampaignStatus(Integer value, String code) { + this.value = value; + this.code = code; + } + + public static SmsCampaignStatus fromInt(final Integer statusValue) { + + SmsCampaignStatus enumeration = SmsCampaignStatus.INVALID; + switch (statusValue) { + case 100: + enumeration = SmsCampaignStatus.PENDING; + break; + case 300: + enumeration = SmsCampaignStatus.ACTIVE; + break; + case 600: + enumeration = SmsCampaignStatus.CLOSED; + break; + } + return enumeration; + } + + public Integer getValue() { + return value; + } + + public String getCode() { + return code; + } + + public boolean isActive() { + return this.value.equals(SmsCampaignStatus.ACTIVE.getValue()); + } + + public boolean isPending() { + return this.value.equals(SmsCampaignStatus.PENDING.getValue()); + } + + public boolean isClosed() { + return this.value.equals(SmsCampaignStatus.CLOSED.getValue()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java new file mode 100644 index 000000000..a133e6f9d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignTriggerType.java @@ -0,0 +1,98 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.constants; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +public enum SmsCampaignTriggerType { + INVALID(-1, "triggerType.invalid"), DIRECT(1, "triggerType.direct"), SCHEDULE(2, "triggerType.schedule"), TRIGGERED(3, + "triggerType.triggered"); + + private Integer value; + private String code; + + private SmsCampaignTriggerType(Integer value, String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return value; + } + + public String getCode() { + return code; + } + + public static SmsCampaignTriggerType fromInt(final Integer typeValue) { + SmsCampaignTriggerType type = null; + switch (typeValue) { + case 1: + type = DIRECT; + break; + case 2: + type = SCHEDULE; + break; + case 3: + type = TRIGGERED; + break; + } + return type; + } + + public static EnumOptionData triggerType(final Integer triggerTypeId) { + return triggerType(SmsCampaignTriggerType.fromInt(triggerTypeId)); + } + + public static EnumOptionData triggerType(final SmsCampaignTriggerType triggerType) { + EnumOptionData optionData = new EnumOptionData(SmsCampaignTriggerType.INVALID.getValue().longValue(), + SmsCampaignTriggerType.INVALID.getCode(), "Invalid"); + switch (triggerType) { + case INVALID: + optionData = new EnumOptionData(SmsCampaignTriggerType.INVALID.getValue().longValue(), + SmsCampaignTriggerType.INVALID.getCode(), "Invalid"); + break; + case DIRECT: + optionData = new EnumOptionData(SmsCampaignTriggerType.DIRECT.getValue().longValue(), + SmsCampaignTriggerType.DIRECT.getCode(), "Direct"); + break; + case SCHEDULE: + optionData = new EnumOptionData(SmsCampaignTriggerType.SCHEDULE.getValue().longValue(), + SmsCampaignTriggerType.SCHEDULE.getCode(), "Schedule"); + break; + case TRIGGERED: + optionData = new EnumOptionData(SmsCampaignTriggerType.TRIGGERED.getValue().longValue(), + SmsCampaignTriggerType.TRIGGERED.getCode(), "Triggered"); + break; + } + return optionData; + } + + public boolean isDirect() { + return this.value.equals(SmsCampaignTriggerType.DIRECT.getValue()); + } + + public boolean isSchedule() { + return this.value.equals(SmsCampaignTriggerType.SCHEDULE.getValue()); + } + + public boolean isTriggered() { + return this.value.equals(SmsCampaignTriggerType.TRIGGERED.getValue()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignPreviewData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignPreviewData.java new file mode 100644 index 000000000..73ca41f73 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignPreviewData.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +public class CampaignPreviewData { + + private final String campaignMessage; + + private final Integer totalNumberOfMessages; + + public CampaignPreviewData(String campaignMessage, Integer totalNumberOfMessages) { + this.campaignMessage = campaignMessage; + this.totalNumberOfMessages = totalNumberOfMessages; + } + + public String getCampaignMessage() { + return campaignMessage; + } + + public Integer getTotalNumberOfMessages() { + return totalNumberOfMessages; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java new file mode 100644 index 000000000..c23771fc7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java @@ -0,0 +1,298 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +public final class CampaignTriggerWithSubTypes { + + public enum ActualCampaignTriggerType { + INVALID(0, "campaignTriggerType.invalid"), // + LOAN(1, "campaignTriggerType.loan"), // + SAVING(2, "campaignTriggerType.saving"), // + CLIENT(3, "campaignTriggerType.client"); + + private Integer value; + private String code; + + private ActualCampaignTriggerType(Integer value, String code) { + this.value = value; + this.code = code; + } + + public static ActualCampaignTriggerType fromInt(final Integer typeValue) { + ActualCampaignTriggerType type = ActualCampaignTriggerType.INVALID; + switch (typeValue) { + case 1: + type = LOAN; + break; + case 2: + type = SAVING; + break; + case 3: + type = CLIENT; + break; + } + return type; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } + + public static EnumOptionData toEnumOptionData(final ActualCampaignTriggerType triggerType) { + final EnumOptionData optionData = new EnumOptionData(new Long(triggerType.getValue()), triggerType.getCode(), triggerType.name()); + return optionData; + } + + public static EnumOptionData toEnumOptionData(final Integer triggerTypeValue) { + ActualCampaignTriggerType actualCampaignTriggerType = ActualCampaignTriggerType.fromInt(triggerTypeValue); + final EnumOptionData optionData = new EnumOptionData(new Long(actualCampaignTriggerType.getValue()), + actualCampaignTriggerType.getCode(), actualCampaignTriggerType.name()); + return optionData; + } + + public boolean isInvalid() { + return this.value.equals(ActualCampaignTriggerType.INVALID.getValue()); + } + } + + public enum CampaignTriggerSubType { + INVALID(0, ActualCampaignTriggerType.INVALID, "campaignTriggerSubType.invalid"), // + + DISBURSE(101, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.disburse"), // + REPAYMENT(102, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.repayment"), // + UNDO_DISBURSAL(103, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.undodisbursal"), // + WRITE_OFF(104, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.writeoff"), // + ADJUST(105, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.adjust"), // + UNDO_WRITE_OFF(106, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.undowriteoff"), // + FORECLOSURE(107, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.foreclosure"), // + APPROVED(108, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.approved"), // + WAIVE_INTEREST(109, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.waive.interest"), // + CLOSE(110, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.close"), // + CLOSE_AS_RESCHEDULE(111, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.close.as.rescheduled"), // + ADD_CHARGE(112, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.add.charge"), // + UPDATE_CHARGE(113, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.update.charge"), // + WAIVE_CHARGE(114, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.waive.charge"), // + DELETE_CHARGE(115, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.delete.charge"), // + CHARGE_PAYMENT(116, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.charge.payment"), // + INITIATE_TRANSFER(117, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.initiate.transfer"), // + ACCEPT_TRANSFER(118, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.accept.transfer"), // + WITHDRAW_TRANSFER(119, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.withdraw.transfer"), // + REJECT_TRANSFER(120, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reject.transfer"), // + REASSIGN_OFFICER(121, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // + REMOVE_OFFICER(122, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // + APPLY_OVERDUE_CHARGE(123, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // + INTEREST_RECALCULATION(124, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // + REFUND(125, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // + + SAVINGS_DEPOSIT(201, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.deposit"), // + SAVINGS_WITHDRAWAL(202, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.withdrawal"), // + SAVINGS_ACTIVATE(203, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.activate"), // + SAVINGS_ADJUST_TRANSACTION(204, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.adjust"), // + SAVINGS_APPLY_ANNUAL_FEE(205, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.apply.annual.fee"), // + SAVINGS_CALCULATE_INTEREST(206, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.calclulate.interest"), // + SAVINGS_CLOSE(207, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.close"), // + SAVINGS_POST_INTEREST(208, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.post"), // + SAVINGS_REJECT(209, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.reject"), // + SAVINGS_UNDO(210, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.undo"), // + SAVINGS_ADD_CHARGE(211, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.add.charge"), // + SAVINGS_WAIVE_CHARGE(212, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.waive.charge"), // + SAVINGS_PAY_CHARGE(213, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.pay.charge"), // + + CLIENTS_ACTIVATE(301, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.activate"), // + CLIENTS_CLOSE(302, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.close"), // + CLIENTS_ACCEPT_TRANSFER(303, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.accept.transfer"), // + CLIENTS_ASSIGN_STAFF(304, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.assign.staff"), // + CLIENTS_CREATE(305, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.create"), // + CLIENTS_DELETE(306, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.delete"), // + CLIENTS_PROPOSE_TRANSFER(307, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.propose.transfer"), // + CLIENTS_REACTIVATE(308, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.reactivate"), // + CLIENTS_REJECT(309, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.reject"), // + CLIENTS_REJECT_TRANSFER(310, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.reject.transfer"), // + CLIENTS_WITHDRAW(311, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.withdraw"), // + CLIENTS_WITHDRAW_TRANSFER(312, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.withdraw.transfer"); + + private Integer id; + private ActualCampaignTriggerType type; + private String code; + + private CampaignTriggerSubType(Integer id, ActualCampaignTriggerType type, String code) { + this.id = id; + this.type = type; + this.code = code; + } + + public static CampaignTriggerSubType fromInt(final Integer subTypeValue) { + CampaignTriggerSubType subType = CampaignTriggerSubType.INVALID; + switch (subTypeValue) { + case 101: + subType = DISBURSE; + break; + case 102: + subType = REPAYMENT; + break; + case 103: + subType = ADJUST; + break; + case 104: + subType = UNDO_DISBURSAL; + break; + case 105: + subType = WRITE_OFF; + break; + case 106: + subType = UNDO_WRITE_OFF; + break; + + case 201: + subType = SAVINGS_DEPOSIT; + break; + case 202: + subType = SAVINGS_WITHDRAWAL; + break; + case 203: + subType = SAVINGS_ACTIVATE; + break; + case 204: + subType = SAVINGS_ADJUST_TRANSACTION; + break; + case 205: + subType = SAVINGS_APPLY_ANNUAL_FEE; + break; + case 206: + subType = SAVINGS_CALCULATE_INTEREST; + break; + case 207: + subType = SAVINGS_CLOSE; + break; + case 208: + subType = SAVINGS_POST_INTEREST; + break; + case 209: + subType = SAVINGS_REJECT; + break; + case 210: + subType = SAVINGS_UNDO; + break; + case 211: + subType = SAVINGS_ADD_CHARGE; + break; + case 212: + subType = SAVINGS_WAIVE_CHARGE; + break; + case 213: + subType = SAVINGS_PAY_CHARGE; + break; + + case 301: + subType = CLIENTS_ACTIVATE; + break; + case 302: + subType = CLIENTS_CLOSE; + break; + case 303: + subType = CLIENTS_ACCEPT_TRANSFER; + break; + case 304: + subType = CLIENTS_ASSIGN_STAFF; + break; + case 305: + subType = CLIENTS_CREATE; + break; + case 306: + subType = CLIENTS_DELETE; + break; + case 307: + subType = CLIENTS_PROPOSE_TRANSFER; + break; + case 308: + subType = CLIENTS_REACTIVATE; + break; + case 309: + subType = CLIENTS_REJECT; + break; + case 310: + subType = CLIENTS_REJECT_TRANSFER; + break; + case 311: + subType = CLIENTS_WITHDRAW; + break; + case 312: + subType = CLIENTS_WITHDRAW_TRANSFER; + break; + } + return subType; + } + + public Integer getId() { + return this.id; + } + + public ActualCampaignTriggerType getType() { + return this.type; + } + + public String getCode() { + return this.code; + } + + public static EnumOptionData toEnumOptionData(final Integer triggerSubType) { + CampaignTriggerSubType subTypeEnum = CampaignTriggerSubType.fromInt(triggerSubType); + final EnumOptionData optionData = new EnumOptionData(new Long(subTypeEnum.getId()), subTypeEnum.getCode(), subTypeEnum.name()); + return optionData; + } + } + + public static List addTypeSubTypeMapping(ActualCampaignTriggerType type) { + List subTypeList = new ArrayList<>(); + EnumOptionData optionData = null; + for (CampaignTriggerSubType subType : CampaignTriggerSubType.values()) { + if (subType.getType().equals(type)) { + optionData = new EnumOptionData(subType.getId().longValue(), subType.getCode(), subType.name()); + subTypeList.add(optionData); + } + } + return subTypeList; + } + + public static Collection getTriggerTypeAndSubTypes() { + final Collection typesList = new ArrayList<>(); + EnumOptionData actualTriggerType = null; + for (ActualCampaignTriggerType triggerType : ActualCampaignTriggerType.values()) { + if (triggerType.isInvalid()) { + continue; + } + List subTypeList = addTypeSubTypeMapping(triggerType); + actualTriggerType = ActualCampaignTriggerType.toEnumOptionData(triggerType); + TriggerTypeWithSubTypesData triggerTypeWithSubTypesData = new TriggerTypeWithSubTypesData(actualTriggerType, subTypeList); + typesList.add(triggerTypeWithSubTypesData); + } + return typesList; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/MessageGatewayConfigurationData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/MessageGatewayConfigurationData.java new file mode 100644 index 000000000..f2222ee64 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/MessageGatewayConfigurationData.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +public class MessageGatewayConfigurationData { + + private final Long id; + private final String connectionName; + private final String hostName; + private final int portNumber; + private final String endPoint; + private final String userName; + private final String password; + private final boolean sslEnabled; + private final String tenantAppKey; + + public MessageGatewayConfigurationData(final Long id, final String connectionName, final String hostName, final int portNumber, + final String endPoint, final String userName, final String password, final boolean sslEnabled, final String tenantAppKey) { + this.id = id; + this.connectionName = connectionName; + this.hostName = hostName; + this.portNumber = portNumber; + this.endPoint = endPoint; + this.userName = userName; + this.password = password; + this.sslEnabled = sslEnabled; + this.tenantAppKey = tenantAppKey; + } + + public Long getId() { + return this.id; + } + + public String getConnectionName() { + return this.connectionName; + } + + public String getHostName() { + return this.hostName; + } + + public int getPortNumber() { + return this.portNumber; + } + + public String getEndPoint() { + return this.endPoint; + } + + public String getUserName() { + return this.userName; + } + + public String getPassword() { + return this.password; + } + + public boolean isSslEnabled() { + return this.sslEnabled; + } + + public String getTenantAppKey() { + return this.tenantAppKey; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsBusinessRulesData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsBusinessRulesData.java new file mode 100644 index 000000000..adee0940c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsBusinessRulesData.java @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +import java.util.Map; + +public class SmsBusinessRulesData { + + private final Long reportId; + + private final String reportName; + + private final String reportType; + + private final String reportSubType; + + private final String reportDescription; + + private final Map reportParamName; + + public SmsBusinessRulesData(final Long reportId, final String reportName, final String reportType, final String reportSubType, + final Map reportParamName, final String reportDescription) { + this.reportId = reportId; + this.reportName = reportName; + this.reportType = reportType; + this.reportSubType = reportSubType; + this.reportParamName = reportParamName; + this.reportDescription = reportDescription; + } + + public static SmsBusinessRulesData instance(final Long reportId, final String reportName, final String reportType, + final String reportSubType, final Map reportParamName, final String reportDescription) { + return new SmsBusinessRulesData(reportId, reportName, reportType, reportSubType, reportParamName, reportDescription); + } + + public Map getReportParamName() { + return reportParamName; + } + + public String getReportType() { + return reportType; + } + + public String getReportSubType() { + return this.reportSubType; + } + + public String getReportName() { + return reportName; + } + + public Long getReportId() { + return reportId; + } + + public String getReportDescription() { + return reportDescription; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SmsBusinessRulesData that = (SmsBusinessRulesData) o; + + if (reportId != null ? !reportId.equals(that.reportId) : that.reportId != null) return false; + + return true; + } + + @Override + public int hashCode() { + return reportId != null ? reportId.hashCode() : 0; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java new file mode 100644 index 000000000..8a6e6a310 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignData.java @@ -0,0 +1,206 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.joda.time.DateTime; +import org.joda.time.LocalDate; + +@SuppressWarnings("unused") +public class SmsCampaignData { + + private Long id; + private final String campaignName; + private final EnumOptionData campaignType; + private final Long runReportId; + private final String reportName; + private final String paramValue; + private final EnumOptionData campaignStatus; + private final EnumOptionData triggerType; + private final String campaignMessage; + private final DateTime nextTriggerDate; + private final LocalDate lastTriggerDate; + private final SmsCampaignTimeLine smsCampaignTimeLine; + private final DateTime recurrenceStartDate; + private final String recurrence; + private final Long providerId; + + private final Collection smsProviderOptions; + + private final Collection campaignTypeOptions; + + private final Collection triggerTypeOptions; + + private final Collection businessRulesOptions; + + private final Collection months; + + private final Collection weekDays; + + private final Collection frequencyTypeOptions; + + private final Collection periodFrequencyOptions; + + private SmsCampaignData(final Long id, final String campaignName, final EnumOptionData campaignType, final EnumOptionData triggerType, + final Long runReportId, + final String reportName, final String paramValue, final EnumOptionData campaignStatus, + final String message, final DateTime nextTriggerDate, final LocalDate lastTriggerDate, + final SmsCampaignTimeLine smsCampaignTimeLine, final DateTime recurrenceStartDate, final String recurrence, + final Long providerId, final Collection businessRulesOptions, + final Collection smsProviderOptions, final Collection campaignTypeOptions, + final Collection triggerTypeOptions, final Collection months, + final Collection weekDays, final Collection frequencyTypeOptions, + final Collection periodFrequencyOptions) { + this.id = id; + this.campaignName = campaignName; + this.campaignType = campaignType; + this.triggerType = triggerType; + this.runReportId = runReportId; + this.reportName = reportName; + this.paramValue = paramValue; + this.campaignStatus = campaignStatus; + this.campaignMessage = message; + if (nextTriggerDate != null) { + this.nextTriggerDate = nextTriggerDate; + } else { + this.nextTriggerDate = null; + } + if (lastTriggerDate != null) { + this.lastTriggerDate = lastTriggerDate; + } else { + this.lastTriggerDate = null; + } + this.smsCampaignTimeLine = smsCampaignTimeLine; + this.recurrenceStartDate = recurrenceStartDate; + this.recurrence = recurrence; + this.providerId = providerId; + this.businessRulesOptions = businessRulesOptions; + this.smsProviderOptions = smsProviderOptions; + this.campaignTypeOptions = campaignTypeOptions; + this.triggerTypeOptions = triggerTypeOptions; + this.months = months; + this.weekDays = weekDays; + this.frequencyTypeOptions = frequencyTypeOptions; + this.periodFrequencyOptions = periodFrequencyOptions; + } + + public static SmsCampaignData instance(final Long id, final String campaignName, final EnumOptionData campaignType, + final EnumOptionData triggerType, + final Long runReportId, final String reportName, final String paramValue, final EnumOptionData campaignStatus, + final String message, final DateTime nextTriggerDate, final LocalDate lastTriggerDate, + final SmsCampaignTimeLine smsCampaignTimeLine, final DateTime recurrenceStartDate, final String recurrence, + final Long providerId) { + final Collection businessRulesOptions = null; + final Collection smsProviderOptions = null; + final Collection campaignTypeOptions = null; + final Collection triggerTypeOptions = null; + final Collection months = null; + final Collection weekDays = null; + final Collection frequencyTypeOptions = null; + final Collection periodFrequencyOptions = null; + + return new SmsCampaignData(id, campaignName, campaignType, triggerType, runReportId, + reportName, paramValue, campaignStatus, message, nextTriggerDate, lastTriggerDate, smsCampaignTimeLine, + recurrenceStartDate, recurrence, providerId, businessRulesOptions, smsProviderOptions, campaignTypeOptions, + triggerTypeOptions, months, weekDays, frequencyTypeOptions, periodFrequencyOptions); + } + + public static SmsCampaignData template(final Collection smsProviderOptions, + final Collection campaignTypeOptions, final Collection businessRulesOptions, + final Collection triggerTypeOptions, final Collection months, + final Collection weekDays, final Collection frequencyTypeOptions, + final Collection periodFrequencyOptions) { + final Long id = null; + final String campaignName = null; + final EnumOptionData campaignType = null; + final Long runReportId = null; + final String paramValue = null; + final EnumOptionData campaignStatus = null; + final String message = null; + final DateTime nextTriggerDate = null; + final LocalDate lastTriggerDate = null; + final SmsCampaignTimeLine smsCampaignTimeLine = null; + final DateTime recurrenceStartDate = null; + final String recurrence = null; + final EnumOptionData triggerType = null; + final String reportName = null; + final Long providerId = null; + return new SmsCampaignData(id, campaignName, campaignType, triggerType, runReportId, + reportName, paramValue, campaignStatus, message, nextTriggerDate, lastTriggerDate, smsCampaignTimeLine, + recurrenceStartDate, recurrence, providerId, businessRulesOptions, smsProviderOptions, campaignTypeOptions, + triggerTypeOptions, months, weekDays, frequencyTypeOptions, periodFrequencyOptions); + } + + public Long getId() { + return id; + } + + public String getCampaignName() { + return this.campaignName; + } + + public EnumOptionData getCampaignType() { + return this.campaignType; + } + + public Long getRunReportId() { + return this.runReportId; + } + + public String getParamValue() { + return this.paramValue; + } + + public EnumOptionData getCampaignStatus() { + return this.campaignStatus; + } + + public String getMessage() { + return this.campaignMessage; + } + + public DateTime getNextTriggerDate() { + return this.nextTriggerDate; + } + + public LocalDate getLastTriggerDate() { + return this.lastTriggerDate; + } + + public String getRecurrence() { + return this.recurrence; + } + + public DateTime getRecurrenceStartDate() { + return this.recurrenceStartDate; + } + + public String getReportName() { + return this.reportName; + } + + public Long providerId() { + return this.providerId; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignTimeLine.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignTimeLine.java new file mode 100644 index 000000000..faf60735e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsCampaignTimeLine.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +import org.joda.time.LocalDate; + +@SuppressWarnings("unused") +public class SmsCampaignTimeLine { + + private final LocalDate submittedOnDate; + private final String submittedByUsername; + + private final LocalDate activatedOnDate; + private final String activatedByUsername; + + private final LocalDate closedOnDate; + private final String closedByUsername; + + public SmsCampaignTimeLine(final LocalDate submittedOnDate, final String submittedByUsername, final LocalDate activatedOnDate, + final String activatedByUsername, final LocalDate closedOnDate, final String closedByUsername) { + this.submittedOnDate = submittedOnDate; + this.submittedByUsername = submittedByUsername; + this.activatedOnDate = activatedOnDate; + this.activatedByUsername = activatedByUsername; + this.closedOnDate = closedOnDate; + this.closedByUsername = closedByUsername; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsProviderData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsProviderData.java new file mode 100644 index 000000000..9ae976890 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/SmsProviderData.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +public class SmsProviderData { + + private Long id; + + private String tenantId; + + private String phoneNo; + + private String providerAppKey; + + private String providerName; + + private String providerDescription; + + public SmsProviderData(final Long id, final String providerAppKey, final String providerName, final String providerDescription, final String tenantId, + final String phoneNo) { + this.id = id; + this.providerAppKey = providerAppKey; + this.providerName = providerName; + this.providerDescription = providerDescription; + this.tenantId = tenantId; + this.phoneNo = phoneNo; + } + + public SmsProviderData() { + + } + + public Long getId() { + return this.id; + } + + public String getProviderAppKey() { + return this.providerAppKey; + } + + public String getProviderName() { + return this.providerName; + } + + public String getProviderDescription() { + return this.providerDescription; + } + + public String getTenantId() { + return this.tenantId; + } + + public String getPhoneNo() { + return this.phoneNo; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java new file mode 100644 index 000000000..b6e9c3c6a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.data; + +import java.util.List; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +public class TriggerTypeWithSubTypesData { + + private final EnumOptionData actualTriggerType; + private final List triggerSubTypes; + + public TriggerTypeWithSubTypesData(final EnumOptionData actualTriggerType, List triggerSubTypes) { + this.actualTriggerType = actualTriggerType; + this.triggerSubTypes = triggerSubTypes; + } + + public EnumOptionData getActualTriggerType() { + return this.actualTriggerType; + } + + public List getTriggerSubTypes() { + return this.triggerSubTypes; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java new file mode 100644 index 000000000..3e1610e89 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaign.java @@ -0,0 +1,561 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.domain; + +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; + +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.campaigns.constants.CampaignType; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.dataqueries.domain.Report; +import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; +import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; +import org.apache.fineract.useradministration.domain.AppUser; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +@Entity +@Table(name = "sms_campaign", uniqueConstraints = {@UniqueConstraint(columnNames = { "campaign_name" }, name = "campaign_name_UNIQUE")}) +public class SmsCampaign extends AbstractPersistableCustom { + + @Column(name = "campaign_name", nullable = false) + private String campaignName; + + @Column(name = "campaign_type", nullable = false) + private Integer campaignType ; //defines email or sms, etc.. + + @Column(name = "campaign_trigger_type", nullable = false) + private Integer triggerType; //defines direct, scheduled, transaction + + @Column(name = "provider_id", nullable = false) + private Long providerId; // defined provider details + + @ManyToOne + @JoinColumn(name = "report_id", nullable = false) + private Report businessRuleId; + + @Column(name = "param_value") + private String paramValue; + + @Column(name = "status_enum", nullable = false) + private Integer status; + + @Column(name = "message", nullable = false) + private String message; + + @Column(name = "closedon_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date closureDate; + + @ManyToOne(optional = true) + @JoinColumn(name = "closedon_userid", nullable = true) + private AppUser closedBy; + + @Column(name = "submittedon_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date submittedOnDate; + + @ManyToOne(optional = true) + @JoinColumn(name = "submittedon_userid", nullable = true) + private AppUser submittedBy; + + @Column(name = "approvedon_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date approvedOnDate; + + @ManyToOne(optional = true) + @JoinColumn(name = "approvedon_userid", nullable = true) + private AppUser approvedBy; + + @Column(name = "recurrence", nullable = true) + private String recurrence; + + @Column(name = "next_trigger_date", nullable = true) + @Temporal(TemporalType.TIMESTAMP) + private Date nextTriggerDate; + + @Column(name = "last_trigger_date", nullable = true) + @Temporal(TemporalType.TIMESTAMP) + private Date lastTriggerDate; + + @Column(name = "recurrence_start_date", nullable = true) + @Temporal(TemporalType.TIMESTAMP) + private Date recurrenceStartDate; + + @Column(name = "is_visible", nullable = true) + private boolean isVisible; + + public SmsCampaign() {} + + private SmsCampaign(final String campaignName, final Integer campaignType, + final Integer triggerType, final Report businessRuleId, final Long providerId, final String paramValue, + final String message, final LocalDate submittedOnDate, final AppUser submittedBy, final String recurrence, + final LocalDateTime localDateTime) { + this.campaignName = campaignName; + this.campaignType = campaignType; + this.triggerType = SmsCampaignTriggerType.fromInt(triggerType).getValue(); + this.businessRuleId = businessRuleId; + this.providerId = providerId; + this.paramValue = paramValue; + this.status = SmsCampaignStatus.PENDING.getValue(); + this.message = message; + this.submittedOnDate = submittedOnDate.toDate(); + this.submittedBy = submittedBy; + this.recurrence = recurrence; + LocalDateTime recurrenceStartDate = new LocalDateTime(); + this.isVisible = true; + if (localDateTime != null) { + this.recurrenceStartDate = localDateTime.toDate(); + } else { + this.recurrenceStartDate = recurrenceStartDate.toDate(); + } + } + + public static SmsCampaign instance(final AppUser submittedBy, final Report report, final JsonCommand command) { + + final String campaignName = command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName); + final Long campaignType = command.longValueOfParameterNamed(SmsCampaignValidator.campaignType); + final Long triggerType = command.longValueOfParameterNamed(SmsCampaignValidator.triggerType); + final Long providerId = command.longValueOfParameterNamed(SmsCampaignValidator.providerId); + final String paramValue = command.jsonFragment(SmsCampaignValidator.paramValue); + + final String message = command.stringValueOfParameterNamed(SmsCampaignValidator.message); + LocalDate submittedOnDate = new LocalDate(); + if (command.hasParameter(SmsCampaignValidator.submittedOnDateParamName)) { + submittedOnDate = command.localDateValueOfParameterNamed(SmsCampaignValidator.submittedOnDateParamName); + } + String recurrence = null; + + LocalDateTime recurrenceStartDate = new LocalDateTime(); + if (SmsCampaignTriggerType.fromInt(triggerType.intValue()).isSchedule()) { + final Locale locale = command.extractLocale(); + String dateTimeFormat = null; + if (command.hasParameter(SmsCampaignValidator.dateTimeFormat)) { + dateTimeFormat = command.stringValueOfParameterNamed(SmsCampaignValidator.dateTimeFormat); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(dateTimeFormat).withLocale(locale); + if (command.hasParameter(SmsCampaignValidator.recurrenceStartDate)) { + recurrenceStartDate = LocalDateTime.parse( + command.stringValueOfParameterNamed(SmsCampaignValidator.recurrenceStartDate), fmt); + } + recurrence = constructRecurrence(command); + } + } else { + recurrenceStartDate = null; + } + + return new SmsCampaign(campaignName, campaignType.intValue(), triggerType.intValue(), report, + providerId, paramValue, message, submittedOnDate, submittedBy, recurrence, recurrenceStartDate); + } + + public Map update(JsonCommand command) { + + final Map actualChanges = new LinkedHashMap<>(5); + + if (command.isChangeInStringParameterNamed(SmsCampaignValidator.campaignName, this.campaignName)) { + final String newValue = command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName); + actualChanges.put(SmsCampaignValidator.campaignName, newValue); + this.campaignName = StringUtils.defaultIfEmpty(newValue, null); + } + if (command.isChangeInStringParameterNamed(SmsCampaignValidator.message, this.message)) { + final String newValue = command.stringValueOfParameterNamed(SmsCampaignValidator.message); + actualChanges.put(SmsCampaignValidator.message, newValue); + this.message = StringUtils.defaultIfEmpty(newValue, null); + } + if (command.isChangeInStringParameterNamed(SmsCampaignValidator.paramValue, this.paramValue)) { + final String newValue = command.jsonFragment(SmsCampaignValidator.paramValue); + actualChanges.put(SmsCampaignValidator.paramValue, newValue); + this.paramValue = StringUtils.defaultIfEmpty(newValue, null); + } + if (command.isChangeInIntegerParameterNamed(SmsCampaignValidator.campaignType, this.campaignType)) { + final Integer newValue = command.integerValueOfParameterNamed(SmsCampaignValidator.campaignType); + actualChanges.put(SmsCampaignValidator.campaignType, CampaignType.fromInt(newValue)); + this.campaignType = CampaignType.fromInt(newValue).getValue(); + } + + if (command.isChangeInIntegerParameterNamed(SmsCampaignValidator.triggerType, this.triggerType)) { + final Integer newValue = command.integerValueOfParameterNamed(SmsCampaignValidator.triggerType); + actualChanges.put(SmsCampaignValidator.triggerType, SmsCampaignTriggerType.fromInt(newValue)); + this.triggerType = SmsCampaignTriggerType.fromInt(newValue).getValue(); + } + + if (command.isChangeInLongParameterNamed(SmsCampaignValidator.runReportId, + (this.businessRuleId != null) ? this.businessRuleId.getId() : null)) { + final String newValue = command.stringValueOfParameterNamed(SmsCampaignValidator.runReportId); + actualChanges.put(SmsCampaignValidator.runReportId, newValue); + } + if (command.isChangeInStringParameterNamed(SmsCampaignValidator.recurrenceParamName, this.recurrence)) { + final String newValue = command.stringValueOfParameterNamed(SmsCampaignValidator.recurrenceParamName); + actualChanges.put(SmsCampaignValidator.recurrenceParamName, newValue); + this.recurrence = StringUtils.defaultIfEmpty(newValue, null); + } + if (command.isChangeInLongParameterNamed(SmsCampaignValidator.providerId, this.providerId)) { + final Long newValue = command.longValueOfParameterNamed(SmsCampaignValidator.providerId); + actualChanges.put(SmsCampaignValidator.providerId, newValue); + } + + if (SmsCampaignTriggerType.fromInt(this.triggerType).isSchedule()) { + final String dateFormatAsInput = command.dateFormat(); + final String dateTimeFormatAsInput = command.stringValueOfParameterNamed(SmsCampaignValidator.dateTimeFormat); + final String localeAsInput = command.locale(); + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(dateTimeFormatAsInput).withLocale(locale); + final String valueAsInput = command.stringValueOfParameterNamed(SmsCampaignValidator.recurrenceStartDate); + actualChanges.put(SmsCampaignValidator.recurrenceStartDate, valueAsInput); + actualChanges.put(SmsCampaignValidator.dateFormatParamName, dateFormatAsInput); + actualChanges.put(SmsCampaignValidator.dateTimeFormat, dateTimeFormatAsInput); + actualChanges.put(SmsCampaignValidator.localeParamName, localeAsInput); + + final LocalDateTime newValue = LocalDateTime.parse(valueAsInput, fmt); + + this.recurrenceStartDate = newValue.toDate(); + } + + return actualChanges; + } + + public void activate(final AppUser currentUser, final DateTimeFormatter formatter, final LocalDate activationLocalDate) { + + if (isActive()) { + // handle errors if already activated + final String defaultUserMessage = "Cannot activate campaign. Campaign is already active."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.already.active", defaultUserMessage, + SmsCampaignValidator.activationDateParamName, activationLocalDate.toString(formatter)); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + this.approvedOnDate = activationLocalDate.toDate(); + this.approvedBy = currentUser; + this.status = SmsCampaignStatus.ACTIVE.getValue(); + + validate(); + } + + public void close(final AppUser currentUser, final DateTimeFormatter dateTimeFormatter, final LocalDate closureLocalDate) { + if (isClosed()) { + // handle errors if already activated + final String defaultUserMessage = "Cannot close campaign. Campaign already in closed state."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.already.closed", defaultUserMessage, + SmsCampaignValidator.statusParamName, SmsCampaignStatus.fromInt(this.status).getCode()); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + if (this.triggerType.intValue() == SmsCampaignTriggerType.SCHEDULE.getValue()) { + this.nextTriggerDate = null; + this.lastTriggerDate = null; + } + this.closedBy = currentUser; + this.closureDate = closureLocalDate.toDate(); + this.status = SmsCampaignStatus.CLOSED.getValue(); + validateClosureDate(); + } + + public void reactivate(final AppUser currentUser, final DateTimeFormatter dateTimeFormat, final LocalDate reactivateLocalDate) { + + if (!isClosed()) { + // handle errors if already activated + final String defaultUserMessage = "Cannot reactivate campaign. Campaign must be in closed state."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.must.be.closed", defaultUserMessage, + SmsCampaignValidator.statusParamName, SmsCampaignStatus.fromInt(this.status).getCode()); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + this.approvedOnDate = reactivateLocalDate.toDate(); + this.status = SmsCampaignStatus.ACTIVE.getValue(); + this.approvedBy = currentUser; + this.closureDate = null; + this.isVisible = true; + this.closedBy = null; + + validateReactivate(); + } + + public void delete() { + if (!isClosed()) { + // handle errors if already activated + final String defaultUserMessage = "Cannot delete campaign. Campaign must be in closed state."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.must.be.closed", defaultUserMessage, + SmsCampaignValidator.statusParamName, SmsCampaignStatus.fromInt(this.status).getCode()); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + this.isVisible = false; + } + + public boolean isActive() { + return SmsCampaignStatus.fromInt(this.status).isActive(); + } + + public boolean isPending() { + return SmsCampaignStatus.fromInt(this.status).isPending(); + } + + public boolean isClosed() { + return SmsCampaignStatus.fromInt(this.status).isClosed(); + } + + public boolean isDirect() { + return SmsCampaignTriggerType.fromInt(this.triggerType).isDirect(); + } + + public boolean isSchedule() { + return SmsCampaignTriggerType.fromInt(this.triggerType).isSchedule(); + } + + public boolean isTriggered() { + return SmsCampaignTriggerType.fromInt(this.triggerType).isTriggered(); + } + + private void validate() { + final List dataValidationErrors = new ArrayList<>(); + validateActivationDate(dataValidationErrors); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private void validateReactivate() { + final List dataValidationErrors = new ArrayList<>(); + validateReactivationDate(dataValidationErrors); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private void validateClosureDate() { + final List dataValidationErrors = new ArrayList<>(); + validateClosureDate(dataValidationErrors); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private void validateActivationDate(final List dataValidationErrors) { + + if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { + + final String defaultUserMessage = "submitted date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.in.the.future", + defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); + + dataValidationErrors.add(error); + } + + if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) { + + final String defaultUserMessage = "submitted date cannot be after the activation date"; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.after.activation.date", + defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); + + dataValidationErrors.add(error); + } + + if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) { + + final String defaultUserMessage = "Activation date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.activationDate.in.the.future", + defaultUserMessage, SmsCampaignValidator.activationDateParamName, getActivationLocalDate()); + + dataValidationErrors.add(error); + } + + } + + private void validateReactivationDate(final List dataValidationErrors) { + if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) { + + final String defaultUserMessage = "Activation date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.activationDate.in.the.future", + defaultUserMessage, SmsCampaignValidator.activationDateParamName, getActivationLocalDate()); + + dataValidationErrors.add(error); + } + if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) { + + final String defaultUserMessage = "submitted date cannot be after the activation date"; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.after.activation.date", + defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); + + dataValidationErrors.add(error); + } + if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) { + + final String defaultUserMessage = "submitted date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.submittedOnDate.in.the.future", + defaultUserMessage, SmsCampaignValidator.submittedOnDateParamName, this.submittedOnDate); + + dataValidationErrors.add(error); + } + + } + + private void validateClosureDate(final List dataValidationErrors) { + if (getClosureDate() != null && isDateInTheFuture(getClosureDate())) { + final String defaultUserMessage = "closure date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.campaign.closureDate.in.the.future", + defaultUserMessage, SmsCampaignValidator.closureDateParamName, this.closureDate); + + dataValidationErrors.add(error); + } + } + + public LocalDate getSubmittedOnDate() { + return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null); + + } + + public LocalDate getClosureDate() { + return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.closureDate), null); + } + + public LocalDate getActivationLocalDate() { + LocalDate activationLocalDate = null; + if (this.approvedOnDate != null) { + activationLocalDate = LocalDate.fromDateFields(this.approvedOnDate); + } + return activationLocalDate; + } + + private boolean isDateInTheFuture(final LocalDate localDate) { + return localDate.isAfter(DateUtils.getLocalDateOfTenant()); + } + + public Report getBusinessRuleId() { + return this.businessRuleId; + } + + public String getCampaignName() { + return this.campaignName; + } + + public String getMessage() { + return this.message; + } + + public String getParamValue() { + return this.paramValue; + } + + public String getRecurrence() { + return this.recurrence; + } + + public LocalDate getRecurrenceStartDate() { + return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.recurrenceStartDate), null); + } + + public LocalDateTime getRecurrenceStartDateTime() { + return (LocalDateTime) ObjectUtils.defaultIfNull(new LocalDateTime(this.recurrenceStartDate), null); + } + + public void setLastTriggerDate(Date lastTriggerDate) { + this.lastTriggerDate = lastTriggerDate; + } + + public void setNextTriggerDate(Date nextTriggerDate) { + this.nextTriggerDate = nextTriggerDate; + } + + public LocalDateTime getNextTriggerDate() { + return (LocalDateTime) ObjectUtils.defaultIfNull(new LocalDateTime(this.nextTriggerDate), null); + + } + + public Date getNextTriggerDateInDate() { + return this.nextTriggerDate; + } + + public LocalDate getLastTriggerDate() { + return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.lastTriggerDate), null); + } + + public void updateIsVisible(boolean isVisible) { + this.isVisible = isVisible; + } + + public void updateBusinessRuleId(final Report report) { + this.businessRuleId = report; + } + + public Long getProviderId() { + return this.providerId; + } + + private static String constructRecurrence(final JsonCommand command) { + final Integer frequency = command.integerValueOfParameterNamed(SmsCampaignValidator.frequencyParamName); + final CalendarFrequencyType frequencyType = CalendarFrequencyType.fromInt(frequency); + final Integer interval = command.integerValueOfParameterNamed(SmsCampaignValidator.intervalParamName); + Integer repeatsOnDay = null; + if (frequencyType.isWeekly()) { + repeatsOnDay = command.integerValueOfParameterNamed(SmsCampaignValidator.repeatsOnDayParamName); + } + return constructRecurrence(frequencyType, interval, repeatsOnDay); + } + + private static String constructRecurrence(final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay) { + final StringBuilder recurrenceBuilder = new StringBuilder(200); + + recurrenceBuilder.append("FREQ="); + recurrenceBuilder.append(frequencyType.toString().toUpperCase()); + if (interval > 1) { + recurrenceBuilder.append(";INTERVAL="); + recurrenceBuilder.append(interval); + } + if (frequencyType.isWeekly()) { + if (repeatsOnDay != null) { + final CalendarWeekDaysType weekDays = CalendarWeekDaysType.fromInt(repeatsOnDay); + if (!weekDays.isInvalid()) { + recurrenceBuilder.append(";BYDAY="); + recurrenceBuilder.append(weekDays.toString().toUpperCase()); + } + } + } + return recurrenceBuilder.toString(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java new file mode 100644 index 000000000..436f9ed63 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.domain; + +import java.util.Collection; +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface SmsCampaignRepository extends JpaRepository, JpaSpecificationExecutor { + + List findByCampaignType(final Integer campaignType); + + Collection findByCampaignTypeAndTriggerTypeAndStatus(final Integer campaignType, final Integer triggerType, + final Integer status); + + Collection findByTriggerType(final Integer triggerType) ; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignStatusEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignStatusEnumerations.java new file mode 100644 index 000000000..9147a29f5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignStatusEnumerations.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.domain; + +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +public class SmsCampaignStatusEnumerations { + public static EnumOptionData status(final Integer statusId) { + return status(SmsCampaignStatus.fromInt(statusId)); + } + + public static EnumOptionData status(final SmsCampaignStatus status) { + EnumOptionData optionData = new EnumOptionData(SmsCampaignStatus.INVALID.getValue().longValue(), + SmsCampaignStatus.INVALID.getCode(), "Invalid"); + switch (status) { + case INVALID: + optionData = new EnumOptionData + (SmsCampaignStatus.INVALID.getValue().longValue(), + SmsCampaignStatus.INVALID.getCode(), "Invalid"); + break; + case PENDING: + optionData = new EnumOptionData(SmsCampaignStatus.PENDING.getValue().longValue(), + SmsCampaignStatus.PENDING.getCode(), "Pending"); + break; + case ACTIVE: + optionData = new EnumOptionData(SmsCampaignStatus.ACTIVE.getValue().longValue(), SmsCampaignStatus.ACTIVE.getCode(), + "active"); + break; + case CLOSED: + optionData = new EnumOptionData(SmsCampaignStatus.CLOSED.getValue().longValue(), + SmsCampaignStatus.CLOSED.getCode(), "closed"); + break; + + } + + return optionData; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/ConnectionFailureException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/ConnectionFailureException.java new file mode 100644 index 000000000..b7d9d976c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/ConnectionFailureException.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException; + +public class ConnectionFailureException extends AbstractPlatformServiceUnavailableException { + + public ConnectionFailureException(final String connectionName) { + super("error.msg.unable.to.connect.to.intermediate.server", "Unable to connect to the server with connection name - " + + connectionName, connectionName); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/GatewayConnectionConfigurationNotFound.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/GatewayConnectionConfigurationNotFound.java new file mode 100644 index 000000000..cf9a31a24 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/GatewayConnectionConfigurationNotFound.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class GatewayConnectionConfigurationNotFound extends AbstractPlatformResourceNotFoundException { + + public GatewayConnectionConfigurationNotFound(String connectionName) { + super("error.msg.gateway.config.not.found", "Gateway Connection Configuration with name `" + connectionName + "` does not exist", + connectionName); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToBeDeletedException.java new file mode 100644 index 000000000..96fd8d3d7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToBeDeletedException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class SmsCampaignMustBeClosedToBeDeletedException extends AbstractPlatformDomainRuleException { + + public SmsCampaignMustBeClosedToBeDeletedException(final Long resourceId) { + super("error.msg.sms.campaign.cannot.be.deleted", + "Campaign with identifier " + resourceId + " cannot be deleted as it is not in `Closed` state.", resourceId); } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToEditException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToEditException.java new file mode 100644 index 000000000..5798d8f7e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignMustBeClosedToEditException.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class SmsCampaignMustBeClosedToEditException extends AbstractPlatformDomainRuleException { + + public SmsCampaignMustBeClosedToEditException(final Long resourceId) { + super("error.msg.sms.campaign.cannot.be.updated", + "Campaign with identifier " + resourceId + " cannot be updated as it is not in `Closed` state.", resourceId); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNotFound.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNotFound.java new file mode 100644 index 000000000..6bf0f4746 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNotFound.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class SmsCampaignNotFound extends AbstractPlatformResourceNotFoundException{ + + public SmsCampaignNotFound(final Long resourceId) { + super("error.msg.sms.campaign.identifier.not.found", "Sms Campaign with identifier `" + resourceId + "` does not exist", resourceId); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ActivateSmsCampaignCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ActivateSmsCampaignCommandHandler.java new file mode 100644 index 000000000..a6b56d353 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ActivateSmsCampaignCommandHandler.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "SMSCAMPAIGN", action = "ACTIVATE") +public class ActivateSmsCampaignCommandHandler implements NewCommandSourceHandler { + + private SmsCampaignWritePlatformService smsCampaignWritePlatformService; + + @Autowired + public ActivateSmsCampaignCommandHandler(final SmsCampaignWritePlatformService smsCampaignWritePlatformService) { + this.smsCampaignWritePlatformService = smsCampaignWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.smsCampaignWritePlatformService.activateSmsCampaign(command.entityId(), command); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CloseSmsCampaignCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CloseSmsCampaignCommandHandler.java new file mode 100644 index 000000000..0f1217e11 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CloseSmsCampaignCommandHandler.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "SMSCAMPAIGN", action = "CLOSE") +public class CloseSmsCampaignCommandHandler implements NewCommandSourceHandler { + private final SmsCampaignWritePlatformService smsCampaignWritePlatformService; + + @Autowired + public CloseSmsCampaignCommandHandler(final SmsCampaignWritePlatformService smsCampaignWritePlatformService) { + this.smsCampaignWritePlatformService = smsCampaignWritePlatformService; + } + + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.smsCampaignWritePlatformService.closeSmsCampaign(command.entityId(), command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CreateSmsCampaignCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CreateSmsCampaignCommandHandler.java new file mode 100644 index 000000000..687d9af16 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/CreateSmsCampaignCommandHandler.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "SMSCAMPAIGN", action = "CREATE") +public class CreateSmsCampaignCommandHandler implements NewCommandSourceHandler { + + private SmsCampaignWritePlatformService smsCampaignWritePlatformService; + + @Autowired + public CreateSmsCampaignCommandHandler(final SmsCampaignWritePlatformService smsCampaignWritePlatformService) { + this.smsCampaignWritePlatformService = smsCampaignWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.smsCampaignWritePlatformService.create(command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/DeleteSmsCampaignCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/DeleteSmsCampaignCommandHandler.java new file mode 100644 index 000000000..ebb5ca505 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/DeleteSmsCampaignCommandHandler.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "SMSCAMPAIGN", action = "DELETE") +public class DeleteSmsCampaignCommandHandler implements NewCommandSourceHandler { + + private final SmsCampaignWritePlatformService smsCampaignWritePlatformService; + + @Autowired + public DeleteSmsCampaignCommandHandler(SmsCampaignWritePlatformService smsCampaignWritePlatformService) { + this.smsCampaignWritePlatformService = smsCampaignWritePlatformService; + } + @Transactional + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.smsCampaignWritePlatformService.delete(command.entityId()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ReactivateSmsCampaignCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ReactivateSmsCampaignCommandHandler.java new file mode 100644 index 000000000..aa8bebe6f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/ReactivateSmsCampaignCommandHandler.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "SMSCAMPAIGN", action = "REACTIVATE") +public class ReactivateSmsCampaignCommandHandler implements NewCommandSourceHandler { + + private final SmsCampaignWritePlatformService smsCampaignWritePlatformService; + + @Autowired + public ReactivateSmsCampaignCommandHandler(SmsCampaignWritePlatformService smsCampaignWritePlatformService) { + this.smsCampaignWritePlatformService = smsCampaignWritePlatformService; + } + @Transactional + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.smsCampaignWritePlatformService.reactivateSmsCampaign(command.entityId(),command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/UpdateSmsCampaignCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/UpdateSmsCampaignCommandHandler.java new file mode 100644 index 000000000..e3c7f6615 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/handler/UpdateSmsCampaignCommandHandler.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "SMSCAMPAIGN", action = "UPDATE") +public class UpdateSmsCampaignCommandHandler implements NewCommandSourceHandler { + + private SmsCampaignWritePlatformService smsCampaignWritePlatformService; + + @Autowired + public UpdateSmsCampaignCommandHandler(final SmsCampaignWritePlatformService smsCampaignWritePlatformService) { + this.smsCampaignWritePlatformService = smsCampaignWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return this.smsCampaignWritePlatformService.update(command.entityId(), command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java new file mode 100644 index 000000000..948405490 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/serialization/SmsCampaignValidator.java @@ -0,0 +1,302 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.serialization; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; +import org.joda.time.LocalDate; +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 SmsCampaignValidator { + + public static final String RESOURCE_NAME = "sms"; + public static final String campaignName = "campaignName"; + public static final String campaignType = "campaignType"; + public static final String triggerType = "triggerType"; + public static final String triggerEntityType = "triggerEntityType"; + public static final String triggerActionType = "triggerActionType"; + public static final String providerId = "providerId"; + public static final String runReportId = "runReportId"; + public static final String paramValue = "paramValue"; + public static final String message = "message"; + public static final String activationDateParamName = "activationDate"; + public static final String recurrenceStartDate = "recurrenceStartDate"; + public static final String dateTimeFormat = "dateTimeFormat"; + public static final String submittedOnDateParamName = "submittedOnDate"; + public static final String closureDateParamName = "closureDate"; + public static final String recurrenceParamName = "recurrence"; + public static final String statusParamName = "status"; + + public static final String localeParamName = "locale"; + public static final String dateFormatParamName = "dateFormat"; + public static final String frequencyParamName = "frequency"; + public static final String intervalParamName = "interval"; + public static final String repeatsOnDayParamName = "repeatsOnDay"; + + private final FromJsonHelper fromApiJsonHelper; + + public static final Set supportedParams = new HashSet<>(Arrays.asList(campaignName, campaignType, localeParamName, + dateFormatParamName, runReportId, paramValue, message, recurrenceStartDate, activationDateParamName, submittedOnDateParamName, + closureDateParamName, recurrenceParamName, providerId, triggerType, frequencyParamName, intervalParamName, + repeatsOnDayParamName, triggerEntityType, triggerActionType, dateTimeFormat)); + + public static final Set supportedParamsForUpdate = new HashSet<>(Arrays.asList(campaignName, campaignType, localeParamName, + dateFormatParamName, runReportId, paramValue, message, recurrenceStartDate, activationDateParamName, recurrenceParamName, + providerId, triggerType, triggerEntityType, triggerActionType, dateTimeFormat)); + + public static final Set ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName, + activationDateParamName)); + + public static final Set CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName, + closureDateParamName)); + + public static final Set PREVIEW_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(paramValue, message)); + + @Autowired + public SmsCampaignValidator(FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateCreate(String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.supportedParams); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList<>(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsCampaignValidator.RESOURCE_NAME); + + final String campaignName = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.campaignName, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.campaignName).value(campaignName).notBlank().notExceedingLengthOf(100); + + final Long campaignType = this.fromApiJsonHelper.extractLongNamed(SmsCampaignValidator.campaignType, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.campaignType).value(campaignType).notNull().integerGreaterThanZero(); + + final Long triggerType = this.fromApiJsonHelper.extractLongNamed(SmsCampaignValidator.triggerType, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.triggerType).value(triggerType).notNull().integerGreaterThanZero(); + + if (triggerType.intValue() == SmsCampaignTriggerType.SCHEDULE.getValue()) { + + final Integer frequencyParam = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(SmsCampaignValidator.frequencyParamName, + element); + baseDataValidator.reset().parameter(SmsCampaignValidator.frequencyParamName).value(frequencyParam).notNull() + .integerGreaterThanZero(); + + final String intervalParam = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.intervalParamName, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.intervalParamName).value(intervalParam).notBlank(); + + if (frequencyParam != null && frequencyParam.equals(CalendarFrequencyType.WEEKLY.getValue())) { + final String repeatsOnDayParam = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.repeatsOnDayParamName, + element); + baseDataValidator.reset().parameter(SmsCampaignValidator.repeatsOnDayParamName).value(repeatsOnDayParam).notBlank(); + } + final String recurrenceStartDate = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.recurrenceStartDate, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.recurrenceStartDate).value(recurrenceStartDate).notBlank(); + } + + final Long runReportId = this.fromApiJsonHelper.extractLongNamed(SmsCampaignValidator.runReportId, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.runReportId).value(runReportId).notNull().integerGreaterThanZero(); + + final String message = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.message, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.message).value(message).notBlank().notExceedingLengthOf(480); + + final JsonElement paramValueJsonObject = this.fromApiJsonHelper.extractJsonObjectNamed(SmsCampaignValidator.paramValue, element); + if (triggerType.intValue() != SmsCampaignTriggerType.TRIGGERED.getValue()) { + baseDataValidator.reset().parameter(SmsCampaignValidator.paramValue).value(paramValueJsonObject).notBlank(); + if (paramValueJsonObject != null && paramValueJsonObject.isJsonObject()) { + for (Map.Entry entry : paramValueJsonObject.getAsJsonObject().entrySet()) { + final JsonElement inner = entry.getValue(); + baseDataValidator.reset().parameter(entry.getKey()).value(inner).notBlank(); + } + } + } + + if (this.fromApiJsonHelper.parameterExists(SmsCampaignValidator.submittedOnDateParamName, element)) { + final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(SmsCampaignValidator.submittedOnDateParamName, + element); + baseDataValidator.reset().parameter(SmsCampaignValidator.submittedOnDateParamName).value(submittedOnDate).notNull(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); + + } + + public void validateForUpdate(String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.supportedParamsForUpdate); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList<>(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsCampaignValidator.RESOURCE_NAME); + + final String campaignName = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.campaignName, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.campaignName).value(campaignName).notBlank().notExceedingLengthOf(100); + + final Long campaignType = this.fromApiJsonHelper.extractLongNamed(SmsCampaignValidator.campaignType, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.campaignType).value(campaignType).notNull().integerGreaterThanZero(); + + final Long triggerType = this.fromApiJsonHelper.extractLongNamed(SmsCampaignValidator.triggerType, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.triggerType).value(triggerType).notNull().integerGreaterThanZero(); + + if (triggerType.intValue() == SmsCampaignTriggerType.SCHEDULE.getValue()) { + if (this.fromApiJsonHelper.parameterExists(SmsCampaignValidator.recurrenceParamName, element)) { + final String recurrenceParamName = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.recurrenceParamName, + element); + baseDataValidator.reset().parameter(SmsCampaignValidator.recurrenceParamName).value(recurrenceParamName).notBlank(); + } + if (this.fromApiJsonHelper.parameterExists(SmsCampaignValidator.recurrenceStartDate, element)) { + final String recurrenceStartDate = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.recurrenceStartDate, + element); + baseDataValidator.reset().parameter(SmsCampaignValidator.recurrenceStartDate).value(recurrenceStartDate).notBlank(); + } + } + + if(this.fromApiJsonHelper.parameterExists(SmsCampaignValidator.runReportId, element)) { + final Long runReportId = this.fromApiJsonHelper.extractLongNamed(SmsCampaignValidator.runReportId, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.runReportId).value(runReportId).notNull().integerGreaterThanZero(); + } + + final String message = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.message, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.message).value(message).notBlank().notExceedingLengthOf(480); + + final JsonElement paramValueJsonObject = this.fromApiJsonHelper.extractJsonObjectNamed(SmsCampaignValidator.paramValue, element); + if (triggerType.intValue() != SmsCampaignTriggerType.TRIGGERED.getValue()) { + baseDataValidator.reset().parameter(SmsCampaignValidator.paramValue).value(paramValueJsonObject).notBlank(); + if (paramValueJsonObject != null && paramValueJsonObject.isJsonObject()) { + for (Map.Entry entry : paramValueJsonObject.getAsJsonObject().entrySet()) { + final JsonElement inner = entry.getValue(); + baseDataValidator.reset().parameter(entry.getKey()).value(inner).notBlank(); + } + } + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + + } + + public void validatePreviewMessage(String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.PREVIEW_REQUEST_DATA_PARAMETERS); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsCampaignValidator.RESOURCE_NAME); + final JsonElement paramValueJsonObject = this.fromApiJsonHelper.extractJsonObjectNamed(SmsCampaignValidator.paramValue, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.paramValue).value(paramValueJsonObject).notBlank(); + if (!paramValueJsonObject.isJsonNull() && paramValueJsonObject.isJsonObject()) { + for (Map.Entry entry : paramValueJsonObject.getAsJsonObject().entrySet()) { + final JsonElement inner = entry.getValue(); + baseDataValidator.reset().parameter(entry.getKey()).value(inner).notBlank(); + } + } + + final String message = this.fromApiJsonHelper.extractStringNamed(SmsCampaignValidator.message, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.message).value(message).notBlank().notExceedingLengthOf(480); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + + } + + public void validateClosedDate(String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.CLOSE_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsCampaignValidator.RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(SmsCampaignValidator.closureDateParamName, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.closureDateParamName).value(closeDate).notNull(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateActivation(String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.ACTIVATION_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsCampaignValidator.RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final LocalDate activationDate = this.fromApiJsonHelper + .extractLocalDateNamed(SmsCampaignValidator.activationDateParamName, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.activationDateParamName).value(activationDate).notNull(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void ValidateClosure(String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.CLOSE_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SmsCampaignValidator.RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(SmsCampaignValidator.closureDateParamName, element); + baseDataValidator.reset().parameter(SmsCampaignValidator.closureDateParamName).value(closeDate).notNull(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainService.java new file mode 100644 index 000000000..11faeb314 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainService.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.service; + +/** + * Created by Stefan on 4-7-2016. + */ +public interface SmsCampaignDomainService { +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java new file mode 100644 index 000000000..00926c4c4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java @@ -0,0 +1,537 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.campaigns.sms.service; + +import java.io.IOException; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.annotation.PostConstruct; + +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository; +import org.apache.fineract.infrastructure.sms.domain.SmsMessage; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository; +import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService; +import org.apache.fineract.organisation.office.domain.Office; +import org.apache.fineract.organisation.office.domain.OfficeRepository; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; +import org.apache.fineract.portfolio.common.service.BusinessEventListner; +import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.group.domain.Group; +import org.apache.fineract.portfolio.group.domain.GroupRepository; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { + + private static final Logger logger = LoggerFactory.getLogger(SmsCampaignDomainServiceImpl.class); + + private final SmsCampaignRepository smsCampaignRepository; + private final SmsMessageRepository smsMessageRepository; + private final OfficeRepository officeRepository; + private final BusinessEventNotifierService businessEventNotifierService; + private final SmsCampaignWritePlatformService smsCampaignWritePlatformCommandHandler; + private final GroupRepository groupRepository; + + private final SmsMessageScheduledJobService smsMessageScheduledJobService; + + @Autowired + public SmsCampaignDomainServiceImpl(final SmsCampaignRepository smsCampaignRepository, final SmsMessageRepository smsMessageRepository, + final BusinessEventNotifierService businessEventNotifierService, final OfficeRepository officeRepository, + final SmsCampaignWritePlatformService smsCampaignWritePlatformCommandHandler, + final GroupRepository groupRepository, + final SmsMessageScheduledJobService smsMessageScheduledJobService){ + this.smsCampaignRepository = smsCampaignRepository; + this.smsMessageRepository = smsMessageRepository; + this.businessEventNotifierService = businessEventNotifierService; + this.officeRepository = officeRepository; + this.smsCampaignWritePlatformCommandHandler = smsCampaignWritePlatformCommandHandler; + this.groupRepository = groupRepository; + this.smsMessageScheduledJobService = smsMessageScheduledJobService ; + } + + @PostConstruct + public void addListners() { + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPROVED, new SendSmsOnLoanApproved()); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_REJECTED, new SendSmsOnLoanRejected()); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, new SendSmsOnLoanRepayment()); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.CLIENTS_ACTIVATE, new ClientActivatedListener()); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.CLIENTS_REJECT, new ClientRejectedListener()); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_ACTIVATE, new SavingsAccountActivatedListener()); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_REJECT, new SavingsAccountRejectedListener()); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_DEPOSIT, new SavingsAccountTransactionListener(true)); + this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_WITHDRAWAL, new SavingsAccountTransactionListener(false)); + + + } + + private void notifyRejectedLoanOwner(Loan loan) { + ArrayList smsCampaigns = retrieveSmsCampaigns("loan rejected"); + if(smsCampaigns.size()>0){ + for (SmsCampaign campaign:smsCampaigns){ + if(campaign.isActive()) { + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(loan, campaign); + } + } + } + } + + private void notifyAcceptedLoanOwner(Loan loan) { + ArrayList smsCampaigns = retrieveSmsCampaigns("loan approved"); + + if(smsCampaigns.size()>0){ + for (SmsCampaign campaign:smsCampaigns){ + if(campaign.isActive()) { + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(loan, campaign); + } + } + } + } + + private void notifyClientActivated(final Client client) { + ArrayList smsCampaigns = retrieveSmsCampaigns("client activated"); + if(smsCampaigns.size()>0){ + for (SmsCampaign campaign:smsCampaigns){ + if(campaign.isActive()) { + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(client, campaign); + } + } + } + + } + + private void notifyClientRejected(final Client client) { + ArrayList smsCampaigns = retrieveSmsCampaigns("client rejected"); + if(smsCampaigns.size()>0){ + for (SmsCampaign campaign:smsCampaigns){ + if(campaign.isActive()) { + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(client, campaign); + } + } + } + + } + + private void notifySavingsAccountActivated(final SavingsAccount savingsAccount) { + ArrayList smsCampaigns = retrieveSmsCampaigns("savings activated"); + if (smsCampaigns.size() > 0) { + for (SmsCampaign campaign : smsCampaigns) { + if (campaign.isActive()) { + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(savingsAccount, + campaign); + } + } + } + + } + + private void notifySavingsAccountRejected(final SavingsAccount savingsAccount) { + ArrayList smsCampaigns = retrieveSmsCampaigns("savings rejected"); + if (smsCampaigns.size() > 0) { + for (SmsCampaign campaign : smsCampaigns) { + if (campaign.isActive()) { + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(savingsAccount, + campaign); + } + } + } + + } + + private void sendSmsForLoanRepayment(LoanTransaction loanTransaction) { + ArrayList smsCampaigns = retrieveSmsCampaigns("loan repayment"); + if (smsCampaigns.size() > 0) { + for (SmsCampaign smsCampaign : smsCampaigns) { + if (smsCampaign.isActive()) { + try { + Loan loan = loanTransaction.getLoan(); + final Set groupClients = new HashSet<>(); + if (loan.hasInvalidLoanType()) { + throw new InvalidLoanTypeException("Loan Type cannot be Invalid for the Triggered Sms Campaign"); + } + if (loan.isGroupLoan()) { + Group group = this.groupRepository.findOne(loan.getGroupId()); + groupClients.addAll(group.getClientMembers()); + } else { + groupClients.add(loan.client()); + } + HashMap campaignParams = new ObjectMapper() + .readValue(smsCampaign.getParamValue(), new TypeReference>() { + }); + + if (groupClients.size() > 0) { + for (Client client : groupClients) { + HashMap smsParams = processRepaymentDataForSms(loanTransaction, client); + for (String key : campaignParams.keySet()) { + String value = campaignParams.get(key); + String spvalue = null; + boolean spkeycheck = smsParams.containsKey(key); + if (spkeycheck) { + spvalue = smsParams.get(key).toString(); + } + if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { + if (key.equals("officeId")) { + Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value)); + if (campaignOffice + .doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) { + throw new RuntimeException(); + } + } else { + throw new RuntimeException(); + } + } + } + String message = this.smsCampaignWritePlatformCommandHandler.compileSmsTemplate( + smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams); + Object mobileNo = smsParams.get("mobileNo"); + if (mobileNo != null) { + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message, + mobileNo.toString(), smsCampaign); + this.smsMessageRepository.save(smsMessage); + Collection messages = new ArrayList<>(); + messages.add(smsMessage); + Map> smsDataMap = new HashMap<>(); + smsDataMap.put(smsCampaign, messages); + this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); + } + } + } + } catch (final IOException e) { + logger.error("smsParams does not contain the key: " + e.getMessage()); + } catch (final RuntimeException e) { + logger.debug("Client Office Id and SMS Campaign Office id doesn't match"); + } + } + } + } + } + + private void sendSmsForSavingsTransaction(final SavingsAccountTransaction savingsTransaction, boolean isDeposit) { + String campaignName = isDeposit ? "savings deposit" : "savings withdrawal"; + ArrayList smsCampaigns = retrieveSmsCampaigns(campaignName); + if (smsCampaigns.size() > 0) { + for (SmsCampaign smsCampaign : smsCampaigns) { + if (smsCampaign.isActive()) { + try { + final SavingsAccount savingsAccount = savingsTransaction.getSavingsAccount(); + final Client client = savingsAccount.getClient(); + HashMap campaignParams = new ObjectMapper() + .readValue(smsCampaign.getParamValue(), new TypeReference>() { + }); + HashMap smsParams = processSavingsTransactionDataForSms(savingsTransaction, + client); + for (String key : campaignParams.keySet()) { + String value = campaignParams.get(key); + String spvalue = null; + boolean spkeycheck = smsParams.containsKey(key); + if (spkeycheck) { + spvalue = smsParams.get(key).toString(); + } + if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { + if (key.equals("officeId")) { + Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value)); + if (campaignOffice + .doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) { + throw new RuntimeException(); + } + } else { + throw new RuntimeException(); + } + } + } + String message = this.smsCampaignWritePlatformCommandHandler + .compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams); + Object mobileNo = smsParams.get("mobileNo"); + if (mobileNo != null) { + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message, + mobileNo.toString(), smsCampaign); + this.smsMessageRepository.save(smsMessage); + Collection messages = new ArrayList<>(); + messages.add(smsMessage); + Map> smsDataMap = new HashMap<>(); + smsDataMap.put(smsCampaign, messages); + this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); + } + } catch (final IOException e) { + logger.error("smsParams does not contain the key: " + e.getMessage()); + } catch (final RuntimeException e) { + logger.debug("Client Office Id and SMS Campaign Office id doesn't match"); + } + } + } + } + } + + private ArrayList retrieveSmsCampaigns(String paramValue){ + Collection initialSmsCampaignList = smsCampaignRepository.findByTriggerType(SmsCampaignTriggerType.TRIGGERED.getValue()); + ArrayList smsCampaigns = new ArrayList<>(); + + for(SmsCampaign campaign : initialSmsCampaignList){ + if(campaign.getParamValue().toLowerCase().contains(paramValue)){ + smsCampaigns.add(campaign); + } + } + return smsCampaigns; + } + + private HashMap processRepaymentDataForSms(final LoanTransaction loanTransaction, Client groupClient){ + + HashMap smsParams = new HashMap(); + Loan loan = loanTransaction.getLoan(); + final Client client; + if(loan.isGroupLoan() && groupClient != null){ + client = groupClient; + }else if(loan.isIndividualLoan()){ + client = loan.getClient(); + }else{ + throw new InvalidParameterException(""); + } + + DateTimeFormatter timeFormatter = DateTimeFormat.forPattern("HH:mm"); + DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("MMM:d:yyyy"); + + smsParams.put("id",loanTransaction.getLoan().getClientId()); + smsParams.put("firstname",client.getFirstname()); + smsParams.put("middlename",client.getMiddlename()); + smsParams.put("lastname",client.getLastname()); + smsParams.put("FullName",client.getDisplayName()); + smsParams.put("mobileNo",client.mobileNo()); + smsParams.put("LoanAmount",loan.getPrincpal()); + smsParams.put("LoanOutstanding",loanTransaction.getOutstandingLoanBalance()); + smsParams.put("loanId",loan.getId()); + smsParams.put("LoanAccountId", loan.getAccountNumber()); + smsParams.put("officeId", client.getOffice().getId()); + + if(client.getStaff() != null) { + smsParams.put("loanOfficerId", client.getStaff().getId()); + }else { + smsParams.put("loanOfficerId", -1); + } + + smsParams.put("repaymentAmount", loanTransaction.getAmount(loan.getCurrency())); + smsParams.put("RepaymentDate", loanTransaction.getCreatedDateTime().toLocalDate().toString(dateFormatter)); + smsParams.put("RepaymentTime", loanTransaction.getCreatedDateTime().toLocalTime().toString(timeFormatter)); + + if(loanTransaction.getPaymentDetail() != null) { + smsParams.put("receiptNumber", loanTransaction.getPaymentDetail().getReceiptNumber()); + }else { + smsParams.put("receiptNumber", -1); + } + return smsParams; + } + + private HashMap processSavingsTransactionDataForSms(final SavingsAccountTransaction savingsAccountTransaction, Client client){ + + // {{savingsId}} {{id}} {{firstname}} {{middlename}} {{lastname}} {{FullName}} {{mobileNo}} {{savingsAccountId}} {{depositAmount}} {{balance}} + + //transactionDate + HashMap smsParams = new HashMap(); + SavingsAccount savingsAccount = savingsAccountTransaction.getSavingsAccount() ; + DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("MMM:d:yyyy"); + smsParams.put("clientId",client.getId()); + smsParams.put("firstname",client.getFirstname()); + smsParams.put("middlename",client.getMiddlename()); + smsParams.put("lastname",client.getLastname()); + smsParams.put("FullName",client.getDisplayName()); + smsParams.put("mobileNo",client.mobileNo()); + smsParams.put("savingsId", savingsAccount.getId()) ; + smsParams.put("savingsAccountNo",savingsAccount.getAccountNumber()); + smsParams.put("withdrawAmount",savingsAccountTransaction.getAmount(savingsAccount.getCurrency())); + smsParams.put("depositAmount",savingsAccountTransaction.getAmount(savingsAccount.getCurrency())); + smsParams.put("balance",savingsAccount.getWithdrawableBalance()); + smsParams.put("officeId", client.getOffice().getId()); + smsParams.put("transactionDate", savingsAccountTransaction.getTransactionLocalDate().toString(dateFormatter)) ; + smsParams.put("savingsTransactionId", savingsAccountTransaction.getId()) ; + + if(client.getStaff() != null) { + smsParams.put("loanOfficerId", client.getStaff().getId()); + }else { + smsParams.put("loanOfficerId", -1); + } + + if(savingsAccountTransaction.getPaymentDetail() != null) { + smsParams.put("receiptNumber", savingsAccountTransaction.getPaymentDetail().getReceiptNumber()); + }else { + smsParams.put("receiptNumber", -1); + } + return smsParams; + } + + private class SendSmsOnLoanApproved implements BusinessEventListner{ + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.LOAN); + if (entity instanceof Loan) { + Loan loan = (Loan) entity; + notifyAcceptedLoanOwner(loan); + } + } + } + + private class SendSmsOnLoanRejected implements BusinessEventListner{ + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.LOAN); + if (entity instanceof Loan) { + Loan loan = (Loan) entity; + notifyRejectedLoanOwner(loan); + } + } + } + + private class SendSmsOnLoanRepayment implements BusinessEventListner{ + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.LOAN_TRANSACTION); + if (entity instanceof LoanTransaction) { + LoanTransaction loanTransaction = (LoanTransaction) entity; + sendSmsForLoanRepayment(loanTransaction); + } + } + } + + private class ClientActivatedListener implements BusinessEventListner { + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.CLIENT); + if(entity instanceof Client) { + notifyClientActivated((Client)entity); + } + } + } + + private class ClientRejectedListener implements BusinessEventListner { + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.CLIENT); + if(entity instanceof Client) { + notifyClientRejected((Client)entity); + } + + } + } + + private class SavingsAccountActivatedListener implements BusinessEventListner { + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.SAVING); + if(entity instanceof SavingsAccount) { + notifySavingsAccountActivated((SavingsAccount)entity); + } + + } + } + + private class SavingsAccountRejectedListener implements BusinessEventListner { + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.SAVING); + if(entity instanceof SavingsAccount) { + notifySavingsAccountRejected((SavingsAccount)entity); + } + } + } + + private class SavingsAccountTransactionListener implements BusinessEventListner { + + final boolean isDeposit ; + + public SavingsAccountTransactionListener(final boolean isDeposit) { + this.isDeposit = isDeposit ; + } + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + // TODO Auto-generated method stub + + } + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.SAVINGS_TRANSACTION); + if(entity instanceof SavingsAccountTransaction) { + sendSmsForSavingsTransaction((SavingsAccountTransaction)entity, this.isDeposit); + } + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java new file mode 100644 index 000000000..bb9814c2c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.service; + +import java.util.Collection; + +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData; +import org.apache.fineract.infrastructure.campaigns.sms.data.TriggerTypeWithSubTypesData; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +public interface SmsCampaignDropdownReadPlatformService { + + Collection retrieveCampaignTriggerTypes(); + + Collection retrieveSmsProviders(); + + Collection retrieveCampaignTypes(); + + Collection retrieveWeeks(); + + Collection retrieveMonths(); + + Collection retrivePeriodFrequencyTypes(); + + Collection getTriggerTypeAndSubTypes(); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java new file mode 100644 index 000000000..44c92b95a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java @@ -0,0 +1,125 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.service; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.fineract.infrastructure.campaigns.constants.CampaignType; +import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignEnumerations; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignTriggerWithSubTypes; +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData; +import org.apache.fineract.infrastructure.campaigns.sms.data.TriggerTypeWithSubTypesData; +import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; +import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class SmsCampaignDropdownReadPlatformServiceImpl implements SmsCampaignDropdownReadPlatformService { + + private final RestTemplate restTemplate; + + private final SmsConfigUtils smsConfigUtils ; + + @Autowired + public SmsCampaignDropdownReadPlatformServiceImpl(final SmsConfigUtils smsConfigUtils) { + this.restTemplate = new RestTemplate(); + this.smsConfigUtils = smsConfigUtils ; + } + + @Override + public Collection retrieveCampaignTriggerTypes() { + final List triggerTypeCodeValues = Arrays.asList( // + SmsCampaignEnumerations.smscampaignTriggerType(SmsCampaignTriggerType.DIRECT), // + SmsCampaignEnumerations.smscampaignTriggerType(SmsCampaignTriggerType.SCHEDULE), // + SmsCampaignEnumerations.smscampaignTriggerType(SmsCampaignTriggerType.TRIGGERED) // + ); + + return triggerTypeCodeValues; + } + + @Override + public Collection retrieveSmsProviders() { + Collection smsProviderOptions = new ArrayList<>(); + try { + Map hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("smsbridges", null); + URI uri = (URI) hostConfig.get("uri"); + HttpEntity entity = (HttpEntity) hostConfig.get("entity"); + ResponseEntity> responseOne = restTemplate.exchange(uri, HttpMethod.GET, entity, + new ParameterizedTypeReference>() {}); + smsProviderOptions = responseOne.getBody(); + if (!responseOne.getStatusCode().equals(HttpStatus.OK)) { + System.out.println(responseOne.getStatusCode().name()); + throw new ConnectionFailureException(SmsCampaignConstants.SMS_BRIDGE); + } + } catch (Exception e) { + e.getStackTrace(); + } + return smsProviderOptions; + } + + @Override + public Collection retrieveCampaignTypes() { + final List campaignTypeCodeValues = Arrays.asList( // + SmsCampaignEnumerations.smscampaignType(CampaignType.SMS)// + ); + return campaignTypeCodeValues; + } + + @Override + public Collection retrieveMonths() { + Collection monthsList = SmsCampaignEnumerations.calendarMonthType(); + return monthsList; + } + + @Override + public Collection retrieveWeeks() { + Collection weeksList = CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.values()); + return weeksList; + } + + @Override + public Collection retrivePeriodFrequencyTypes() { + Collection periodFrequencyTypes = SmsCampaignEnumerations + .calendarPeriodFrequencyTypes(PeriodFrequencyType.values()); + return periodFrequencyTypes; + } + + @Override + public Collection getTriggerTypeAndSubTypes() { + return CampaignTriggerWithSubTypes.getTriggerTypeAndSubTypes(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformService.java new file mode 100644 index 000000000..8f478bce3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformService.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.service; + +import java.util.Collection; +import java.util.List; + +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsCampaignData; + +public interface SmsCampaignReadPlatformService { + + public SmsCampaignData retrieveOne(final Long campaignId); + + public List retrieveAll(); + + public SmsCampaignData retrieveTemplate(final String reportType); + + Collection retrieveAllScheduleActiveCampaign(); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java new file mode 100644 index 000000000..ba7be611f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignReadPlatformServiceImpl.java @@ -0,0 +1,260 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.campaigns.constants.CampaignType; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsBusinessRulesData; +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsCampaignData; +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsCampaignTimeLine; +import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignStatusEnumerations; +import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.portfolio.calendar.service.CalendarDropdownReadPlatformService; +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class SmsCampaignReadPlatformServiceImpl implements SmsCampaignReadPlatformService { + + private final BusinessRuleMapper businessRuleMapper; + private final JdbcTemplate jdbcTemplate; + private final SmsCampaignDropdownReadPlatformService smsCampaignDropdownReadPlatformService; + private final SmsCampaignMapper smsCampaignMapper; + private final CalendarDropdownReadPlatformService calendarDropdownReadPlatformService; + + @Autowired + public SmsCampaignReadPlatformServiceImpl(final RoutingDataSource dataSource, + SmsCampaignDropdownReadPlatformService smsCampaignDropdownReadPlatformService, + final CalendarDropdownReadPlatformService calendarDropdownReadPlatformService) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + businessRuleMapper = new BusinessRuleMapper(); + this.smsCampaignDropdownReadPlatformService = smsCampaignDropdownReadPlatformService; + smsCampaignMapper = new SmsCampaignMapper(); + this.calendarDropdownReadPlatformService = calendarDropdownReadPlatformService; + } + + @Override + public SmsCampaignData retrieveOne(Long campaignId) { + final Integer isVisible = 1; + try { + final String sql = "select " + this.smsCampaignMapper.schema + " where sc.id = ? and sc.is_visible = ?"; + return this.jdbcTemplate.queryForObject(sql, this.smsCampaignMapper, new Object[] { campaignId, isVisible }); + } catch (final EmptyResultDataAccessException e) { + throw new SmsCampaignNotFound(campaignId); + } + } + + @Override + public List retrieveAll() { + final Integer visible = 1; + final String sql = "select " + this.smsCampaignMapper.schema() + " where sc.is_visible = ?"; + return this.jdbcTemplate.query(sql, this.smsCampaignMapper, new Object[] {visible}); + } + + @Override + public SmsCampaignData retrieveTemplate(final String reportType) { + String sql = "select " + this.businessRuleMapper.schema(); + if (!StringUtils.isEmpty(reportType)) { + sql = sql + " where sr.report_type = ?"; + } + final Collection businessRulesOptions = this.jdbcTemplate.query(sql, this.businessRuleMapper, + new Object[] { reportType }); + final Collection smsProviderOptions = this.smsCampaignDropdownReadPlatformService.retrieveSmsProviders(); + final Collection campaignTypeOptions = this.smsCampaignDropdownReadPlatformService.retrieveCampaignTypes(); + final Collection campaignTriggerTypeOptions = this.smsCampaignDropdownReadPlatformService + .retrieveCampaignTriggerTypes(); + final Collection months = this.smsCampaignDropdownReadPlatformService.retrieveMonths(); + final Collection weekDays = this.smsCampaignDropdownReadPlatformService.retrieveWeeks(); + final Collection frequencyTypeOptions = this.calendarDropdownReadPlatformService + .retrieveCalendarFrequencyTypeOptions(); + final Collection periodFrequencyOptions = this.smsCampaignDropdownReadPlatformService.retrivePeriodFrequencyTypes(); + //final Collection triggerTypeSubTypeOptions = this.smsCampaignDropdownReadPlatformService.getTriggerTypeAndSubTypes(); + return SmsCampaignData.template(smsProviderOptions, campaignTypeOptions, businessRulesOptions, campaignTriggerTypeOptions, months, + weekDays, frequencyTypeOptions, periodFrequencyOptions); + } + + @Override + public Collection retrieveAllScheduleActiveCampaign() { + return null; + } + + private static final class BusinessRuleMapper implements ResultSetExtractor> { + + final String schema; + + private BusinessRuleMapper() { + final StringBuilder sql = new StringBuilder(300); + sql.append("sr.id as id, "); + sql.append("sr.report_name as reportName, "); + sql.append("sr.report_type as reportType, "); + sql.append("sr.report_subtype as reportSubType, "); + sql.append("sr.description as description, "); + sql.append("sp.parameter_variable as params, "); + sql.append("sp.parameter_FormatType as paramType, "); + sql.append("sp.parameter_label as paramLabel, "); + sql.append("sp.parameter_name as paramName "); + sql.append("from stretchy_report sr "); + sql.append("left join stretchy_report_parameter as srp on srp.report_id = sr.id "); + sql.append("left join stretchy_parameter as sp on sp.id = srp.parameter_id "); + + this.schema = sql.toString(); + } + + public String schema() { + return this.schema; + } + + @Override + public List extractData(ResultSet rs) throws SQLException, DataAccessException { + List smsBusinessRulesDataList = new ArrayList(); + + SmsBusinessRulesData smsBusinessRulesData = null; + + Map mapOfSameObjects = new HashMap(); + + while (rs.next()) { + final Long id = rs.getLong("id"); + smsBusinessRulesData = mapOfSameObjects.get(id); + if (smsBusinessRulesData == null) { + final String reportName = rs.getString("reportName"); + final String reportType = rs.getString("reportType"); + final String reportSubType = rs.getString("reportSubType"); + final String paramName = rs.getString("paramName"); + final String paramLabel = rs.getString("paramLabel"); + final String description = rs.getString("description"); + + Map hashMap = new HashMap(); + hashMap.put(paramLabel, paramName); + smsBusinessRulesData = SmsBusinessRulesData.instance(id, reportName, reportType, reportSubType, hashMap, description); + mapOfSameObjects.put(id, smsBusinessRulesData); + // add to the list + smsBusinessRulesDataList.add(smsBusinessRulesData); + } + // add new paramType to the existing object + Map hashMap = new HashMap(); + final String paramName = rs.getString("paramName"); + final String paramLabel = rs.getString("paramLabel"); + hashMap.put(paramLabel, paramName); + + // get existing map and add new items to it + smsBusinessRulesData.getReportParamName().putAll(hashMap); + } + + return smsBusinessRulesDataList; + } + } + + private static final class SmsCampaignMapper implements RowMapper { + + final String schema; + + private SmsCampaignMapper() { + final StringBuilder sql = new StringBuilder(400); + sql.append("sc.id as id, "); + sql.append("sc.campaign_name as campaignName, "); + sql.append("sc.campaign_type as campaignType, "); + sql.append("sc.campaign_trigger_type as triggerType, "); + sql.append("sc.report_id as runReportId, "); + sql.append("sc.message as message, "); + sql.append("sc.param_value as paramValue, "); + sql.append("sc.status_enum as status, "); + sql.append("sc.recurrence as recurrence, "); + sql.append("sc.recurrence_start_date as recurrenceStartDate, "); + sql.append("sc.next_trigger_date as nextTriggerDate, "); + sql.append("sc.last_trigger_date as lastTriggerDate, "); + sql.append("sc.submittedon_date as submittedOnDate, "); + sql.append("sbu.username as submittedByUsername, "); + sql.append("sc.closedon_date as closedOnDate, "); + sql.append("clu.username as closedByUsername, "); + sql.append("acu.username as activatedByUsername, "); + sql.append("sc.approvedon_date as activatedOnDate, "); + sql.append("sr.report_name as reportName, "); + sql.append("provider_id as providerId "); + sql.append("from sms_campaign sc "); + sql.append("left join m_appuser sbu on sbu.id = sc.submittedon_userid "); + sql.append("left join m_appuser acu on acu.id = sc.approvedon_userid "); + sql.append("left join m_appuser clu on clu.id = sc.closedon_userid "); + sql.append("left join stretchy_report sr on sr.id = sc.report_id "); + + this.schema = sql.toString(); + } + + public String schema() { + return this.schema; + } + + @Override + public SmsCampaignData mapRow(ResultSet rs, int rowNum) throws SQLException { + final Long id = JdbcSupport.getLong(rs, "id"); + final String campaignName = rs.getString("campaignName"); + final Integer campaignType = JdbcSupport.getInteger(rs, "campaignType"); + final EnumOptionData campaignTypeEnum = CampaignType.campaignType(campaignType); + final Long runReportId = JdbcSupport.getLong(rs, "runReportId"); + final String paramValue = rs.getString("paramValue"); + final String message = rs.getString("message"); + + final Integer statusId = JdbcSupport.getInteger(rs, "status"); + final EnumOptionData status = SmsCampaignStatusEnumerations.status(statusId); + final Integer triggerType = JdbcSupport.getInteger(rs, "triggerType"); + final EnumOptionData triggerTypeEnum = SmsCampaignTriggerType.triggerType(triggerType); + + final DateTime nextTriggerDate = JdbcSupport.getDateTime(rs, "nextTriggerDate"); + final LocalDate lastTriggerDate = JdbcSupport.getLocalDate(rs, "lastTriggerDate"); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); + final String activatedByUsername = rs.getString("activatedByUsername"); + final String recurrence = rs.getString("recurrence"); + final DateTime recurrenceStartDate = JdbcSupport.getDateTime(rs, "recurrenceStartDate"); + final SmsCampaignTimeLine smsCampaignTimeLine = new SmsCampaignTimeLine(submittedOnDate, submittedByUsername, activatedOnDate, + activatedByUsername, closedOnDate, closedByUsername); + final String reportName = rs.getString("reportName"); + final Long providerId = rs.getLong("providerId"); + + return SmsCampaignData.instance(id, campaignName, campaignTypeEnum, triggerTypeEnum, runReportId, reportName, paramValue, status, message, nextTriggerDate, lastTriggerDate, + smsCampaignTimeLine, recurrenceStartDate, recurrence, providerId); + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformService.java new file mode 100644 index 000000000..b045a9390 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformService.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.service; + +import java.util.Map; + +import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignPreviewData; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.api.JsonQuery; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; + +public interface SmsCampaignWritePlatformService { + + CommandProcessingResult create(JsonCommand command); + + CommandProcessingResult update(Long resourceId, JsonCommand command); + + CommandProcessingResult delete(Long resourceId); + + CommandProcessingResult activateSmsCampaign(Long campaignId, JsonCommand command); + + CommandProcessingResult closeSmsCampaign(Long campaignId, JsonCommand command); + + CommandProcessingResult reactivateSmsCampaign(Long campaignId, JsonCommand command); + + void insertDirectCampaignIntoSmsOutboundTable(Loan loan, SmsCampaign smsCampaign); + + String compileSmsTemplate(String textMessageTemplate, String campaignName, Map smsParams); + + CampaignPreviewData previewMessage(JsonQuery query); + + public void storeTemplateMessageIntoSmsOutBoundTable() throws JobExecutionException; + + public void insertDirectCampaignIntoSmsOutboundTable(final Client client, final SmsCampaign smsCampaign) ; + + public void insertDirectCampaignIntoSmsOutboundTable(final SavingsAccount savingsAccount, final SmsCampaign smsCampaign) ; + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java new file mode 100644 index 000000000..c4d47e858 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java @@ -0,0 +1,687 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.service; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.fineract.infrastructure.campaigns.constants.CampaignType; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignPreviewData; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository; +import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToBeDeletedException; +import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToEditException; +import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound; +import org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.api.JsonQuery; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData; +import org.apache.fineract.infrastructure.dataqueries.domain.Report; +import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository; +import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException; +import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService; +import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService; +import org.apache.fineract.infrastructure.jobs.annotation.CronTarget; +import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.sms.domain.SmsMessage; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType; +import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.Group; +import org.apache.fineract.portfolio.group.domain.GroupRepository; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.useradministration.domain.AppUser; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; +import com.google.gson.JsonElement; + +@Service +public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWritePlatformService { + + private final static Logger logger = LoggerFactory.getLogger(SmsCampaignWritePlatformServiceJpaImpl.class); + + private final PlatformSecurityContext context; + + private final SmsCampaignRepository smsCampaignRepository; + private final SmsCampaignValidator smsCampaignValidator; + private final ReportRepository reportRepository; + private final SmsMessageRepository smsMessageRepository; + private final ClientRepositoryWrapper clientRepositoryWrapper; + private final GroupRepository groupRepository; + private final ReadReportingService readReportingService; + private final GenericDataService genericDataService; + private final FromJsonHelper fromJsonHelper; + + private final SmsMessageScheduledJobService smsMessageScheduledJobService; + + @Autowired + public SmsCampaignWritePlatformServiceJpaImpl(final PlatformSecurityContext context, final SmsCampaignRepository smsCampaignRepository, + final SmsCampaignValidator smsCampaignValidator, final ReportRepository reportRepository, + final SmsMessageRepository smsMessageRepository, final ClientRepositoryWrapper clientRepositoryWrapper, + final ReadReportingService readReportingService, final GenericDataService genericDataService, + final FromJsonHelper fromJsonHelper, final GroupRepository groupRepository, + final SmsMessageScheduledJobService smsMessageScheduledJobService) { + this.context = context; + this.smsCampaignRepository = smsCampaignRepository; + this.smsCampaignValidator = smsCampaignValidator; + this.reportRepository = reportRepository; + this.smsMessageRepository = smsMessageRepository; + this.clientRepositoryWrapper = clientRepositoryWrapper; + this.readReportingService = readReportingService; + this.genericDataService = genericDataService; + this.fromJsonHelper = fromJsonHelper; + this.groupRepository = groupRepository; + this.smsMessageScheduledJobService = smsMessageScheduledJobService ; + } + + @Transactional + @Override + public CommandProcessingResult create(JsonCommand command) { + + final AppUser currentUser = this.context.authenticatedUser(); + this.smsCampaignValidator.validateCreate(command.json()); + final Long runReportId = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId); + Report report = this.reportRepository.findOne(runReportId); + if (report == null) { throw new ReportNotFoundException(runReportId); } + SmsCampaign smsCampaign = SmsCampaign.instance(currentUser, report, command); + + this.smsCampaignRepository.save(smsCampaign); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(smsCampaign.getId()) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult update(final Long resourceId, final JsonCommand command) { + try { + this.context.authenticatedUser(); + + this.smsCampaignValidator.validateForUpdate(command.json()); + final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(resourceId); + + if (smsCampaign == null) { throw new SmsCampaignNotFound(resourceId); } + if (smsCampaign.isActive()) { throw new SmsCampaignMustBeClosedToEditException(smsCampaign.getId()); } + final Map changes = smsCampaign.update(command); + + if (changes.containsKey(SmsCampaignValidator.runReportId)) { + final Long newValue = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId); + final Report reportId = this.reportRepository.findOne(newValue); + if (reportId == null) { throw new ReportNotFoundException(newValue); } + ; + smsCampaign.updateBusinessRuleId(reportId); + } + + if (!changes.isEmpty()) { + this.smsCampaignRepository.saveAndFlush(smsCampaign); + } + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(resourceId) // + .with(changes) // + .build(); + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + + } + + @Transactional + @Override + public CommandProcessingResult delete(final Long resourceId) { + this.context.authenticatedUser(); + final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(resourceId); + + if (smsCampaign == null) { throw new SmsCampaignNotFound(resourceId); } + if (smsCampaign.isActive()) { throw new SmsCampaignMustBeClosedToBeDeletedException(smsCampaign.getId()); } + + /* + * Do not delete but set a boolean is_visible to zero + */ + smsCampaign.delete(); + this.smsCampaignRepository.saveAndFlush(smsCampaign); + + return new CommandProcessingResultBuilder() // + .withEntityId(smsCampaign.getId()) // + .build(); + + } + + private void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign) { + try { + HashMap campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + + HashMap queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + + List> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"), + queryParamForRunReport); + + if (runReportObject != null) { + for (HashMap entry : runReportObject) { + String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), entry); + Integer clientId = (Integer) entry.get("id"); + Object mobileNo = entry.get("mobileNo"); + + Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue()); + if (mobileNo != null) { +// String countryCode = this.smsReadPlatformService.retrieveCountryCode(client.getOffice().getId()).getCountryCode(); + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNo.toString(), + smsCampaign); + this.smsMessageRepository.save(smsMessage); + } + } + } + } catch (final IOException e) { + // TODO throw something here + System.out.println(e.getMessage()); + } + + } + + @Override + public void insertDirectCampaignIntoSmsOutboundTable(final Loan loan, final SmsCampaign smsCampaign) { + try { + if (loan.hasInvalidLoanType()) { throw new InvalidLoanTypeException("Loan Type cannot be 0 for the Triggered Sms Campaign"); } + + Set clientSet = new HashSet<>(); + + HashMap campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + campaignParams.put("loanId", loan.getId().toString()); + + HashMap queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + queryParamForRunReport.put("loanId", loan.getId().toString()); + + if (loan.isGroupLoan()) { + Group group = this.groupRepository.findOne(loan.getGroupId()); + clientSet.addAll(group.getClientMembers()); + queryParamForRunReport.put("groupId", group.getId().toString()); + } else { + Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(loan.getClientId()); + clientSet.add(client); + } + + for (Client client : clientSet) { + campaignParams.put("clientId", client.getId().toString()); + queryParamForRunReport.put("clientId", client.getId().toString()); + + List> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"), + queryParamForRunReport); + + if (runReportObject != null && runReportObject.size() > 0) { + for (HashMap entry : runReportObject) { + String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), entry); + Object mobileNo = entry.get("mobileNo"); + + if (mobileNo != null) { + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNo.toString(), + smsCampaign); + smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue()); + this.smsMessageRepository.save(smsMessage); + Collection messages = new ArrayList<>() ; + messages.add(smsMessage) ; + Map> smsDataMap = new HashMap<>() ; + smsDataMap.put(smsCampaign, messages) ; + this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); + } + } + } + } + } catch (final IOException e) { + System.out.println("IOException: " + e.getMessage()); + } catch (final RuntimeException e) { + System.out.println("RuntimeException: " + e.getMessage()); + } + } + + @Override + public void insertDirectCampaignIntoSmsOutboundTable(final Client client, final SmsCampaign smsCampaign) { + try { + HashMap campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + campaignParams.put("clientId", client.getId().toString()); + HashMap queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + + campaignParams.put("clientId", client.getId().toString()); + queryParamForRunReport.put("clientId", client.getId().toString()); + + List> runReportObject = this + .getRunReportByServiceImpl(campaignParams.get("reportName"), queryParamForRunReport); + + if (runReportObject != null && runReportObject.size() > 0) { + for (HashMap entry : runReportObject) { + String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(), + smsCampaign.getCampaignName(), entry); + Object mobileNo = entry.get("mobileNo"); + + if (mobileNo != null) { + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, + mobileNo.toString(), smsCampaign); + smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue()); + this.smsMessageRepository.save(smsMessage); + Collection messages = new ArrayList<>(); + messages.add(smsMessage); + Map> smsDataMap = new HashMap<>(); + smsDataMap.put(smsCampaign, messages); + this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); + } + } + } + } catch (final IOException e) { + System.out.println("IOException: " + e.getMessage()); + } catch (final RuntimeException e) { + System.out.println("RuntimeException: " + e.getMessage()); + } + } + + @Override + public void insertDirectCampaignIntoSmsOutboundTable(final SavingsAccount savingsAccount, final SmsCampaign smsCampaign) { + try { + HashMap campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + campaignParams.put("savingsId", savingsAccount.getId().toString()); + HashMap queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() {}); + queryParamForRunReport.put("savingsId", savingsAccount.getId().toString()); + + Client client = savingsAccount.getClient() ; + List> runReportObject = this + .getRunReportByServiceImpl(campaignParams.get("reportName"), queryParamForRunReport); + + if (runReportObject != null && runReportObject.size() > 0) { + for (HashMap entry : runReportObject) { + String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(), + smsCampaign.getCampaignName(), entry); + Object mobileNo = entry.get("mobileNo"); + + if (mobileNo != null) { + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, + mobileNo.toString(), smsCampaign); + smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue()); + this.smsMessageRepository.save(smsMessage); + Collection messages = new ArrayList<>(); + messages.add(smsMessage); + Map> smsDataMap = new HashMap<>(); + smsDataMap.put(smsCampaign, messages); + this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); + } + } + } + } catch (final IOException e) { + System.out.println("IOException: " + e.getMessage()); + } catch (final RuntimeException e) { + System.out.println("RuntimeException: " + e.getMessage()); + } + } + + private void updateTriggerDates(Long campaignId) { + final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId); + if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); } + LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate(); + smsCampaign.setLastTriggerDate(nextTriggerDate.toDate()); + // calculate new trigger date and insert into next trigger date + + /** + * next run time has to be in the future if not calculate a new future + * date + */ + LocalDate nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate() + .toLocalDate(), nextTriggerDate.toLocalDate()); + if (nextRuntime.isBefore(DateUtils.getLocalDateOfTenant())) { // means + // next + // run + // time is + // in the + // past + // calculate + // a new + // future + // date + nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate().toLocalDate(), + DateUtils.getLocalDateOfTenant()); + } + final LocalDateTime getTime = smsCampaign.getRecurrenceStartDateTime(); + final String dateString = nextRuntime.toString() + " " + getTime.getHourOfDay() + ":" + getTime.getMinuteOfHour() + ":" + + getTime.getSecondOfMinute(); + final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); + final LocalDateTime newTriggerDateWithTime = LocalDateTime.parse(dateString, simpleDateFormat); + + smsCampaign.setNextTriggerDate(newTriggerDateWithTime.toDate()); + this.smsCampaignRepository.saveAndFlush(smsCampaign); + } + + @Transactional + @Override + public CommandProcessingResult activateSmsCampaign(Long campaignId, JsonCommand command) { + final AppUser currentUser = this.context.authenticatedUser(); + + this.smsCampaignValidator.validateActivation(command.json()); + + final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId); + + if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); + + smsCampaign.activate(currentUser, fmt, activationDate); + + this.smsCampaignRepository.saveAndFlush(smsCampaign); + + if (smsCampaign.isDirect()) { + insertDirectCampaignIntoSmsOutboundTable(smsCampaign); + } else if (smsCampaign.isSchedule()) { + + /** + * if recurrence start date is in the future calculate next trigger + * date if not use recurrence start date us next trigger date when + * activating + */ + LocalDate nextTriggerDate = null; + if (smsCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())) { + nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getRecurrenceStartDate(), + DateUtils.getLocalDateOfTenant()); + } else { + nextTriggerDate = smsCampaign.getRecurrenceStartDate(); + } + // to get time of tenant + final LocalDateTime getTime = smsCampaign.getRecurrenceStartDateTime(); + + final String dateString = nextTriggerDate.toString() + " " + getTime.getHourOfDay() + ":" + getTime.getMinuteOfHour() + ":" + + getTime.getSecondOfMinute(); + final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); + final LocalDateTime nextTriggerDateWithTime = LocalDateTime.parse(dateString, simpleDateFormat); + + smsCampaign.setNextTriggerDate(nextTriggerDateWithTime.toDate()); + this.smsCampaignRepository.saveAndFlush(smsCampaign); + } + + /* + * if campaign is direct insert campaign message into sms outbound table + * else if its a schedule create a job process for it + */ + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(smsCampaign.getId()) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult closeSmsCampaign(Long campaignId, JsonCommand command) { + + final AppUser currentUser = this.context.authenticatedUser(); + this.smsCampaignValidator.validateClosedDate(command.json()); + + final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId); + if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate closureDate = command.localDateValueOfParameterNamed("closureDate"); + + smsCampaign.close(currentUser, fmt, closureDate); + + this.smsCampaignRepository.saveAndFlush(smsCampaign); +// this.serviceui.sendMessagesToGateway(); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(smsCampaign.getId()) // + .build(); + } + + @Override + public String compileSmsTemplate(final String textMessageTemplate, final String campaignName, final Map smsParams) { + final MustacheFactory mf = new DefaultMustacheFactory(); + final Mustache mustache = mf.compile(new StringReader(textMessageTemplate), campaignName); + + final StringWriter stringWriter = new StringWriter(); + mustache.execute(stringWriter, smsParams); + + return stringWriter.toString(); + } + + private List> getRunReportByServiceImpl(final String reportName, final Map queryParams) + throws IOException { + final String reportType = "report"; + + List> resultList = new ArrayList<>(); + final GenericResultsetData results = this.readReportingService.retrieveGenericResultSetForSmsCampaign(reportName, reportType, + queryParams); + + try { + final String response = this.genericDataService.generateJsonFromGenericResultsetData(results); + resultList = new ObjectMapper().readValue(response, new TypeReference>>() {}); + } catch (JsonParseException e) { + logger.info("Conversion of report query results to JSON failed: " + e.getMessage() + " - Location: " + e.getLocation()); + return resultList; + } + // loop changes array date to string date + for (HashMap entry : resultList) { + for (Map.Entry map : entry.entrySet()) { + String key = map.getKey(); + Object ob = map.getValue(); + if (ob instanceof ArrayList && ((ArrayList) ob).size() == 3) { + String changeArrayDateToStringDate = ((ArrayList) ob).get(2).toString() + "-" + ((ArrayList) ob).get(1).toString() + + "-" + ((ArrayList) ob).get(0).toString(); + entry.put(key, changeArrayDateToStringDate); + } + } + } + return resultList; + } + + @Override + public CampaignPreviewData previewMessage(final JsonQuery query) { + CampaignPreviewData campaignMessage = null; + this.context.authenticatedUser(); + this.smsCampaignValidator.validatePreviewMessage(query.json()); + // final String smsParams = + // this.fromJsonHelper.extractJsonObjectNamed("paramValue", + // query.parsedJson()).getAsString(); + final JsonElement smsParamsElement = this.fromJsonHelper + .extractJsonObjectNamed(SmsCampaignValidator.paramValue, query.parsedJson()); + String smsParams = smsParamsElement.toString(); + final String textMessageTemplate = this.fromJsonHelper.extractStringNamed("message", query.parsedJson()); + + try { + HashMap campaignParams = new ObjectMapper().readValue(smsParams, + new TypeReference>() {}); + + HashMap queryParamForRunReport = new ObjectMapper().readValue(smsParams, + new TypeReference>() {}); + + List> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"), + queryParamForRunReport); + + if (runReportObject != null && !runReportObject.isEmpty()) { + for (HashMap entry : runReportObject) { + // add string object to campaignParam object + String textMessage = this.compileSmsTemplate(textMessageTemplate, "SmsCampaign", entry); + if (!textMessage.isEmpty()) { + final Integer totalMessage = runReportObject.size(); + campaignMessage = new CampaignPreviewData(textMessage, totalMessage); + break; + } + } + } else { + campaignMessage = new CampaignPreviewData(textMessageTemplate, 0); + } + } catch (final IOException e) { + // TODO throw something here + } + return campaignMessage; + } + + @Transactional + @Override + public CommandProcessingResult reactivateSmsCampaign(final Long campaignId, JsonCommand command) { + + this.smsCampaignValidator.validateActivation(command.json()); + + final AppUser currentUser = this.context.authenticatedUser(); + + final SmsCampaign smsCampaign = this.smsCampaignRepository.findOne(campaignId); + + if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate reactivationDate = command.localDateValueOfParameterNamed("activationDate"); + smsCampaign.reactivate(currentUser, fmt, reactivationDate); + if (smsCampaign.isDirect()) { + insertDirectCampaignIntoSmsOutboundTable(smsCampaign); + } else if (smsCampaign.isSchedule()) { + + /** + * if recurrence start date is in the future calculate next trigger + * date if not use recurrence start date us next trigger date when + * activating + */ + LocalDate nextTriggerDate = null; + if (smsCampaign.getRecurrenceStartDateTime().isBefore(tenantDateTime())) { + nextTriggerDate = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getRecurrenceStartDate(), + DateUtils.getLocalDateOfTenant()); + } else { + nextTriggerDate = smsCampaign.getRecurrenceStartDate(); + } + // to get time of tenant + final LocalDateTime getTime = smsCampaign.getRecurrenceStartDateTime(); + + final String dateString = nextTriggerDate.toString() + " " + getTime.getHourOfDay() + ":" + getTime.getMinuteOfHour() + ":" + + getTime.getSecondOfMinute(); + final DateTimeFormatter simpleDateFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); + final LocalDateTime nextTriggerDateWithTime = LocalDateTime.parse(dateString, simpleDateFormat); + + smsCampaign.setNextTriggerDate(nextTriggerDateWithTime.toDate()); + } + this.smsCampaignRepository.saveAndFlush(smsCampaign); + + return new CommandProcessingResultBuilder() // + .withEntityId(smsCampaign.getId()) // + .build(); + + } + + @Override + @CronTarget(jobName = JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE) + public void storeTemplateMessageIntoSmsOutBoundTable() throws JobExecutionException { + final Collection smsCampaignDataCollection = this.smsCampaignRepository.findByCampaignTypeAndTriggerTypeAndStatus( + CampaignType.SMS.getValue(), SmsCampaignTriggerType.SCHEDULE.getValue(), SmsCampaignStatus.ACTIVE.getValue()); + if (smsCampaignDataCollection != null) { + for (SmsCampaign smsCampaign : smsCampaignDataCollection) { + LocalDateTime tenantDateNow = tenantDateTime(); + LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate(); + + logger.info("tenant time " + tenantDateNow.toString() + " trigger time " + nextTriggerDate.toString() + JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name()); + if (nextTriggerDate.isBefore(tenantDateNow)) { + insertDirectCampaignIntoSmsOutboundTable(smsCampaign); + this.updateTriggerDates(smsCampaign.getId()); + } + } + } + } + + private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) { + final Throwable realCause = dve.getMostSpecificCause(); + + throw new PlatformDataIntegrityException("error.msg.sms.campaign.unknown.data.integrity.issue", + "Unknown data integrity issue with resource: " + realCause.getMessage()); + } + + private LocalDateTime tenantDateTime() { + LocalDateTime today = new LocalDateTime(); + final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); + + if (tenant != null) { + final DateTimeZone zone = DateTimeZone.forID(tenant.getTimezoneId()); + if (zone != null) { + today = new LocalDateTime(zone); + } + } + return today; + } + +// private String formatDestinationPhoneNumber(String phoneNumber, String countryCallingCode) { +// StringBuilder formatedPhoneNumber = new StringBuilder("+"); +// +// try { +// Long phoneNumberToLong = Long.parseLong(phoneNumber); +// Long countryCallingCodeToLong = Long.parseLong(countryCallingCode); +// formatedPhoneNumber.append(Long.toString(countryCallingCodeToLong)); +// formatedPhoneNumber.append(Long.toString(phoneNumberToLong)); +// } +// +// catch (Exception e) { +// logger.error("Invalid phone number or country calling code, must contain only numbers", e); +// } +// +// return formatedPhoneNumber.toString(); +// } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java index 698ee4329..9b5aeb2d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/serialization/ExternalServicesPropertiesCommandFromApiJsonDeserializer.java @@ -25,6 +25,7 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.fineract.infrastructure.configuration.exception.ExternalServiceConfigurationNotFoundException; import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.S3_JSON_INPUT_PARAMS; +import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.SMS_JSON_INPUT_PARAMS; import org.apache.fineract.infrastructure.configuration.service.ExternalServicesConstants.SMTP_JSON_INPUT_PARAMS; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; @@ -38,6 +39,7 @@ public class ExternalServicesPropertiesCommandFromApiJsonDeserializer { private final Set S3SupportedParameters = S3_JSON_INPUT_PARAMS.getAllValues(); private final Set SMTPSupportedParameters = SMTP_JSON_INPUT_PARAMS.getAllValues(); + private final Set SMSSupportedParameters = SMS_JSON_INPUT_PARAMS.getAllValues(); private final FromJsonHelper fromApiJsonHelper; @Autowired @@ -58,6 +60,10 @@ public class ExternalServicesPropertiesCommandFromApiJsonDeserializer { this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.SMTPSupportedParameters); break; + case "SMS": + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.SMSSupportedParameters); + break; + default: throw new ExternalServiceConfigurationNotFoundException(externalServiceName); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java index c36b4444d..44a56ca7e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java @@ -35,6 +35,12 @@ public class ExternalServicesConstants { public static final String SMTP_PORT = "port"; public static final String SMTP_USE_TLS = "useTLS"; + public static final String SMS_SERVICE_NAME = "MESSAGE_GATEWAY"; + public static final String SMS_HOST = "host_name"; + public static final String SMS_PORT = "port_number"; + public static final String SMS_END_POINT = "end_point"; + public static final String SMS_TENANT_APP_KEY = "tenant_app_key"; + public static enum EXTERNALSERVICEPROPERTIES_JSON_INPUT_PARAMS { EXTERNAL_SERVICE_ID("external_service_id"), NAME("name"), VALUE("value"); @@ -96,6 +102,37 @@ public class ExternalServicesConstants { return this.value; } } + + public static enum SMS_JSON_INPUT_PARAMS { + HASTNAME("host_name"), PORT("port_number"), END_POINT("end_point"), TENANT_APP_KEY("tenant_app_key"); + + private final String value; + + private SMS_JSON_INPUT_PARAMS(final String value) { + this.value = value; + } + + private static final Set values = new HashSet<>(); + + static { + for (final SMS_JSON_INPUT_PARAMS type : SMS_JSON_INPUT_PARAMS.values()) { + values.add(type.value); + } + } + + public static Set getAllValues() { + return values; + } + + @Override + public String toString() { + return name().toString().replaceAll("_", " "); + } + + public String getValue() { + return this.value; + } + } public static enum S3_JSON_INPUT_PARAMS { S3_ACCESS_KEY("s3_access_key"), S3_BUCKET_NAME("s3_bucket_name"), S3_SECRET_KEY("s3_secret_key"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java index 023d06cb7..2d4683570 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformService.java @@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.configuration.service; import java.util.Collection; +import org.apache.fineract.infrastructure.campaigns.sms.data.MessageGatewayConfigurationData; import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData; import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData; import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData; @@ -30,6 +31,8 @@ public interface ExternalServicesPropertiesReadPlatformService { SMTPCredentialsData getSMTPCredentials(); + MessageGatewayConfigurationData getSMSGateway(); + Collection retrieveOne(String serviceName); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java index a01d2a523..ccd401231 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java @@ -22,6 +22,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import org.apache.fineract.infrastructure.campaigns.sms.data.MessageGatewayConfigurationData; import org.apache.fineract.infrastructure.configuration.data.ExternalServicesPropertiesData; import org.apache.fineract.infrastructure.configuration.data.S3CredentialsData; import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData; @@ -107,6 +108,30 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern } + private static final class MessageGatewayDataExtractor implements ResultSetExtractor { + + @Override + public MessageGatewayConfigurationData extractData(final ResultSet rs) throws SQLException, DataAccessException { + String host = null; + int port = 9191; + String endPoint = null; + String tenantAppKey = null; + + while (rs.next()) { + if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_HOST)) { + host = rs.getString("value"); + } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_PORT)) { + port = rs.getInt("value"); + } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_END_POINT)) { + endPoint = rs.getString("value"); + } else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMS_TENANT_APP_KEY)) { + tenantAppKey = rs.getString("value"); + } + } + return new MessageGatewayConfigurationData(null, null, host, port, endPoint, null, null, false, tenantAppKey); + } + } + @Override public S3CredentialsData getS3Credentials() { final ResultSetExtractor resultSetExtractor = new S3CredentialsDataExtractor(); @@ -126,6 +151,15 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern return smtpCredentialsData; } + @Override + public MessageGatewayConfigurationData getSMSGateway() { + final ResultSetExtractor resultSetExtractor = new MessageGatewayDataExtractor(); + final String sql = "SELECT esp.name, esp.value FROM c_external_service_properties esp inner join c_external_service es on esp.external_service_id = es.id where es.name = '" + + ExternalServicesConstants.SMS_SERVICE_NAME + "'"; + final MessageGatewayConfigurationData messageGatewayConfigurationData = this.jdbcTemplate.query(sql, resultSetExtractor, new Object[] {}); + return messageGatewayConfigurationData; + } + @Override public Collection retrieveOne(String serviceName) { String serviceNameToUse = null; @@ -138,6 +172,10 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern serviceNameToUse = ExternalServicesConstants.SMTP_SERVICE_NAME; break; + case "SMS": + serviceNameToUse = ExternalServicesConstants.SMS_SERVICE_NAME; + break; + default: throw new ExternalServiceConfigurationNotFoundException(serviceName); } @@ -147,4 +185,5 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern return this.jdbcTemplate.query(sql, mapper, new Object[] {}); } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java index db25cead8..44f41647b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesReadPlatformServiceImpl.java @@ -54,6 +54,10 @@ public class ExternalServicesReadPlatformServiceImpl implements ExternalServices serviceNameToUse = ExternalServicesConstants.SMTP_SERVICE_NAME; break; + case "SMS": + serviceNameToUse = ExternalServicesConstants.SMS_SERVICE_NAME; + break; + default: throw new ExternalServiceConfigurationNotFoundException(serviceName); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java index 93c9a5ece..9d14ea91b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java @@ -270,6 +270,10 @@ public class FromJsonHelper { return this.helperDelegator.extractMonthDayFormatParameter(element); } + public JsonObject extractJsonObjectNamed(final String parameterName, final JsonElement element) { + return this.helperDelegator.extractJsonObjectNamed(parameterName, element); + } + public Gson getGsonConverter() { return this.gsonConverter; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java index 2ce0b002c..a3e489a9d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java @@ -271,6 +271,19 @@ public class JsonParserHelper { return jsonArray; } + public JsonObject extractJsonObjectNamed(final String parameterName, final JsonElement element) { + JsonObject jsonObject = null; + + if (element.isJsonObject()) { + final JsonObject object = element.getAsJsonObject(); + if (object.has(parameterName)) { + jsonObject = object.get(parameterName).getAsJsonObject(); + } + } + + return jsonObject; + } + /** * Used with the local date is in array format */ diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java index 827bacd60..30a78f35e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java @@ -36,4 +36,14 @@ public class PaginationHelper { return new Page<>(items, totalFilteredRecords); } + + public Page fetchPage(JdbcTemplate jdbcTemplate, String sql, String sqlCountRows, Class type) { + final List items = jdbcTemplate.queryForList(sql, type); + + // determine how many rows are available + @SuppressWarnings("deprecation") + final int totalFilteredRecords = jdbcTemplate.queryForInt(sqlCountRows); + + return new Page<>(items, totalFilteredRecords); + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java index c1da25245..34d397f11 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java @@ -209,6 +209,22 @@ public final class SearchParameters { staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser); } + public static SearchParameters forSMSCampaign(final String sqlSearch, final Integer offset, final Integer limit, final String orderBy, + final String sortOrder) { + + final String externalId = null; + final Integer maxLimitAllowed = getCheckedLimit(limit); + final Long staffId = null; + final String accountNo = null; + final Long loanId = null; + final Long savingsId = null; + final Boolean orphansOnly = false; + final boolean isSelfUser = false; + + return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, + staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser); + } + private SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name, final String hierarchy, final String firstname, final String lastname, final Integer offset, final Integer limit, final String orderBy, final String sortOrder, final Long staffId, final String accountNo, final Long loanId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java index eb75fe02c..a21f645b8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java @@ -44,4 +44,9 @@ public interface ReadReportingService { ReportData retrieveReport(final Long id); Collection getAllowedReportTypes(); + + //needed for smsCampaign jobs where securityContext is null + GenericResultsetData retrieveGenericResultSetForSmsCampaign(String name, String type, Map extractedQueryParams); + + String sqlToRunForSmsCampaign(String name, String type, Map queryParams); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java index a6e97aed8..f25c371f8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java @@ -484,4 +484,35 @@ public class ReadReportingServiceImpl implements ReadReportingService { return new ReportParameterData(id, null, null, parameterName); } } + + @Override + public GenericResultsetData retrieveGenericResultSetForSmsCampaign(String name, String type, Map queryParams) { + final long startTime = System.currentTimeMillis(); + logger.info("STARTING REPORT: " + name + " Type: " + type); + + final String sql = sqlToRunForSmsCampaign(name, type, queryParams); + + final GenericResultsetData result = this.genericDataService.fillGenericResultSet(sql); + + final long elapsed = System.currentTimeMillis() - startTime; + logger.info("FINISHING Report/Request Name: " + name + " - " + type + " Elapsed Time: " + elapsed); + return result; + } + + @Override + public String sqlToRunForSmsCampaign(final String name, final String type, final Map queryParams) { + String sql = getSql(name, type); + + final Set keys = queryParams.keySet(); + for (String key : keys) { + final String pValue = queryParams.get(key); + // logger.info("(" + key + " : " + pValue + ")"); + key = "${" + key + "}"; + sql = this.genericDataService.replace(sql, key, pValue); + } + + sql = this.genericDataService.wrapSQL(sql); + + return sql; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java index d893c3b68..e2589e9db 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java @@ -42,7 +42,10 @@ public enum JobName { POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), // UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), // ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Loans With Income Posted As Transactions"), - EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"); + EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"), + UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update Sms Outbound with campaign message"), + SEND_MESSAGES_TO_SMS_GATEWAY("Send messages to SMS gateway"), + GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get delivery reports from SMS gateway"); private final String name; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java index 298076483..310aea39c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/SmsApiConstants.java @@ -36,13 +36,14 @@ public class SmsApiConstants { public static final String clientIdParamName = "clientId"; public static final String staffIdParamName = "staffId"; public static final String messageParamName = "message"; + public static final String campaignIdParamName = "campaignId"; // response parameters public static final String statusParamName = "status"; public static final Set CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, - dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName)); + dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName, campaignIdParamName)); - public static final Set UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName)); + public static final Set UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName, campaignIdParamName)); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java index 506dde8ad..9f73ea8ee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/api/SmsApiResource.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.sms.api; import java.util.Collection; +import java.util.Date; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -28,10 +29,12 @@ 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.fineract.accounting.journalentry.api.DateParam; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; @@ -39,6 +42,8 @@ import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.sms.data.SmsData; import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService; @@ -103,6 +108,35 @@ public class SmsApiResource { return this.toApiJsonSerializer.serialize(settings, smsMessage); } + @GET + @Path("{campaignId}/messageByStatus") + public String retrieveAllSmsByStatus(@PathParam("campaignId") final Long campaignId, @Context final UriInfo uriInfo, + @QueryParam("status") final Long status, @QueryParam("fromDate") final DateParam fromDateParam, + @QueryParam("toDate") final DateParam toDateParam, @QueryParam("locale") final String locale, + @QueryParam("dateFormat") final String dateFormat, @QueryParam("sqlSearch") final String sqlSearch, + @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit, + @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final SearchParameters searchParameters = SearchParameters.forSMSCampaign(sqlSearch, offset, limit, orderBy, sortOrder); + + Date fromDate = null; + if (fromDateParam != null) { + fromDate = fromDateParam.getDate("fromDate", dateFormat, locale); + } + Date toDate = null; + if (toDateParam != null) { + toDate = toDateParam.getDate("toDate", dateFormat, locale); + } + + final Page smsMessages = this.readPlatformService.retrieveSmsByStatus(campaignId, searchParameters, status.intValue(), fromDate, + toDate); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, smsMessages); + } + @PUT @Path("{resourceId}") public String update(@PathParam("resourceId") final Long resourceId, final String apiRequestBodyAsJson) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java index d5449e202..1edda9ed1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsData.java @@ -25,28 +25,23 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData; */ public class SmsData { - @SuppressWarnings("unused") private final Long id; - @SuppressWarnings("unused") private final Long groupId; - @SuppressWarnings("unused") private final Long clientId; - @SuppressWarnings("unused") private final Long staffId; - @SuppressWarnings("unused") private final EnumOptionData status; - @SuppressWarnings("unused") private final String mobileNo; - @SuppressWarnings("unused") private final String message; + private final Long providerId; + private final String campaignName; public static SmsData instance(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status, - final String mobileNo, final String message) { - return new SmsData(id, groupId, clientId, staffId, status, mobileNo, message); + final String mobileNo, final String message, final Long providerId, final String camapignName) { + return new SmsData(id, groupId, clientId, staffId, status, mobileNo, message, providerId, camapignName); } private SmsData(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status, - final String mobileNo, final String message) { + final String mobileNo, final String message, final Long providerId, final String camapignName) { this.id = id; this.groupId = groupId; this.clientId = clientId; @@ -54,5 +49,44 @@ public class SmsData { this.status = status; this.mobileNo = mobileNo; this.message = message; + this.providerId = providerId; + this.campaignName = camapignName; } + + public Long getId() { + return this.id; + } + + public Long getGroupId() { + return this.groupId; + } + + public Long getClientId() { + return this.clientId; + } + + public Long getStaffId() { + return this.staffId; + } + + public EnumOptionData getStatus() { + return this.status; + } + + public String getMobileNo() { + return this.mobileNo; + } + + public String getMessage() { + return this.message; + } + + public Long getProviderId() { + return this.providerId; + } + + public String getCampaignName() { + return this.campaignName; + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java new file mode 100644 index 000000000..f932b057e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiQueueResourceData.java @@ -0,0 +1,125 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.sms.data; + +import java.util.Collection; + +import com.google.gson.Gson; + +/** + * Immutable data object representing the API request body sent in the POST + * request to the "/queue" resource + **/ +public class SmsMessageApiQueueResourceData { + + private Long internalId; + private String tenantId; + private String createdOnDate; + private String sourceAddress; + private String mobileNumber; + private String message; + private Long providerId; + + /** + * SmsMessageApiQueueResourceData constructor + **/ + private SmsMessageApiQueueResourceData(Long internalId, String mifosTenantIdentifier, String createdOnDate, String sourceAddress, + String mobileNumber, String message, Long providerId) { + this.internalId = internalId; + this.tenantId = mifosTenantIdentifier; + this.createdOnDate = createdOnDate; + this.sourceAddress = sourceAddress; + this.mobileNumber = mobileNumber; + this.message = message; + this.providerId = providerId; + } + + /** + * SmsMessageApiQueueResourceData constructor + **/ + protected SmsMessageApiQueueResourceData() {} + + /** + * @return a new instance of the SmsMessageApiQueueResourceData class + **/ + public static final SmsMessageApiQueueResourceData instance(Long internalId, String mifosTenantIdentifier, String createdOnDate, + String sourceAddress, String mobileNumber, String message, Long providerId) { + + return new SmsMessageApiQueueResourceData(internalId, mifosTenantIdentifier, createdOnDate, sourceAddress, mobileNumber, message, + providerId); + } + + /** + * @return the internalId + */ + public Long getInternalId() { + return internalId; + } + + /** + * @return the mifosTenantIdentifier + */ + public String getTenantId() { + return tenantId; + } + + /** + * @return the createdOnDate + */ + public String getCreatedOnDate() { + return createdOnDate; + } + + /** + * @return the sourceAddress + */ + public String getSourceAddress() { + return sourceAddress; + } + + /** + * @return the mobileNumber + */ + public String getMobileNumber() { + return mobileNumber; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @return the providerId + */ + public Long getproviderId() { + return providerId; + } + + /** + * @return JSON representation of the object + **/ + public static String toJsonString(Collection smsResourceData) { + Gson gson = new Gson(); + + return gson.toJson(smsResourceData); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java new file mode 100644 index 000000000..3858aaf0b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiReportResourceData.java @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.sms.data; + +import java.util.List; + +import com.google.gson.Gson; + +/** + * Immutable data object representing the API request body sent in the POST request to + * the "/report" resource + **/ +public class SmsMessageApiReportResourceData { + private List externalIds; + private String mifosTenantIdentifier; + + /** + * SmsMessageApiReportResourceData constructor + **/ + private SmsMessageApiReportResourceData(List externalIds, String mifosTenantIdentifier) { + this.externalIds = externalIds; + this.mifosTenantIdentifier = mifosTenantIdentifier; + } + + /** + * SmsMessageApiReportResourceData constructor + **/ + protected SmsMessageApiReportResourceData() {} + + /** + * @return new instance of the SmsMessageApiReportResourceData class + **/ + public static final SmsMessageApiReportResourceData instance(List externalIds, String mifosTenantIdentifier) { + return new SmsMessageApiReportResourceData(externalIds, mifosTenantIdentifier); + } + + /** + * @return the externalIds + */ + public List getExternalIds() { + return externalIds; + } + + /** + * @return the mifosTenantIdentifier + */ + public String getMifosTenantIdentifier() { + return mifosTenantIdentifier; + } + + /** + * @return JSON representation of the object + **/ + public String toJsonString() { + Gson gson = new Gson(); + + return gson.toJson(this); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java new file mode 100644 index 000000000..d247bc79d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageApiResponseData.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.sms.data; + +import java.util.List; + +/** + * Immutable data object representing an outbound SMS message API response data + **/ +public class SmsMessageApiResponseData { + private Integer httpStatusCode; + private List data; + + /** + * SmsMessageApiResponseData constructor + * + * @return void + **/ + private SmsMessageApiResponseData(Integer httpStatusCode, List data) { + this.httpStatusCode = httpStatusCode; + this.data = data; + } + + /** + * Default SmsMessageApiResponseData constructor + * + * @return void + **/ + protected SmsMessageApiResponseData() {} + + /** + * @return an instance of the SmsMessageApiResponseData class + **/ + public static SmsMessageApiResponseData getInstance(Integer httpStatusCode, List data) { + return new SmsMessageApiResponseData(httpStatusCode, data); + } + + /** + * @return the httpStatusCode + */ + public Integer getHttpStatusCode() { + return httpStatusCode; + } + + /** + * @return the data + */ + public List getData() { + return data; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java new file mode 100644 index 000000000..91471b226 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/data/SmsMessageDeliveryReportData.java @@ -0,0 +1,113 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.sms.data; + +/** + * Immutable data object representing an outbound SMS message delivery report data + **/ +public class SmsMessageDeliveryReportData { + private Long id; + private String externalId; + private String addedOnDate; + private String deliveredOnDate; + private Integer deliveryStatus; + private Boolean hasError; + private String errorMessage; + + /** + * SmsMessageDeliveryReportData constructor + * + * @return void + **/ + private SmsMessageDeliveryReportData(Long id, String externalId, String addedOnDate, String deliveredOnDate, + Integer deliveryStatus, Boolean hasError, String errorMessage) { + this.id = id; + this.externalId = externalId; + this.addedOnDate = addedOnDate; + this.deliveredOnDate = deliveredOnDate; + this.deliveryStatus = deliveryStatus; + this.hasError = hasError; + this.errorMessage = errorMessage; + } + + /** + * Default SmsMessageDeliveryReportData constructor + * + * @return void + **/ + protected SmsMessageDeliveryReportData() {} + + /** + * @return an instance of the SmsMessageDeliveryReportData class + **/ + public static SmsMessageDeliveryReportData getInstance(Long id, String externalId, String addedOnDate, String deliveredOnDate, + Integer deliveryStatus, Boolean hasError, String errorMessage) { + + return new SmsMessageDeliveryReportData(id, externalId, addedOnDate, deliveredOnDate, deliveryStatus, hasError, errorMessage); + } + + /** + * @return the id + */ + public Long getId() { + return id; + } + + /** + * @return the externalId + */ + public String getExternalId() { + return externalId; + } + + /** + * @return the addedOnDate + */ + public String getAddedOnDate() { + return addedOnDate; + } + + /** + * @return the deliveredOnDate + */ + public String getDeliveredOnDate() { + return deliveredOnDate; + } + + /** + * @return the deliveryStatus + */ + public Integer getDeliveryStatus() { + return deliveryStatus; + } + + /** + * @return the hasError + */ + public Boolean getHasError() { + return hasError; + } + + /** + * @return the errorMessage + */ + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java index 377834aa9..a388bb5b9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessage.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.infrastructure.sms.domain; +import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; @@ -26,19 +27,26 @@ import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.sms.SmsApiConstants; import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.group.domain.Group; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.joda.time.LocalDate; @Entity @Table(name = "sms_messages_outbound") public class SmsMessage extends AbstractPersistableCustom { + @Column(name = "external_id", nullable = true) + private Long externalId; + @ManyToOne @JoinColumn(name = "group_id", nullable = true) private Group group; @@ -51,6 +59,10 @@ public class SmsMessage extends AbstractPersistableCustom { @JoinColumn(name = "staff_id", nullable = true) private Staff staff; + @ManyToOne + @JoinColumn(name = "campaign_id", nullable = false) + private SmsCampaign smsCampaign; + @Column(name = "status_enum", nullable = false) private Integer statusType; @@ -60,23 +72,51 @@ public class SmsMessage extends AbstractPersistableCustom { @Column(name = "message", nullable = false) private String message; - public static SmsMessage pendingSms(final Group group, final Client client, final Staff staff, final String message, - final String mobileNo) { - return new SmsMessage(group, client, staff, SmsMessageStatusType.PENDING, message, mobileNo); +// @Column(name = "provider_id", nullable = true) +// private Long providerId; +// +// @Column(name = "campaign_name", nullable = true) +// private String campaignName; + + @Column(name = "submittedon_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date submittedOnDate; + + @Column(name = "delivered_on_date", nullable = true) + @Temporal(TemporalType.TIMESTAMP) + private Date deliveredOnDate; + + public static SmsMessage pendingSms(final Long externalId, final Group group, final Client client, final Staff staff, + final String message, final String mobileNo, final SmsCampaign smsCampaign) { + return new SmsMessage(externalId, group, client, staff, SmsMessageStatusType.PENDING, message, mobileNo, smsCampaign); + } + + public static SmsMessage sentSms(final Long externalId, final Group group, final Client client, final Staff staff, + final String message, final String mobileNo, final SmsCampaign smsCampaign) { + return new SmsMessage(externalId, group, client, staff, SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT, message, mobileNo, smsCampaign); + } + + public static SmsMessage instance(Long externalId, final Group group, final Client client, final Staff staff, + final SmsMessageStatusType statusType, final String message, final String mobileNo, final SmsCampaign smsCampaign) { + + return new SmsMessage(externalId, group, client, staff, statusType, message, mobileNo, smsCampaign); } protected SmsMessage() { // } - private SmsMessage(final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType, - final String message, final String mobileNo) { + private SmsMessage(Long externalId, final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType, + final String message, final String mobileNo, final SmsCampaign smsCampaign) { + this.externalId = externalId; this.group = group; this.client = client; this.staff = staff; this.statusType = statusType.getValue(); this.mobileNo = mobileNo; this.message = message; + this.smsCampaign = smsCampaign; + this.submittedOnDate = LocalDate.now().toDate(); } public Map update(final JsonCommand command) { @@ -91,4 +131,56 @@ public class SmsMessage extends AbstractPersistableCustom { return actualChanges; } + + public Long getExternalId() { + return this.externalId; + } + + public SmsCampaign getSmsCampaign() { + return this.smsCampaign; + } + + public Group getGroup() { + return group; + } + + public Client getClient() { + return client; + } + + public Staff getStaff() { + return staff; + } + + public Integer getStatusType() { + return statusType; + } + + public String getMobileNo() { + return mobileNo; + } + + public String getMessage() { + return message; + } + + public void setExternalId(final Long externalId) { + this.externalId = externalId; + } + + public void setStatusType(final Integer statusType) { + this.statusType = statusType; + } + + public Date getSubmittedOnDate() { + return this.submittedOnDate; + } + + public Date getDeliveredOnDate() { + return this.deliveredOnDate; + } + + public void setDeliveredOnDate(final Date deliveredOnDate) { + this.deliveredOnDate = deliveredOnDate; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java index 32a56e152..695edfb27 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java @@ -18,6 +18,9 @@ */ package org.apache.fineract.infrastructure.sms.domain; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository; +import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.sms.SmsApiConstants; @@ -40,17 +43,19 @@ public class SmsMessageAssembler { private final GroupRepositoryWrapper groupRepository; private final ClientRepositoryWrapper clientRepository; private final StaffRepositoryWrapper staffRepository; + private final SmsCampaignRepository smsCampaignRepository; private final FromJsonHelper fromApiJsonHelper; @Autowired public SmsMessageAssembler(final SmsMessageRepository smsMessageRepository, final GroupRepositoryWrapper groupRepositoryWrapper, final ClientRepositoryWrapper clientRepository, final StaffRepositoryWrapper staffRepository, - final FromJsonHelper fromApiJsonHelper) { + final FromJsonHelper fromApiJsonHelper, final SmsCampaignRepository smsCampaignRepository) { this.smsMessageRepository = smsMessageRepository; this.groupRepository = groupRepositoryWrapper; this.clientRepository = clientRepository; this.staffRepository = staffRepository; this.fromApiJsonHelper = fromApiJsonHelper; + this.smsCampaignRepository = smsCampaignRepository; } public SmsMessage assembleFromJson(final JsonCommand command) { @@ -58,13 +63,20 @@ public class SmsMessageAssembler { final JsonElement element = command.parsedJson(); String mobileNo = null; - Group group = null; + Long externalId = null; if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) { final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element); group = this.groupRepository.findOneWithNotFoundDetection(groupId); } + SmsCampaign smsCampaign = null; + if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.campaignIdParamName, element)) { + final Long campaignId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.campaignIdParamName, element); + smsCampaign = this.smsCampaignRepository.findOne(campaignId); + if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); } + } + Client client = null; if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) { final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element); @@ -81,7 +93,7 @@ public class SmsMessageAssembler { final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element); - return SmsMessage.pendingSms(group, client, staff, message, mobileNo); + return SmsMessage.pendingSms(externalId, group, client, staff, message, mobileNo, smsCampaign); } public SmsMessage assembleFromResourceId(final Long resourceId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java index 4d9768a4b..9dadb5901 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java @@ -18,9 +18,12 @@ */ package org.apache.fineract.infrastructure.sms.domain; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface SmsMessageRepository extends JpaRepository, JpaSpecificationExecutor { - // no extra behaviour + + Page findByStatusType(final Integer status, Pageable pageable); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java index 72dce3851..a97c2c6f1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java @@ -22,6 +22,7 @@ public enum SmsMessageStatusType { INVALID(0, "smsMessageStatusType.invalid"), // PENDING(100, "smsMessageStatusType.pending"), // + WAITING_FOR_DELIVERY_REPORT(150, "smsMessageStatusType.waitingForDeliveryReport"), SENT(200, "smsMessageStatusType.sent"), // DELIVERED(300, "smsMessageStatusType.delivered"), // FAILED(400, "smsMessageStatusType.failed"); @@ -36,6 +37,9 @@ public enum SmsMessageStatusType { case 100: enumeration = SmsMessageStatusType.PENDING; break; + case 150: + enumeration = SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT; + break; case 200: enumeration = SmsMessageStatusType.SENT; break; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java new file mode 100644 index 000000000..bfdbaadb4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.sms.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class SmsCountryCodeNotFoundException extends AbstractPlatformResourceNotFoundException { + + public SmsCountryCodeNotFoundException() { + super("error.msg.sms.country.code.not.found", "SMS country code does not exist"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java new file mode 100644 index 000000000..8d3e545ba --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.sms.scheduler; + +import java.util.Collection; +import java.util.Map; + +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; +import org.apache.fineract.infrastructure.sms.domain.SmsMessage; + +/** + * Scheduled Job service interface for SMS message + **/ +public interface SmsMessageScheduledJobService { + + /** + * sends a batch of SMS messages to the SMS gateway + **/ + public void sendMessagesToGateway(); + + /** + * sends triggered batch SMS messages to SMS gateway + * @param smsDataMap + */ + public void sendTriggeredMessages(Map> smsDataMap); + + /** + * get delivery report from the SMS gateway + **/ + public void getDeliveryReports(); +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java new file mode 100644 index 000000000..3e23a10f3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java @@ -0,0 +1,295 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.sms.scheduler; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.annotation.PostConstruct; + +import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants; +import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign; +import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.jobs.annotation.CronTarget; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.infrastructure.sms.data.SmsMessageApiQueueResourceData; +import org.apache.fineract.infrastructure.sms.data.SmsMessageDeliveryReportData; +import org.apache.fineract.infrastructure.sms.domain.SmsMessage; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType; +import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import com.google.gson.Gson; + +/** + * Scheduled job services that send SMS messages and get delivery reports for + * the sent SMS messages + **/ +@Service +public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJobService { + + private final SmsMessageRepository smsMessageRepository; + private final SmsReadPlatformService smsReadPlatformService; + private static final Logger logger = LoggerFactory.getLogger(SmsMessageScheduledJobServiceImpl.class); + private final RestTemplate restTemplate = new RestTemplate(); + private ExecutorService genericExecutorService ; + private ExecutorService triggeredExecutorService ; + private final SmsConfigUtils smsConfigUtils ; + + + /** + * SmsMessageScheduledJobServiceImpl constructor + **/ + @Autowired + public SmsMessageScheduledJobServiceImpl(SmsMessageRepository smsMessageRepository, SmsReadPlatformService smsReadPlatformService, + final SmsConfigUtils smsConfigUtils) { + this.smsMessageRepository = smsMessageRepository; + this.smsReadPlatformService = smsReadPlatformService; + this.smsConfigUtils = smsConfigUtils ; + } + + @PostConstruct + public void initializeExecutorService() { + genericExecutorService = Executors.newSingleThreadExecutor(); + triggeredExecutorService = Executors.newSingleThreadExecutor() ; + } + + /** + * Send batches of SMS messages to the SMS gateway (or intermediate gateway) + **/ + @Override + @Transactional + @CronTarget(jobName = JobName.SEND_MESSAGES_TO_SMS_GATEWAY) + public void sendMessagesToGateway() { + Integer pageLimit = 200; + Integer page = 0; + int totalRecords = 0; + do { + PageRequest pageRequest = new PageRequest(0, pageLimit); + org.springframework.data.domain.Page pendingMessages = this.smsMessageRepository.findByStatusType( + SmsMessageStatusType.PENDING.getValue(), pageRequest); + List toSaveMessages = new ArrayList<>() ; + try { + + if (pendingMessages.getContent().size() > 0) { + final String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); + Iterator pendingMessageIterator = pendingMessages.iterator(); + Collection apiQueueResourceDatas = new ArrayList<>(); + while (pendingMessageIterator.hasNext()) { + SmsMessage smsData = pendingMessageIterator.next(); + + SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsData.getId(), + tenantIdentifier, null, null, smsData.getMobileNo(), smsData.getMessage(), smsData.getSmsCampaign() + .getProviderId()); + apiQueueResourceDatas.add(apiQueueResourceData); + smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue()); + toSaveMessages.add(smsData) ; + } + this.smsMessageRepository.save(toSaveMessages); + this.smsMessageRepository.flush(); + this.genericExecutorService.execute(new SmsTask(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas)); + +// new MyThread(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas).start(); + } + } catch (Exception e) { + throw new ConnectionFailureException(SmsCampaignConstants.SMS); + } + page ++; + totalRecords = pendingMessages.getTotalPages(); + } while (page < totalRecords); + } + + class SmsTask implements Runnable, ApplicationListener { + + private final FineractPlatformTenant tenant; + private final Collection apiQueueResourceDatas; + + public SmsTask(final FineractPlatformTenant tenant, final Collection apiQueueResourceDatas) { + this.tenant = tenant; + this.apiQueueResourceDatas = apiQueueResourceDatas; + } + + @Override + public void run() { + ThreadLocalContextUtil.setTenant(tenant); + connectAndSendToIntermediateServer(tenant.getTenantIdentifier(), apiQueueResourceDatas); + } + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + genericExecutorService.shutdown(); + logger.info("Shutting down the ExecutorService"); + } + } + + private void connectAndSendToIntermediateServer(String tenantIdentifier, + Collection apiQueueResourceDatas) { + Map hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms", SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas)) ; + URI uri = (URI)hostConfig.get("uri") ; + HttpEntity entity = (HttpEntity)hostConfig.get("entity") ; + ResponseEntity responseOne = restTemplate.exchange(uri, HttpMethod.POST, entity, + new ParameterizedTypeReference() {}); + if (responseOne != null) { +// String smsResponse = responseOne.getBody(); + if (!responseOne.getStatusCode().equals(HttpStatus.ACCEPTED)) { + System.out.println(responseOne.getStatusCode().name()); + throw new ConnectionFailureException(SmsCampaignConstants.SMS); + } + } + } + + @Override + public void sendTriggeredMessages(Map> smsDataMap) { + try { + if (!smsDataMap.isEmpty()) { + for (Entry> entry : smsDataMap.entrySet()) { + Iterator smsMessageIterator = entry.getValue().iterator(); + Collection apiQueueResourceDatas = new ArrayList<>(); + StringBuilder request = new StringBuilder(); + while (smsMessageIterator.hasNext()) { + SmsMessage smsMessage = smsMessageIterator.next(); + SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsMessage.getId(), + null, null, null, smsMessage.getMobileNo(), smsMessage.getMessage(), entry.getKey().getProviderId()); + apiQueueResourceDatas.add(apiQueueResourceData); + smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue()); + } + this.smsMessageRepository.save(entry.getValue()) ; + request.append(SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas)); + logger.info("Sending triggered SMS with request - " + request.toString()); + this.triggeredExecutorService.execute(new SmsTask(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas)); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + /** + * get SMS message delivery reports from the SMS gateway (or intermediate + * gateway) + **/ + @Override + @Transactional + @CronTarget(jobName = JobName.GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY) + public void getDeliveryReports() { + int page = 0; + int totalRecords = 0; + Integer limit = 200; + do { + Page smsMessageInternalIds = this.smsReadPlatformService.retrieveAllWaitingForDeliveryReport(limit); + // only proceed if there are sms message with status type enum 300 + try { + + if (smsMessageInternalIds.getPageItems().size() > 0) { + // make request + Map hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms", new Gson().toJson(smsMessageInternalIds.getPageItems())) ; + URI uri = (URI)hostConfig.get("uri") ; + HttpEntity entity = (HttpEntity)hostConfig.get("entity") ; + ResponseEntity> responseOne = restTemplate.exchange(uri, HttpMethod.POST, entity, + new ParameterizedTypeReference>() {}); + + Collection smsMessageDeliveryReportDatas = responseOne.getBody(); + Iterator responseReportIterator = smsMessageDeliveryReportDatas.iterator(); + while (responseReportIterator.hasNext()) { + SmsMessageDeliveryReportData smsMessageDeliveryReportData = responseReportIterator.next(); + Integer deliveryStatus = smsMessageDeliveryReportData.getDeliveryStatus(); + + if (!smsMessageDeliveryReportData.getHasError() + && (deliveryStatus != 100)) { + SmsMessage smsMessage = this.smsMessageRepository.findOne(smsMessageDeliveryReportData.getId()); + Integer statusType = smsMessage.getStatusType(); + boolean statusChanged = false; + + switch (deliveryStatus) { + case 0: + statusType = SmsMessageStatusType.INVALID.getValue(); + break; + case 150: + statusType = SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue(); + break; + case 200: + statusType = SmsMessageStatusType.SENT.getValue(); + break; + case 300: + statusType = SmsMessageStatusType.DELIVERED.getValue(); + break; + + case 400: + statusType = SmsMessageStatusType.FAILED.getValue(); + break; + + default: + statusType = smsMessage.getStatusType(); + break; + } + + statusChanged = !statusType.equals(smsMessage.getStatusType()); + + // update the status Type enum + smsMessage.setStatusType(statusType); + + // save the SmsMessage entity + this.smsMessageRepository.save(smsMessage); + + if (statusChanged) { + logger.info("Status of SMS message id: " + smsMessage.getId() + " successfully changed to " + statusType); + } + } + } + + if (smsMessageDeliveryReportDatas.size() > 0) { + logger.info(smsMessageDeliveryReportDatas.size() + " " + + "delivery report(s) successfully received from the intermediate gateway - sms"); + } + } + } + + catch (Exception e) { + logger.error(e.getMessage(), e); + } + page ++; + totalRecords = smsMessageInternalIds.getTotalFilteredRecords(); + } while (page < totalRecords); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java index db330daf5..68a9a99e6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java @@ -19,7 +19,11 @@ package org.apache.fineract.infrastructure.sms.service; import java.util.Collection; +import java.util.Date; +import java.util.List; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.sms.data.SmsData; public interface SmsReadPlatformService { @@ -27,4 +31,20 @@ public interface SmsReadPlatformService { Collection retrieveAll(); SmsData retrieveOne(Long resourceId); + + Collection retrieveAllPending(final Long campaignId, final Integer limit); + + Collection retrieveAllSent(Integer limit); + + Collection retrieveAllDelivered(Integer limit); + + Collection retrieveAllFailed(Integer limit); + + Page retrieveSmsByStatus(final Long campaignId, SearchParameters searchParameters, Integer status, Date dateFrom, Date dateTo); + + List retrieveExternalIdsOfAllSent(Integer limit); + + Page retrieveAllWaitingForDeliveryReport(Integer limit); + + List retrieveAllPending(Integer limit); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java index c4fd08834..5ad0eac0e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java @@ -20,13 +20,22 @@ package org.apache.fineract.infrastructure.sms.service; import java.sql.ResultSet; import java.sql.SQLException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Collection; +import java.util.Date; +import java.util.List; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.sms.data.SmsData; import org.apache.fineract.infrastructure.sms.domain.SmsMessageEnumerations; +import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType; import org.apache.fineract.infrastructure.sms.exception.SmsNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -39,6 +48,7 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService { private final JdbcTemplate jdbcTemplate; private final SmsMapper smsRowMapper; + private final PaginationHelper paginationHelper = new PaginationHelper<>(); @Autowired public SmsReadPlatformServiceImpl(final RoutingDataSource dataSource) { @@ -58,8 +68,11 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService { sql.append("smo.staff_id as staffId, "); sql.append("smo.status_enum as statusId, "); sql.append("smo.mobile_no as mobileNo, "); - sql.append("smo.message as message "); - sql.append("from sms_messages_outbound smo"); + sql.append("smo.message as message, "); + sql.append("smc.provider_id as providerId, "); + sql.append("smc.campaign_name as campaignName "); + sql.append("from sms_messages_outbound smo "); + sql.append("join sms_campaign smc on smc.id = smo.campaign_id "); this.schema = sql.toString(); } @@ -67,6 +80,10 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService { public String schema() { return this.schema; } + + public String tableName() { + return "sms_messages_outbound"; + } @Override public SmsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { @@ -82,7 +99,11 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService { final Integer statusId = JdbcSupport.getInteger(rs, "statusId"); final EnumOptionData status = SmsMessageEnumerations.status(statusId); - return SmsData.instance(id, groupId, clientId, staffId, status, mobileNo, message); + final Long providerId = JdbcSupport.getLong(rs, "providerId"); + + final String campaignName = rs.getString("campaignName"); + + return SmsData.instance(id, groupId, clientId, staffId, status, mobileNo, message, providerId, campaignName); } } @@ -104,4 +125,122 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService { throw new SmsNotFoundException(resourceId); } } + + @Override + public Collection retrieveAllPending(final Long campaignId, final Integer limit) { + final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : ""; + String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum = " + SmsMessageStatusType.PENDING.getValue(); + if (campaignId != null) { + sql += " and smo.campaign_id = " + campaignId; + } + + sql += sqlPlusLimit; + + return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {}); + } + + @Override + public Collection retrieveAllSent(final Integer limit) { + final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : ""; + final String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum IN (" + SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue() + + "," + SmsMessageStatusType.SENT.getValue() + ")" + sqlPlusLimit; + + return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {}); + } + + @Override + public List retrieveExternalIdsOfAllSent(final Integer limit) { + final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : ""; + final String sql = "select external_id from " + this.smsRowMapper.tableName() + " where status_enum = " + + SmsMessageStatusType.SENT.getValue() + sqlPlusLimit; + + return this.jdbcTemplate.queryForList(sql, Long.class); + } + + @Override + public Page retrieveAllWaitingForDeliveryReport(final Integer limit) { + final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : ""; + final String sql = "select id from " + this.smsRowMapper.tableName() + " where status_enum = " + + SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue() + sqlPlusLimit; + final String sqlCountRows = "SELECT FOUND_ROWS()"; + return this.paginationHelper.fetchPage(jdbcTemplate, sql, sqlCountRows, Long.class); + //(this.jdbcTemplate, sqlCountRows, new Object [] {}, Long.class); this.jdbcTemplate.queryForList(sql, Long.class); + } + + @Override + public List retrieveAllPending(final Integer limit) { + final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : ""; + final String sql = "select external_id from " + this.smsRowMapper.tableName() + " where status_enum = " + + SmsMessageStatusType.PENDING.getValue() + sqlPlusLimit; + + return this.jdbcTemplate.queryForList(sql, Long.class); + } + + @Override + public Collection retrieveAllDelivered(final Integer limit) { + final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : ""; + final String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum = " + SmsMessageStatusType.DELIVERED.getValue() + + sqlPlusLimit; + + return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {}); + } + + @Override + public Collection retrieveAllFailed(final Integer limit) { + final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : ""; + final String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum = " + SmsMessageStatusType.FAILED.getValue() + + sqlPlusLimit; + + return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {}); + } + + @Override + public Page retrieveSmsByStatus(final Long campaignId, final SearchParameters searchParameters, final Integer status, final Date dateFrom, final Date dateTo) { + final StringBuilder sqlBuilder = new StringBuilder(200); + final Object[] objectArray = new Object[10]; + int arrayPos = 0; + sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); + sqlBuilder.append(this.smsRowMapper.schema()); + if (status != null) { + sqlBuilder.append(" where smo.campaign_id = ? and smo.status_enum= ? "); + objectArray[arrayPos] = campaignId; + arrayPos = arrayPos + 1; + objectArray[arrayPos] = status; + arrayPos = arrayPos + 1; + } + String fromDateString = null; + String toDateString = null; + if (dateFrom != null && dateTo != null) { + final DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + fromDateString = df.format(dateFrom); + toDateString = df.format(dateTo); + sqlBuilder.append(" and smo.submittedon_date >= ? and smo.submittedon_date <= ? "); + objectArray[arrayPos] = fromDateString; + arrayPos = arrayPos + 1; + + objectArray[arrayPos] = toDateString; + arrayPos = arrayPos + 1; + } + + if (searchParameters.isOrderByRequested()) { + sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); + + if (searchParameters.isSortOrderProvided()) { + sqlBuilder.append(' ').append(searchParameters.getSortOrder()); + } + } else { + sqlBuilder.append(" order by smo.submittedon_date, smo.id"); + } + + if (searchParameters.isLimited()) { + sqlBuilder.append(" limit ").append(searchParameters.getLimit()); + if (searchParameters.isOffset()) { + sqlBuilder.append(" offset ").append(searchParameters.getOffset()); + } + } + final String sqlCountRows = "SELECT FOUND_ROWS()"; + final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos); + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, this.smsRowMapper); + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java index 053045244..5e829707e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java @@ -46,6 +46,7 @@ import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.documentmanagement.domain.Image; @@ -57,7 +58,6 @@ import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormatter; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; @Entity @Table(name = "m_client", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "account_no_UNIQUE"), // @@ -1004,4 +1004,10 @@ public final class Client extends AbstractPersistableCustom { public void loadLazyCollections() { this.groups.size() ; } + + public String getFirstname(){return this.firstname;} + + public String getMiddlename(){return this.middlename;} + + public String getLastname(){return this.lastname;} } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java index c8e726615..b7656f880 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java @@ -205,6 +205,10 @@ public class ClientTransaction extends AbstractPersistableCustom { return client.getId(); } + public Client getClient() { + return this.client ; + } + public Money getAmount() { return Money.of(getCurrency(), this.amount); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index 3c620abb7..5db7a022c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.client.service; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -63,6 +64,9 @@ import org.apache.fineract.portfolio.client.exception.ClientHasNoStaffException; import org.apache.fineract.portfolio.client.exception.ClientMustBePendingToBeDeletedException; import org.apache.fineract.portfolio.client.exception.InvalidClientSavingProductException; import org.apache.fineract.portfolio.client.exception.InvalidClientStateTransitionException; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; +import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.GroupRepository; import org.apache.fineract.portfolio.group.exception.GroupMemberCountNotInPermissibleRangeException; @@ -113,13 +117,15 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP private final CommandProcessingService commandProcessingService; private final ConfigurationDomainService configurationDomainService; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; - private final FromJsonHelper fromApiJsonHelper; - private final ConfigurationReadPlatformService configurationReadPlatformService; - private final AddressWritePlatformService addressWritePlatformService; + private final FromJsonHelper fromApiJsonHelper; + private final ConfigurationReadPlatformService configurationReadPlatformService; + private final AddressWritePlatformService addressWritePlatformService; + private final BusinessEventNotifierService businessEventNotifierService; @Autowired public ClientWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final ClientRepositoryWrapper clientRepository, final ClientNonPersonRepositoryWrapper clientNonPersonRepository, final OfficeRepositoryWrapper officeRepositoryWrapper, final NoteRepository noteRepository, + final ClientRepositoryWrapper clientRepository, final ClientNonPersonRepositoryWrapper clientNonPersonRepository, + final OfficeRepositoryWrapper officeRepositoryWrapper, final NoteRepository noteRepository, final ClientDataValidator fromApiJsonDeserializer, final AccountNumberGenerator accountNumberGenerator, final GroupRepository groupRepository, final StaffRepositoryWrapper staffRepository, final CodeValueRepositoryWrapper codeValueRepository, final LoanRepositoryWrapper loanRepositoryWrapper, @@ -128,7 +134,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final CommandProcessingService commandProcessingService, final ConfigurationDomainService configurationDomainService, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final FromJsonHelper fromApiJsonHelper, final ConfigurationReadPlatformService configurationReadPlatformService, - final AddressWritePlatformService addressWritePlatformService) { + final AddressWritePlatformService addressWritePlatformService, final BusinessEventNotifierService businessEventNotifierService) { this.context = context; this.clientRepository = clientRepository; this.clientNonPersonRepository = clientNonPersonRepository; @@ -147,8 +153,9 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.configurationDomainService = configurationDomainService; this.accountNumberFormatRepository = accountNumberFormatRepository; this.fromApiJsonHelper = fromApiJsonHelper; - this.configurationReadPlatformService = configurationReadPlatformService; - this.addressWritePlatformService = addressWritePlatformService; + this.configurationReadPlatformService = configurationReadPlatformService; + this.addressWritePlatformService = addressWritePlatformService; + this.businessEventNotifierService = businessEventNotifierService; } @Transactional @@ -158,7 +165,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); if (client.isNotPending()) { throw new ClientMustBePendingToBeDeletedException(clientId); } - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_DELETE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); final List relatedNotes = this.noteRepository.findByClientId(clientId); this.noteRepository.deleteInBatch(relatedNotes); @@ -167,7 +175,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.clientNonPersonRepository.delete(clientNonPerson); this.clientRepository.delete(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_DELETE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withOfficeId(client.officeId()) // .withClientId(clientId) // @@ -275,6 +284,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final Client newClient = Client.createNew(currentUser, clientOffice, clientParentGroup, staff, savingsProductId, gender, clientType, clientClassification, legalFormValue, command); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_CREATE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, newClient)); boolean rollbackTransaction = false; if (newClient.isActive()) { validateParentGroupRulesBeforeClientActivation(newClient); @@ -306,7 +317,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.addressWritePlatformService.addNewClientAddress(newClient, command); } - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_CREATE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, newClient)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withOfficeId(clientOffice.getId()) // @@ -523,7 +535,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId, true); validateParentGroupRulesBeforeClientActivation(client); - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_ACTIVATE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); @@ -532,7 +545,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP client.activate(currentUser, fmt, activationDate); CommandProcessingResult result = openSavingsAccount(client, fmt); this.clientRepository.saveAndFlush(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_ACTIVATE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withOfficeId(client.officeId()) // @@ -609,7 +623,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.fromApiJsonDeserializer.validateForAssignStaff(command.json()); final Client clientForUpdate = this.clientRepository.findOneWithNotFoundDetection(clientId); - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_ASSIGN_STAFF, + constructEntityMap(BUSINESS_ENTITY.CLIENT, clientForUpdate)); Staff staff = null; final Long staffId = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName); if (staffId != null) { @@ -625,7 +640,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.clientRepository.saveAndFlush(clientForUpdate); actualChanges.put(ClientApiConstants.staffIdParamName, staffId); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_ASSIGN_STAFF, + constructEntityMap(BUSINESS_ENTITY.CLIENT, clientForUpdate)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withOfficeId(clientForUpdate.officeId()) // @@ -644,6 +660,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.fromApiJsonDeserializer.validateClose(command); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_CLOSE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); final LocalDate closureDate = command.localDateValueOfParameterNamed(ClientApiConstants.closureDateParamName); final Long closureReasonId = command.longValueOfParameterNamed(ClientApiConstants.closureReasonIdParamName); @@ -689,7 +707,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP client.close(currentUser, closureReason, closureDate.toDate()); this.clientRepository.saveAndFlush(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_CLOSE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withClientId(clientId) // @@ -763,6 +782,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.fromApiJsonDeserializer.validateRejection(command); final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_REJECT, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); final LocalDate rejectionDate = command.localDateValueOfParameterNamed(ClientApiConstants.rejectionDateParamName); final Long rejectionReasonId = command.longValueOfParameterNamed(ClientApiConstants.rejectionReasonIdParamName); @@ -780,7 +801,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP } client.reject(currentUser, rejectionReason, rejectionDate.toDate()); this.clientRepository.saveAndFlush(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_REJECT, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withClientId(entityId) // @@ -794,6 +816,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.fromApiJsonDeserializer.validateWithdrawn(command); final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_WITHDRAW, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); final LocalDate withdrawalDate = command.localDateValueOfParameterNamed(ClientApiConstants.withdrawalDateParamName); final Long withdrawalReasonId = command.longValueOfParameterNamed(ClientApiConstants.withdrawalReasonIdParamName); @@ -811,7 +835,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP } client.withdraw(currentUser, withdrawalReason, withdrawalDate.toDate()); this.clientRepository.saveAndFlush(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_WITHDRAW, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withClientId(entityId) // @@ -825,6 +850,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.fromApiJsonDeserializer.validateReactivate(command); final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_REACTIVATE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); final LocalDate reactivateDate = command.localDateValueOfParameterNamed(ClientApiConstants.reactivationDateParamName); if (!client.isClosed()) { @@ -837,7 +864,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP } client.reActivate(currentUser, reactivateDate.toDate()); this.clientRepository.saveAndFlush(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_REACTIVATE, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withClientId(entityId) // @@ -899,4 +927,10 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP .withEntityId(entityId) // .build(); } + + private Map constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { + Map map = new HashMap<>(1); + map.put(entityEvent, entity); + return map; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java index b5f5258fd..9532b5247 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java @@ -24,15 +24,25 @@ import java.util.Set; public class BusinessEventNotificationConstants { public static enum BUSINESS_EVENTS { - LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_LASTDISBURSAL("loan_undo_lastdisbursal"),LOAN_UNDO_TRANSACTION( - "loan_undo_transaction"), LOAN_ADJUST_TRANSACTION("loan_adjust_transaction"), LOAN_MAKE_REPAYMENT( - "loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF("loan_undo_writtenoff"), LOAN_DISBURSAL( - "loan_disbursal"), LOAN_WAIVE_INTEREST("loan_waive_interest"), LOAN_CLOSE("loan_close"), LOAN_CLOSE_AS_RESCHEDULE( - "loan_close_as_reschedule"), LOAN_ADD_CHARGE("loan_add_charge"), LOAN_UPDATE_CHARGE("loan_update_charge"), LOAN_WAIVE_CHARGE( - "loan_waive_charge"), LOAN_DELETE_CHARGE("loan_delete_charge"), LOAN_CHARGE_PAYMENT("loan_charge_payment"), LOAN_INITIATE_TRANSFER( - "loan_initiate_transfer"), LOAN_ACCEPT_TRANSFER("loan_accept_transfer"), LOAN_WITHDRAW_TRANSFER("loan_withdraw_transfer"), LOAN_REJECT_TRANSFER( - "loan_reject_transfer"), LOAN_REASSIGN_OFFICER("loan_reassign_officer"), LOAN_REMOVE_OFFICER("loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE( - "loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"), LOAN_REFUND("loan_refund"), LOAN_FORECLOSURE("loan_foreclosure"); + LOAN_APPROVED("loan_approved"), LOAN_REJECTED("loan_reject"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_LASTDISBURSAL( + "loan_undo_lastdisbursal"), LOAN_UNDO_TRANSACTION("loan_undo_transaction"), LOAN_ADJUST_TRANSACTION( + "loan_adjust_transaction"), LOAN_MAKE_REPAYMENT("loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF( + "loan_undo_writtenoff"), LOAN_DISBURSAL("loan_disbursal"), LOAN_WAIVE_INTEREST("loan_waive_interest"), LOAN_CLOSE( + "loan_close"), LOAN_CLOSE_AS_RESCHEDULE("loan_close_as_reschedule"), LOAN_ADD_CHARGE("loan_add_charge"), LOAN_UPDATE_CHARGE( + "loan_update_charge"), LOAN_WAIVE_CHARGE("loan_waive_charge"), LOAN_DELETE_CHARGE("loan_delete_charge"), LOAN_CHARGE_PAYMENT( + "loan_charge_payment"), LOAN_INITIATE_TRANSFER("loan_initiate_transfer"), LOAN_ACCEPT_TRANSFER("loan_accept_transfer"), LOAN_WITHDRAW_TRANSFER( + "loan_withdraw_transfer"), LOAN_REJECT_TRANSFER("loan_reject_transfer"), LOAN_REASSIGN_OFFICER("loan_reassign_officer"), LOAN_REMOVE_OFFICER( + "loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE("loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION( + "loan_interest_recalculation"), LOAN_REFUND("loan_refund"), LOAN_FORECLOSURE("loan_foreclosure"), SAVINGS_ACTIVATE( + "savings_activated"), SAVINGS_ADJUST_TRANSACTION("savings_adjust_transaction"), SAVINGS_APPLY_ANNUAL_FEE( + "savings_apply_annual_fee"), SAVINGS_CALCULATE_INTEREST("savings_calculate_interest"), SAVINGS_CLOSE("savings_close"), SAVINGS_POST_INTEREST( + "savings_post_interest"), SAVINGS_REJECT("savings_reject"), SAVINGS_DEPOSIT("savings_deposit"), SAVINGS_WITHDRAWAL( + "savings_withdrawal"), SAVINGS_UNDO("savings_undo"), SAVINGS_ADD_CHARGE("savings_add_charge"), SAVINGS_WAIVE_CHARGE( + "savings_waive_charge"), SAVINGS_PAY_CHARGE("savings_pay_charge"), CLIENTS_ACCEPT_TRANSFER("clients_accept_transfer"), CLIENTS_ACTIVATE( + "clients_activate"), CLIENTS_ASSIGN_STAFF("clients_assign_staff"), CLIENTS_CLOSE("clients_close"), CLIENTS_CREATE( + "clients_create"), CLIENTS_DELETE("clients_delete"), CLIENTS_PROPOSE_TRANSFER("clients_propose_transfer"), CLIENTS_REACTIVATE( + "clients_reactivate"), CLIENTS_REJECT("clients_reject"), CLIENTS_REJECT_TRANSFER("clients_reject_transfer"), CLIENTS_WITHDRAW( + "clients_withdraw"), CLIENTS_WITHDRAW_TRANSFER("clients_withdraw_transfer"); private final String value; @@ -58,7 +68,7 @@ public class BusinessEventNotificationConstants { public static enum BUSINESS_ENTITY { LOAN("loan"), LOAN_TRANSACTION("loan_transaction"), LOAN_CHARGE("loan_charge"), LOAN_ADJUSTED_TRANSACTION( - "loan_adjusted_transaction"); + "loan_adjusted_transaction"), SAVING("saving"), CLIENT("client"), SAVINGS_TRANSACTION("Savings Transaction"); private final String value; @@ -70,4 +80,5 @@ public class BusinessEventNotificationConstants { return this.value; } } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 0b6fbf115..c236e37e7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -358,7 +358,7 @@ public class LoansApiResource { public String retrieveLoan(@PathParam("loanId") final Long loanId, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, @Context final UriInfo uriInfo) { - + long start = System.currentTimeMillis() ; this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId); @@ -392,9 +392,7 @@ public class LoansApiResource { if(calendarData != null) loanBasicDetails = LoanAccountData.withLoanCalendarData(loanBasicDetails, calendarData); } - Collection interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanBasicDetails); - Collection loanRepayments = null; LoanScheduleData repaymentSchedule = null; Collection charges = null; @@ -597,7 +595,13 @@ public class LoansApiResource { final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), mandatoryResponseParameters); - return this.toApiJsonSerializer.serialize(settings, loanAccount, this.LOAN_DATA_PARAMETERS); + long end = System.currentTimeMillis() ; + System.out.println("LoansApiResource.retrieveLoan() Time took: "+(end-start)); + start = System.currentTimeMillis() ; + String toReturn = this.toApiJsonSerializer.serialize(settings, loanAccount, this.LOAN_DATA_PARAMETERS); + end = System.currentTimeMillis() ; + System.out.println("LoansApiResource.retrieveLoan() Time took to Serialize: "+(end-start)); + return toReturn ; } @GET diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index c1b0f2cce..15e23cb30 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -6389,7 +6389,6 @@ public class Loan extends AbstractPersistableCustom { public Collection getLoanCharges() { return this.charges; } - public void initializeLazyCollections() { this.charges.size() ; this.trancheCharges.size() ; @@ -6412,4 +6411,9 @@ public class Loan extends AbstractPersistableCustom { public void initializeRepaymentSchedule() { this.repaymentScheduleInstallments.size() ; } + public boolean hasInvalidLoanType() { + return AccountType.fromInt(this.loanType).isInvalid(); + } + + public boolean isIndividualLoan(){return AccountType.fromInt(this.loanType).isIndividualAccount();} } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 3dc1cb728..0da639319 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -778,6 +778,14 @@ public class LoanTransaction extends AbstractPersistableCustom { return isAccrual(); } + public BigDecimal getOutstandingLoanBalance() { + return outstandingLoanBalance; + } + + public PaymentDetail getPaymentDetail() { + return this.paymentDetail; + } + public boolean isPaymentTransaction() { return this.isNotReversed() && !(this.isDisbursement() || this.isAccrual() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction() || this diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java new file mode 100644 index 000000000..6c64060b2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * {@link AbstractPlatformDomainRuleException} thrown an action to transition a + * loan from one state to another violates a domain rule. + */ +public class InvalidLoanTypeException extends AbstractPlatformDomainRuleException { + + public InvalidLoanTypeException(final String defaultUserMessage, final Object... defaultUserMessageArgs) { + super("error.msg.loan.type.invalid", defaultUserMessage, defaultUserMessageArgs); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index b697797bf..221273f38 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -1233,7 +1233,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.noteRepository.save(note); } } - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REJECTED, constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(loan.getId()) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 75a44ea70..46d979539 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -392,7 +392,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { String sql = "select " + mapper.schema() + " where l.id =?"; LoanTransactionData loanTransactionData = this.jdbcTemplate.queryForObject(sql, mapper, LoanTransactionType.REPAYMENT.getValue(), loanId, loanId); - final Collection paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); return LoanTransactionData.templateOnTop(loanTransactionData, paymentOptions); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java index c02bc9de1..be390118d 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java @@ -115,4 +115,5 @@ public final class PaymentDetail extends AbstractPersistableCustom { return this.paymentType; } + public String getReceiptNumber() { return this.receiptNumber; } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index 64260263e..ae1b0f461 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -25,6 +25,9 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; +import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; @@ -40,6 +43,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.MathContext; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -55,6 +59,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final ConfigurationDomainService configurationDomainService; private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; + private final BusinessEventNotifierService businessEventNotifierService; @Autowired public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository, @@ -62,7 +67,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService, final ConfigurationDomainService configurationDomainService, final PlatformSecurityContext context, - final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) { + final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository, + final BusinessEventNotifierService businessEventNotifierService) { this.savingsAccountRepository = savingsAccountRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; @@ -70,6 +76,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi this.configurationDomainService = configurationDomainService; this.context = context; this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository; + this.businessEventNotifierService = businessEventNotifierService; } @Transactional @@ -82,9 +89,10 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) { throw new DepositAccountTransactionNotAllowedException( account.getId(), "withdraw", account.depositAccountType()); } + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_WITHDRAWAL, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); final Set existingTransactionIds = new HashSet<>(); final boolean interestPostAsOn = false; final Set existingReversedTransactionIds = new HashSet<>(); @@ -92,7 +100,6 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, new Date(), user); final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee()); - final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); @@ -114,7 +121,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi this.savingsAccountRepository.save(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer()); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_WITHDRAWAL, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); return withdrawal; } @@ -147,7 +155,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi if (isRegularTransaction && !account.allowDeposit()) { throw new DepositAccountTransactionNotAllowedException(account.getId(), "deposit", account.depositAccountType()); } - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_DEPOSIT, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); boolean isInterestTransfer = false; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); @@ -172,7 +181,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi this.savingsAccountRepository.save(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_DEPOSIT, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); return deposit; } @@ -218,4 +228,10 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final boolean isAccountTransfer = false; postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); } + + private Map constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { + Map map = new HashMap<>(1); + map.put(entityEvent, entity); + return map; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index 56e648579..c623709bf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -729,16 +729,15 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -211,6 +216,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_ACTIVATE, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // @@ -272,6 +279,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi boolean isRegularTransaction = true; final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_DEPOSIT, + constructEntityMap(BUSINESS_ENTITY.SAVINGS_TRANSACTION, deposit)); + return new CommandProcessingResultBuilder() // .withEntityId(deposit.getId()) // .withOfficeId(account.officeId()) // @@ -315,6 +325,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, transactionAmount, paymentDetail, transactionBooleanValues); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_WITHDRAWAL, + constructEntityMap(BUSINESS_ENTITY.SAVINGS_TRANSACTION, withdrawal)); + return new CommandProcessingResultBuilder() // .withEntityId(withdrawal.getId()) // .withOfficeId(account.officeId()) // @@ -333,11 +346,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( savingsAccountChargeId, accountId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_APPLY_ANNUAL_FEE, + constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccountCharge)); final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt, user); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_APPLY_ANNUAL_FEE, + constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccountCharge)); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountCharge.getId()) // .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) // @@ -357,7 +373,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); checkClientOrGroupActive(account); - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_CALCULATE_INTEREST, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; @@ -366,6 +383,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepositoryWrapper.save(account); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_CALCULATE_INTEREST, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); + return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // .withOfficeId(account.officeId()) // @@ -416,7 +436,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_POST_INTEREST, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); if (account.getNominalAnnualInterestRate().compareTo(BigDecimal.ZERO) > 0 || (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) { final Set existingTransactionIds = new HashSet<>(); @@ -442,6 +463,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_POST_INTEREST, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); } } @@ -454,6 +477,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_UNDO, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -506,7 +531,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi account.activateAccountBasedOnBalance(); this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_UNDO, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // .withOfficeId(account.officeId()) // @@ -541,6 +567,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final LocalDate today = DateUtils.getLocalDateOfTenant(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_ADJUST_TRANSACTION, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); if (account.isNotActive()) { throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction); } @@ -595,6 +623,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi account.activateAccountBasedOnBalance(); this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_ADJUST_TRANSACTION, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); return new CommandProcessingResultBuilder() // .withEntityId(newtransactionId) // .withOfficeId(account.officeId()) // @@ -633,7 +663,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingsAccountTransactionDataValidator.validateClosing(command); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_CLOSE, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId); if (isLinkedWithAnyActiveLoan) { @@ -700,7 +731,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi // disable all standing orders linked to the savings account this.disableStandingInstructionsLinkedToClosedSavings(account); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_CLOSE, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // .withOfficeId(account.officeId()) // @@ -829,6 +861,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); checkClientOrGroupActive(savingsAccount); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_ADD_CHARGE, + constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccount)); final Locale locale = command.extractLocale(); final String format = command.dateFormat(); @@ -867,7 +901,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition); this.savingsAccountChargeRepository.save(savingsAccountCharge); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_ADD_CHARGE, + constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccount)); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountCharge.getId()) // .withOfficeId(savingsAccount.officeId()) // @@ -946,6 +981,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi // Get Savings account from savings charge final SavingsAccount account = savingsAccountCharge.savingsAccount(); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_WAIVE_CHARGE, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); this.savingAccountAssembler.assignSavingAccountHelpers(account); final Set existingTransactionIds = new HashSet<>(); @@ -977,7 +1014,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_WAIVE_CHARGE, + constructEntityMap(BUSINESS_ENTITY.SAVING, account)); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountChargeId) // .withOfficeId(account.officeId()) // @@ -1023,6 +1061,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( savingsAccountChargeId, savingsAccountId); + + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_PAY_CHARGE, + constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccountCharge)); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -1044,6 +1085,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt, user); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_PAY_CHARGE, + constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccountCharge)); return new CommandProcessingResultBuilder() // .withEntityId(savingsAccountCharge.getId()) // .withOfficeId(savingsAccountCharge.savingsAccount().officeId()) // @@ -1352,4 +1395,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } } } + + private Map constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { + Map map = new HashMap<>(1); + map.put(entityEvent, entity); + return map; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index a63b49cd8..6ebb7ee2e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.savings.service; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -53,6 +54,9 @@ import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; +import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.GroupRepository; import org.apache.fineract.portfolio.group.exception.CenterNotActiveException; @@ -101,7 +105,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl private final SavingsAccountDomainService savingsAccountDomainService; private final SavingsAccountWritePlatformService savingsAccountWritePlatformService; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; - + private final BusinessEventNotifierService businessEventNotifierService; + @Autowired public SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final SavingsAccountRepositoryWrapper savingAccountRepository, final SavingsAccountAssembler savingAccountAssembler, @@ -113,7 +118,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final CommandProcessingService commandProcessingService, final SavingsAccountDomainService savingsAccountDomainService, final SavingsAccountWritePlatformService savingsAccountWritePlatformService, - final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository) { + final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, + final BusinessEventNotifierService businessEventNotifierService) { this.context = context; this.savingAccountRepository = savingAccountRepository; this.savingAccountAssembler = savingAccountAssembler; @@ -130,6 +136,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl this.savingsAccountDomainService = savingsAccountDomainService; this.accountNumberFormatRepository = accountNumberFormatRepository; this.savingsAccountWritePlatformService = savingsAccountWritePlatformService; + this.businessEventNotifierService = businessEventNotifierService ; } /* @@ -415,7 +422,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl this.noteRepository.save(note); } } - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_REJECT, constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccount)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(savingsId) // @@ -506,4 +513,11 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl .setRollbackTransaction(rollbackTransaction)// .build(); } + + + private Map constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { + Map map = new HashMap<>(1); + map.put(entityEvent, entity); + return map; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java index c63bac702..522c9d343 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java @@ -20,7 +20,9 @@ package org.apache.fineract.portfolio.transfer.service; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -40,6 +42,9 @@ import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; import org.apache.fineract.portfolio.client.domain.ClientStatus; import org.apache.fineract.portfolio.client.exception.ClientHasBeenClosedException; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; +import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException; @@ -79,6 +84,7 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr private final TransfersDataValidator transfersDataValidator; private final NoteWritePlatformService noteWritePlatformService; private final StaffRepositoryWrapper staffRepositoryWrapper; + private final BusinessEventNotifierService businessEventNotifierService; @Autowired public TransferWritePlatformServiceJpaRepositoryImpl(final ClientRepositoryWrapper clientRepositoryWrapper, @@ -87,7 +93,8 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr final LoanRepositoryWrapper loanRepositoryWrapper, final TransfersDataValidator transfersDataValidator, final NoteWritePlatformService noteWritePlatformService, final StaffRepositoryWrapper staffRepositoryWrapper, final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, - final SavingsAccountWritePlatformService savingsAccountWritePlatformService) { + final SavingsAccountWritePlatformService savingsAccountWritePlatformService, + final BusinessEventNotifierService businessEventNotifierService) { this.clientRepositoryWrapper = clientRepositoryWrapper; this.officeRepository = officeRepository; this.calendarInstanceRepository = calendarInstanceRepository; @@ -99,6 +106,7 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr this.staffRepositoryWrapper = staffRepositoryWrapper; this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper; this.savingsAccountWritePlatformService = savingsAccountWritePlatformService; + this.businessEventNotifierService = businessEventNotifierService; } @Override @@ -293,9 +301,12 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName); final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId); final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_PROPOSE_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); handleClientTransferLifecycleEvent(client, office, TransferEventType.PROPOSAL, jsonCommand); this.clientRepositoryWrapper.save(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_PROPOSE_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withClientId(clientId) // .withEntityId(clientId) // @@ -321,9 +332,12 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr this.transfersDataValidator.validateForAcceptClientTransfer(jsonCommand.json()); final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId, true); validateClientAwaitingTransferAcceptance(client); - + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_ACCEPT_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); handleClientTransferLifecycleEvent(client, client.getTransferToOffice(), TransferEventType.ACCEPTANCE, jsonCommand); this.clientRepositoryWrapper.save(client); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_ACCEPT_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withClientId(clientId) // @@ -337,11 +351,13 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr // validation this.transfersDataValidator.validateForWithdrawClientTransfer(jsonCommand.json()); final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_WITHDRAW_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); validateClientAwaitingTransferAcceptanceOnHold(client); - handleClientTransferLifecycleEvent(client, client.getOffice(), TransferEventType.WITHDRAWAL, jsonCommand); this.clientRepositoryWrapper.save(client); - + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_WITHDRAW_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); return new CommandProcessingResultBuilder() // .withClientId(clientId) // .withEntityId(clientId) // @@ -354,9 +370,14 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr // validation this.transfersDataValidator.validateForRejectClientTransfer(jsonCommand.json()); final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_REJECT_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); handleClientTransferLifecycleEvent(client, client.getOffice(), TransferEventType.REJECTION, jsonCommand); this.clientRepositoryWrapper.save(client); + this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_REJECT_TRANSFER, + constructEntityMap(BUSINESS_ENTITY.CLIENT, client)); + return new CommandProcessingResultBuilder() // .withClientId(clientId) // .withEntityId(clientId) // @@ -503,6 +524,12 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr if (!client.isTransferInProgressOrOnHold()) { throw new ClientNotAwaitingTransferApprovalOrOnHoldException(client.getId()); } } + private Map constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { + Map map = new HashMap<>(1); + map.put(entityEvent, entity); + return map; + } + /** * private void validateGroupAwaitingTransferAcceptanceOnHold(final Group * group) { if (!group.isTransferInProgressOrOnHold()) { throw new diff --git a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml index 998492622..80ebec663 100644 --- a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml +++ b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml @@ -79,6 +79,7 @@ + diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V322__sms_campaign.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V322__sms_campaign.sql new file mode 100644 index 000000000..025cc3dc7 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V322__sms_campaign.sql @@ -0,0 +1,277 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +CREATE TABLE `sms_campaign` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `campaign_name` VARCHAR(100) NOT NULL, + `campaign_type` INT(11) NOT NULL, + `campaign_trigger_type` INT(11) NOT NULL, + `report_id` INT(11) NOT NULL, + `provider_id` BIGINT(20) NOT NULL, + `param_value` TEXT NULL, + `status_enum` INT(11) NOT NULL, + `message` TEXT NOT NULL, + `submittedon_date` DATE NULL DEFAULT NULL, + `submittedon_userid` BIGINT(20) NULL DEFAULT NULL, + `approvedon_date` DATE NULL DEFAULT NULL, + `approvedon_userid` BIGINT(20) NULL DEFAULT NULL, + `closedon_date` DATE NULL DEFAULT NULL, + `closedon_userid` BIGINT(20) NULL DEFAULT NULL, + `recurrence` VARCHAR(100) NULL DEFAULT NULL, + `next_trigger_date` DATETIME NULL DEFAULT NULL, + `last_trigger_date` DATETIME NULL DEFAULT NULL, + `recurrence_start_date` DATETIME NULL DEFAULT NULL, + `is_visible` TINYINT(1) NULL DEFAULT '1', + PRIMARY KEY (`id`), + INDEX `report_id` (`report_id`), + CONSTRAINT `sms_campaign_ibfk_1` FOREIGN KEY (`report_id`) REFERENCES `stretchy_report` (`id`) +); + + +ALTER TABLE `sms_messages_outbound` + ADD COLUMN `campaign_id` BIGINT(20) NOT NULL, + ADD COLUMN `external_id` BIGINT(20) NULL DEFAULT NULL, + ADD COLUMN `submittedon_date` DATE NULL DEFAULT NULL, + ADD COLUMN `delivered_on_date` DATETIME NULL DEFAULT NULL, + ADD INDEX `FKCAMPAIGN00000001` (`campaign_id`), + ADD CONSTRAINT `FKCAMPAIGN00000001` FOREIGN KEY (`campaign_id`) REFERENCES `sms_campaign` (`id`); + + +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`, `is_misfired`) VALUES +('Update Sms Outbound with campaign message', 'Update Sms Outbound with campaign message', '0 0 0 1/1 * ? *', NOW(), 3, NULL, NULL, NULL, 'Update Sms Outbound with campaign message1 _ DEFAULT', NULL, 0, 0, 1, 1, 0), +('Send messages to SMS gateway', 'Send messages to SMS gateway', '0 0 0 1/1 * ? *', NOW(), 3, NULL, NULL, NULL, 'Send messages to SMS gateway1 _ DEFAULT', NULL, 0, 0, 1, 1, 0), +('Get delivery reports from SMS gateway', 'Get delivery reports from SMS gateway', '0 0 0 1/1 * ? *', NOW(), 3, NULL, NULL, NULL, 'Get delivery reports from SMS gateway1 _ DEFAULT', NULL, 0, 0, 1, 1, 0); + +INSERT INTO `m_permission` +(`grouping` ,`code` ,`entity_name`, `action_name`, `can_maker_checker`) VALUES +('organisation', 'READ_SMSCAMPAIGN', 'SMSCAMPAIGN', 'READ', '0'), +('organisation', 'CREATE_SMSCAMPAIGN', 'SMSCAMPAIGN', 'CREATE', '0'), +('organisation', 'CREATE_SMSCAMPAIGN_CHECKER', 'SMSCAMPAIGN', 'CREATE', '0'), +('organisation', 'UPDATE_SMSCAMPAIGN', 'SMSCAMPAIGN', 'UPDATE', '0'), +('organisation', 'UPDATE_SMSCAMPAIGN_CHECKER', 'SMSCAMPAIGN', 'UPDATE', '0'), +('organisation', 'DELETE_SMSCAMPAIGN', 'SMSCAMPAIGN', 'DELETE', '0'), +('organisation', 'DELETE_SMSCAMPAIGN_CHECKER', 'SMSCAMPAIGN', 'DELETE', '0'), +('organisation', 'ACTIVATE_SMSCAMPAIGN', 'SMSCAMPAIGN', 'ACTIVATE', '0'), +('organisation', 'CLOSE_SMSCAMPAIGN', 'SMSCAMPAIGN', 'CLOSE', '0'), +('organisation', 'REACTIVATE_SMSCAMPAIGN', 'SMSCAMPAIGN', 'REACTIVATE', '0'); + + + + +INSERT INTO `m_code_value` (`code_id`, `code_value`, `code_description`, `order_position`, `code_score`, `is_active`, `is_mandatory`) VALUES ((SELECT `id` FROM `m_code` mc WHERE mc.code_name = "GROUPROLE" AND is_system_defined), 'Leader', 'Group Leader Role', 1, NULL, 1, 0); + + + + +INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES ('cycleXSelect', 'cycleX', 'Cycle X Number', 'text', 'number', 'n/a', NULL, NULL, NULL, NULL, NULL), +('cycleYSelect', 'cycleY', 'Cycle Y Number', 'text', 'number', 'n/a', NULL, NULL, NULL, NULL, NULL), +('fromXSelect', 'fromX', 'From X Number', 'text', 'number', 'n/a', NULL, NULL, NULL, NULL, NULL), +('toYSelect', 'toY', 'To Y Number', 'text', 'number', 'n/a', NULL, NULL, NULL, NULL, NULL), +('overdueXSelect', 'overdueX', 'Overdue X Number', 'text', 'number', 'n/a', NULL, NULL, NULL, NULL, NULL), +('overdueYSelect', 'overdueY', 'Overdue Y Number', 'text', 'number', 'n/a', NULL, NULL, NULL, NULL, NULL); + + + +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Active Clients', 'SMS', 'NonTriggered', 'Clients', 'SELECT c.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH(\r\nREPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) AS "officeName", \r\no.id AS "officeNumber"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nLEFT JOIN r_enum_value r ON r.enum_name = \'status_enum\' AND r.enum_id = c.status_enum\r\nWHERE o.id = ${officeId} AND c.status_enum = 300 AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId})\r\nGROUP BY c.id\r\nORDER BY ounder.hierarchy, c.account_no', 'All clients with the status ‘Active’', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Prospective Clients', 'SMS', 'NonTriggered', 'Clients', 'SELECT c.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH(\r\nREPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) AS "officeName", \r\no.id AS "officeNumber"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nLEFT JOIN r_enum_value r ON r.enum_name = \'status_enum\' AND r.enum_id = c.status_enum\r\nLEFT JOIN m_loan l ON l.client_id = c.id\r\nWHERE o.id = ${officeId} AND c.status_enum = 300 AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND l.client_id IS NULL\r\nGROUP BY c.id\r\nORDER BY ounder.hierarchy, c.account_no', 'All clients with the status ‘Active’ who have never had a loan before', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Active Loan Clients', 'SMS', 'NonTriggered', 'Clients', 'SELECT \r\nc.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", \r\nl.principal_amount AS "loanAmount", \r\n(IFNULL(l.principal_outstanding_derived, 0) + IFNULL(l.interest_outstanding_derived, 0) + IFNULL(l.fee_charges_outstanding_derived, 0) + IFNULL(l.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nl.principal_disbursed_derived AS "loanDisbursed",\r\nounder.id AS "officeNumber", \r\nl.account_no AS "loanAccountId", \r\ngua.lastname AS "guarantorLastName", COUNT(gua.id) AS "numberOfGuarantors",\r\ng.display_name AS "groupName"\r\n\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan l ON l.client_id = c.id\r\nJOIN m_product_loan pl ON pl.id = l.product_id\r\nLEFT JOIN m_group_client gc ON gc.client_id = c.id\r\nLEFT JOIN m_group g ON g.id = gc.group_id\r\nLEFT JOIN m_staff lo ON lo.id = l.loan_officer_id\r\nLEFT JOIN m_currency cur ON cur.code = l.currency_code\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = l.id\r\nWHERE o.id = ${officeId} AND (IFNULL(l.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND l.loan_status_id = 300 AND (DATEDIFF(CURDATE(), l.disbursedon_date) BETWEEN ${cycleX} AND ${cycleY})\r\nGROUP BY l.id\r\nORDER BY ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'All clients with an outstanding loan', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Loan in arrears', 'SMS', 'NonTriggered', 'Loan', 'SELECT \r\nmc.id AS "id", \r\nmc.firstname AS "firstName",\r\nmc.middlename AS "middleName",\r\nmc.lastname AS "lastName",\r\nmc.display_name AS "fullName",\r\nmc.mobile_no AS "mobileNo", \r\nml.principal_amount AS "loanAmount", \r\n(IFNULL(ml.principal_outstanding_derived, 0) + IFNULL(ml.interest_outstanding_derived, 0) + IFNULL(ml.fee_charges_outstanding_derived, 0) + IFNULL(ml.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nml.principal_disbursed_derived AS "loanDisbursed",\r\nlaa.overdue_since_date_derived AS "paymentDueDate",\r\nIFNULL(laa.total_overdue_derived, 0) AS "totalDue",\r\nounder.id AS "officeNumber", \r\nml.account_no AS "loanAccountId", \r\ngua.lastname AS "guarantorLastName", \r\nCOUNT(gua.id) AS "numberOfGuarantors",\r\ng.display_name AS "groupName"\r\n\r\nFROM m_office mo\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\nINNER JOIN m_client mc ON mc.office_id=ounder.id\r\nINNER JOIN m_loan ml ON ml.client_id = mc.id\r\nINNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = \'loan_status_id\'\r\nINNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\nLEFT JOIN m_currency cur ON cur.code = ml.currency_code\r\nLEFT JOIN m_group_client gc ON gc.client_id = mc.id\r\nLEFT JOIN m_group g ON g.id = gc.group_id\r\nLEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = ml.id\r\nWHERE ml.loan_status_id=300 AND mo.id=${officeId} AND (IFNULL(ml.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND (DATEDIFF(CURDATE(), laa.overdue_since_date_derived) BETWEEN ${fromX} AND ${toY})\r\nGROUP BY ml.id\r\nORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no', 'All clients with an outstanding loan in arrears between X and Y days', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Loan payments due', 'SMS', 'NonTriggered', 'Loan', 'SELECT \r\ncl.id AS "id", \r\ncl.firstname AS "firstName",\r\ncl.middlename AS "middleName",\r\ncl.lastname AS "lastName",\r\ncl.display_name AS "fullName",\r\ncl.mobile_no AS "mobileNo", \r\nl.principal_amount AS "loanAmount",\r\nof.id AS "officeNumber",\r\n(IFNULL(l.principal_outstanding_derived, 0) + IFNULL(l.interest_outstanding_derived, 0) + IFNULL(l.fee_charges_outstanding_derived, 0) + IFNULL(l.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nl.principal_disbursed_derived AS "loanDisbursed",\r\nls.duedate AS "paymentDueDate",\r\n(IFNULL(SUM(ls.principal_amount),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0)\r\n + IFNULL(SUM(ls.interest_amount),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.interest_waived_derived),0)\r\n + IFNULL(SUM(ls.fee_charges_amount),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\r\n + IFNULL(SUM(ls.penalty_charges_amount),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\r\n) AS "totalDue",\r\nlaa.total_overdue_derived AS "totalOverdue",\r\nl.account_no AS "loanAccountId",\r\ngua.lastname AS "guarantorLastName",\r\nCOUNT(gua.id) AS "numberOfGuarantors",\r\ngp.display_name AS "groupName"\r\n\r\nFROM m_office of\r\nLEFT JOIN m_client cl ON of.id = cl.office_id\r\nLEFT JOIN m_loan l ON cl.id = l.client_id\r\nLEFT JOIN m_group_client gc ON gc.client_id = cl.id\r\nLEFT JOIN m_group gp ON gp.id = l.group_id\r\nLEFT JOIN m_loan_repayment_schedule ls ON l.id = ls.loan_id\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = l.id\r\nINNER JOIN m_loan_arrears_aging laa ON laa.loan_id=l.id\r\nWHERE of.id = ${officeId} AND (IFNULL(l.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND (DATEDIFF(CURDATE(), ls.duedate) BETWEEN ${fromX} AND ${toY}) \r\nAND (of.hierarchy LIKE CONCAT((\r\nSELECT ino.hierarchy\r\nFROM m_office ino\r\nWHERE ino.id = ${}),"%"))\r\nGROUP BY l.id\r\nORDER BY of.hierarchy, l.currency_code, cl.account_no, l.account_no', 'All clients with an unpaid installment due on their loan between X and Y days', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Dormant Prospects', 'SMS', 'NonTriggered', 'Clients', 'SELECT CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH(\r\nREPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) AS "officeName", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", \r\no.id AS "officeNumber", \r\nTIMESTAMPDIFF(MONTH, c.activation_date, CURDATE()) AS "dormant"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nLEFT JOIN r_enum_value r ON r.enum_name = \'status_enum\' AND r.enum_id = c.status_enum\r\nLEFT JOIN m_loan l ON l.client_id = c.id\r\nWHERE o.id = ${officeId} AND c.status_enum = 300 AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND l.client_id IS NULL AND (TIMESTAMPDIFF(MONTH, c.activation_date, CURDATE()) > 3)\r\nGROUP BY c.id\r\nORDER BY ounder.hierarchy, c.account_no', 'All individuals who have not yet received a loan but were also entered into the system more than 3 months', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Active group leaders', 'SMS', 'NonTriggered', 'Clients', 'SELECT c.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH(\r\nREPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) AS "officeName", \r\no.id AS "officeNumber"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_group g ON g.office_id = ounder.id\r\nJOIN m_client c ON c.office_id = ounder.id\r\nLEFT JOIN m_group_client gc ON gc.group_id = g.id AND gc.client_id = c.id\r\nLEFT JOIN m_group_roles gr ON gr.group_id = g.id AND gr.client_id = c.id\r\nLEFT JOIN m_staff ms ON ms.id = c.staff_id\r\nLEFT JOIN r_enum_value r ON r.enum_name = \'status_enum\' AND r.enum_id = c.status_enum\r\nLEFT JOIN m_code_value cv ON cv.id = gr.role_cv_id\r\nLEFT JOIN m_code code ON code.id = cv.code_id\r\nWHERE o.id = ${officeId} AND g.status_enum = 300 AND c.status_enum = 300 AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND code.code_name = \'GROUPROLE\' AND cv.code_value = \'Leader\'\r\nGROUP BY c.id\r\nORDER BY ounder.hierarchy, c.account_no', 'All active group chairmen', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Loan payments due (Overdue Loans)', 'SMS', 'NonTriggered', 'Loan', 'SELECT \r\nmc.id AS "id", \r\nmc.firstname AS "firstName",\r\nmc.middlename AS "middleName",\r\nmc.lastname AS "lastName",\r\nmc.display_name AS "fullName",\r\nmc.mobile_no AS "mobileNo", \r\nml.principal_amount AS "loanAmount", \r\n(IFNULL(ml.principal_outstanding_derived, 0) + IFNULL(ml.interest_outstanding_derived, 0) + IFNULL(ml.fee_charges_outstanding_derived, 0) + IFNULL(ml.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nml.principal_disbursed_derived AS "loanDisbursed",\r\nlaa.overdue_since_date_derived AS "paymentDueDate",\r\n(IFNULL(SUM(ls.principal_amount),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0)\r\n + IFNULL(SUM(ls.interest_amount),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.interest_waived_derived),0)\r\n + IFNULL(SUM(ls.fee_charges_amount),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\r\n + IFNULL(SUM(ls.penalty_charges_amount),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\r\n) AS "totalDue",\r\nlaa.total_overdue_derived AS "totalOverdue",\r\nounder.id AS "officeNumber", \r\nml.account_no AS "loanAccountId", \r\ngua.lastname AS "guarantorLastName", \r\nCOUNT(gua.id) AS "numberOfGuarantors",\r\ng.display_name AS "groupName"\r\n\r\nFROM m_office mo\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\nINNER JOIN m_client mc ON mc.office_id=ounder.id\r\nINNER JOIN m_loan ml ON ml.client_id = mc.id\r\nINNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = \'loan_status_id\'\r\nINNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\nLEFT JOIN m_loan_repayment_schedule ls ON ls.loan_id = ml.id\r\nLEFT JOIN m_currency cur ON cur.code = ml.currency_code\r\nLEFT JOIN m_group_client gc ON gc.client_id = mc.id\r\nLEFT JOIN m_group g ON g.id = gc.group_id\r\nLEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = ml.id\r\nWHERE ml.loan_status_id=300 AND mo.id=${officeId} AND (IFNULL(ml.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) \r\nAND (DATEDIFF(CURDATE(), ls.duedate) BETWEEN ${fromX} AND ${toY})\r\nAND (DATEDIFF(CURDATE(), laa.overdue_since_date_derived) BETWEEN ${overdueX} AND ${overdueY})\r\nGROUP BY ml.id\r\nORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no', 'Loan Payments Due between X to Y days for clients in arrears between X and Y days', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Loan payments received (Active Loans)', 'SMS', 'NonTriggered', 'Loan', 'SELECT \r\nmc.id AS "id", \r\nmc.firstname AS "firstName",\r\nmc.middlename AS "middleName",\r\nmc.lastname AS "lastName",\r\nmc.display_name AS "fullName",\r\nmc.mobile_no AS "mobileNo", \r\nml.principal_amount AS "loanAmount", \r\n(IFNULL(ml.principal_outstanding_derived, 0) + IFNULL(ml.interest_outstanding_derived, 0) + IFNULL(ml.fee_charges_outstanding_derived, 0) + IFNULL(ml.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nounder.id AS "officeNumber", \r\nml.account_no AS "loanAccountNumber",\r\nSUM(lt.amount) AS "repaymentAmount"\r\nFROM m_office mo\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\nINNER JOIN m_client mc ON mc.office_id=ounder.id\r\nINNER JOIN m_loan ml ON ml.client_id = mc.id\r\nINNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = \'loan_status_id\'\r\nINNER JOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nINNER JOIN m_appuser au ON au.id = lt.appuser_id\r\nLEFT JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\nLEFT JOIN m_payment_detail mpd ON mpd.id=lt.payment_detail_id\r\nLEFT JOIN m_currency cur ON cur.code = ml.currency_code\r\nLEFT JOIN m_group_client gc ON gc.client_id = mc.id\r\nLEFT JOIN m_group g ON g.id = gc.group_id\r\nLEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = ml.id\r\nWHERE ml.loan_status_id=300 AND mo.id=${officeId} AND (IFNULL(ml.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND (DATEDIFF(CURDATE(), lt.transaction_date) BETWEEN ${fromX} AND ${toY}) AND lt.is_reversed=0 AND lt.transaction_type_enum=2 AND laa.loan_id IS NULL\r\nGROUP BY ml.id\r\nORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no', 'Payments received in the last X to Y days for any loan with the status Active (on-time)', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Loan payments received (Overdue Loans)', 'SMS', 'NonTriggered', 'Loan', 'SELECT \r\nml.id AS "loanId", \r\nmc.id AS "id", \r\nmc.firstname AS "firstName",\r\nmc.middlename AS "middleName",\r\nmc.lastname AS "lastName",\r\nmc.display_name AS "fullName",\r\nmc.mobile_no AS "mobileNo", \r\nml.principal_amount AS "loanAmount", \r\n(IFNULL(ml.principal_outstanding_derived, 0) + IFNULL(ml.interest_outstanding_derived, 0) + IFNULL(ml.fee_charges_outstanding_derived, 0) + IFNULL(ml.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nounder.id AS "officeNumber", \r\nml.account_no AS "loanAccountNumber",\r\nSUM(lt.amount) AS "repaymentAmount"\r\nFROM m_office mo\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\r\nINNER JOIN m_client mc ON mc.office_id=ounder.id\r\nINNER JOIN m_loan ml ON ml.client_id = mc.id\r\nINNER JOIN r_enum_value rev ON rev.enum_id=ml.loan_status_id AND rev.enum_name = \'loan_status_id\'\r\nINNER JOIN m_loan_arrears_aging laa ON laa.loan_id=ml.id\r\nINNER JOIN m_loan_transaction lt ON lt.loan_id = ml.id\r\nINNER JOIN m_appuser au ON au.id = lt.appuser_id\r\nLEFT JOIN m_payment_detail mpd ON mpd.id=lt.payment_detail_id\r\nLEFT JOIN m_currency cur ON cur.code = ml.currency_code\r\nLEFT JOIN m_group_client gc ON gc.client_id = mc.id\r\nLEFT JOIN m_group g ON g.id = gc.group_id\r\nLEFT JOIN m_staff lo ON lo.id = ml.loan_officer_id\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = ml.id\r\nWHERE ml.loan_status_id=300 AND mo.id=${officeId} AND (IFNULL(ml.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND (DATEDIFF(CURDATE(), lt.transaction_date) BETWEEN ${fromX} AND ${toY}) AND (DATEDIFF(CURDATE(), laa.overdue_since_date_derived) BETWEEN ${overdueX} AND ${overdueY}) AND lt.is_reversed=0 AND lt.transaction_type_enum=2\r\nGROUP BY ml.id\r\nORDER BY ounder.hierarchy, ml.currency_code, mc.account_no, ml.account_no', 'Payments received in the last X to Y days for any loan with the status Overdue (arrears) between X and Y days', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Happy Birthday', 'SMS', 'NonTriggered', 'Clients', 'SELECT \r\nc.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH(\r\nREPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) AS "officeName", \r\no.id AS "officeNumber", \r\nc.date_of_birth AS "dateOfBirth",\r\nIF(c.date_of_birth IS NULL, 0, CEIL(DATEDIFF (NOW(), c.date_of_birth)/365)) AS "age"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nLEFT JOIN r_enum_value r ON r.enum_name = \'status_enum\' AND r.enum_id = c.status_enum\r\nLEFT JOIN m_staff ms ON ms.id = c.staff_id\r\nWHERE o.id = ${officeId} AND c.status_enum = 300 AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND c.date_of_birth IS NOT NULL AND c.date_of_birth = CURDATE()\r\nORDER BY ounder.hierarchy, c.account_no', 'This sends a message to all clients with the status Active on their Birthday', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Loan fully repaid', 'SMS', 'NonTriggered', 'Loan', 'SELECT \r\nc.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", \r\nl.principal_amount AS "loanAmount",\r\n(IFNULL(l.principal_outstanding_derived, 0) + IFNULL(l.interest_outstanding_derived, 0) + IFNULL(l.fee_charges_outstanding_derived, 0) + IFNULL(l.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nl.principal_disbursed_derived AS "loanDisbursed",\r\no.id AS "officeNumber",\r\nl.account_no AS "loanAccountId",\r\ngua.lastname AS "guarantorLastName", COUNT(gua.id) AS "numberOfGuarantors",\r\nls.duedate AS "dueDate",\r\nlaa.total_overdue_derived AS "totalDue",\r\ngp.display_name AS "groupName",\r\nl.total_repayment_derived AS "totalFullyPaid"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan l ON l.client_id = c.id\r\nLEFT JOIN m_staff lo ON lo.id = l.loan_officer_id\r\nLEFT JOIN m_currency cur ON cur.code = l.currency_code\r\nLEFT JOIN m_group_client gc ON gc.client_id = c.id\r\nLEFT JOIN m_group gp ON gp.id = l.group_id\r\nLEFT JOIN m_loan_repayment_schedule ls ON l.id = ls.loan_id\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = l.id\r\nINNER JOIN m_loan_arrears_aging laa ON laa.loan_id=l.id\r\nWHERE o.id = ${officeId} AND (IFNULL(l.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND \r\n(DATEDIFF(CURDATE(), l.closedon_date) BETWEEN ${fromX} AND ${toY})\r\n AND (l.loan_status_id = 600 OR l.loan_status_id = 700)\r\nGROUP BY l.id\r\nORDER BY ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'All loans that have been fully repaid (Closed or Overpaid) in the last X to Y days', 0, 1); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Loan outstanding after final instalment date', 'SMS', 'NonTriggered', 'Loan', 'SELECT \r\nc.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", \r\nl.principal_amount AS "loanAmount",\r\no.id AS "officeNumber",\r\n(IFNULL(l.principal_outstanding_derived, 0) + IFNULL(l.interest_outstanding_derived, 0) + IFNULL(l.fee_charges_outstanding_derived, 0) + IFNULL(l.penalty_charges_outstanding_derived, 0)) AS "loanOutstanding",\r\nl.principal_disbursed_derived AS "loanDisbursed",\r\nls.duedate AS "paymentDueDate",\r\n(IFNULL(SUM(ls.principal_amount),0) - IFNULL(SUM(ls.principal_writtenoff_derived),0)\r\n + IFNULL(SUM(ls.interest_amount),0) - IFNULL(SUM(ls.interest_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.interest_waived_derived),0)\r\n + IFNULL(SUM(ls.fee_charges_amount),0) - IFNULL(SUM(ls.fee_charges_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.fee_charges_waived_derived),0)\r\n + IFNULL(SUM(ls.penalty_charges_amount),0) - IFNULL(SUM(ls.penalty_charges_writtenoff_derived),0) \r\n - IFNULL(SUM(ls.penalty_charges_waived_derived),0)\r\n) AS "totalDue",\r\nlaa.total_overdue_derived AS "totalOverdue",\r\nl.account_no AS "loanAccountId",\r\ngua.lastname AS "guarantorLastName",\r\nCOUNT(gua.id) AS "numberOfGuarantors",\r\ngp.display_name AS "groupName"\r\n\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_loan l ON l.client_id = c.id\r\nLEFT JOIN m_staff lo ON lo.id = l.loan_officer_id\r\nLEFT JOIN m_currency cur ON cur.code = l.currency_code\r\nLEFT JOIN m_loan_arrears_aging laa ON laa.loan_id = l.id\r\nLEFT JOIN m_group_client gc ON gc.client_id = c.id\r\nLEFT JOIN m_group gp ON gp.id = l.group_id\r\nLEFT JOIN m_loan_repayment_schedule ls ON l.id = ls.loan_id\r\nLEFT JOIN m_guarantor gua ON gua.loan_id = l.id\r\nWHERE o.id = ${officeId} AND (IFNULL(l.loan_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND l.loan_status_id = 300 AND l.expected_maturedon_date < CURDATE() \r\nAND (DATEDIFF(CURDATE(), l.expected_maturedon_date) BETWEEN ${fromX} AND ${toY})\r\nGROUP BY l.id\r\nORDER BY ounder.hierarchy, l.currency_code, c.account_no, l.account_no', 'All active loans (with an outstanding balance) between X to Y days after the final instalment date on their loan schedule', 0, 1); + + +INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES +((SELECT id FROM stretchy_report WHERE report_name='Active Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Active Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), + +((SELECT id FROM stretchy_report WHERE report_name='Prospective Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Prospective Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), + +((SELECT id FROM stretchy_report WHERE report_name='Active Loan Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Active Loan Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Active Loan Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='cycleXSelect' AND parameter_variable = 'cycleX'), 'cycleX'), +((SELECT id FROM stretchy_report WHERE report_name='Active Loan Clients' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='cycleYSelect' AND parameter_variable = 'cycleY'), 'cycleY'), + +((SELECT id FROM stretchy_report WHERE report_name='Loan in arrears' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan in arrears' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan in arrears' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='fromXSelect' AND parameter_variable = 'fromX'), 'fromX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan in arrears' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='toYSelect' AND parameter_variable = 'toY'), 'toY'), + +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='fromXSelect' AND parameter_variable = 'fromX'), 'fromX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='toYSelect' AND parameter_variable = 'toY'), 'toY'), + +((SELECT id FROM stretchy_report WHERE report_name='Dormant Prospects' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Dormant Prospects' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), + +((SELECT id FROM stretchy_report WHERE report_name='Active group leaders' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Active group leaders' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), + +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='fromXSelect' AND parameter_variable = 'fromX'), 'fromX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='toYSelect' AND parameter_variable = 'toY'), 'toY'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='overdueXSelect' AND parameter_variable = 'overdueX'), 'overdueX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments due (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='overdueYSelect' AND parameter_variable = 'overdueY'), 'overdueY'), + +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Active Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Active Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Active Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='fromXSelect' AND parameter_variable = 'fromX'), 'fromX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Active Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='toYSelect' AND parameter_variable = 'toY'), 'toY'), + +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='fromXSelect' AND parameter_variable = 'fromX'), 'fromX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='toYSelect' AND parameter_variable = 'toY'), 'toY'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='overdueXSelect' AND parameter_variable = 'overdueX'), 'overdueX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan payments received (Overdue Loans)' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='overdueYSelect' AND parameter_variable = 'overdueY'), 'overdueY'), + +((SELECT id FROM stretchy_report WHERE report_name='Happy Birthday' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Happy Birthday' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), + +((SELECT id FROM stretchy_report WHERE report_name='Loan fully repaid' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan fully repaid' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan fully repaid' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='fromXSelect' AND parameter_variable = 'fromX'), 'fromX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan fully repaid' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='toYSelect' AND parameter_variable = 'toY'), 'toY'), + +((SELECT id FROM stretchy_report WHERE report_name='Loan outstanding after final instalment date' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='OfficeIdSelectOne' AND parameter_variable = 'officeId'), 'officeId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan outstanding after final instalment date' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='loanOfficerIdSelectAll' AND parameter_variable = 'loanOfficerId'), 'loanOfficerId'), +((SELECT id FROM stretchy_report WHERE report_name='Loan outstanding after final instalment date' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='fromXSelect' AND parameter_variable = 'fromX'), 'fromX'), +((SELECT id FROM stretchy_report WHERE report_name='Loan outstanding after final instalment date' AND report_type = 'SMS'), (SELECT id FROM stretchy_parameter WHERE parameter_name='toYSelect' AND parameter_variable = 'toY'), 'toY'); + + + +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES +('Loan Repayment', 'SMS', 'Triggered', NULL, 'select ml.id as loanId,mc.id, mc.firstname, ifnull(mc.middlename,\'\') as middlename, mc.lastname, mc.display_name as FullName, mobile_no as mobileNo, mc.group_name as GroupName, round(ml.principal_amount, ml.currency_digits) as LoanAmount, round(ml.`total_outstanding_derived`, ml.currency_digits) as LoanOutstanding,\nml.`account_no` as LoanAccountId, round(mlt.amountPaid, ml.currency_digits) as repaymentAmount\nFROM m_office mo\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\')\nAND ounder.hierarchy like CONCAT(\'.\', \'%\')\nLEFT JOIN (\n select \n ml.id as loanId, \n ifnull(mc.id,mc2.id) as id, \n ifnull(mc.firstname,mc2.firstname) as firstname, \n ifnull(mc.middlename,ifnull(mc2.middlename,(\'\'))) as middlename, \n ifnull(mc.lastname,mc2.lastname) as lastname, \n ifnull(mc.display_name,mc2.display_name) as display_name, \n ifnull(mc.status_enum,mc2.status_enum) as status_enum,\n ifnull(mc.mobile_no,mc2.mobile_no) as mobile_no,\n ifnull(mg.office_id,mc2.office_id) as office_id,\n ifnull(mg.staff_id,mc2.staff_id) as staff_id,\n mg.id as group_id, \nmg.display_name as group_name\n from\n m_loan ml\n left join m_group mg on mg.id = ml.group_id\n left join m_group_client mgc on mgc.group_id = mg.id\n left join m_client mc on mc.id = mgc.client_id\n left join m_client mc2 on mc2.id = ml.client_id\n order by loanId\n ) mc on mc.office_id = ounder.id\nright join m_loan as ml on mc.loanId = ml.id\nright join(\nselect mlt.amount as amountPaid,mlt.id,mlt.loan_id\nfrom m_loan_transaction mlt\nwhere mlt.is_reversed = 0 \ngroup by mlt.loan_id\n) as mlt on mlt.loan_id = ml.id\nright join m_loan_repayment_schedule as mls1 on ml.id = mls1.loan_id and mls1.`completed_derived` = 0\nand mls1.installment = (SELECT MIN(installment) from m_loan_repayment_schedule where loan_id = ml.id and duedate <= CURDATE() and completed_derived=0)\nwhere mc.status_enum = 300 and mobile_no is not null and ml.`loan_status_id` = 300\nand (mo.id = ${officeId} or ${officeId} = -1)\nand (mc.staff_id = ${loanOfficerId} or ${loanOfficerId} = -1)\nand (ml.loan_type_enum = ${loanType} or ${loanType} = -1)\nand ml.id in (select mla.loan_id from m_loan_arrears_aging mla)\ngroup by ml.id', 'Loan Repayment', 0, 0); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES +('Loan Approved', 'SMS', 'Triggered', NULL, 'SELECT mc.id, mc.firstname, mc.middlename as middlename, mc.lastname, mc.display_name as FullName, mc.mobile_no as mobileNo, mc.group_name as GroupName, mo.name as officename, ml.id as loanId, ml.account_no as accountnumber, ml.principal_amount_proposed as loanamount, ml.annual_nominal_interest_rate as annualinterestrate FROM m_office mo JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\') AND ounder.hierarchy like CONCAT(\'.\', \'%\') LEFT JOIN ( select ml.id as loanId, ifnull(mc.id,mc2.id) as id, ifnull(mc.firstname,mc2.firstname) as firstname, ifnull(mc.middlename,ifnull(mc2.middlename,(\'\'))) as middlename, ifnull(mc.lastname,mc2.lastname) as lastname, ifnull(mc.display_name,mc2.display_name) as display_name, ifnull(mc.status_enum,mc2.status_enum) as status_enum, ifnull(mc.mobile_no,mc2.mobile_no) as mobile_no, ifnull(mg.office_id,mc2.office_id) as office_id, ifnull(mg.staff_id,mc2.staff_id) as staff_id, mg.id as group_id, mg.display_name as group_name from m_loan ml left join m_group mg on mg.id = ml.group_id left join m_group_client mgc on mgc.group_id = mg.id left join m_client mc on mc.id = mgc.client_id left join m_client mc2 on mc2.id = ml.client_id order by loanId ) mc on mc.office_id = ounder.id left join m_loan ml on ml.id = mc.loanId WHERE mc.status_enum = 300 and mc.mobile_no is not null and (mo.id = ${officeId} or ${officeId} = -1) and (mc.staff_id = ${loanOfficerId} or ${loanOfficerId} = -1)and (ml.id = ${loanId} or ${loanId} = -1)and (mc.id = ${clientId} or ${clientId} = -1)and (mc.group_id = ${groupId} or ${groupId} = -1)and (ml.loan_type_enum = ${loanType} or ${loanType} = -1)', 'Loan and client data of approved loan', 0, 0); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES +('Loan Rejected', 'SMS', 'Triggered', NULL, 'SELECT mc.id, mc.firstname, mc.middlename as middlename, mc.lastname, mc.display_name as FullName, mc.mobile_no as mobileNo, mc.group_name as GroupName, mo.name as officename, ml.id as loanId, ml.account_no as accountnumber, ml.principal_amount_proposed as loanamount, ml.annual_nominal_interest_rate as annualinterestrate FROM m_office mo JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\') AND ounder.hierarchy like CONCAT(\'.\', \'%\') LEFT JOIN ( select ml.id as loanId, ifnull(mc.id,mc2.id) as id, ifnull(mc.firstname,mc2.firstname) as firstname, ifnull(mc.middlename,ifnull(mc2.middlename,(\'\'))) as middlename, ifnull(mc.lastname,mc2.lastname) as lastname, ifnull(mc.display_name,mc2.display_name) as display_name, ifnull(mc.status_enum,mc2.status_enum) as status_enum, ifnull(mc.mobile_no,mc2.mobile_no) as mobile_no, ifnull(mg.office_id,mc2.office_id) as office_id, ifnull(mg.staff_id,mc2.staff_id) as staff_id, mg.id as group_id, mg.display_name as group_name from m_loan ml left join m_group mg on mg.id = ml.group_id left join m_group_client mgc on mgc.group_id = mg.id left join m_client mc on mc.id = mgc.client_id left join m_client mc2 on mc2.id = ml.client_id order by loanId ) mc on mc.office_id = ounder.id left join m_loan ml on ml.id = mc.loanId WHERE mc.status_enum = 300 and mc.mobile_no is not null and (mo.id = ${officeId} or ${officeId} = -1) and (mc.staff_id = ${loanOfficerId} or ${loanOfficerId} = -1) and (ml.id = ${loanId} or ${loanId} = -1) and (mc.id = ${clientId} or ${clientId} = -1) and (mc.group_id = ${groupId} or ${groupId} = -1) and (ml.loan_type_enum = ${loanType} or ${loanType} = -1)', 'Loan and client data of rejected loan', 0, 0); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `use_report`) VALUES +('Client Rejected', 'SMS', 'Triggered', 'Clients', 'SELECT c.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH(\r\nREPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) AS "officeName", \r\no.id AS "officeNumber"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nLEFT JOIN r_enum_value r ON r.enum_name = \'status_enum\' AND r.enum_id = c.status_enum\r\nWHERE o.id = ${officeId} AND c.id = ${clientId} AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId})', 'Client Rejection', '1'), +('Client Activated', 'SMS', 'Triggered', 'Clients', 'SELECT c.id AS "id", \r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH(\r\nREPLACE(ounder.`hierarchy`, \'.\', \'\')) - 1))), ounder.`name`) AS "officeName", \r\no.id AS "officeNumber"\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nLEFT JOIN r_enum_value r ON r.enum_name = \'status_enum\' AND r.enum_id = c.status_enum\r\nWHERE o.id = ${officeId} AND c.id = ${clientId} AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId})', 'Client Activation', '1'), +('Savings Rejected', 'SMS', 'Triggered', 'Savings', 'SELECT \r\nc.id AS "id",\r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo",\r\ns.account_no AS "savingsAccountNo",\r\nounder.id AS "officeNumber",\r\nounder.name AS "officeName"\r\n\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_savings_account s ON s.client_id = c.id\r\nJOIN m_savings_product sp ON sp.id = s.product_id\r\nLEFT JOIN m_staff st ON st.id = s.field_officer_id\r\nLEFT JOIN m_currency cur ON cur.code = s.currency_code\r\nWHERE o.id = ${officeId} AND (IFNULL(s.field_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND s.id = ${savingsId}', 'Savings Rejected', '1'), +('Savings Activated', 'SMS', 'Triggered', 'Savings', 'SELECT \r\nc.id AS "id",\r\nc.firstname AS "firstName",\r\nc.middlename AS "middleName",\r\nc.lastname AS "lastName",\r\nc.display_name AS "fullName",\r\nc.mobile_no AS "mobileNo",\r\ns.account_no AS "savingsAccountNo",\r\nounder.id AS "officeNumber",\r\nounder.name AS "officeName"\r\n\r\nFROM m_office o\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, \'%\')\r\nJOIN m_client c ON c.office_id = ounder.id\r\nJOIN m_savings_account s ON s.client_id = c.id\r\nJOIN m_savings_product sp ON sp.id = s.product_id\r\nLEFT JOIN m_staff st ON st.id = s.field_officer_id\r\nLEFT JOIN m_currency cur ON cur.code = s.currency_code\r\nWHERE o.id = ${officeId} AND (IFNULL(s.field_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND s.id = ${savingsId}', 'Savings Activation', '1'); + +INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) +VALUES ('loan_type_enum', '-1', 'All', 'All', '0'); +INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) +VALUES ('loan_type_enum', '1', 'Individual Loan', 'Individual Loan', '0'); +INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) +VALUES ('loan_type_enum', '2', 'Group Loan', 'Group Loan', '0'); + + +INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `selectAll`, `parameter_sql`, `parent_id`) +VALUES ('DefaultLoan', 'loanId', 'Loan', 'none', 'number', '-1', 'Y', 'select ml.id \nfrom m_loan ml \nleft join m_client mc on mc.id = ml.client_id \nleft join m_office mo on mo.id = mc.office_id \nwhere mo.id = ${officeId} or ${officeId} = -1', '5'); +INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `selectAll`, `parameter_sql`, `parent_id`) +VALUES ('DefaultClient', 'clientId', 'Client', 'none', 'number', '-1', 'Y', 'select mc.id \nfrom m_client mc\n left join m_office on mc.office_id = mo.id\n where mo.id = ${officeId} or ${officeId} = -1', '5'); +INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `selectAll`, `parameter_sql`, `parent_id`) +VALUES ('DefaultGroup', 'groupId', 'Group', 'none', 'number', '-1', 'Y', 'select mg.id \nfrom m_group mg\nleft join m_office mo on mg.office_id = mo.id\nwhere mo.id = ${officeId} or ${officeId} = -1', '5'); +INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `selectAll`, `parameter_sql`, `parent_id`) +VALUES ('SelectLoanType', 'loanType', 'Loan Type', 'select', 'number', '-1', 'Y', "select\nenum_id as id,\nenum_value as value\nfrom r_enum_value\nwhere enum_name = 'loan_type_enum'", NULL); +INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `special`, `selectOne`, `selectAll`, `parameter_sql`, `parent_id`) VALUES +('DefaultSavings', 'savingsId', 'Savings', 'none', 'number', '-1', NULL, NULL, 'Y', NULL, 5); +INSERT INTO `mifostenant-default`.`stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `selectAll`, `parameter_sql`, `parent_id`) VALUES ('DefaultSavingsTransactionId', 'savingsTransactionId', 'Savings Transaction', 'none', 'number', '-1', 'Y', NULL, '5'); + +SET @LRej = (select id from `stretchy_report` where `report_name`='Loan Rejected'); +SET @LApp = (select id from `stretchy_report` where `report_name`='Loan Approved'); +SET @LRep = (select id from `stretchy_report` where `report_name`='Loan Repayment'); +SET @Office = (select id from `stretchy_parameter` where `parameter_name`='OfficeIdSelectOne'); +SET @loanOfficer = (select id from `stretchy_parameter` where `parameter_name`='loanOfficerIdSelectAll'); +SET @DLoan = (select id from `stretchy_parameter` where `parameter_name`='DefaultLoan'); +SET @DClient = (select id from `stretchy_parameter` where `parameter_name`='DefaultClient'); +SET @DGroup = (select id from `stretchy_parameter` where `parameter_name`='DefaultGroup'); +SET @LoanType = (select id from `stretchy_parameter` where `parameter_name`='SelectLoanType'); + +INSERT IGNORE INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) +VALUES (@LRej, @Office, 'officeId'), +(@LApp, @Office, 'officeId'), +(@LRep, @Office, 'officeId'), +(@LRej, @loanOfficer, 'loanOfficerId'), +(@LApp, @loanOfficer, 'loanOfficerId'), +(@LRep, @loanOfficer, 'loanOfficerId'), +(@LRej, @DLoan, 'loanId'), +(@LApp, @DLoan, 'loanId'), +(@LRej, @DClient, 'clientId'), +(@LApp, @DClient, 'clientId'), +(@LRej, @DGroup, 'groupId'), +(@LApp, @DGroup, 'groupId'), +(@LRej, @LoanType, 'loanType'), +(@LApp, @LoanType, 'loanType'), +(@LRep, @LoanType, 'loanType'); + +SET @CRej = (select id from `stretchy_report` where `report_name`='Client Rejected'); +SET @CAct = (select id from `stretchy_report` where `report_name`='Client Activated'); +SET @SRej = (select id from `stretchy_report` where `report_name`='Savings Rejected'); +SET @SAct = (select id from `stretchy_report` where `report_name`='Savings Activated'); + +SET @Office = (select id from `stretchy_parameter` where `parameter_name`='OfficeIdSelectOne'); +SET @fieldOfficer = (select id from `stretchy_parameter` where `parameter_name`='loanOfficerIdSelectAll'); +SET @DClient = (select id from `stretchy_parameter` where `parameter_name`='DefaultClient'); +SET @DSavings = (select id from `stretchy_parameter` where `parameter_name`='DefaultSavings'); + +INSERT INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES +(@CRej, @Office, 'officeId'), +(@CAct, @Office, 'officeId'), +(@CRej, @fieldOfficer, 'loanOfficerId'), +(@CAct, @fieldOfficer, 'loanOfficerId'), +(@CRej, @DClient, 'clientId'), +(@CAct, @DClient, 'clientId'), + +(@SRej, @Office, 'officeId'), +(@SRej, @fieldOfficer, 'loanOfficerId'), +(@SRej, @DSavings, 'savingsId'), +(@SAct, @Office, 'officeId'), +(@SAct, @fieldOfficer, 'loanOfficerId'), +(@SAct, @DSavings, 'savingsId'); + +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Savings Deposit', 'SMS', 'Triggered', NULL, 'SELECT sc.savingsId AS savingsId, sc.id AS clientId, sc.firstname, IFNULL(sc.middlename,\'\') AS middlename, sc.lastname, sc.display_name AS FullName, sc.mobile_no AS mobileNo,\r\nms.`account_no` AS savingsAccountNo, ROUND(mst.amountPaid, ms.currency_digits) AS depositAmount, ms.account_balance_derived AS balance, \r\nmst.transactionDate AS transactionDate\r\nFROM m_office mo\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\') AND ounder.hierarchy LIKE CONCAT(\'.\', \'%\')\r\nLEFT JOIN (\r\nSELECT \r\n sa.id AS savingsId, mc.id AS id, mc.firstname AS firstname, mc.middlename AS middlename, mc.lastname AS lastname, \r\n mc.display_name AS display_name, mc.status_enum AS status_enum, \r\n mc.mobile_no AS mobile_no, mc.office_id AS office_id, \r\n mc.staff_id AS staff_id\r\nFROM\r\nm_savings_account sa\r\nLEFT JOIN m_client mc ON mc.id = sa.client_id\r\nORDER BY savingsId) sc ON sc.office_id = ounder.id\r\nRIGHT JOIN m_savings_account AS ms ON sc.savingsId = ms.id\r\nRIGHT JOIN(\r\nSELECT st.amount AS amountPaid, st.id, st.savings_account_id, st.id AS savingsTransactionId, st.transaction_date AS transactionDate\r\nFROM m_savings_account_transaction st\r\nWHERE st.is_reversed = 0\r\nGROUP BY st.savings_account_id\r\n) AS mst ON mst.savings_account_id = ms.id\r\nWHERE sc.mobile_no IS NOT NULL AND (mo.id = ${officeId} OR ${officeId} = -1) AND (sc.staff_id = ${loanOfficerId} OR ${loanOfficerId} = -1) AND mst.savingsTransactionId = ${savingsTransactionId}', 'Savings Deposit', 0, 1), +('Savings Withdrawal', 'SMS', 'Triggered', NULL, 'SELECT sc.savingsId AS savingsId, sc.id AS clientId, sc.firstname, IFNULL(sc.middlename,\'\') AS middlename, sc.lastname, sc.display_name AS FullName, sc.mobile_no AS mobileNo,\r\nms.`account_no` AS savingsAccountNo, ROUND(mst.amountPaid, ms.currency_digits) AS withdrawAmount, ms.account_balance_derived AS balance, \r\nmst.transactionDate AS transactionDate\r\nFROM m_office mo\r\nJOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, \'%\') AND ounder.hierarchy LIKE CONCAT(\'.\', \'%\')\r\nLEFT JOIN (\r\nSELECT \r\n sa.id AS savingsId, mc.id AS id, mc.firstname AS firstname, mc.middlename AS middlename, mc.lastname AS lastname, \r\n mc.display_name AS display_name, mc.status_enum AS status_enum, \r\n mc.mobile_no AS mobile_no, mc.office_id AS office_id, \r\n mc.staff_id AS staff_id\r\nFROM\r\nm_savings_account sa\r\nLEFT JOIN m_client mc ON mc.id = sa.client_id\r\nORDER BY savingsId) sc ON sc.office_id = ounder.id\r\nRIGHT JOIN m_savings_account AS ms ON sc.savingsId = ms.id\r\nRIGHT JOIN(\r\nSELECT st.amount AS amountPaid, st.id, st.savings_account_id, st.id AS savingsTransactionId, st.transaction_date AS transactionDate\r\nFROM m_savings_account_transaction st\r\nWHERE st.is_reversed = 0\r\nGROUP BY st.savings_account_id\r\n) AS mst ON mst.savings_account_id = ms.id\r\nWHERE sc.mobile_no IS NOT NULL AND (mo.id = ${officeId} OR ${officeId} = -1) AND (sc.staff_id = ${loanOfficerId} OR ${loanOfficerId} = -1) AND mst.savingsTransactionId = ${savingsTransactionId}', 'Savings Withdrawal', 0, 1); + + +SET @SDep = (select id from `stretchy_report` where `report_name`='Savings Deposit'); +SET @SWith = (select id from `stretchy_report` where `report_name`='Savings Withdrawal'); + +SET @savingsTransaction = (select id from `stretchy_parameter` where `parameter_name`='DefaultSavingsTransactionId'); + +INSERT IGNORE INTO `stretchy_report_parameter` (`report_id`, `parameter_id`, `report_parameter_name`) VALUES +(@SDep, @Office, 'officeId'), +(@SDep, @loanOfficer, 'loanOfficerId'), +(@SDep, @savingsTransaction, 'savingsTransactionId'), +(@SWith, @Office, 'officeId'), +(@SWith, @loanOfficer, 'loanOfficerId'), +(@SWith, @savingsTransaction, 'savingsTransactionId'); + +INSERT INTO `c_external_service` (`name`) VALUES ('MESSAGE_GATEWAY'); + +SET @EId = (SELECT `id` FROM `c_external_service` WHERE name = 'MESSAGE_GATEWAY'); + +INSERT INTO `c_external_service_properties` (`name`, `value`, `external_service_id`) VALUES +('host_name', 'localhost', @EId), +('port_number', '9191', @EId), +('end_point', '/', @EId), +('tenant_app_key', NULL, @EId); \ No newline at end of file From 2d1f6ab39137094af30aaf26a1766b8691ca09e0 Mon Sep 17 00:00:00 2001 From: Nazeer Hussain Shaik Date: Mon, 14 Nov 2016 17:50:37 +0530 Subject: [PATCH 2/2] Addressing SMS Review Comments --- api-docs/apiLive.htm | 156 ++++++++- .../service/CommandWrapperBuilder.java | 12 +- .../sms/constants/SmsCampaignConstants.java | 11 - .../constants/SmsCampaignEnumerations.java | 10 +- .../sms/data/CampaignTriggerWithSubTypes.java | 298 ---------------- .../sms/data/TriggerTypeWithSubTypesData.java | 43 --- .../sms/domain/SmsCampaignRepository.java | 5 + .../service/SmsCampaignDomainServiceImpl.java | 330 ++++++++---------- ...msCampaignDropdownReadPlatformService.java | 3 - ...mpaignDropdownReadPlatformServiceImpl.java | 15 +- ...msCampaignWritePlatformServiceJpaImpl.java | 15 +- .../SmsMessageScheduledJobServiceImpl.java | 2 +- 12 files changed, 326 insertions(+), 574 deletions(-) delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index c2a06be6b..467d6c543 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -21159,12 +21159,15 @@ Request Body:  
-

Retrieve a Share Product

+

Retrieve a SMS Campaign

Example Requests:

smscampaigns/1


smscampaigns/1?template=true
+

+
smscampaigns/template
+
GET https://Domain Name/api/v1/smscampaigns/1 @@ -21197,7 +21200,150 @@ Request Body: "recurrenceStartDate":"22 Jan 2016", "recurrence": "YEARLY" } - + + + smscampaigns/1?template=true + + { + "id": 1, + "campaignName": "Savings Deposit Campaign" + "campaignType": "SMS", + "triggerType": 3, + "providerId": 2, + "runReportId": 180, + "reportName" : "Savings Deposit", + "paramValue": "{"officeId":1,"loanOfficerId":-1,"transactionId":1,"reportName":"Savings Deposit"}", + "message": "Hello {fullName} your account is credited with {depositAmount} on {transactionDate}", + "status" : { "id":"","code":"","value":""} + + "smsProviderOptions": [{ + "id": 1, + "tenantId": "1", + "phoneNo": "+XXXXXXXXXX", + "providerName": "Twilio SMS Provider", + "providerDescription": "Twilio, just for testing" + }], + "businessRulesOptions": [ + { + "reportId": 166, + "reportName": "Active Clients", + "reportType": "SMS", + "reportSubType": "NonTriggered", + "reportDescription": "All clients with the status ‘Active’", + "reportParamName": { + "Office": "OfficeIdSelectOne", + "Loan Officer": "loanOfficerIdSelectAll" + } + } + ], + "triggerTypeOptions": [ + {"id": 1,"code": "triggerType.direct", "value": "Direct"}, + {"id": 2, "code": "triggerType.schedule","value": "Scheduled"}, + {"id": 3,"code": "triggerType.triggered","value": "Triggered"} + ], + "months": [ + { "id": 1, "code": "JANUARY", "value": "JANUARY" }, + { "id": 2, "code": "FEBRUARY", "value": "FEBRUARY" }, + { "id": 3, "code": "MARCH", "value": "MARCH" }, + { "id": 4, "code": "APRIL", "value": "APRIL" }, + { "id": 5, "code": "MAY", "value": "MAY" }, + { "id": 6, "code": "JUNE", "value": "JUNE" }, + { "id": 7, "code": "JULY", "value": "JULY" }, + { "id": 8, "code": "AUGUST", "value": "AUGUST" }, + { "id": 9, "code": "SEPTEMBER", "value": "SEPTEMBER"}, + { "id": 10, "code": "OCTOBER", "value": "OCTOBER"}, + { "id": 11, "code": "NOVEMBER", "value": "NOVEMBER" } + ], + "weekDays": [ + { "id": 1, "code": "calendarWeekDaysType.monday", "value": "MO" }, + { "id": 2, "code": "calendarWeekDaysType.tuesday", "value": "TU" }, + { "id": 3, "code": "calendarWeekDaysType.wednesday", "value": "WE" }, + { "id": 4, "code": "calendarWeekDaysType.thursday", "value": "TH"}, + { "id": 5, "code": "calendarWeekDaysType.friday", "value": "FR" }, + { "id": 6, "code": "calendarWeekDaysType.saturday", "value": "SA" }, + { "id": 7, "code": "calendarWeekDaysType.sunday", "value": "SU" } + ], + "frequencyTypeOptions": [ + { "id": 1, "code": "calendarFrequencyType.daily", "value": "DAILY" }, + { "id": 2, "code": "calendarFrequencyType.weekly", "value": "WEEKLY"}, + { "id": 3, "code": "calendarFrequencyType.monthly", "value": "MONTHLY"}, + { "id": 4, "code": "calendarFrequencyType.yearly", "value": "YEARLY" } + ], + "periodFrequencyOptions": [ + { "id": 0, "code": "periodFrequencyType.days", "value": "DAYS" }, + { "id": 1, "code": "periodFrequencyType.weeks", "value": "WEEKS" }, + { "id": 2, "code": "periodFrequencyType.months", "value": "MONTHS" }, + { "id": 3, "code": "periodFrequencyType.years", "value": "YEARS" } + ] +} + +smscampaigns/template + + { + "smsProviderOptions": [{ + "id": 1, + "tenantId": "1", + "phoneNo": "+XXXXXXXXXX", + "providerName": "Twilio SMS Provider", + "providerDescription": "Twilio, just for testing" + }], + "businessRulesOptions": [ + { + "reportId": 166, + "reportName": "Active Clients", + "reportType": "SMS", + "reportSubType": "NonTriggered", + "reportDescription": "All clients with the status ‘Active’", + "reportParamName": { + "Office": "OfficeIdSelectOne", + "Loan Officer": "loanOfficerIdSelectAll" + } + } + ], + "triggerTypeOptions": [ + {"id": 1,"code": "triggerType.direct", "value": "Direct"}, + {"id": 2, "code": "triggerType.schedule","value": "Scheduled"}, + {"id": 3,"code": "triggerType.triggered","value": "Triggered"} + ], + "months": [ + { "id": 1, "code": "JANUARY", "value": "JANUARY" }, + { "id": 2, "code": "FEBRUARY", "value": "FEBRUARY" }, + { "id": 3, "code": "MARCH", "value": "MARCH" }, + { "id": 4, "code": "APRIL", "value": "APRIL" }, + { "id": 5, "code": "MAY", "value": "MAY" }, + { "id": 6, "code": "JUNE", "value": "JUNE" }, + { "id": 7, "code": "JULY", "value": "JULY" }, + { "id": 8, "code": "AUGUST", "value": "AUGUST" }, + { "id": 9, "code": "SEPTEMBER", "value": "SEPTEMBER"}, + { "id": 10, "code": "OCTOBER", "value": "OCTOBER"}, + { "id": 11, "code": "NOVEMBER", "value": "NOVEMBER" } + ], + "weekDays": [ + { "id": 1, "code": "calendarWeekDaysType.monday", "value": "MO" }, + { "id": 2, "code": "calendarWeekDaysType.tuesday", "value": "TU" }, + { "id": 3, "code": "calendarWeekDaysType.wednesday", "value": "WE" }, + { "id": 4, "code": "calendarWeekDaysType.thursday", "value": "TH"}, + { "id": 5, "code": "calendarWeekDaysType.friday", "value": "FR" }, + { "id": 6, "code": "calendarWeekDaysType.saturday", "value": "SA" }, + { "id": 7, "code": "calendarWeekDaysType.sunday", "value": "SU" } + ], + "frequencyTypeOptions": [ + { "id": 1, "code": "calendarFrequencyType.daily", "value": "DAILY" }, + { "id": 2, "code": "calendarFrequencyType.weekly", "value": "WEEKLY"}, + { "id": 3, "code": "calendarFrequencyType.monthly", "value": "MONTHLY"}, + { "id": 4, "code": "calendarFrequencyType.yearly", "value": "YEARLY" } + ], + "periodFrequencyOptions": [ + { "id": 0, "code": "periodFrequencyType.days", "value": "DAYS" }, + { "id": 1, "code": "periodFrequencyType.weeks", "value": "WEEKS" }, + { "id": 2, "code": "periodFrequencyType.months", "value": "MONTHS" }, + { "id": 3, "code": "periodFrequencyType.years", "value": "YEARS" } + ] +} + + + +
@@ -21279,7 +21425,7 @@ Request Body: "status": { "id": 300, "code": "smsCampaignStatus.active", - "value": "Activated", + "value": "active", }, "locale": "en", "dateFormat": "dd MMMM yyyy", @@ -21314,7 +21460,7 @@ Request Body: "status": { "id": 600, "code": "smsCampaignStatus.closed", - "value": "Activated", + "value": "closed", }, "locale": "en", "dateFormat": "dd MMMM yyyy", @@ -21350,7 +21496,7 @@ Request Body: "status": { "id": 300, "code": "smsCampaignStatus.active", - "value": "Activated", + "value": "active", }, "locale": "en", "dateFormat": "dd MMMM yyyy", diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index ed92a3879..0cdc7e2f0 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -2808,7 +2808,7 @@ public class CommandWrapperBuilder { this.actionName = "CREATE"; this.entityName = "SMSCAMPAIGN"; this.entityId = null; - this.href = "/smscampaign"; + this.href = "/smscampaigns"; return this; } @@ -2816,7 +2816,7 @@ public class CommandWrapperBuilder { this.actionName = "UPDATE"; this.entityName = "SMSCAMPAIGN"; this.entityId = resourceId; - this.href = "/smscampaign/"+resourceId; + this.href = "/smscampaigns/"+resourceId; return this; } @@ -2824,7 +2824,7 @@ public class CommandWrapperBuilder { this.actionName = "ACTIVATE"; this.entityName = "SMSCAMPAIGN"; this.entityId = resourceId; - this.href = "/smscampaign/"+resourceId + "?command=activate"; + this.href = "/smscampaigns/"+resourceId + "?command=activate"; return this; } @@ -2832,14 +2832,14 @@ public class CommandWrapperBuilder { this.actionName = "CLOSE"; this.entityName = "SMSCAMPAIGN"; this.entityId = resourceId; - this.href = "/smscampaign/"+resourceId + "?command=close"; + this.href = "/smscampaigns/"+resourceId + "?command=close"; return this; } public CommandWrapperBuilder reactivateSmsCampaign(final Long resourceId) { this.actionName = "REACTIVATE"; this.entityName = "SMSCAMPAIGN"; this.entityId = resourceId; - this.href = "/smscampaign/"+resourceId + "?command=reactivate"; + this.href = "/smscampaigns/"+resourceId + "?command=reactivate"; return this; } @@ -2847,7 +2847,7 @@ public class CommandWrapperBuilder { this.actionName = "DELETE"; this.entityName = "SMSCAMPAIGN"; this.entityId = resourceId; - this.href = "/smscampaign/"+resourceId; + this.href = "/smscampaigns/"+resourceId; return this; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java index 104f4c73e..964beda30 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignConstants.java @@ -20,8 +20,6 @@ package org.apache.fineract.infrastructure.campaigns.sms.constants; public interface SmsCampaignConstants { - String SMS_CAMPAIGN_NAME = "sms"; - String RESOURCE_NAME = "SMSCAMPAIGN"; String SMS_BRIDGE = "sms_bridge"; @@ -31,13 +29,4 @@ public interface SmsCampaignConstants { String FINERACT_PLATFORM_TENANT_ID = "Fineract-Platform-TenantId"; String FINERACT_TENANT_APP_KEY = "Fineract-Tenant-App-Key"; - - //Supported Triggered Campaigns - - String LOAN_REJECT = "Loan Rejected" ; - - String LOAN_APPROVED = "Loan Approved" ; - - String LOAN_REPAYMENT = "Loan Repayment" ; - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java index 16ae3cd24..99de55ecc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/constants/SmsCampaignEnumerations.java @@ -73,7 +73,7 @@ public class SmsCampaignEnumerations { public static List calendarMonthType() { final List optionDatas = new ArrayList<>(); for (final Month monthType : Month.values()) { - if (Month.DECEMBER.compareTo(monthType) != 0) { + if (Month.DECEMBER.compareTo(monthType) != 0) { //We are removing December because we are adding yearly frequency optionDatas.add(calendarMonthType(monthType)); } } @@ -83,9 +83,11 @@ public class SmsCampaignEnumerations { public static List calendarPeriodFrequencyTypes(final PeriodFrequencyType[] periodFrequencyTypes) { final List optionDatas = new ArrayList<>(); for (final PeriodFrequencyType periodFrequencyType : periodFrequencyTypes) { - final EnumOptionData optionData = new EnumOptionData(periodFrequencyType.getValue().longValue(), periodFrequencyType.getCode(), - periodFrequencyType.toString()); - optionDatas.add(optionData); + if(!periodFrequencyType.getValue().equals(PeriodFrequencyType.INVALID.getValue())) { + final EnumOptionData optionData = new EnumOptionData(periodFrequencyType.getValue().longValue(), periodFrequencyType.getCode(), + periodFrequencyType.toString()); + optionDatas.add(optionData); + } } return optionDatas; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java deleted file mode 100644 index c23771fc7..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/CampaignTriggerWithSubTypes.java +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.campaigns.sms.data; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.apache.fineract.infrastructure.core.data.EnumOptionData; - -public final class CampaignTriggerWithSubTypes { - - public enum ActualCampaignTriggerType { - INVALID(0, "campaignTriggerType.invalid"), // - LOAN(1, "campaignTriggerType.loan"), // - SAVING(2, "campaignTriggerType.saving"), // - CLIENT(3, "campaignTriggerType.client"); - - private Integer value; - private String code; - - private ActualCampaignTriggerType(Integer value, String code) { - this.value = value; - this.code = code; - } - - public static ActualCampaignTriggerType fromInt(final Integer typeValue) { - ActualCampaignTriggerType type = ActualCampaignTriggerType.INVALID; - switch (typeValue) { - case 1: - type = LOAN; - break; - case 2: - type = SAVING; - break; - case 3: - type = CLIENT; - break; - } - return type; - } - - public Integer getValue() { - return this.value; - } - - public String getCode() { - return this.code; - } - - public static EnumOptionData toEnumOptionData(final ActualCampaignTriggerType triggerType) { - final EnumOptionData optionData = new EnumOptionData(new Long(triggerType.getValue()), triggerType.getCode(), triggerType.name()); - return optionData; - } - - public static EnumOptionData toEnumOptionData(final Integer triggerTypeValue) { - ActualCampaignTriggerType actualCampaignTriggerType = ActualCampaignTriggerType.fromInt(triggerTypeValue); - final EnumOptionData optionData = new EnumOptionData(new Long(actualCampaignTriggerType.getValue()), - actualCampaignTriggerType.getCode(), actualCampaignTriggerType.name()); - return optionData; - } - - public boolean isInvalid() { - return this.value.equals(ActualCampaignTriggerType.INVALID.getValue()); - } - } - - public enum CampaignTriggerSubType { - INVALID(0, ActualCampaignTriggerType.INVALID, "campaignTriggerSubType.invalid"), // - - DISBURSE(101, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.disburse"), // - REPAYMENT(102, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.repayment"), // - UNDO_DISBURSAL(103, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.undodisbursal"), // - WRITE_OFF(104, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.writeoff"), // - ADJUST(105, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.adjust"), // - UNDO_WRITE_OFF(106, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.undowriteoff"), // - FORECLOSURE(107, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.foreclosure"), // - APPROVED(108, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.approved"), // - WAIVE_INTEREST(109, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.waive.interest"), // - CLOSE(110, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.close"), // - CLOSE_AS_RESCHEDULE(111, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.close.as.rescheduled"), // - ADD_CHARGE(112, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.add.charge"), // - UPDATE_CHARGE(113, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.update.charge"), // - WAIVE_CHARGE(114, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.waive.charge"), // - DELETE_CHARGE(115, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.delete.charge"), // - CHARGE_PAYMENT(116, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.charge.payment"), // - INITIATE_TRANSFER(117, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.initiate.transfer"), // - ACCEPT_TRANSFER(118, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.accept.transfer"), // - WITHDRAW_TRANSFER(119, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.withdraw.transfer"), // - REJECT_TRANSFER(120, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reject.transfer"), // - REASSIGN_OFFICER(121, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // - REMOVE_OFFICER(122, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // - APPLY_OVERDUE_CHARGE(123, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // - INTEREST_RECALCULATION(124, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // - REFUND(125, ActualCampaignTriggerType.LOAN, "campaignTriggerSubType.reassign.officer"), // - - SAVINGS_DEPOSIT(201, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.deposit"), // - SAVINGS_WITHDRAWAL(202, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.withdrawal"), // - SAVINGS_ACTIVATE(203, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.activate"), // - SAVINGS_ADJUST_TRANSACTION(204, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.adjust"), // - SAVINGS_APPLY_ANNUAL_FEE(205, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.apply.annual.fee"), // - SAVINGS_CALCULATE_INTEREST(206, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.calclulate.interest"), // - SAVINGS_CLOSE(207, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.close"), // - SAVINGS_POST_INTEREST(208, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.post"), // - SAVINGS_REJECT(209, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.reject"), // - SAVINGS_UNDO(210, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.undo"), // - SAVINGS_ADD_CHARGE(211, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.add.charge"), // - SAVINGS_WAIVE_CHARGE(212, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.waive.charge"), // - SAVINGS_PAY_CHARGE(213, ActualCampaignTriggerType.SAVING, "campaignTriggerSubType.savings.pay.charge"), // - - CLIENTS_ACTIVATE(301, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.activate"), // - CLIENTS_CLOSE(302, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.close"), // - CLIENTS_ACCEPT_TRANSFER(303, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.accept.transfer"), // - CLIENTS_ASSIGN_STAFF(304, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.assign.staff"), // - CLIENTS_CREATE(305, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.create"), // - CLIENTS_DELETE(306, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.delete"), // - CLIENTS_PROPOSE_TRANSFER(307, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.propose.transfer"), // - CLIENTS_REACTIVATE(308, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.reactivate"), // - CLIENTS_REJECT(309, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.reject"), // - CLIENTS_REJECT_TRANSFER(310, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.reject.transfer"), // - CLIENTS_WITHDRAW(311, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.withdraw"), // - CLIENTS_WITHDRAW_TRANSFER(312, ActualCampaignTriggerType.CLIENT, "campaignTriggerSubType.withdraw.transfer"); - - private Integer id; - private ActualCampaignTriggerType type; - private String code; - - private CampaignTriggerSubType(Integer id, ActualCampaignTriggerType type, String code) { - this.id = id; - this.type = type; - this.code = code; - } - - public static CampaignTriggerSubType fromInt(final Integer subTypeValue) { - CampaignTriggerSubType subType = CampaignTriggerSubType.INVALID; - switch (subTypeValue) { - case 101: - subType = DISBURSE; - break; - case 102: - subType = REPAYMENT; - break; - case 103: - subType = ADJUST; - break; - case 104: - subType = UNDO_DISBURSAL; - break; - case 105: - subType = WRITE_OFF; - break; - case 106: - subType = UNDO_WRITE_OFF; - break; - - case 201: - subType = SAVINGS_DEPOSIT; - break; - case 202: - subType = SAVINGS_WITHDRAWAL; - break; - case 203: - subType = SAVINGS_ACTIVATE; - break; - case 204: - subType = SAVINGS_ADJUST_TRANSACTION; - break; - case 205: - subType = SAVINGS_APPLY_ANNUAL_FEE; - break; - case 206: - subType = SAVINGS_CALCULATE_INTEREST; - break; - case 207: - subType = SAVINGS_CLOSE; - break; - case 208: - subType = SAVINGS_POST_INTEREST; - break; - case 209: - subType = SAVINGS_REJECT; - break; - case 210: - subType = SAVINGS_UNDO; - break; - case 211: - subType = SAVINGS_ADD_CHARGE; - break; - case 212: - subType = SAVINGS_WAIVE_CHARGE; - break; - case 213: - subType = SAVINGS_PAY_CHARGE; - break; - - case 301: - subType = CLIENTS_ACTIVATE; - break; - case 302: - subType = CLIENTS_CLOSE; - break; - case 303: - subType = CLIENTS_ACCEPT_TRANSFER; - break; - case 304: - subType = CLIENTS_ASSIGN_STAFF; - break; - case 305: - subType = CLIENTS_CREATE; - break; - case 306: - subType = CLIENTS_DELETE; - break; - case 307: - subType = CLIENTS_PROPOSE_TRANSFER; - break; - case 308: - subType = CLIENTS_REACTIVATE; - break; - case 309: - subType = CLIENTS_REJECT; - break; - case 310: - subType = CLIENTS_REJECT_TRANSFER; - break; - case 311: - subType = CLIENTS_WITHDRAW; - break; - case 312: - subType = CLIENTS_WITHDRAW_TRANSFER; - break; - } - return subType; - } - - public Integer getId() { - return this.id; - } - - public ActualCampaignTriggerType getType() { - return this.type; - } - - public String getCode() { - return this.code; - } - - public static EnumOptionData toEnumOptionData(final Integer triggerSubType) { - CampaignTriggerSubType subTypeEnum = CampaignTriggerSubType.fromInt(triggerSubType); - final EnumOptionData optionData = new EnumOptionData(new Long(subTypeEnum.getId()), subTypeEnum.getCode(), subTypeEnum.name()); - return optionData; - } - } - - public static List addTypeSubTypeMapping(ActualCampaignTriggerType type) { - List subTypeList = new ArrayList<>(); - EnumOptionData optionData = null; - for (CampaignTriggerSubType subType : CampaignTriggerSubType.values()) { - if (subType.getType().equals(type)) { - optionData = new EnumOptionData(subType.getId().longValue(), subType.getCode(), subType.name()); - subTypeList.add(optionData); - } - } - return subTypeList; - } - - public static Collection getTriggerTypeAndSubTypes() { - final Collection typesList = new ArrayList<>(); - EnumOptionData actualTriggerType = null; - for (ActualCampaignTriggerType triggerType : ActualCampaignTriggerType.values()) { - if (triggerType.isInvalid()) { - continue; - } - List subTypeList = addTypeSubTypeMapping(triggerType); - actualTriggerType = ActualCampaignTriggerType.toEnumOptionData(triggerType); - TriggerTypeWithSubTypesData triggerTypeWithSubTypesData = new TriggerTypeWithSubTypesData(actualTriggerType, subTypeList); - typesList.add(triggerTypeWithSubTypesData); - } - return typesList; - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java deleted file mode 100644 index b6e9c3c6a..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/data/TriggerTypeWithSubTypesData.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.campaigns.sms.data; - -import java.util.List; - -import org.apache.fineract.infrastructure.core.data.EnumOptionData; - -public class TriggerTypeWithSubTypesData { - - private final EnumOptionData actualTriggerType; - private final List triggerSubTypes; - - public TriggerTypeWithSubTypesData(final EnumOptionData actualTriggerType, List triggerSubTypes) { - this.actualTriggerType = actualTriggerType; - this.triggerSubTypes = triggerSubTypes; - } - - public EnumOptionData getActualTriggerType() { - return this.actualTriggerType; - } - - public List getTriggerSubTypes() { - return this.triggerSubTypes; - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java index 436f9ed63..bc3bb7d08 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java @@ -23,6 +23,8 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface SmsCampaignRepository extends JpaRepository, JpaSpecificationExecutor { @@ -32,4 +34,7 @@ public interface SmsCampaignRepository extends JpaRepository, final Integer status); Collection findByTriggerType(final Integer triggerType) ; + + @Query("SELECT campaign FROM SmsCampaign campaign WHERE campaign.paramValue LIKE :reportPattern AND campaign.triggerType=:triggerType AND campaign.status=300") + List findActiveSmsCampaigns(@Param("reportPattern") final String reportPattern, @Param("triggerType") final Integer triggerType) ; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java index 00926c4c4..42d7b8b2f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -65,6 +66,8 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { private static final Logger logger = LoggerFactory.getLogger(SmsCampaignDomainServiceImpl.class); + //private final static int POOL_SIZE = 5 ; + private final SmsCampaignRepository smsCampaignRepository; private final SmsMessageRepository smsMessageRepository; private final OfficeRepository officeRepository; @@ -100,216 +103,192 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_REJECT, new SavingsAccountRejectedListener()); this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_DEPOSIT, new SavingsAccountTransactionListener(true)); this.businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_WITHDRAWAL, new SavingsAccountTransactionListener(false)); - - } - private void notifyRejectedLoanOwner(Loan loan) { - ArrayList smsCampaigns = retrieveSmsCampaigns("loan rejected"); - if(smsCampaigns.size()>0){ - for (SmsCampaign campaign:smsCampaigns){ - if(campaign.isActive()) { - this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(loan, campaign); - } - } - } - } + private void notifyRejectedLoanOwner(Loan loan) { + List smsCampaigns = retrieveSmsCampaigns("Loan Rejected"); + if (smsCampaigns.size() > 0) { + for (SmsCampaign campaign : smsCampaigns) { + if (campaign.isActive()) { + SmsCampaignDomainServiceImpl.this.smsCampaignWritePlatformCommandHandler + .insertDirectCampaignIntoSmsOutboundTable(loan, campaign); + } + } + } + } private void notifyAcceptedLoanOwner(Loan loan) { - ArrayList smsCampaigns = retrieveSmsCampaigns("loan approved"); - + List smsCampaigns = retrieveSmsCampaigns("Loan Approved"); if(smsCampaigns.size()>0){ for (SmsCampaign campaign:smsCampaigns){ - if(campaign.isActive()) { - this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(loan, campaign); - } + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(loan, campaign); } } } private void notifyClientActivated(final Client client) { - ArrayList smsCampaigns = retrieveSmsCampaigns("client activated"); + List smsCampaigns = retrieveSmsCampaigns("Client Activated"); if(smsCampaigns.size()>0){ for (SmsCampaign campaign:smsCampaigns){ - if(campaign.isActive()) { - this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(client, campaign); - } + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(client, campaign); } } } private void notifyClientRejected(final Client client) { - ArrayList smsCampaigns = retrieveSmsCampaigns("client rejected"); + List smsCampaigns = retrieveSmsCampaigns("Client Rejected"); if(smsCampaigns.size()>0){ for (SmsCampaign campaign:smsCampaigns){ - if(campaign.isActive()) { - this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(client, campaign); - } + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(client, campaign); } } } private void notifySavingsAccountActivated(final SavingsAccount savingsAccount) { - ArrayList smsCampaigns = retrieveSmsCampaigns("savings activated"); + List smsCampaigns = retrieveSmsCampaigns("Savings Activated"); if (smsCampaigns.size() > 0) { for (SmsCampaign campaign : smsCampaigns) { - if (campaign.isActive()) { - this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(savingsAccount, + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(savingsAccount, campaign); - } } } } private void notifySavingsAccountRejected(final SavingsAccount savingsAccount) { - ArrayList smsCampaigns = retrieveSmsCampaigns("savings rejected"); + List smsCampaigns = retrieveSmsCampaigns("Savings Rejected"); if (smsCampaigns.size() > 0) { for (SmsCampaign campaign : smsCampaigns) { - if (campaign.isActive()) { - this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(savingsAccount, + this.smsCampaignWritePlatformCommandHandler.insertDirectCampaignIntoSmsOutboundTable(savingsAccount, campaign); - } } } } private void sendSmsForLoanRepayment(LoanTransaction loanTransaction) { - ArrayList smsCampaigns = retrieveSmsCampaigns("loan repayment"); + List smsCampaigns = retrieveSmsCampaigns("Loan Repayment"); if (smsCampaigns.size() > 0) { for (SmsCampaign smsCampaign : smsCampaigns) { - if (smsCampaign.isActive()) { - try { - Loan loan = loanTransaction.getLoan(); - final Set groupClients = new HashSet<>(); - if (loan.hasInvalidLoanType()) { - throw new InvalidLoanTypeException("Loan Type cannot be Invalid for the Triggered Sms Campaign"); - } - if (loan.isGroupLoan()) { - Group group = this.groupRepository.findOne(loan.getGroupId()); - groupClients.addAll(group.getClientMembers()); - } else { - groupClients.add(loan.client()); - } - HashMap campaignParams = new ObjectMapper() - .readValue(smsCampaign.getParamValue(), new TypeReference>() { - }); + try { + Loan loan = loanTransaction.getLoan(); + final Set groupClients = new HashSet<>(); + if (loan.hasInvalidLoanType()) { + throw new InvalidLoanTypeException( + "Loan Type cannot be Invalid for the Triggered Sms Campaign"); + } + if (loan.isGroupLoan()) { + Group group = this.groupRepository.findOne(loan.getGroupId()); + groupClients.addAll(group.getClientMembers()); + } else { + groupClients.add(loan.client()); + } + HashMap campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() { + }); - if (groupClients.size() > 0) { - for (Client client : groupClients) { - HashMap smsParams = processRepaymentDataForSms(loanTransaction, client); - for (String key : campaignParams.keySet()) { - String value = campaignParams.get(key); - String spvalue = null; - boolean spkeycheck = smsParams.containsKey(key); - if (spkeycheck) { - spvalue = smsParams.get(key).toString(); - } - if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { - if (key.equals("officeId")) { - Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value)); - if (campaignOffice - .doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) { - throw new RuntimeException(); - } - } else { + if (groupClients.size() > 0) { + for (Client client : groupClients) { + HashMap smsParams = processRepaymentDataForSms(loanTransaction, client); + for (String key : campaignParams.keySet()) { + String value = campaignParams.get(key); + String spvalue = null; + boolean spkeycheck = smsParams.containsKey(key); + if (spkeycheck) { + spvalue = smsParams.get(key).toString(); + } + if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { + if (key.equals("officeId")) { + Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value)); + if (campaignOffice + .doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) { throw new RuntimeException(); } + } else { + throw new RuntimeException(); } } - String message = this.smsCampaignWritePlatformCommandHandler.compileSmsTemplate( - smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams); - Object mobileNo = smsParams.get("mobileNo"); - if (mobileNo != null) { - SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message, - mobileNo.toString(), smsCampaign); - this.smsMessageRepository.save(smsMessage); - Collection messages = new ArrayList<>(); - messages.add(smsMessage); - Map> smsDataMap = new HashMap<>(); - smsDataMap.put(smsCampaign, messages); - this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); - } + } + String message = this.smsCampaignWritePlatformCommandHandler.compileSmsTemplate( + smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams); + Object mobileNo = smsParams.get("mobileNo"); + if (mobileNo != null) { + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message, + mobileNo.toString(), smsCampaign); + this.smsMessageRepository.save(smsMessage); + Collection messages = new ArrayList<>(); + messages.add(smsMessage); + Map> smsDataMap = new HashMap<>(); + smsDataMap.put(smsCampaign, messages); + this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); } } - } catch (final IOException e) { - logger.error("smsParams does not contain the key: " + e.getMessage()); - } catch (final RuntimeException e) { - logger.debug("Client Office Id and SMS Campaign Office id doesn't match"); } + } catch (final IOException e) { + logger.error("smsParams does not contain the key: " + e.getMessage()); + } catch (final RuntimeException e) { + logger.debug("Client Office Id and SMS Campaign Office id doesn't match"); } } } } private void sendSmsForSavingsTransaction(final SavingsAccountTransaction savingsTransaction, boolean isDeposit) { - String campaignName = isDeposit ? "savings deposit" : "savings withdrawal"; - ArrayList smsCampaigns = retrieveSmsCampaigns(campaignName); + String campaignName = isDeposit ? "Savings Deposit" : "Savings Withdrawal"; + List smsCampaigns = retrieveSmsCampaigns(campaignName); if (smsCampaigns.size() > 0) { for (SmsCampaign smsCampaign : smsCampaigns) { - if (smsCampaign.isActive()) { - try { - final SavingsAccount savingsAccount = savingsTransaction.getSavingsAccount(); - final Client client = savingsAccount.getClient(); - HashMap campaignParams = new ObjectMapper() - .readValue(smsCampaign.getParamValue(), new TypeReference>() { - }); - HashMap smsParams = processSavingsTransactionDataForSms(savingsTransaction, - client); - for (String key : campaignParams.keySet()) { - String value = campaignParams.get(key); - String spvalue = null; - boolean spkeycheck = smsParams.containsKey(key); - if (spkeycheck) { - spvalue = smsParams.get(key).toString(); - } - if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { - if (key.equals("officeId")) { - Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value)); - if (campaignOffice - .doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) { - throw new RuntimeException(); - } - } else { + try { + final SavingsAccount savingsAccount = savingsTransaction.getSavingsAccount(); + final Client client = savingsAccount.getClient(); + HashMap campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), + new TypeReference>() { + }); + HashMap smsParams = processSavingsTransactionDataForSms(savingsTransaction, client); + for (String key : campaignParams.keySet()) { + String value = campaignParams.get(key); + String spvalue = null; + boolean spkeycheck = smsParams.containsKey(key); + if (spkeycheck) { + spvalue = smsParams.get(key).toString(); + } + if (spkeycheck && !(value.equals("-1") || spvalue.equals(value))) { + if (key.equals("officeId")) { + Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value)); + if (campaignOffice.doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) { throw new RuntimeException(); } + } else { + throw new RuntimeException(); } } - String message = this.smsCampaignWritePlatformCommandHandler - .compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams); - Object mobileNo = smsParams.get("mobileNo"); - if (mobileNo != null) { - SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message, - mobileNo.toString(), smsCampaign); - this.smsMessageRepository.save(smsMessage); - Collection messages = new ArrayList<>(); - messages.add(smsMessage); - Map> smsDataMap = new HashMap<>(); - smsDataMap.put(smsCampaign, messages); - this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); - } - } catch (final IOException e) { - logger.error("smsParams does not contain the key: " + e.getMessage()); - } catch (final RuntimeException e) { - logger.debug("Client Office Id and SMS Campaign Office id doesn't match"); } + String message = this.smsCampaignWritePlatformCommandHandler + .compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), smsParams); + Object mobileNo = smsParams.get("mobileNo"); + if (mobileNo != null) { + SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, message, + mobileNo.toString(), smsCampaign); + this.smsMessageRepository.save(smsMessage); + Collection messages = new ArrayList<>(); + messages.add(smsMessage); + Map> smsDataMap = new HashMap<>(); + smsDataMap.put(smsCampaign, messages); + this.smsMessageScheduledJobService.sendTriggeredMessages(smsDataMap); + } + } catch (final IOException e) { + logger.error("smsParams does not contain the key: " + e.getMessage()); + } catch (final RuntimeException e) { + logger.debug("Client Office Id and SMS Campaign Office id doesn't match"); } } } } - private ArrayList retrieveSmsCampaigns(String paramValue){ - Collection initialSmsCampaignList = smsCampaignRepository.findByTriggerType(SmsCampaignTriggerType.TRIGGERED.getValue()); - ArrayList smsCampaigns = new ArrayList<>(); - - for(SmsCampaign campaign : initialSmsCampaignList){ - if(campaign.getParamValue().toLowerCase().contains(paramValue)){ - smsCampaigns.add(campaign); - } - } + private List retrieveSmsCampaigns(String paramValue){ + List smsCampaigns = smsCampaignRepository.findActiveSmsCampaigns("%"+paramValue+"%", SmsCampaignTriggerType.TRIGGERED.getValue()); return smsCampaigns; } @@ -396,12 +375,15 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { return smsParams; } - private class SendSmsOnLoanApproved implements BusinessEventListner{ + private abstract class SmsBusinessEventAdapter implements BusinessEventListner { - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - - } + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + //Nothing to do + } + } + + private class SendSmsOnLoanApproved extends SmsBusinessEventAdapter{ @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -413,12 +395,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } - private class SendSmsOnLoanRejected implements BusinessEventListner{ - - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - - } + private class SendSmsOnLoanRejected extends SmsBusinessEventAdapter{ @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -430,12 +407,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } - private class SendSmsOnLoanRepayment implements BusinessEventListner{ - - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - - } + private class SendSmsOnLoanRepayment extends SmsBusinessEventAdapter{ @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -447,12 +419,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } - private class ClientActivatedListener implements BusinessEventListner { - - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - - } + private class ClientActivatedListener extends SmsBusinessEventAdapter { @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -463,12 +430,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } - private class ClientRejectedListener implements BusinessEventListner { - - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - - } + private class ClientRejectedListener extends SmsBusinessEventAdapter { @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -480,12 +442,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } - private class SavingsAccountActivatedListener implements BusinessEventListner { - - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - - } + private class SavingsAccountActivatedListener extends SmsBusinessEventAdapter{ @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -497,12 +454,7 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } - private class SavingsAccountRejectedListener implements BusinessEventListner { - - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - - } + private class SavingsAccountRejectedListener extends SmsBusinessEventAdapter { @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -513,18 +465,13 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } - private class SavingsAccountTransactionListener implements BusinessEventListner { + private class SavingsAccountTransactionListener extends SmsBusinessEventAdapter { final boolean isDeposit ; public SavingsAccountTransactionListener(final boolean isDeposit) { this.isDeposit = isDeposit ; } - @Override - public void businessEventToBeExecuted(Map businessEventEntity) { - // TODO Auto-generated method stub - - } @Override public void businessEventWasExecuted(Map businessEventEntity) { @@ -534,4 +481,19 @@ public class SmsCampaignDomainServiceImpl implements SmsCampaignDomainService { } } } + + /*private abstract class Task implements Runnable { + + protected final FineractPlatformTenant tenant; + + protected final String reportName ; + + private final Object entity ; + + public Task(final FineractPlatformTenant tenant, final String reportName, final Object entity) { + this.tenant = tenant; + this.reportName = reportName ; + this.entity = entity ; + } + }*/ } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java index bb9814c2c..2da3a5920 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformService.java @@ -21,7 +21,6 @@ package org.apache.fineract.infrastructure.campaigns.sms.service; import java.util.Collection; import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData; -import org.apache.fineract.infrastructure.campaigns.sms.data.TriggerTypeWithSubTypesData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; public interface SmsCampaignDropdownReadPlatformService { @@ -38,6 +37,4 @@ public interface SmsCampaignDropdownReadPlatformService { Collection retrivePeriodFrequencyTypes(); - Collection getTriggerTypeAndSubTypes(); - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java index 44c92b95a..d974d7ab3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDropdownReadPlatformServiceImpl.java @@ -27,12 +27,9 @@ import java.util.Map; import org.apache.fineract.infrastructure.campaigns.constants.CampaignType; import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils; -import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants; import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignEnumerations; import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; -import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignTriggerWithSubTypes; import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData; -import org.apache.fineract.infrastructure.campaigns.sms.data.TriggerTypeWithSubTypesData; import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; @@ -74,19 +71,20 @@ public class SmsCampaignDropdownReadPlatformServiceImpl implements SmsCampaignDr @Override public Collection retrieveSmsProviders() { Collection smsProviderOptions = new ArrayList<>(); + String hostName = "" ; try { Map hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("smsbridges", null); URI uri = (URI) hostConfig.get("uri"); + hostName = uri.getHost() ; HttpEntity entity = (HttpEntity) hostConfig.get("entity"); ResponseEntity> responseOne = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference>() {}); smsProviderOptions = responseOne.getBody(); if (!responseOne.getStatusCode().equals(HttpStatus.OK)) { - System.out.println(responseOne.getStatusCode().name()); - throw new ConnectionFailureException(SmsCampaignConstants.SMS_BRIDGE); + throw new ConnectionFailureException(hostName); } } catch (Exception e) { - e.getStackTrace(); + throw new ConnectionFailureException(hostName); } return smsProviderOptions; } @@ -117,9 +115,4 @@ public class SmsCampaignDropdownReadPlatformServiceImpl implements SmsCampaignDr .calendarPeriodFrequencyTypes(PeriodFrequencyType.values()); return periodFrequencyTypes; } - - @Override - public Collection getTriggerTypeAndSubTypes() { - return CampaignTriggerWithSubTypes.getTriggerTypeAndSubTypes(); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java index c4d47e858..477b534c0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java @@ -234,8 +234,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP } } } catch (final IOException e) { - // TODO throw something here - System.out.println(e.getMessage()); + logger.error(e.getMessage()); } } @@ -291,9 +290,9 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP } } } catch (final IOException e) { - System.out.println("IOException: " + e.getMessage()); + logger.error(e.getMessage()) ; } catch (final RuntimeException e) { - System.out.println("RuntimeException: " + e.getMessage()); + logger.error(e.getMessage()) ; } } @@ -332,9 +331,9 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP } } } catch (final IOException e) { - System.out.println("IOException: " + e.getMessage()); + logger.error(e.getMessage()) ; } catch (final RuntimeException e) { - System.out.println("RuntimeException: " + e.getMessage()); + logger.error(e.getMessage()) ; } } @@ -372,9 +371,9 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP } } } catch (final IOException e) { - System.out.println("IOException: " + e.getMessage()); + logger.error(e.getMessage()) ; } catch (final RuntimeException e) { - System.out.println("RuntimeException: " + e.getMessage()); + logger.error(e.getMessage()) ; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java index 3e23a10f3..4b34f41f4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java @@ -173,7 +173,7 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob if (responseOne != null) { // String smsResponse = responseOne.getBody(); if (!responseOne.getStatusCode().equals(HttpStatus.ACCEPTED)) { - System.out.println(responseOne.getStatusCode().name()); + logger.debug(responseOne.getStatusCode().name()); throw new ConnectionFailureException(SmsCampaignConstants.SMS); } }