SMS Campaign feature implementation

This commit is contained in:
Satish
2016-11-11 18:56:12 +05:30
parent 580840c35e
commit 911cab85b0
86 changed files with 6882 additions and 94 deletions
+325 -2
View File
@@ -2064,7 +2064,46 @@
<td><a href="#shareproducts_retrieve">Retrieve a Share product</a></td>
<td><a href="#shareproducts_update">Update a Share product</a></td>
</tr>
<tr>
<td><a href="#smscampaigns">SMS Campaigns</a></td>
<td>smscampaigns</td>
<td><a href="#smscampaigns_create">Create a SMS Campaign</a></td>
<td><a href="#smscampaigns_list">List SMS Campaigns</a></td>
<td></td>
<td></td>
</tr>
tr>
<td></td>
<td>smscampaigns/{campaignId}</td>
<td></td>
<td><a href="#smscampaigns_retrieve">Retrieve a SMS Campaign</a></td>
<td><a href="#smscampaigns_update">Update a SMS Campaign</a></td>
<td><a href="#smscampaigns_delete">Delete a SMS Campaign</a></td>
</tr>
<tr>
<td></td>
<td>smscampaigns/{campaignId}command=activate</td>
<td></td>
<td><a href="#smscampaigns_activate">Activate a SMS Campaign</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>smscampaigns/{campaignId}command=close</td>
<td></td>
<td><a href="#smscampaigns_close">Close a SMS Campaign</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>smscampaigns/{campaignId}command=reactivate</td>
<td></td>
<td><a href="#smscampaigns_reactivate">Reactivate a SMS Campaign</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="#configs">Currency</a></td>
<td>currencies</td>
@@ -21038,7 +21077,291 @@ Content-Type: application/json
</div>
</div>
<a id="shareproducts_create" name="shareproducts_create" class="old-syle-anchor">&nbsp;</a>
<a id="smscampaigns_create" name="smscampaigns_create" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Create a SMS Campaign</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
<tr class=alt>
<td>campaignName, campaignType, triggerType, providerId, runReportId, message</td>
</tr>
</table>
<br/>
<table class=matrixHeading>
<tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields for Cash based on selected report id</div></td></tr>
<tr class=alt>
<td>paramValue in json format</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/smscampaigns</code>
<code class="method-request">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}",
}
</code>
<code class="method-response">
{
"resourceId": 1
}
</code>
</div>
</div>
<a id="smscampaigns_list" name="smscampaigns_list" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>List SMS Campaigns</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG"><td><div class="fineractHeading2">Mandatory Fields</div></td></tr>
<tr class=alt>
<td></td>
</tr>
</table>
<p>Example Requests:</p>
<div class=apiClick>smscampaigns</div>
</div>
<div class="method-example">
<code class="method-declaration">GET https://Domain Name/api/v1/smscampaigns</code>
<code class="method-response">
[{
"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":""}
}]
</code>
</div>
</div>
<a id="smscampaigns_retrieve" name="smscampaigns_retrieve" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve a Share Product</h4>
<p>Example Requests: </p>
<div class=apiClick>smscampaigns/1</div>
<br><br>
<div class=apiClick>smscampaigns/1?template=true</div>
</div>
<div class="method-example">
<code class="method-declaration">GET https://Domain Name/api/v1/smscampaigns/1</code>
<code class="method-response">
{
"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":""}
}
<B>In case of Scheduled Campaign</B>
{
"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"
}
</code>
</div>
</div>
<a id="smscampaigns_update" name="smscampaigns_update" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Update a Campaign</h4>
</div>
<div class="method-example">
<code class="method-declaration">PUT https://Domain Name/api/v1/smscampaigns/{campaignId}</code>
<code class="method-request">POST smscampaigns/1
Content-Type: application/json
Request Body:
{
"message": "Hello your savings account {savingsAccountNo} debited with {withdrawAmount}"
}
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {
"message": "Hello your savings account {savingsAccountNo} debited with {withdrawAmount}"
}
}
</code>
</div>
</div>
<a id="smscampaigns_delete" name="smscampaigns_delete" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Delete a SMS Campaign</h4>
<p>
<b>Note:</b> Only closed SMS Campaigns can be
deleted
</p>
</div>
<div class="method-example">
<code class="method-declaration">
DELETE https://DomainName/api/v1/smscampaigns/{campaignId}
</code>
<code class="method-request">
DELETE smscampaigns/1
Content-Type: application/json
No Request Body:
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {}
}
</code>
</div>
</div>
<a id="smscampaigns_activate" name="smscampaigns_activate" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Activates SMS Campaign</h2>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/smscampaigns/{campaignId}?command=activate</code>
<code class="method-request">POST smscampaigns/1?command=activate
Content-Type: application/json
Request Body:
{
"activationDate":"01 May 2016",
"locale":"en",
"dateFormat":"dd MMMM yyyy"
}
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {
"status": {
"id": 300,
"code": "smsCampaignStatus.active",
"value": "Activated",
},
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"activationDate": "01 May 2016"
}
}
</code>
</div>
</div>
<a id="smscampaigns_close" name="smscampaigns_close" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Deactivates SMS Campaign</h2>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/smscampaigns/{campaignId}?command=close</code>
<code class="method-request">POST smscampaigns/1?command=close
Content-Type: application/json
Request Body:
{
"closureDate":"01 May 2016",
"locale":"en",
"dateFormat":"dd MMMM yyyy"
}
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {
"status": {
"id": 600,
"code": "smsCampaignStatus.closed",
"value": "Activated",
},
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"closureDate": "01 May 2016"
}
}
</code>
</div>
</div>
<a id="smscampaigns_reactivate" name="smscampaigns_reactivate" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Reactivates SMS Campaign</h2>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/smscampaigns/{campaignId}?command=reactivate</code>
<code class="method-request">POST smscampaigns/1?command=reactivate
Content-Type: application/json
Request Body:
{
"activationDate":"01 May 2016",
"locale":"en",
"dateFormat":"dd MMMM yyyy"
}
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {
"status": {
"id": 300,
"code": "smsCampaignStatus.active",
"value": "Activated",
},
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"activationDate": "01 May 2016"
}
}
</code>
</div>
</div>
<a id="shareproducts_create" name="shareproducts_create" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Create a Share Product</h4>
@@ -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;
}
}
@@ -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());
}
}
@@ -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<String, Object> getMessageGateWayRequestURI(final String apiEndPoint, String apiQueueResourceDatas) {
Map<String, Object> 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;
}
}
@@ -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<SmsCampaignData> toApiJsonSerializer;
private final SmsCampaignReadPlatformService smsCampaignReadPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final FromJsonHelper fromJsonHelper;
private final DefaultToApiJsonSerializer<CampaignPreviewData> 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<SmsCampaignData> toApiJsonSerializer,
final SmsCampaignReadPlatformService smsCampaignReadPlatformService, final ApiRequestParameterHelper apiRequestParameterHelper,
final FromJsonHelper fromJsonHelper,
final DefaultToApiJsonSerializer<CampaignPreviewData> 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<SmsCampaignData> 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<String>());
}
@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);
}
}
@@ -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" ;
}
@@ -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<EnumOptionData> calendarMonthType() {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final Month monthType : Month.values()) {
if (Month.DECEMBER.compareTo(monthType) != 0) {
optionDatas.add(calendarMonthType(monthType));
}
}
return optionDatas;
}
public static List<EnumOptionData> calendarPeriodFrequencyTypes(final PeriodFrequencyType[] periodFrequencyTypes) {
final List<EnumOptionData> 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;
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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;
}
}
@@ -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<EnumOptionData> addTypeSubTypeMapping(ActualCampaignTriggerType type) {
List<EnumOptionData> 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<TriggerTypeWithSubTypesData> getTriggerTypeAndSubTypes() {
final Collection<TriggerTypeWithSubTypesData> typesList = new ArrayList<>();
EnumOptionData actualTriggerType = null;
for (ActualCampaignTriggerType triggerType : ActualCampaignTriggerType.values()) {
if (triggerType.isInvalid()) {
continue;
}
List<EnumOptionData> subTypeList = addTypeSubTypeMapping(triggerType);
actualTriggerType = ActualCampaignTriggerType.toEnumOptionData(triggerType);
TriggerTypeWithSubTypesData triggerTypeWithSubTypesData = new TriggerTypeWithSubTypesData(actualTriggerType, subTypeList);
typesList.add(triggerTypeWithSubTypesData);
}
return typesList;
}
}
@@ -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;
}
}
@@ -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<String, Object> reportParamName;
public SmsBusinessRulesData(final Long reportId, final String reportName, final String reportType, final String reportSubType,
final Map<String, Object> 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<String, Object> reportParamName, final String reportDescription) {
return new SmsBusinessRulesData(reportId, reportName, reportType, reportSubType, reportParamName, reportDescription);
}
public Map<String, Object> 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;
}
}
@@ -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<SmsProviderData> smsProviderOptions;
private final Collection<EnumOptionData> campaignTypeOptions;
private final Collection<EnumOptionData> triggerTypeOptions;
private final Collection<SmsBusinessRulesData> businessRulesOptions;
private final Collection<EnumOptionData> months;
private final Collection<EnumOptionData> weekDays;
private final Collection<EnumOptionData> frequencyTypeOptions;
private final Collection<EnumOptionData> 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<SmsBusinessRulesData> businessRulesOptions,
final Collection<SmsProviderData> smsProviderOptions, final Collection<EnumOptionData> campaignTypeOptions,
final Collection<EnumOptionData> triggerTypeOptions, final Collection<EnumOptionData> months,
final Collection<EnumOptionData> weekDays, final Collection<EnumOptionData> frequencyTypeOptions,
final Collection<EnumOptionData> 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<SmsBusinessRulesData> businessRulesOptions = null;
final Collection<SmsProviderData> smsProviderOptions = null;
final Collection<EnumOptionData> campaignTypeOptions = null;
final Collection<EnumOptionData> triggerTypeOptions = null;
final Collection<EnumOptionData> months = null;
final Collection<EnumOptionData> weekDays = null;
final Collection<EnumOptionData> frequencyTypeOptions = null;
final Collection<EnumOptionData> 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<SmsProviderData> smsProviderOptions,
final Collection<EnumOptionData> campaignTypeOptions, final Collection<SmsBusinessRulesData> businessRulesOptions,
final Collection<EnumOptionData> triggerTypeOptions, final Collection<EnumOptionData> months,
final Collection<EnumOptionData> weekDays, final Collection<EnumOptionData> frequencyTypeOptions,
final Collection<EnumOptionData> 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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<EnumOptionData> triggerSubTypes;
public TriggerTypeWithSubTypesData(final EnumOptionData actualTriggerType, List<EnumOptionData> triggerSubTypes) {
this.actualTriggerType = actualTriggerType;
this.triggerSubTypes = triggerSubTypes;
}
public EnumOptionData getActualTriggerType() {
return this.actualTriggerType;
}
public List<EnumOptionData> getTriggerSubTypes() {
return this.triggerSubTypes;
}
}
@@ -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<Long> {
@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<String, Object> update(JsonCommand command) {
final Map<String, Object> 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<ApiParameterError> 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<ApiParameterError> 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<ApiParameterError> 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<ApiParameterError> 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<ApiParameterError> dataValidationErrors = new ArrayList<>();
validateActivationDate(dataValidationErrors);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateReactivate() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
validateReactivationDate(dataValidationErrors);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateClosureDate() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
validateClosureDate(dataValidationErrors);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateActivationDate(final List<ApiParameterError> 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<ApiParameterError> 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<ApiParameterError> 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();
}
}
@@ -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<SmsCampaign, Long>, JpaSpecificationExecutor<SmsCampaign> {
List<SmsCampaign> findByCampaignType(final Integer campaignType);
Collection<SmsCampaign> findByCampaignTypeAndTriggerTypeAndStatus(final Integer campaignType, final Integer triggerType,
final Integer status);
Collection<SmsCampaign> findByTriggerType(final Integer triggerType) ;
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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); }
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<String> 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<String> supportedParamsForUpdate = new HashSet<>(Arrays.asList(campaignName, campaignType, localeParamName,
dateFormatParamName, runReportId, paramValue, message, recurrenceStartDate, activationDateParamName, recurrenceParamName,
providerId, triggerType, triggerEntityType, triggerActionType, dateTimeFormat));
public static final Set<String> ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName,
activationDateParamName));
public static final Set<String> CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName,
closureDateParamName));
public static final Set<String> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.supportedParams);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> 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<String, JsonElement> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.supportedParamsForUpdate);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> 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<String, JsonElement> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.PREVIEW_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> 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<String, JsonElement> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.CLOSE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.ACTIVATION_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsCampaignValidator.CLOSE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> 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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -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 {
}
@@ -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<SmsCampaign> 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<SmsCampaign> 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<SmsCampaign> 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<SmsCampaign> 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<SmsCampaign> 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<SmsCampaign> 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<SmsCampaign> smsCampaigns = retrieveSmsCampaigns("loan repayment");
if (smsCampaigns.size() > 0) {
for (SmsCampaign smsCampaign : smsCampaigns) {
if (smsCampaign.isActive()) {
try {
Loan loan = loanTransaction.getLoan();
final Set<Client> 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<String, String> campaignParams = new ObjectMapper()
.readValue(smsCampaign.getParamValue(), new TypeReference<HashMap<String, String>>() {
});
if (groupClients.size() > 0) {
for (Client client : groupClients) {
HashMap<String, Object> 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<SmsMessage> messages = new ArrayList<>();
messages.add(smsMessage);
Map<SmsCampaign, Collection<SmsMessage>> 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<SmsCampaign> 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<String, String> campaignParams = new ObjectMapper()
.readValue(smsCampaign.getParamValue(), new TypeReference<HashMap<String, String>>() {
});
HashMap<String, Object> 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<SmsMessage> messages = new ArrayList<>();
messages.add(smsMessage);
Map<SmsCampaign, Collection<SmsMessage>> 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<SmsCampaign> retrieveSmsCampaigns(String paramValue){
Collection<SmsCampaign> initialSmsCampaignList = smsCampaignRepository.findByTriggerType(SmsCampaignTriggerType.TRIGGERED.getValue());
ArrayList<SmsCampaign> smsCampaigns = new ArrayList<>();
for(SmsCampaign campaign : initialSmsCampaignList){
if(campaign.getParamValue().toLowerCase().contains(paramValue)){
smsCampaigns.add(campaign);
}
}
return smsCampaigns;
}
private HashMap<String, Object> processRepaymentDataForSms(final LoanTransaction loanTransaction, Client groupClient){
HashMap<String, Object> smsParams = new HashMap<String, Object>();
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<String, Object> processSavingsTransactionDataForSms(final SavingsAccountTransaction savingsAccountTransaction, Client client){
// {{savingsId}} {{id}} {{firstname}} {{middlename}} {{lastname}} {{FullName}} {{mobileNo}} {{savingsAccountId}} {{depositAmount}} {{balance}}
//transactionDate
HashMap<String, Object> smsParams = new HashMap<String, Object>();
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<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> businessEventEntity) {
}
@Override
public void businessEventWasExecuted(Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> 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<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> businessEventEntity) {
}
@Override
public void businessEventWasExecuted(Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> 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<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> businessEventEntity) {
}
@Override
public void businessEventWasExecuted(Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> 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<BUSINESS_ENTITY, Object> businessEventEntity) {
}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> 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<BUSINESS_ENTITY, Object> businessEventEntity) {
}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> 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<BUSINESS_ENTITY, Object> businessEventEntity) {
}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> 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<BUSINESS_ENTITY, Object> businessEventEntity) {
}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> 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<BUSINESS_ENTITY, Object> businessEventEntity) {
// TODO Auto-generated method stub
}
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.SAVINGS_TRANSACTION);
if(entity instanceof SavingsAccountTransaction) {
sendSmsForSavingsTransaction((SavingsAccountTransaction)entity, this.isDeposit);
}
}
}
}
@@ -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<EnumOptionData> retrieveCampaignTriggerTypes();
Collection<SmsProviderData> retrieveSmsProviders();
Collection<EnumOptionData> retrieveCampaignTypes();
Collection<EnumOptionData> retrieveWeeks();
Collection<EnumOptionData> retrieveMonths();
Collection<EnumOptionData> retrivePeriodFrequencyTypes();
Collection<TriggerTypeWithSubTypesData> getTriggerTypeAndSubTypes();
}
@@ -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<EnumOptionData> retrieveCampaignTriggerTypes() {
final List<EnumOptionData> triggerTypeCodeValues = Arrays.asList( //
SmsCampaignEnumerations.smscampaignTriggerType(SmsCampaignTriggerType.DIRECT), //
SmsCampaignEnumerations.smscampaignTriggerType(SmsCampaignTriggerType.SCHEDULE), //
SmsCampaignEnumerations.smscampaignTriggerType(SmsCampaignTriggerType.TRIGGERED) //
);
return triggerTypeCodeValues;
}
@Override
public Collection<SmsProviderData> retrieveSmsProviders() {
Collection<SmsProviderData> smsProviderOptions = new ArrayList<>();
try {
Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("smsbridges", null);
URI uri = (URI) hostConfig.get("uri");
HttpEntity<?> entity = (HttpEntity<?>) hostConfig.get("entity");
ResponseEntity<Collection<SmsProviderData>> responseOne = restTemplate.exchange(uri, HttpMethod.GET, entity,
new ParameterizedTypeReference<Collection<SmsProviderData>>() {});
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<EnumOptionData> retrieveCampaignTypes() {
final List<EnumOptionData> campaignTypeCodeValues = Arrays.asList( //
SmsCampaignEnumerations.smscampaignType(CampaignType.SMS)//
);
return campaignTypeCodeValues;
}
@Override
public Collection<EnumOptionData> retrieveMonths() {
Collection<EnumOptionData> monthsList = SmsCampaignEnumerations.calendarMonthType();
return monthsList;
}
@Override
public Collection<EnumOptionData> retrieveWeeks() {
Collection<EnumOptionData> weeksList = CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.values());
return weeksList;
}
@Override
public Collection<EnumOptionData> retrivePeriodFrequencyTypes() {
Collection<EnumOptionData> periodFrequencyTypes = SmsCampaignEnumerations
.calendarPeriodFrequencyTypes(PeriodFrequencyType.values());
return periodFrequencyTypes;
}
@Override
public Collection<TriggerTypeWithSubTypesData> getTriggerTypeAndSubTypes() {
return CampaignTriggerWithSubTypes.getTriggerTypeAndSubTypes();
}
}
@@ -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<SmsCampaignData> retrieveAll();
public SmsCampaignData retrieveTemplate(final String reportType);
Collection<SmsCampaignData> retrieveAllScheduleActiveCampaign();
}
@@ -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<SmsCampaignData> 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<SmsBusinessRulesData> businessRulesOptions = this.jdbcTemplate.query(sql, this.businessRuleMapper,
new Object[] { reportType });
final Collection<SmsProviderData> smsProviderOptions = this.smsCampaignDropdownReadPlatformService.retrieveSmsProviders();
final Collection<EnumOptionData> campaignTypeOptions = this.smsCampaignDropdownReadPlatformService.retrieveCampaignTypes();
final Collection<EnumOptionData> campaignTriggerTypeOptions = this.smsCampaignDropdownReadPlatformService
.retrieveCampaignTriggerTypes();
final Collection<EnumOptionData> months = this.smsCampaignDropdownReadPlatformService.retrieveMonths();
final Collection<EnumOptionData> weekDays = this.smsCampaignDropdownReadPlatformService.retrieveWeeks();
final Collection<EnumOptionData> frequencyTypeOptions = this.calendarDropdownReadPlatformService
.retrieveCalendarFrequencyTypeOptions();
final Collection<EnumOptionData> periodFrequencyOptions = this.smsCampaignDropdownReadPlatformService.retrivePeriodFrequencyTypes();
//final Collection<TriggerTypeWithSubTypesData> triggerTypeSubTypeOptions = this.smsCampaignDropdownReadPlatformService.getTriggerTypeAndSubTypes();
return SmsCampaignData.template(smsProviderOptions, campaignTypeOptions, businessRulesOptions, campaignTriggerTypeOptions, months,
weekDays, frequencyTypeOptions, periodFrequencyOptions);
}
@Override
public Collection<SmsCampaignData> retrieveAllScheduleActiveCampaign() {
return null;
}
private static final class BusinessRuleMapper implements ResultSetExtractor<List<SmsBusinessRulesData>> {
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<SmsBusinessRulesData> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<SmsBusinessRulesData> smsBusinessRulesDataList = new ArrayList<SmsBusinessRulesData>();
SmsBusinessRulesData smsBusinessRulesData = null;
Map<Long, SmsBusinessRulesData> mapOfSameObjects = new HashMap<Long, SmsBusinessRulesData>();
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<String, Object> hashMap = new HashMap<String, Object>();
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<String, Object> hashMap = new HashMap<String, Object>();
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<SmsCampaignData> {
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);
}
}
}
@@ -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<String, Object> 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) ;
}
@@ -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<String, Object> 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<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
List<HashMap<String, Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),
queryParamForRunReport);
if (runReportObject != null) {
for (HashMap<String, Object> 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<Client> clientSet = new HashSet<>();
HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
campaignParams.put("loanId", loan.getId().toString());
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
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<HashMap<String, Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),
queryParamForRunReport);
if (runReportObject != null && runReportObject.size() > 0) {
for (HashMap<String, Object> 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<SmsMessage> messages = new ArrayList<>() ;
messages.add(smsMessage) ;
Map<SmsCampaign, Collection<SmsMessage>> 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<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
campaignParams.put("clientId", client.getId().toString());
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
campaignParams.put("clientId", client.getId().toString());
queryParamForRunReport.put("clientId", client.getId().toString());
List<HashMap<String, Object>> runReportObject = this
.getRunReportByServiceImpl(campaignParams.get("reportName"), queryParamForRunReport);
if (runReportObject != null && runReportObject.size() > 0) {
for (HashMap<String, Object> 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<SmsMessage> messages = new ArrayList<>();
messages.add(smsMessage);
Map<SmsCampaign, Collection<SmsMessage>> 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<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
campaignParams.put("savingsId", savingsAccount.getId().toString());
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
new TypeReference<HashMap<String, String>>() {});
queryParamForRunReport.put("savingsId", savingsAccount.getId().toString());
Client client = savingsAccount.getClient() ;
List<HashMap<String, Object>> runReportObject = this
.getRunReportByServiceImpl(campaignParams.get("reportName"), queryParamForRunReport);
if (runReportObject != null && runReportObject.size() > 0) {
for (HashMap<String, Object> 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<SmsMessage> messages = new ArrayList<>();
messages.add(smsMessage);
Map<SmsCampaign, Collection<SmsMessage>> 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<String, Object> 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<HashMap<String, Object>> getRunReportByServiceImpl(final String reportName, final Map<String, String> queryParams)
throws IOException {
final String reportType = "report";
List<HashMap<String, Object>> 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<List<HashMap<String, Object>>>() {});
} 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<String, Object> entry : resultList) {
for (Map.Entry<String, Object> 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<String, String> campaignParams = new ObjectMapper().readValue(smsParams,
new TypeReference<HashMap<String, String>>() {});
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsParams,
new TypeReference<HashMap<String, String>>() {});
List<HashMap<String, Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),
queryParamForRunReport);
if (runReportObject != null && !runReportObject.isEmpty()) {
for (HashMap<String, Object> 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<SmsCampaign> 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();
// }
}
@@ -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<String> S3SupportedParameters = S3_JSON_INPUT_PARAMS.getAllValues();
private final Set<String> SMTPSupportedParameters = SMTP_JSON_INPUT_PARAMS.getAllValues();
private final Set<String> 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);
}
@@ -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<String> values = new HashSet<>();
static {
for (final SMS_JSON_INPUT_PARAMS type : SMS_JSON_INPUT_PARAMS.values()) {
values.add(type.value);
}
}
public static Set<String> 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");
@@ -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<ExternalServicesPropertiesData> retrieveOne(String serviceName);
}
@@ -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<MessageGatewayConfigurationData> {
@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<S3CredentialsData> resultSetExtractor = new S3CredentialsDataExtractor();
@@ -126,6 +151,15 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
return smtpCredentialsData;
}
@Override
public MessageGatewayConfigurationData getSMSGateway() {
final ResultSetExtractor<MessageGatewayConfigurationData> 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<ExternalServicesPropertiesData> 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[] {});
}
}
@@ -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);
}
@@ -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;
}
@@ -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
*/
@@ -36,4 +36,14 @@ public class PaginationHelper<E> {
return new Page<>(items, totalFilteredRecords);
}
public Page<Long> fetchPage(JdbcTemplate jdbcTemplate, String sql, String sqlCountRows, Class<Long> type) {
final List<Long> items = jdbcTemplate.queryForList(sql, type);
// determine how many rows are available
@SuppressWarnings("deprecation")
final int totalFilteredRecords = jdbcTemplate.queryForInt(sqlCountRows);
return new Page<>(items, totalFilteredRecords);
}
}
@@ -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,
@@ -44,4 +44,9 @@ public interface ReadReportingService {
ReportData retrieveReport(final Long id);
Collection<String> getAllowedReportTypes();
//needed for smsCampaign jobs where securityContext is null
GenericResultsetData retrieveGenericResultSetForSmsCampaign(String name, String type, Map<String, String> extractedQueryParams);
String sqlToRunForSmsCampaign(String name, String type, Map<String, String> queryParams);
}
@@ -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<String, String> 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<String, String> queryParams) {
String sql = getSql(name, type);
final Set<String> 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;
}
}
@@ -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;
@@ -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<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName));
dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName, campaignIdParamName));
public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName));
public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(messageParamName, campaignIdParamName));
}
@@ -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<SmsData> 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) {
@@ -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;
}
}
@@ -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<SmsMessageApiQueueResourceData> smsResourceData) {
Gson gson = new Gson();
return gson.toJson(smsResourceData);
}
}
@@ -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<Long> externalIds;
private String mifosTenantIdentifier;
/**
* SmsMessageApiReportResourceData constructor
**/
private SmsMessageApiReportResourceData(List<Long> 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<Long> externalIds, String mifosTenantIdentifier) {
return new SmsMessageApiReportResourceData(externalIds, mifosTenantIdentifier);
}
/**
* @return the externalIds
*/
public List<Long> 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);
}
}
@@ -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<SmsMessageDeliveryReportData> data;
/**
* SmsMessageApiResponseData constructor
*
* @return void
**/
private SmsMessageApiResponseData(Integer httpStatusCode, List<SmsMessageDeliveryReportData> 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<SmsMessageDeliveryReportData> data) {
return new SmsMessageApiResponseData(httpStatusCode, data);
}
/**
* @return the httpStatusCode
*/
public Integer getHttpStatusCode() {
return httpStatusCode;
}
/**
* @return the data
*/
public List<SmsMessageDeliveryReportData> getData() {
return data;
}
}
@@ -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;
}
}
@@ -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<Long> {
@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<Long> {
@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<Long> {
@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<String, Object> update(final JsonCommand command) {
@@ -91,4 +131,56 @@ public class SmsMessage extends AbstractPersistableCustom<Long> {
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;
}
}
@@ -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) {
@@ -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<SmsMessage, Long>, JpaSpecificationExecutor<SmsMessage> {
// no extra behaviour
Page<SmsMessage> findByStatusType(final Integer status, Pageable pageable);
}
@@ -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;
@@ -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");
}
}
@@ -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<SmsCampaign, Collection<SmsMessage>> smsDataMap);
/**
* get delivery report from the SMS gateway
**/
public void getDeliveryReports();
}
@@ -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<SmsMessage> pendingMessages = this.smsMessageRepository.findByStatusType(
SmsMessageStatusType.PENDING.getValue(), pageRequest);
List<SmsMessage> toSaveMessages = new ArrayList<>() ;
try {
if (pendingMessages.getContent().size() > 0) {
final String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
Iterator<SmsMessage> pendingMessageIterator = pendingMessages.iterator();
Collection<SmsMessageApiQueueResourceData> 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<ContextClosedEvent> {
private final FineractPlatformTenant tenant;
private final Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas;
public SmsTask(final FineractPlatformTenant tenant, final Collection<SmsMessageApiQueueResourceData> 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<SmsMessageApiQueueResourceData> apiQueueResourceDatas) {
Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms", SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas)) ;
URI uri = (URI)hostConfig.get("uri") ;
HttpEntity<?> entity = (HttpEntity<?>)hostConfig.get("entity") ;
ResponseEntity<String> responseOne = restTemplate.exchange(uri, HttpMethod.POST, entity,
new ParameterizedTypeReference<String>() {});
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<SmsCampaign, Collection<SmsMessage>> smsDataMap) {
try {
if (!smsDataMap.isEmpty()) {
for (Entry<SmsCampaign, Collection<SmsMessage>> entry : smsDataMap.entrySet()) {
Iterator<SmsMessage> smsMessageIterator = entry.getValue().iterator();
Collection<SmsMessageApiQueueResourceData> 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<Long> 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<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms", new Gson().toJson(smsMessageInternalIds.getPageItems())) ;
URI uri = (URI)hostConfig.get("uri") ;
HttpEntity<?> entity = (HttpEntity<?>)hostConfig.get("entity") ;
ResponseEntity<Collection<SmsMessageDeliveryReportData>> responseOne = restTemplate.exchange(uri, HttpMethod.POST, entity,
new ParameterizedTypeReference<Collection<SmsMessageDeliveryReportData>>() {});
Collection<SmsMessageDeliveryReportData> smsMessageDeliveryReportDatas = responseOne.getBody();
Iterator<SmsMessageDeliveryReportData> 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);
}
}
@@ -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<SmsData> retrieveAll();
SmsData retrieveOne(Long resourceId);
Collection<SmsData> retrieveAllPending(final Long campaignId, final Integer limit);
Collection<SmsData> retrieveAllSent(Integer limit);
Collection<SmsData> retrieveAllDelivered(Integer limit);
Collection<SmsData> retrieveAllFailed(Integer limit);
Page<SmsData> retrieveSmsByStatus(final Long campaignId, SearchParameters searchParameters, Integer status, Date dateFrom, Date dateTo);
List<Long> retrieveExternalIdsOfAllSent(Integer limit);
Page<Long> retrieveAllWaitingForDeliveryReport(Integer limit);
List<Long> retrieveAllPending(Integer limit);
}
@@ -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<SmsData> 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<SmsData> 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<SmsData> 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<Long> 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<Long> 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<Long> 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<SmsData> 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<SmsData> 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<SmsData> 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);
}
}
@@ -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<Long> {
public void loadLazyCollections() {
this.groups.size() ;
}
public String getFirstname(){return this.firstname;}
public String getMiddlename(){return this.middlename;}
public String getLastname(){return this.lastname;}
}
@@ -205,6 +205,10 @@ public class ClientTransaction extends AbstractPersistableCustom<Long> {
return client.getId();
}
public Client getClient() {
return this.client ;
}
public Money getAmount() {
return Money.of(getCurrency(), this.amount);
}
@@ -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<Note> 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<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -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;
}
}
}
@@ -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<InterestRatePeriodData> interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanBasicDetails);
Collection<LoanTransactionData> loanRepayments = null;
LoanScheduleData repaymentSchedule = null;
Collection<LoanChargeData> 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
@@ -6389,7 +6389,6 @@ public class Loan extends AbstractPersistableCustom<Long> {
public Collection<LoanCharge> getLoanCharges() {
return this.charges;
}
public void initializeLazyCollections() {
this.charges.size() ;
this.trancheCharges.size() ;
@@ -6412,4 +6411,9 @@ public class Loan extends AbstractPersistableCustom<Long> {
public void initializeRepaymentSchedule() {
this.repaymentScheduleInstallments.size() ;
}
public boolean hasInvalidLoanType() {
return AccountType.fromInt(this.loanType).isInvalid();
}
public boolean isIndividualLoan(){return AccountType.fromInt(this.loanType).isIndividualAccount();}
}
@@ -778,6 +778,14 @@ public class LoanTransaction extends AbstractPersistableCustom<Long> {
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
@@ -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);
}
}
@@ -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()) //
@@ -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<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
return LoanTransactionData.templateOnTop(loanTransactionData, paymentOptions);
}
@@ -115,4 +115,5 @@ public final class PaymentDetail extends AbstractPersistableCustom<Long> {
return this.paymentType;
}
public String getReceiptNumber() { return this.receiptNumber; }
}
@@ -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<Long> existingTransactionIds = new HashSet<>();
final boolean interestPostAsOn = false;
final Set<Long> 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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -729,16 +729,15 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom<L
public void updateAmount(final Money amount) {
this.amount = amount.getAmount();
}
public Integer getTypeOf() {
return this.typeOf;
}
public SavingsAccount getSavingsAccount() {
return this.savingsAccount;
}
public void setSavingsAccount(SavingsAccount savingsAccount) {
this.savingsAccount = savingsAccount;
}
@@ -747,4 +746,7 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom<L
return this.dateOf;
}
public PaymentDetail getPaymentDetail() {
return this.paymentDetail ;
}
}
@@ -39,8 +39,6 @@ import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException;
import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -73,6 +71,9 @@ import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.client.domain.Client;
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.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.note.domain.Note;
@@ -98,13 +99,13 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.apache.fineract.portfolio.savings.exception.PostInterestAsOnDateException;
import org.apache.fineract.portfolio.savings.exception.PostInterestAsOnDateException.PostInterestAsOnException_TYPE;
import org.apache.fineract.portfolio.savings.exception.PostInterestClosingDateException;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountClosingNotAllowedException;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountTransactionNotFoundException;
import org.apache.fineract.portfolio.savings.exception.SavingsOfficerAssignmentException;
import org.apache.fineract.portfolio.savings.exception.SavingsOfficerUnassignmentException;
import org.apache.fineract.portfolio.savings.exception.TransactionUpdateNotAllowedException;
import org.apache.fineract.portfolio.savings.exception.PostInterestAsOnDateException.PostInterestAsOnException_TYPE;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
import org.joda.time.LocalDate;
@@ -141,6 +142,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
private final AppUserRepositoryWrapper appuserRepository;
private final StandingInstructionRepository standingInstructionRepository;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -161,7 +163,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final StaffRepositoryWrapper staffRepository, final ConfigurationDomainService configurationDomainService,
final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository,
final AppUserRepositoryWrapper appuserRepository,
final StandingInstructionRepository standingInstructionRepository) {
final StandingInstructionRepository standingInstructionRepository,
final BusinessEventNotifierService businessEventNotifierService) {
this.context = context;
this.savingAccountRepositoryWrapper = savingAccountRepositoryWrapper;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
@@ -185,6 +188,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
this.appuserRepository = appuserRepository;
this.standingInstructionRepository = standingInstructionRepository;
this.businessEventNotifierService = businessEventNotifierService;
}
@Transactional
@@ -197,7 +201,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
checkClientOrGroupActive(account);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_ACTIVATE,
constructEntityMap(BUSINESS_ENTITY.SAVING, account));
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<Long> 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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<Long> 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<ApiParameterError> 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<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -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<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -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<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
/**
* private void validateGroupAwaitingTransferAcceptanceOnHold(final Group
* group) { if (!group.isTransferInProgressOrOnHold()) { throw new
@@ -79,6 +79,7 @@
<jpa:repositories base-package="org.apache.fineract.mix.domain" />
<jpa:repositories base-package="org.apache.fineract.scheduledjobs.domain" />
<jpa:repositories base-package="org.apache.fineract.template.domain" />
<jpa:repositories base-package="org.apache.fineract.infrastructure.campaigns.sms.domain" />
<import resource="infrastructure.xml" />
@@ -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);