merge 1.14.0 release into master

This commit is contained in:
Vishwas Babu A J
2013-11-21 23:53:13 +05:30
235 changed files with 31336 additions and 912 deletions
@@ -8,6 +8,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.gson.Gson;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
@@ -74,13 +76,15 @@ public class GroupHelper {
map.put("officeId", "1");
map.put("name", randomNameGenerator("Group_Name_", 5));
map.put("externalId", randomIDGenerator("ID_", 7));
if (active == true) {
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en");
if (active) {
map.put("active", "true");
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en");
map.put("activationDate", activationDate);
} else {
map.put("active", "false");
map.put("submittedOnDate", "04 March 2011");
System.out.println("defaulting to inactive group: 04 March 2011");
}
System.out.println("map : " + map);
@@ -100,10 +104,11 @@ public class GroupHelper {
final HashMap<String, String> map = new HashMap<String, String>();
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en");
if (activationDate != "") {
if (StringUtils.isNotEmpty(activationDate)) {
map.put("activationDate", activationDate);
} else {
map.put("activationDate", "04 March 2011");
System.out.println("defaulting to fixed date: 04 March 2011");
}
System.out.println("map : " + map);
return new Gson().toJson(map);
@@ -267,10 +267,10 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
.getTransactionId());
if (journalEntries.size() <= 1) { throw new JournalEntriesNotFoundException(command.getTransactionId()); }
Long officeId = journalEntries.get(0).getOffice().getId();
final Long officeId = journalEntries.get(0).getOffice().getId();
final String reversalTransactionId = generateTransactionId(officeId);
final boolean manualEntry = true;
for (final JournalEntry journalEntry : journalEntries) {
JournalEntry reversalJournalEntry;
final String reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId()
@@ -330,7 +330,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command) {
/** check if date of Journal entry is valid ***/
final LocalDate entryLocalDate = command.getTransactionDate();
final Date transactionDate = entryLocalDate.toDateMidnight().toDate();
final Date transactionDate = entryLocalDate.toDateTimeAtStartOfDay().toDate();
// shouldn't be in the future
final Date todaysDate = new Date();
if (transactionDate.after(todaysDate)) { throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.FUTURE_DATE,
@@ -347,7 +347,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
final SingleDebitOrCreditEntryCommand[] debits = command.getDebits();
// atleast one debit or credit must be present
if ((credits == null || credits.length <= 0) || (debits == null || debits.length <= 0)) { throw new JournalEntryInvalidException(
if (credits == null || credits.length <= 0 || debits == null || debits.length <= 0) { throw new JournalEntryInvalidException(
GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null, null, null); }
checkDebitAndCreditAmounts(credits, debits);
@@ -381,11 +381,11 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
* TODO: Need a better implementation with guaranteed uniqueness (but not a
* long UUID)...maybe something tied to system clock..
*/
private String generateTransactionId(Long officeId) {
AppUser user = this.context.authenticatedUser();
Long time = System.currentTimeMillis();
String uniqueVal = String.valueOf(time) + user.getId() + officeId;
String transactionId = Long.toHexString(Long.parseLong(uniqueVal));
private String generateTransactionId(final Long officeId) {
final AppUser user = this.context.authenticatedUser();
final Long time = System.currentTimeMillis();
final String uniqueVal = String.valueOf(time) + user.getId() + officeId;
final String transactionId = Long.toHexString(Long.parseLong(uniqueVal));
return transactionId;
}
@@ -143,7 +143,7 @@ public class CommandWrapper {
public String getTransactionId() {
return this.transactionId;
}
public Long getTemplateId() {
return this.templateId;
}
@@ -230,6 +230,10 @@ public class CommandWrapper {
return this.entityName.equalsIgnoreCase("CURRENCY");
}
public boolean isSmsResource() {
return this.entityName.equalsIgnoreCase("SMS");
}
public boolean isCodeResource() {
return this.entityName.equalsIgnoreCase("CODE");
}
@@ -265,7 +269,7 @@ public class CommandWrapper {
public boolean isUpdateRunningbalance() {
return this.actionName.equalsIgnoreCase("UPDATERUNNINGBALANCE") && this.entityName.equalsIgnoreCase("JOURNALENTRY");
}
public boolean isFundResource() {
return this.entityName.equalsIgnoreCase("FUND");
}
@@ -329,7 +333,7 @@ public class CommandWrapper {
public boolean isCollateralResource() {
return this.entityName.equalsIgnoreCase("COLLATERAL");
}
public boolean isTemplateRessource() {
return this.entityName.equalsIgnoreCase("Template");
}
@@ -588,6 +592,10 @@ public class CommandWrapper {
public boolean isHolidayResource() {
return this.entityName.equalsIgnoreCase("HOLIDAY");
}
public boolean isHolidayActivation() {
return this.entityName.equalsIgnoreCase("HOLIDAY") && this.actionName.equalsIgnoreCase("ACTIVATE") && this.entityId != null;
}
public boolean isAccountingRuleResource() {
return this.entityName.equalsIgnoreCase("ACCOUNTINGRULE");
@@ -238,6 +238,30 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder createSms() {
this.actionName = "CREATE";
this.entityName = "SMS";
this.entityId = null;
this.href = "/sms/template";
return this;
}
public CommandWrapperBuilder updateSms(final Long resourceId) {
this.actionName = "UPDATE";
this.entityName = "SMS";
this.entityId = resourceId;
this.href = "/sms/" + resourceId;
return this;
}
public CommandWrapperBuilder deleteSms(final Long resourceId) {
this.actionName = "DELETE";
this.entityName = "SMS";
this.entityId = resourceId;
this.href = "/sms/" + resourceId;
return this;
}
public CommandWrapperBuilder createCode() {
this.actionName = "CREATE";
this.entityName = "CODE";
@@ -362,7 +386,7 @@ public class CommandWrapperBuilder {
this.href = "/clientId/" + clientId + "?command=proposeTransfer";
return this;
}
public CommandWrapperBuilder proposeAndAcceptClientTransfer(final Long clientId) {
this.actionName = "PROPOSEANDACCEPTTRANSFER";
this.entityName = "CLIENT";
@@ -773,7 +797,7 @@ public class CommandWrapperBuilder {
this.href = "/journalentries/" + transactionId;
return this;
}
public CommandWrapperBuilder updateRunningBalanceForJournalEntry() {
this.actionName = "UPDATERUNNINGBALANCE";
this.entityName = "JOURNALENTRY";
@@ -1287,6 +1311,30 @@ public class CommandWrapperBuilder {
this.href = "/holidays/template";
return this;
}
public CommandWrapperBuilder activateHoliday(final Long holidayId) {
this.actionName = "ACTIVATE";
this.entityName = "HOLIDAY";
this.entityId = holidayId;
this.href = "/holidays/" + holidayId + "command=activate";
return this;
}
public CommandWrapperBuilder updateHoliday(final Long holidayId) {
this.actionName = "UPDATE";
this.entityName = "HOLIDAY";
this.entityId = holidayId;
this.href = "/holidays/" + holidayId ;
return this;
}
public CommandWrapperBuilder deleteHoliday(final Long holidayId) {
this.actionName = "DELETE";
this.entityName = "HOLIDAY";
this.entityId = holidayId;
this.href = "/holidays/" + holidayId + "command=delete";
return this;
}
public CommandWrapperBuilder assignRole(final Long groupId) {
this.actionName = "ASSIGNROLE";
@@ -254,6 +254,16 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isSmsResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createSmsCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdate()) {
handler = this.applicationContext.getBean("updateSmsCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDelete()) {
handler = this.applicationContext.getBean("deleteSmsCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isCurrencyResource()) {
handler = this.applicationContext.getBean("updateCurrencyCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isFundResource()) {
@@ -538,6 +548,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else if (wrapper.isHolidayResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createHolidayCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isHolidayActivation()) {
handler = this.applicationContext.getBean("activateHolidayCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdate()) {
handler = this.applicationContext.getBean("updateHolidayCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDelete()) {
handler = this.applicationContext.getBean("deleteHolidayCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isProductMixResource()) {
if (wrapper.isCreate()) {
@@ -28,7 +28,7 @@ import com.google.gson.reflect.TypeToken;
/**
* Immutable representation of a command.
*
*
* Wraps the provided JSON with convenience functions for extracting parameter
* values and checking for changes against an existing value.
*/
@@ -295,7 +295,7 @@ public final class JsonCommand {
public Date DateValueOfParameterNamed(final String parameterName) {
final LocalDate localDate = this.fromApiJsonHelper.extractLocalDateNamed(parameterName, this.parsedCommand);
if (localDate == null) { return null; }
return localDate.toDateMidnight().toDate();
return localDate.toDateTimeAtStartOfDay().toDate();
}
public boolean isChangeInStringParameterNamed(final String parameterName, final String existingValue) {
@@ -0,0 +1,31 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.api;
import java.util.Set;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
public class ParameterListInclusionStrategy implements ExclusionStrategy {
private final Set<String> parameterNamesToInclude;
public ParameterListInclusionStrategy(final Set<String> parameterNamesToSkip) {
this.parameterNamesToInclude = parameterNamesToSkip;
}
@Override
public boolean shouldSkipField(final FieldAttributes f) {
return !this.parameterNamesToInclude.contains(f.getName());
}
@SuppressWarnings("unused")
@Override
public boolean shouldSkipClass(final Class<?> clazz) {
return false;
}
}
@@ -80,6 +80,24 @@ public final class DefaultToApiJsonSerializer<T> implements ToApiJsonSerializer<
return serializeWithSettings(delegatedSerializer, settings, singleObject);
}
@Override
public String serialize(final ApiRequestJsonSerializationSettings settings, final Collection<T> collection) {
final Gson delegatedSerializer = findAppropriateSerializer(settings);
return serializeWithSettings(delegatedSerializer, settings, collection.toArray());
}
@Override
public String serialize(final ApiRequestJsonSerializationSettings settings, final T singleObject) {
final Gson delegatedSerializer = findAppropriateSerializer(settings);
return serializeWithSettings(delegatedSerializer, settings, singleObject);
}
@Override
public String serialize(final ApiRequestJsonSerializationSettings settings, final Page<T> singleObject) {
final Gson delegatedSerializer = findAppropriateSerializer(settings);
return serializeWithSettings(delegatedSerializer, settings, singleObject);
}
private String serializeWithSettings(final Gson gson, final ApiRequestJsonSerializationSettings settings, final Object[] dataObject) {
String json = null;
if (gson != null) {
@@ -116,4 +134,13 @@ public final class DefaultToApiJsonSerializer<T> implements ToApiJsonSerializer<
}
return gson;
}
private Gson findAppropriateSerializer(final ApiRequestJsonSerializationSettings settings) {
Gson gson = null;
if (settings.isPartialResponseRequired()) {
gson = this.helper.createGsonBuilderForPartialResponseFiltering(settings.isPrettyPrint(),
settings.getParametersForPartialResponse());
}
return gson;
}
}
@@ -16,6 +16,7 @@ import org.mifosplatform.infrastructure.core.api.JodaDateTimeAdapter;
import org.mifosplatform.infrastructure.core.api.JodaLocalDateAdapter;
import org.mifosplatform.infrastructure.core.api.JodaMonthDayAdapter;
import org.mifosplatform.infrastructure.core.api.ParameterListExclusionStrategy;
import org.mifosplatform.infrastructure.core.api.ParameterListInclusionStrategy;
import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException;
import org.springframework.stereotype.Service;
@@ -40,6 +41,20 @@ public final class GoogleGsonSerializerHelper {
return builder.create();
}
public Gson createGsonBuilderForPartialResponseFiltering(final boolean prettyPrint, final Set<String> responseParameters) {
final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
builder.registerTypeAdapter(LocalDate.class, new JodaLocalDateAdapter());
builder.registerTypeAdapter(DateTime.class, new JodaDateTimeAdapter());
builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
if (prettyPrint) {
builder.setPrettyPrinting();
}
return builder.create();
}
public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
final boolean prettyPrint, final Set<String> responseParameters) {
@@ -18,6 +18,17 @@ public interface ToApiJsonSerializer<T> {
String serializeResult(Object object);
String serialize(ApiRequestJsonSerializationSettings settings, Collection<T> collection);
String serialize(ApiRequestJsonSerializationSettings settings, T single);
String serialize(ApiRequestJsonSerializationSettings settings, Page<T> singleObject);
// TODO: TECHDEBT - bottom three will be deprecated going forward to remove
// need for people to pass full list of supported parameters. It was only
// used in cases where the partial response features was used (fields=x,y,x)
// to report error if incorrect field was passed. From now on it will just
// ignore unknown fields and fail silently
String serialize(ApiRequestJsonSerializationSettings settings, Collection<T> collection, Set<String> supportedResponseParameters);
String serialize(ApiRequestJsonSerializationSettings settings, T single, Set<String> supportedResponseParameters);
@@ -23,7 +23,7 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation
public class DateUtils {
public static Date getDateOfTenant() {
return getLocalDateOfTenant().toDateMidnight().toDate();
return getLocalDateOfTenant().toDateTimeAtStartOfDay().toDate();
}
public static LocalDate getLocalDateOfTenant() {
@@ -63,7 +63,7 @@ public class DateUtils {
final String formattedSqlDate = df.format(date);
return formattedSqlDate;
}
public static boolean isDateInTheFuture(final LocalDate localDate){
return localDate.isAfter(getLocalDateOfTenant());
}
@@ -34,6 +34,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Path("/jobs")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Component
public class SchedulerJobApiResource {
@@ -59,8 +61,6 @@ public class SchedulerJobApiResource {
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAll(@Context final UriInfo uriInfo) {
final List<JobDetailData> jobDetailDatas = this.schedulerJobRunnerReadService.findAllJobDeatils();
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -69,8 +69,6 @@ public class SchedulerJobApiResource {
@GET
@Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId, @Context final UriInfo uriInfo) {
final JobDetailData jobDetailData = this.schedulerJobRunnerReadService.retrieveOne(jobId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -79,8 +77,6 @@ public class SchedulerJobApiResource {
@GET
@Path("{" + SchedulerJobApiConstants.JOB_ID + "}/" + SchedulerJobApiConstants.JOB_RUN_HISTORY)
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveHistory(@Context final UriInfo uriInfo, @PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId,
@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
@@ -94,8 +90,6 @@ public class SchedulerJobApiResource {
@POST
@Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response executeJob(@PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId,
@QueryParam(SchedulerJobApiConstants.COMMAND) final String commandParam) {
Response response = Response.status(400).build();
@@ -110,8 +104,6 @@ public class SchedulerJobApiResource {
@PUT
@Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateJobDetail(@PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId, final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
@@ -130,5 +122,4 @@ public class SchedulerJobApiResource {
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
}
}
@@ -2,15 +2,20 @@ package org.mifosplatform.infrastructure.jobs.service;
public enum JobName {
UPDATE_LOAN_SUMMARY("Update loan Summary"), UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), UPDATE_LOAN_PAID_IN_ADVANCE(
"Update Loan Paid In Advance"), APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS(
"Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), TRANSFER_FEE_CHARGE_FOR_LOANS(
"Transfer Fee For Loans From Savings"), ACCOUNTING_RUNNING_BALANCE_UPDATE("Update Accounting Running Balances"), PAY_DUE_SAVINGS_CHARGES(
"Pay Due Savings Charges");
UPDATE_LOAN_SUMMARY("Update loan Summary"), //
UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), //
UPDATE_LOAN_PAID_IN_ADVANCE("Update Loan Paid In Advance"), //
APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), //
APPLY_HOLIDAYS_TO_LOANS("Apply Holidays To Loans"), //
POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), //
TRANSFER_FEE_CHARGE_FOR_LOANS("Transfer Fee For Loans From Savings"), //
ACCOUNTING_RUNNING_BALANCE_UPDATE("Update Accounting Running Balances"), //
PAY_DUE_SAVINGS_CHARGES("Pay Due Savings Charges"), //
APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT("Apply penalty to overdue loans");
private final String name;
JobName(final String name) {
private JobName(final String name) {
this.name = name;
}
@@ -18,4 +23,4 @@ public enum JobName {
public String toString() {
return this.name;
}
}
}
@@ -38,7 +38,7 @@ import org.springframework.stereotype.Service;
* Service class to create and load batch jobs to Scheduler using
* {@link SchedulerFactoryBean} ,{@link MethodInvokingJobDetailFactoryBean} and
* {@link CronTriggerFactoryBean}
*
*
*/
@Service
public class JobRegisterServiceImpl implements JobRegisterService {
@@ -351,10 +351,6 @@ public class JobRegisterServiceImpl implements JobRegisterService {
return cronTriggerFactoryBean.getObject();
}
/**
* @param throwable
* @return
*/
private String getStackTraceAsString(final Throwable throwable) {
final StackTraceElement[] stackTraceElements = throwable.getStackTrace();
final StringBuffer sb = new StringBuffer(throwable.toString());
@@ -13,5 +13,5 @@ public interface PlatformSecurityContext {
void validateAccessRights(String resourceOfficeHierarchy);
String OfficeHierarchy();
String officeHierarchy();
}
@@ -48,7 +48,7 @@ public class SpringSecurityPlatformSecurityContext implements PlatformSecurityCo
}
@Override
public String OfficeHierarchy() {
public String officeHierarchy() {
return authenticatedUser().getOffice().getHierarchy();
}
}
@@ -0,0 +1,35 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class SmsApiConstants {
public static final String RESOURCE_NAME = "sms";
// general
public static final String localeParamName = "locale";
public static final String dateFormatParamName = "dateFormat";
// request parameters
public static final String idParamName = "id";
public static final String groupIdParamName = "groupId";
public static final String clientIdParamName = "clientId";
public static final String staffIdParamName = "staffId";
public static final String messageParamName = "message";
// response parameters
public static final String statusParamName = "status";
public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, groupIdParamName, clientIdParamName, staffIdParamName, messageParamName));
public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(messageParamName));
}
@@ -0,0 +1,114 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.api;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.infrastructure.sms.data.SmsData;
import org.mifosplatform.infrastructure.sms.service.SmsReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/sms")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Component
@Scope("singleton")
public class SmsApiResource {
private final String resourceNameForPermissions = "SMS";
private final PlatformSecurityContext context;
private final SmsReadPlatformService readPlatformService;
private final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public SmsApiResource(final PlatformSecurityContext context, final SmsReadPlatformService readPlatformService,
final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context;
this.readPlatformService = readPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
@GET
public String retrieveAll(@Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final Collection<SmsData> smsMessages = this.readPlatformService.retrieveAll();
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, smsMessages);
}
@POST
public String create(final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createSms().withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@GET
@Path("{resourceId}")
public String retrieveOne(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
final SmsData smsMessage = this.readPlatformService.retrieveOne(resourceId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, smsMessage);
}
@PUT
@Path("{resourceId}")
public String update(@PathParam("resourceId") final Long resourceId, final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSms(resourceId).withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@DELETE
@Path("{resourceId}")
public String delete(@PathParam("resourceId") final Long resourceId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSms(resourceId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
}
@@ -0,0 +1,45 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.data;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
/**
* Immutable data object representing a SMS message.
*/
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;
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);
}
private SmsData(final Long id, final Long groupId, final Long clientId, final Long staffId, final EnumOptionData status,
final String mobileNo, final String message) {
this.id = id;
this.groupId = groupId;
this.clientId = clientId;
this.staffId = staffId;
this.status = status;
this.mobileNo = mobileNo;
this.message = message;
}
}
@@ -0,0 +1,124 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.data;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.sms.SmsApiConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
@Component
public final class SmsDataValidator {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public SmsDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsApiConstants.CREATE_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SmsApiConstants.RESOURCE_NAME);
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element);
baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).value(groupId).notNull().integerGreaterThanZero();
// ensure clientId and staffId are not passed
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).failWithCode("cannot.be.passed.with.groupId");
}
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).failWithCode("cannot.be.passed.with.groupId");
}
} else if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element);
baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
// ensure groupId and staffId are not passed
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).failWithCode("cannot.be.passed.with.clientId");
}
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).failWithCode("cannot.be.passed.with.clientId");
}
} else if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
final Long staffId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.staffIdParamName, element);
baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName).value(staffId).ignoreIfNull().longGreaterThanZero();
// ensure groupId and clientId are not passed
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
baseDataValidator.reset().parameter(SmsApiConstants.groupIdParamName).failWithCode("cannot.be.passed.with.staffId");
}
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
baseDataValidator.reset().parameter(SmsApiConstants.clientIdParamName).failWithCode("cannot.be.passed.with.staffId");
}
}
if (!this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)
&& !this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)
&& !this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
baseDataValidator.reset().parameter(SmsApiConstants.staffIdParamName)
.failWithCodeNoParameterAddedToErrorCode("no.entity.provided");
}
final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
baseDataValidator.reset().parameter(SmsApiConstants.messageParamName).value(message).notBlank().notExceedingLengthOf(1000);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForUpdate(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SmsApiConstants.UPDATE_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SmsApiConstants.RESOURCE_NAME);
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.messageParamName, element)) {
final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
baseDataValidator.reset().parameter(SmsApiConstants.messageParamName).value(message).notBlank().notExceedingLengthOf(1000);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -0,0 +1,81 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.domain;
import java.util.LinkedHashMap;
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 org.apache.commons.lang3.StringUtils;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.sms.SmsApiConstants;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.group.domain.Group;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "sms_messages_outbound")
public class SmsMessage extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "group_id", nullable = true)
private Group group;
@ManyToOne
@JoinColumn(name = "client_id", nullable = true)
private Client client;
@ManyToOne
@JoinColumn(name = "staff_id", nullable = true)
private Staff staff;
@Column(name = "status_enum", nullable = false)
private Integer statusType;
@Column(name = "mobile_no", nullable = false, length=50)
private String mobileNo;
@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);
}
protected SmsMessage() {
//
}
private SmsMessage(final Group group, final Client client, final Staff staff, final SmsMessageStatusType statusType,
final String message, final String mobileNo) {
this.group = group;
this.client = client;
this.staff = staff;
this.statusType = statusType.getValue();
this.mobileNo = mobileNo;
this.message = message;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(1);
if (command.isChangeInStringParameterNamed(SmsApiConstants.messageParamName, this.message)) {
final String newValue = command.stringValueOfParameterNamed(SmsApiConstants.messageParamName);
actualChanges.put(SmsApiConstants.messageParamName, newValue);
this.message = StringUtils.defaultIfEmpty(newValue, null);
}
return actualChanges;
}
}
@@ -0,0 +1,81 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.domain;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.sms.SmsApiConstants;
import org.mifosplatform.infrastructure.sms.exception.SmsNotFoundException;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
@Component
public class SmsMessageAssembler {
private final SmsMessageRepository smsMessageRepository;
private final GroupRepositoryWrapper groupRepository;
private final ClientRepositoryWrapper clientRepository;
private final StaffRepositoryWrapper staffRepository;
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public SmsMessageAssembler(final SmsMessageRepository smsMessageRepository,
final GroupRepositoryWrapper groupRepositoryWrapper,
final ClientRepositoryWrapper clientRepository,
final StaffRepositoryWrapper staffRepository,
final FromJsonHelper fromApiJsonHelper) {
this.smsMessageRepository = smsMessageRepository;
this.groupRepository = groupRepositoryWrapper;
this.clientRepository = clientRepository;
this.staffRepository = staffRepository;
this.fromApiJsonHelper = fromApiJsonHelper;
}
public SmsMessage assembleFromJson(final JsonCommand command) {
final JsonElement element = command.parsedJson();
String mobileNo = null;
Group group = null;
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element);
group = this.groupRepository.findOneWithNotFoundDetection(groupId);
}
Client client = null;
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element);
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
mobileNo = client.mobileNo();
}
Staff staff = null;
if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.staffIdParamName, element)) {
final Long staffId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.staffIdParamName, element);
staff = this.staffRepository.findOneWithNotFoundDetection(staffId);
mobileNo = staff.mobileNo();
}
final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
return SmsMessage.pendingSms(group, client, staff, message, mobileNo);
}
public SmsMessage assembleFromResourceId(final Long resourceId) {
final SmsMessage sms = this.smsMessageRepository.findOne(resourceId);
if (sms == null) { throw new SmsNotFoundException(resourceId); }
return sms;
}
}
@@ -0,0 +1,40 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.domain;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
public class SmsMessageEnumerations {
public static EnumOptionData status(final Integer statusId) {
return status(SmsMessageStatusType.fromInt(statusId));
}
public static EnumOptionData status(final SmsMessageStatusType status) {
EnumOptionData optionData = new EnumOptionData(SmsMessageStatusType.INVALID.getValue().longValue(), SmsMessageStatusType.INVALID.getCode(),
"Invalid");
switch (status) {
case INVALID:
optionData = new EnumOptionData(SmsMessageStatusType.INVALID.getValue().longValue(), SmsMessageStatusType.INVALID.getCode(), "Invalid");
break;
case PENDING:
optionData = new EnumOptionData(SmsMessageStatusType.PENDING.getValue().longValue(), SmsMessageStatusType.PENDING.getCode(), "Pending");
break;
case SENT:
optionData = new EnumOptionData(SmsMessageStatusType.SENT.getValue().longValue(), SmsMessageStatusType.SENT.getCode(), "Sent");
break;
case DELIVERED:
optionData = new EnumOptionData(SmsMessageStatusType.DELIVERED.getValue().longValue(), SmsMessageStatusType.DELIVERED.getCode(), "Delivered");
break;
case FAILED:
optionData = new EnumOptionData(SmsMessageStatusType.FAILED.getValue().longValue(), SmsMessageStatusType.FAILED.getCode(), "Failed");
break;
}
return optionData;
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.domain;
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
}
@@ -0,0 +1,46 @@
package org.mifosplatform.infrastructure.sms.domain;
public enum SmsMessageStatusType {
INVALID(0, "smsMessageStatusType.invalid"), //
PENDING(100, "smsMessageStatusType.pending"), //
SENT(200, "smsMessageStatusType.sent"), //
DELIVERED(300, "smsMessageStatusType.delivered"), //
FAILED(400, "smsMessageStatusType.failed");
private final Integer value;
private final String code;
public static SmsMessageStatusType fromInt(final Integer statusValue) {
SmsMessageStatusType enumeration = SmsMessageStatusType.INVALID;
switch (statusValue) {
case 100:
enumeration = SmsMessageStatusType.PENDING;
break;
case 200:
enumeration = SmsMessageStatusType.SENT;
break;
case 300:
enumeration = SmsMessageStatusType.DELIVERED;
break;
case 400:
enumeration = SmsMessageStatusType.FAILED;
break;
}
return enumeration;
}
private SmsMessageStatusType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
}
@@ -0,0 +1,18 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when a code is not found.
*/
public class SmsNotFoundException extends AbstractPlatformResourceNotFoundException {
public SmsNotFoundException(final Long resourceId) {
super("error.msg.sms.identifier.not.found", "SMS with identifier `" + resourceId + "` does not exist", resourceId);
}
}
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.sms.service.SmsWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CreateSmsCommandHandler implements NewCommandSourceHandler {
private final SmsWritePlatformService writePlatformService;
@Autowired
public CreateSmsCommandHandler(final SmsWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.create(command);
}
}
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.sms.service.SmsWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DeleteSmsCommandHandler implements NewCommandSourceHandler {
private final SmsWritePlatformService writePlatformService;
@Autowired
public DeleteSmsCommandHandler(final SmsWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.delete(command.entityId());
}
}
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.sms.service.SmsWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UpdateSmsCommandHandler implements NewCommandSourceHandler {
private final SmsWritePlatformService writePlatformService;
@Autowired
public UpdateSmsCommandHandler(final SmsWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.update(command.entityId(), command);
}
}
@@ -0,0 +1,17 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.service;
import java.util.Collection;
import org.mifosplatform.infrastructure.sms.data.SmsData;
public interface SmsReadPlatformService {
Collection<SmsData> retrieveAll();
SmsData retrieveOne(Long resourceId);
}
@@ -0,0 +1,94 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.sms.data.SmsData;
import org.mifosplatform.infrastructure.sms.domain.SmsMessageEnumerations;
import org.mifosplatform.infrastructure.sms.exception.SmsNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final SmsMapper smsRowMapper;
@Autowired
public SmsReadPlatformServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.smsRowMapper = new SmsMapper();
}
private static final class SmsMapper implements RowMapper<SmsData> {
final String schema;
public SmsMapper() {
final StringBuilder sql = new StringBuilder(300);
sql.append("smo.id as id, ");
sql.append("smo.group_id as groupId, ");
sql.append("smo.client_id as clientId, ");
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");
this.schema = sql.toString();
}
public String schema() {
return this.schema;
}
@Override
public SmsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
final Long groupId = JdbcSupport.getLong(rs, "groupId");
final Long clientId = JdbcSupport.getLong(rs, "clientId");
final Long staffId = JdbcSupport.getLong(rs, "staffId");
final String mobileNo = rs.getString("mobileNo");
final String message = rs.getString("message");
final Integer statusId = JdbcSupport.getInteger(rs, "statusId");
final EnumOptionData status = SmsMessageEnumerations.status(statusId);
return SmsData.instance(id, groupId, clientId, staffId, status, mobileNo, message);
}
}
@Override
public Collection<SmsData> retrieveAll() {
final String sql = "select " + this.smsRowMapper.schema();
return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {});
}
@Override
public SmsData retrieveOne(final Long resourceId) {
try {
final String sql = "select " + this.smsRowMapper.schema() + " where smo.id = ?";
return this.jdbcTemplate.queryForObject(sql, this.smsRowMapper, new Object[] { resourceId });
} catch (final EmptyResultDataAccessException e) {
throw new SmsNotFoundException(resourceId);
}
}
}
@@ -0,0 +1,18 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface SmsWritePlatformService {
CommandProcessingResult create(JsonCommand command);
CommandProcessingResult update(Long resourceId, JsonCommand command);
CommandProcessingResult delete(Long resourceId);
}
@@ -0,0 +1,119 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.sms.service;
import java.util.Map;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.sms.data.SmsDataValidator;
import org.mifosplatform.infrastructure.sms.domain.SmsMessage;
import org.mifosplatform.infrastructure.sms.domain.SmsMessageAssembler;
import org.mifosplatform.infrastructure.sms.domain.SmsMessageRepository;
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;
@Service
public class SmsWritePlatformServiceJpaRepositoryImpl implements SmsWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(SmsWritePlatformServiceJpaRepositoryImpl.class);
private final SmsMessageAssembler assembler;
private final SmsMessageRepository repository;
private final SmsDataValidator validator;
@Autowired
public SmsWritePlatformServiceJpaRepositoryImpl(final SmsMessageAssembler assembler, final SmsMessageRepository repository,
final SmsDataValidator validator) {
this.assembler = assembler;
this.repository = repository;
this.validator = validator;
}
@Transactional
@Override
public CommandProcessingResult create(final JsonCommand command) {
try {
this.validator.validateForCreate(command.json());
final SmsMessage message = this.assembler.assembleFromJson(command);
// TODO: at this point we also want to fire off request using third party service to send SMS.
// TODO: decision to be made on wheter we 'wait' for response or use 'future/promise' to capture response and update the SmsMessage table
this.repository.save(message);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(message.getId()) //
.build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve);
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult update(final Long resourceId, final JsonCommand command) {
try {
this.validator.validateForUpdate(command.json());
final SmsMessage message = this.assembler.assembleFromResourceId(resourceId);
final Map<String, Object> changes = message.update(command);
if (!changes.isEmpty()) {
this.repository.save(message);
}
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) {
try {
final SmsMessage message = this.assembler.assembleFromResourceId(resourceId);
this.repository.delete(message);
this.repository.flush();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(null, dve);
return CommandProcessingResult.empty();
}
return new CommandProcessingResultBuilder().withEntityId(resourceId).build();
}
/*
* Guaranteed to throw an exception no matter what the data integrity issue
* is.
*/
private void handleDataIntegrityIssues(@SuppressWarnings("unused") final JsonCommand command, final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("mobile_no")) {
throw new PlatformDataIntegrityException("error.msg.sms.no.mobile.no.exists", "The group, client or staff provided has no mobile no.", "id");
}
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.sms.unknown.data.integrity.issue",
"Unknown data integrity issue with resource: " + realCause.getMessage());
}
}
@@ -13,18 +13,19 @@ public class HolidayApiConstants {
public static final String dateFormatParamName = "dateFormat";
// request parameters
public static final String name = "name";
public static final String fromDate = "fromDate";
public static final String toDate = "toDate";
public static final String description = "description";
public static final String offices = "offices";
public static final String officeId = "officeId";
public static final String repaymentsRescheduledTo = "repaymentsRescheduledTo";
public static final String idParamName = "id";
public static final String nameParamName = "name";
public static final String fromDateParamName = "fromDate";
public static final String toDateParamName = "toDate";
public static final String descriptionParamName = "description";
public static final String officesParamName = "offices";
public static final String officeIdParamName = "officeId";
public static final String repaymentsRescheduledToParamName = "repaymentsRescheduledTo";
public static final String processed = "processed";
public static final Set<String> HOLIDAY_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, name, fromDate, toDate, description, offices, repaymentsRescheduledTo));
public static final Set<String> HOLIDAY_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, name, fromDate, toDate, repaymentsRescheduledTo));
public static final Set<String> HOLIDAY_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, nameParamName, fromDateParamName, toDateParamName, descriptionParamName, officesParamName, repaymentsRescheduledToParamName));
public static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, nameParamName,
fromDateParamName, descriptionParamName, toDateParamName, repaymentsRescheduledToParamName, localeParamName, dateFormatParamName));
}
@@ -1,24 +1,32 @@
package org.mifosplatform.organisation.holiday.api;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.HOLIDAY_RESOURCE_NAME;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.HOLIDAY_RESPONSE_DATA_PARAMETERS;
import java.util.Collection;
import java.util.Date;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.accounting.journalentry.api.DateParam;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
@@ -33,8 +41,6 @@ import org.springframework.stereotype.Component;
@Scope("singleton")
public class HolidaysApiResource {
private final String resourceNameForPermissions = "HOLIDAY";
private final DefaultToApiJsonSerializer<HolidayData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final PlatformSecurityContext context;
@@ -64,15 +70,84 @@ public class HolidaysApiResource {
return this.toApiJsonSerializer.serialize(result);
}
@POST
@Path("{holidayId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String handleCommands(@PathParam("holidayId") final Long holidayId, @QueryParam("command") final String commandParam,
final String apiRequestBodyAsJson) {
String jsonApiRequest = apiRequestBodyAsJson;
if (StringUtils.isBlank(jsonApiRequest)) {
jsonApiRequest = "{}";
}
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
CommandProcessingResult result = null;
if (is(commandParam, "activate")) {
final CommandWrapper commandRequest = builder.activateHoliday(holidayId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate" }); }
return this.toApiJsonSerializer.serialize(result);
}
@GET
@Path("{holidayId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam("holidayId") final Long holidayId, @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
final HolidayData holidayData = this.holidayReadPlatformService.retrieveHoliday(holidayId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, holidayData, HOLIDAY_RESPONSE_DATA_PARAMETERS);
}
@PUT
@Path("{holidayId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String update(@PathParam("holidayId") final Long holidayId, final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateHoliday(holidayId)
.withJson(jsonRequestBody).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@DELETE
@Path("{holidayId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String delete(@PathParam("holidayId") final Long holidayId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteHoliday(holidayId).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);
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllLoanProducts(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
public String retrieveAllHolidays(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
@QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam,
@QueryParam("locale") final String locale, @QueryParam("dateFormat") final String dateFormat) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
Date fromDate = null;
if (fromDateParam != null) {
@@ -87,7 +162,6 @@ public class HolidaysApiResource {
toDate);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, holidays, HolidayApiConstants.HOLIDAY_REQUEST_DATA_PARAMETERS);
return this.toApiJsonSerializer.serialize(settings, holidays, HOLIDAY_RESPONSE_DATA_PARAMETERS);
}
}
@@ -13,17 +13,17 @@ public class HolidayData {
@SuppressWarnings("unused")
private final LocalDate toDate;
@SuppressWarnings("unused")
private final LocalDate repaymentsScheduleTO;
private final LocalDate repaymentsRescheduledTo;
@SuppressWarnings("unused")
private final Long officeId;
public HolidayData(final Long id, final String name, final LocalDate fromDate, final LocalDate toDate,
final LocalDate repaymentsScheduleTO) {
final LocalDate repaymentsRescheduledTo) {
this.id = id;
this.name = name;
this.fromDate = fromDate;
this.toDate = toDate;
this.repaymentsScheduleTO = repaymentsScheduleTO;
this.repaymentsRescheduledTo = repaymentsRescheduledTo;
this.officeId = null;
}
@@ -38,7 +38,7 @@ public class HolidayDataValidator {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, HolidayApiConstants.HOLIDAY_CREATE_REQUEST_DATA_PARAMETERS);
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, HolidayApiConstants.HOLIDAY_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -46,38 +46,94 @@ public class HolidayDataValidator {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(HolidayApiConstants.HOLIDAY_RESOURCE_NAME);
final String name = this.fromApiJsonHelper.extractStringNamed(HolidayApiConstants.name, element);
baseDataValidator.reset().parameter(HolidayApiConstants.name).value(name).notNull().notExceedingLengthOf(100);
final String name = this.fromApiJsonHelper.extractStringNamed(HolidayApiConstants.nameParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.fromDate, element);
baseDataValidator.reset().parameter(HolidayApiConstants.fromDate).value(fromDate).notNull();
final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.fromDateParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.fromDateParamName).value(fromDate).notNull();
final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDate, element);
baseDataValidator.reset().parameter(HolidayApiConstants.toDate).value(toDate).notNull();
final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull();
final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.repaymentsRescheduledTo,
final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.repaymentsRescheduledToParamName,
element);
baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledTo).value(repaymentsRescheduledTo).notNull();
baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
Set<Long> offices = null;
final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (topLevelJsonElement.has(HolidayApiConstants.offices) && topLevelJsonElement.get(HolidayApiConstants.offices).isJsonArray()) {
if (topLevelJsonElement.has(HolidayApiConstants.officesParamName) && topLevelJsonElement.get(HolidayApiConstants.officesParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.offices).getAsJsonArray();
final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.officesParamName).getAsJsonArray();
if (array.size() > 0) {
offices = new HashSet<Long>(array.size());
for (int i = 0; i < array.size(); i++) {
final JsonObject officeElement = array.get(i).getAsJsonObject();
final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeId, officeElement);
baseDataValidator.reset().parameter(HolidayApiConstants.offices).value(officeId).notNull();
final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeIdParamName, officeElement);
baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(officeId).notNull();
offices.add(officeId);
}
}
}
baseDataValidator.reset().parameter(HolidayApiConstants.offices).value(offices).notNull();
baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(offices).notNull();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForUpdate(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
HolidayApiConstants.HOLIDAY_CREATE_OR_UPDATE_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(HolidayApiConstants.HOLIDAY_RESOURCE_NAME);
if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.nameParamName, element)) {
final String name = this.fromApiJsonHelper.extractStringNamed(HolidayApiConstants.nameParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.nameParamName).value(name).notNull().notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.fromDateParamName, element)) {
final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.fromDateParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.fromDateParamName).value(fromDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.toDateParamName, element)) {
final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.repaymentsRescheduledToParamName, element)) {
final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
HolidayApiConstants.repaymentsRescheduledToParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
}
Set<Long> offices = null;
final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (this.fromApiJsonHelper.parameterExists(HolidayApiConstants.officesParamName, element)) {
if (topLevelJsonElement.has(HolidayApiConstants.officesParamName) && topLevelJsonElement.get(HolidayApiConstants.officesParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.officesParamName).getAsJsonArray();
if (array.size() > 0) {
offices = new HashSet<Long>(array.size());
for (int i = 0; i < array.size(); i++) {
final JsonObject officeElement = array.get(i).getAsJsonObject();
final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeIdParamName, officeElement);
baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(officeId).notNull();
offices.add(officeId);
}
}
}
baseDataValidator.reset().parameter(HolidayApiConstants.officesParamName).value(offices).notNull();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
@@ -5,7 +5,21 @@
*/
package org.mifosplatform.organisation.holiday.domain;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.descriptionParamName;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.fromDateParamName;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.nameParamName;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.officesParamName;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.repaymentsRescheduledToParamName;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.toDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Column;
@@ -22,10 +36,15 @@ import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.organisation.holiday.api.HolidayApiConstants;
import org.mifosplatform.organisation.office.domain.Office;
import org.springframework.data.jpa.domain.AbstractPersistable;
import com.google.gson.JsonArray;
@Entity
@Table(name = "m_holiday", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "holiday_name") })
public class Holiday extends AbstractPersistable<Long> {
@@ -45,6 +64,9 @@ public class Holiday extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE)
private Date repaymentsRescheduledTo;
@Column(name = "status_enum", nullable = false)
private Integer status;
@Column(name = "processed", nullable = false)
private boolean processed;
@@ -56,18 +78,121 @@ public class Holiday extends AbstractPersistable<Long> {
private Set<Office> offices;
public static Holiday createNew(final Set<Office> offices, final JsonCommand command) {
final String name = command.stringValueOfParameterNamed(HolidayApiConstants.name);
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDate);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDate);
final LocalDate repaymentsRescheduledTo = command.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledTo);
final String name = command.stringValueOfParameterNamed(HolidayApiConstants.nameParamName);
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
final LocalDate repaymentsRescheduledTo = command.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
final Integer status = HolidayStatusType.PENDING_FOR_ACTIVATION.getValue();
final boolean processed = false;// default it to false. Only batch job
// should update this field.
final String description = command.stringValueOfParameterNamed(HolidayApiConstants.description);
return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, processed, description, offices);
final String description = command.stringValueOfParameterNamed(HolidayApiConstants.descriptionParamName);
return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, status, processed, description, offices);
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(7);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("holiday" + ".update");
final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(nameParamName);
actualChanges.put(nameParamName, newValue);
this.name = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
actualChanges.put(descriptionParamName, newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null);
}
if (currentStatus.isPendingActivation()) {
if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(fromDateParamName);
actualChanges.put(fromDateParamName, valueAsInput);
actualChanges.put(dateFormatParamName, dateFormatAsInput);
actualChanges.put(localeParamName, localeAsInput);
final LocalDate newValue = command.localDateValueOfParameterNamed(fromDateParamName);
this.fromDate = newValue.toDate();
}
if (command.isChangeInLocalDateParameterNamed(toDateParamName, getToDateLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(toDateParamName);
actualChanges.put(toDateParamName, valueAsInput);
actualChanges.put(dateFormatParamName, dateFormatAsInput);
actualChanges.put(localeParamName, localeAsInput);
final LocalDate newValue = command.localDateValueOfParameterNamed(toDateParamName);
this.toDate = newValue.toDate();
}
if (command.isChangeInLocalDateParameterNamed(repaymentsRescheduledToParamName, getRepaymentsRescheduledToLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(repaymentsRescheduledToParamName);
actualChanges.put(repaymentsRescheduledToParamName, valueAsInput);
actualChanges.put(dateFormatParamName, dateFormatAsInput);
actualChanges.put(localeParamName, localeAsInput);
final LocalDate newValue = command.localDateValueOfParameterNamed(repaymentsRescheduledToParamName);
this.repaymentsRescheduledTo = newValue.toDate();
}
if (command.hasParameter(officesParamName)) {
final JsonArray jsonArray = command.arrayOfParameterNamed(officesParamName);
if (jsonArray != null) {
actualChanges.put(officesParamName, command.jsonFragment(officesParamName));
}
}
} else {
if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) {
baseDataValidator.reset().parameter(fromDateParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (command.isChangeInLocalDateParameterNamed(toDateParamName, getToDateLocalDate())) {
baseDataValidator.reset().parameter(toDateParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (command.isChangeInLocalDateParameterNamed(repaymentsRescheduledToParamName, getRepaymentsRescheduledToLocalDate())) {
baseDataValidator.reset().parameter(repaymentsRescheduledToParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (command.hasParameter(officesParamName)) {
baseDataValidator.reset().parameter(repaymentsRescheduledToParamName).failWithCode("cannot.edit.holiday.in.active.state");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
return actualChanges;
}
public boolean update(final Set<Office> newOffices) {
if (newOffices == null) { return false; }
boolean updated = false;
if (this.offices != null) {
final Set<Office> currentSetOfOffices = new HashSet<Office>(this.offices);
final Set<Office> newSetOfOffices = new HashSet<Office>(newOffices);
if (!(currentSetOfOffices.equals(newSetOfOffices))) {
updated = true;
this.offices = newOffices;
}
} else {
updated = true;
this.offices = newOffices;
}
return updated;
}
private Holiday(final String name, final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo,
final boolean processed, final String description, final Set<Office> offices) {
final Integer status, final boolean processed, final String description, final Set<Office> offices) {
if (StringUtils.isNotBlank(name)) {
this.name = name.trim();
}
@@ -84,6 +209,7 @@ public class Holiday extends AbstractPersistable<Long> {
this.repaymentsRescheduledTo = repaymentsRescheduledTo.toDate();
}
this.status = status;
this.processed = processed;
if (StringUtils.isNotBlank(name)) {
@@ -134,4 +260,30 @@ public class Holiday extends AbstractPersistable<Long> {
public void processed() {
this.processed = true;
}
public void activate() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("holiday" + ".activate");
final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
if (!currentStatus.isPendingActivation()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.pending.for.activation.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.status = HolidayStatusType.ACTIVE.getValue();
}
public void delete() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("holiday" + ".delete");
final HolidayStatusType currentStatus = HolidayStatusType.fromInt(this.status);
if (currentStatus.isDeleted()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("already.in.deleted.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.status = HolidayStatusType.DELETED.getValue();
}
}
@@ -15,9 +15,9 @@ import org.springframework.data.repository.query.Param;
public interface HolidayRepository extends JpaRepository<Holiday, Long>, JpaSpecificationExecutor<Holiday> {
@Query("select holiday from Holiday holiday, IN(holiday.offices) office where (holiday.fromDate >= :date OR :date <= holiday.toDate) and office.id = :officeId")
List<Holiday> findByOfficeIdAndGreaterThanDate(@Param("officeId") Long officeId, @Param("date") Date date);
@Query("select holiday from Holiday holiday, IN(holiday.offices) office where (holiday.fromDate >= :date OR :date <= holiday.toDate) and holiday.status = :status and office.id = :officeId")
List<Holiday> findByOfficeIdAndGreaterThanDate(@Param("officeId") Long officeId, @Param("date") Date date, @Param("status") Integer status);
@Query("from Holiday holiday where holiday.processed = false")
List<Holiday> findUnprocessed();
@Query("from Holiday holiday where holiday.processed = false and holiday.status = :status")
List<Holiday> findUnprocessed(@Param("status") Integer status);
}
@@ -0,0 +1,60 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.organisation.holiday.domain;
import java.util.Date;
import java.util.List;
import org.mifosplatform.organisation.holiday.exception.HolidayNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* Wrapper for {@link HolidayRepository} that adds NULL checking and Error
* handling capabilities
* </p>
*/
@Service
public class HolidayRepositoryWrapper {
private final HolidayRepository repository;
@Autowired
public HolidayRepositoryWrapper(final HolidayRepository repository) {
this.repository = repository;
}
public Holiday findOneWithNotFoundDetection(final Long id) {
final Holiday holiday = this.repository.findOne(id);
if (holiday == null) { throw new HolidayNotFoundException(id); }
return holiday;
}
public void save(final Holiday holiday) {
this.repository.save(holiday);
}
public void save(final Iterable<Holiday> holidays){
this.repository.save(holidays);
}
public void saveAndFlush(final Holiday holiday) {
this.repository.saveAndFlush(holiday);
}
public void delete(final Holiday holiday) {
this.repository.delete(holiday);
}
public List<Holiday> findByOfficeIdAndGreaterThanDate(final Long officeId, final Date date) {
return this.repository.findByOfficeIdAndGreaterThanDate(officeId, date, HolidayStatusType.ACTIVE.getValue());
}
public List<Holiday> findUnprocessed() {
return this.repository.findUnprocessed(HolidayStatusType.ACTIVE.getValue());
}
}
@@ -0,0 +1,66 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.organisation.holiday.domain;
/**
* Enum representation of {@link Holiday} status states.
*/
public enum HolidayStatusType {
INVALID(0, "holidayStatusType.invalid"), //
PENDING_FOR_ACTIVATION(100, "holidayStatusType.pending.for.activation"), //
ACTIVE(300, "holidayStatusType.active"), //
DELETED(600, "savingsAccountStatusType.transfer.in.progress");
private final Integer value;
private final String code;
public static HolidayStatusType fromInt(final Integer type) {
HolidayStatusType enumeration = HolidayStatusType.INVALID;
switch (type) {
case 100:
enumeration = HolidayStatusType.PENDING_FOR_ACTIVATION;
break;
case 300:
enumeration = HolidayStatusType.ACTIVE;
break;
case 600:
enumeration = HolidayStatusType.DELETED;
break;
}
return enumeration;
}
private HolidayStatusType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final HolidayStatusType state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isPendingActivation() {
return this.value.equals(HolidayStatusType.PENDING_FOR_ACTIVATION.getValue());
}
public boolean isActive() {
return this.value.equals(HolidayStatusType.ACTIVE.getValue());
}
public boolean isDeleted() {
return this.value.equals(HolidayStatusType.DELETED.getValue());
}
}
@@ -0,0 +1,18 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.organisation.holiday.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when holiday resources are not found.
*/
public class HolidayNotFoundException extends AbstractPlatformResourceNotFoundException {
public HolidayNotFoundException(final Long id) {
super("error.msg.holiday.id.invalid", "Holiday with identifier " + id + " does not exist", id);
}
}
@@ -0,0 +1,26 @@
package org.mifosplatform.organisation.holiday.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.organisation.holiday.service.HolidayWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ActivateHolidayCommandHandler implements NewCommandSourceHandler {
private final HolidayWritePlatformService holidayWritePlatformService;
@Autowired
public ActivateHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
this.holidayWritePlatformService = holidayWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.holidayWritePlatformService.activateHoliday(command.entityId());
}
}
@@ -0,0 +1,26 @@
package org.mifosplatform.organisation.holiday.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.organisation.holiday.service.HolidayWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DeleteHolidayCommandHandler implements NewCommandSourceHandler {
private final HolidayWritePlatformService holidayWritePlatformService;
@Autowired
public DeleteHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
this.holidayWritePlatformService = holidayWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.holidayWritePlatformService.deleteHoliday(command.entityId());
}
}
@@ -0,0 +1,26 @@
package org.mifosplatform.organisation.holiday.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.organisation.holiday.service.HolidayWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UpdateHolidayCommandHandler implements NewCommandSourceHandler {
private final HolidayWritePlatformService holidayWritePlatformService;
@Autowired
public UpdateHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
this.holidayWritePlatformService = holidayWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.holidayWritePlatformService.updateHoliday(command);
}
}
@@ -0,0 +1,37 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.organisation.holiday.service;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.holiday.domain.HolidayStatusType;
public class HolidayEnumerations {
public static EnumOptionData holidayStatusType(final int id) {
return holidayStatusType(HolidayStatusType.fromInt(id));
}
public static EnumOptionData holidayStatusType(final HolidayStatusType type) {
EnumOptionData optionData = null;
switch (type) {
case INVALID:
optionData = new EnumOptionData(HolidayStatusType.INVALID.getValue().longValue(), HolidayStatusType.INVALID.getCode(),
"Invalid");
break;
case PENDING_FOR_ACTIVATION:
optionData = new EnumOptionData(HolidayStatusType.PENDING_FOR_ACTIVATION.getValue().longValue(),
HolidayStatusType.PENDING_FOR_ACTIVATION.getCode(), "Pending for activation");
break;
case ACTIVE:
optionData = new EnumOptionData(HolidayStatusType.ACTIVE.getValue().longValue(), HolidayStatusType.ACTIVE.getCode(), "Active");
break;
case DELETED:
optionData = new EnumOptionData(HolidayStatusType.DELETED.getValue().longValue(), HolidayStatusType.DELETED.getCode(), "Deleted");
break;
}
return optionData;
}
}
@@ -8,4 +8,6 @@ import org.mifosplatform.organisation.holiday.data.HolidayData;
public interface HolidayReadPlatformService {
Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, Date fromDate, Date toDate);
HolidayData retrieveHoliday(final Long holidayId);
}
@@ -13,7 +13,9 @@ import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.holiday.data.HolidayData;
import org.mifosplatform.organisation.holiday.exception.HolidayNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@@ -38,7 +40,7 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("h.id as id, h.name as name, h.from_date as fromDate, h.to_date as toDate, ");
sqlBuilder.append("h.repayments_rescheduled_to as repaymentsScheduleTO ");
sqlBuilder.append("from m_holiday h join m_holiday_office hf on h.id = hf.holiday_id and ");
sqlBuilder.append("from m_holiday h ");
this.schema = sqlBuilder.toString();
}
@@ -69,7 +71,7 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
int arrayPos = 0;
final HolidayMapper rm = new HolidayMapper();
String sql = "select " + rm.schema() + " hf.office_id = ? ";
String sql = "select " + rm.schema() + " join m_holiday_office hf on h.id = hf.holiday_id and hf.office_id = ? ";
objectArray[arrayPos] = officeId;
arrayPos = arrayPos + 1;
@@ -97,4 +99,17 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
return this.jdbcTemplate.query(sql, rm, finalObjectArray);
}
@Override
public HolidayData retrieveHoliday(Long holidayId) {
try {
final HolidayMapper rm = new HolidayMapper();
final String sql = rm.schema() + " where h.id = ?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { holidayId });
} catch (final EmptyResultDataAccessException e) {
throw new HolidayNotFoundException(holidayId);
}
}
}
@@ -8,6 +8,12 @@ public interface HolidayWritePlatformService {
CommandProcessingResult createHoliday(JsonCommand command);
CommandProcessingResult updateHoliday(JsonCommand command);
CommandProcessingResult activateHoliday(final Long holidayId);
CommandProcessingResult deleteHoliday(final Long holidayId);
boolean isHoliday(Long officeId, LocalDate transactionDate);
boolean isTransactionAllowedOnHoliday();
@@ -1,7 +1,10 @@
package org.mifosplatform.organisation.holiday.service;
import static org.mifosplatform.organisation.holiday.api.HolidayApiConstants.officesParamName;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.joda.time.LocalDate;
@@ -15,7 +18,7 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext
import org.mifosplatform.organisation.holiday.api.HolidayApiConstants;
import org.mifosplatform.organisation.holiday.data.HolidayDataValidator;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
import org.mifosplatform.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.mifosplatform.organisation.holiday.exception.HolidayDateException;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
@@ -39,7 +42,7 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
private final static Logger logger = LoggerFactory.getLogger(HolidayWritePlatformServiceJpaRepositoryImpl.class);
private final HolidayDataValidator fromApiJsonDeserializer;
private final HolidayRepository holidayRepository;
private final HolidayRepositoryWrapper holidayRepository;
private final WorkingDaysRepositoryWrapper daysRepositoryWrapper;
private final ConfigurationDomainService configurationDomainService;
private final PlatformSecurityContext context;
@@ -48,7 +51,7 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
@Autowired
public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer,
final HolidayRepository holidayRepository, final PlatformSecurityContext context, final OfficeRepository officeRepository,
final HolidayRepositoryWrapper holidayRepository, final PlatformSecurityContext context, final OfficeRepository officeRepository,
final FromJsonHelper fromApiJsonHelper, final WorkingDaysRepositoryWrapper daysRepositoryWrapper, final ConfigurationDomainService configurationDomainService) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.holidayRepository = holidayRepository;
@@ -81,17 +84,70 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult updateHoliday(final JsonCommand command) {
try {
this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForUpdate(command.json());
final Holiday holiday = this.holidayRepository.findOneWithNotFoundDetection(command.entityId());
Map<String, Object> changes = holiday.update(command);
validateInputDates(holiday.getFromDateLocalDate(), holiday.getToDateLocalDate(), holiday.getRepaymentsRescheduledToLocalDate());
if (changes.containsKey(officesParamName)) {
final Set<Office> offices = getSelectedOffices(command);
final boolean updated = holiday.update(offices);
if (!updated) {
changes.remove(officesParamName);
}
}
this.holidayRepository.saveAndFlush(holiday);
return new CommandProcessingResultBuilder().withEntityId(holiday.getId()).with(changes).build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve);
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult activateHoliday(final Long holidayId) {
this.context.authenticatedUser();
final Holiday holiday = this.holidayRepository.findOneWithNotFoundDetection(holidayId);
holiday.activate();
this.holidayRepository.saveAndFlush(holiday);
return new CommandProcessingResultBuilder()
.withEntityId(holiday.getId()).build();
}
@Transactional
@Override
public CommandProcessingResult deleteHoliday(final Long holidayId) {
this.context.authenticatedUser();
final Holiday holiday = this.holidayRepository.findOneWithNotFoundDetection(holidayId);
holiday.delete();
this.holidayRepository.saveAndFlush(holiday);
return new CommandProcessingResultBuilder()
.withEntityId(holidayId).build();
}
private Set<Office> getSelectedOffices(final JsonCommand command) {
Set<Office> offices = null;
final JsonObject topLevelJsonElement = this.fromApiJsonHelper.parse(command.json()).getAsJsonObject();
if (topLevelJsonElement.has(HolidayApiConstants.offices) && topLevelJsonElement.get(HolidayApiConstants.offices).isJsonArray()) {
if (topLevelJsonElement.has(HolidayApiConstants.officesParamName) && topLevelJsonElement.get(HolidayApiConstants.officesParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.offices).getAsJsonArray();
final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.officesParamName).getAsJsonArray();
offices = new HashSet<Office>(array.size());
for (int i = 0; i < array.size(); i++) {
final JsonObject officeElement = array.get(i).getAsJsonObject();
final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeId, officeElement);
final Long officeId = this.fromApiJsonHelper.extractLongNamed(HolidayApiConstants.officeIdParamName, officeElement);
final Office office = this.officeRepository.findOne(officeId);
if (office == null) { throw new OfficeNotFoundException(officeId); }
offices.add(office);
@@ -114,9 +170,13 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
}
private void validateInputDates(final JsonCommand command) {
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDate);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDate);
final LocalDate repaymentsRescheduledTo = command.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledTo);
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
final LocalDate repaymentsRescheduledTo = command.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo);
}
private void validateInputDates(final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo) {
String defaultUserMessage = "";
if (toDate.isBefore(fromDate)) {
@@ -76,7 +76,7 @@ public class Office extends AbstractPersistable<Long> {
private Office(final Office parent, final String name, final LocalDate openingDate, final String externalId) {
this.parent = parent;
this.openingDate = openingDate.toDateMidnight().toDate();
this.openingDate = openingDate.toDateTimeAtStartOfDay().toDate();
if (parent != null) {
this.parent.addChild(this);
}
@@ -49,7 +49,7 @@ public class StaffApiResource {
* {@link StaffData}.
*/
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "firstname", "lastname", "displayName",
"officeId", "officeName", "loanOfficerFlag", "externalId", "allowedOffices"));
"officeId", "officeName", "loanOfficerFlag", "externalId", "mobileNo", "allowedOffices"));
private final String resourceNameForPermissions = "STAFF";
@@ -15,33 +15,37 @@ import org.mifosplatform.organisation.office.data.OfficeData;
public class StaffData {
private final Long id;
private final String externalId;
private final String firstname;
private final String lastname;
private final String displayName;
private final String mobileNo;
private final Long officeId;
private final String officeName;
private final Boolean isLoanOfficer;
private final String externalId;
@SuppressWarnings("unused")
private final Collection<OfficeData> allowedOffices;
public static StaffData templateData(final StaffData staff, final Collection<OfficeData> allowedOffices) {
return new StaffData(staff.id, staff.firstname, staff.lastname, staff.displayName, staff.officeId, staff.officeName,
staff.isLoanOfficer, staff.externalId, allowedOffices);
staff.isLoanOfficer, staff.externalId, staff.mobileNo, allowedOffices);
}
public static StaffData lookup(final Long id, final String displayName) {
return new StaffData(id, null, null, displayName, null, null, null, null, null);
return new StaffData(id, null, null, displayName, null, null, null, null, null, null);
}
public static StaffData instance(final Long id, final String firstname, final String lastname, final String displayName,
final Long officeId, final String officeName, final Boolean isLoanOfficer, final String externalId) {
return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, null);
final Long officeId, final String officeName, final Boolean isLoanOfficer, final String externalId, final String mobileNo) {
return new StaffData(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, mobileNo, null);
}
private StaffData(final Long id, final String firstname, final String lastname, final String displayName, final Long officeId,
final String officeName, final Boolean isLoanOfficer, final String externalId, final Collection<OfficeData> allowedOffices) {
final String officeName, final Boolean isLoanOfficer, final String externalId, final String mobileNo,
final Collection<OfficeData> allowedOffices) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
@@ -50,6 +54,7 @@ public class StaffData {
this.isLoanOfficer = isLoanOfficer;
this.externalId = externalId;
this.officeId = officeId;
this.mobileNo = mobileNo;
this.allowedOffices = allowedOffices;
}
@@ -23,7 +23,8 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_staff", uniqueConstraints = { @UniqueConstraint(columnNames = { "display_name" }, name = "display_name"),
@UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE") })
@UniqueConstraint(columnNames = { "external_id" }, name = "external_id_UNIQUE"),
@UniqueConstraint(columnNames = { "mobile_no" }, name = "mobile_no_UNIQUE") })
public class Staff extends AbstractPersistable<Long> {
@Column(name = "firstname", length = 50)
@@ -35,15 +36,16 @@ public class Staff extends AbstractPersistable<Long> {
@Column(name = "display_name", length = 100)
private String displayName;
@Column(name = "mobile_no", length = 50, nullable = false, unique = true)
private String mobileNo;
@Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId;
// Office to which this employee belongs
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
private Office office;
// Flag determines if employee is a loan Officer
@Column(name = "is_loan_officer ", nullable = false)
private boolean loanOfficer;
@@ -65,27 +67,26 @@ public class Staff extends AbstractPersistable<Long> {
final String externalIdParamName = "externalId";
final String externalId = command.stringValueOfParameterNamedAllowingNull(externalIdParamName);
final String mobileNoParamName = "mobileNo";
final String mobileNo = command.stringValueOfParameterNamedAllowingNull(mobileNoParamName);
final String isLoanOfficerParamName = "isLoanOfficer";
final boolean isLoanOfficer = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
return new Staff(staffOffice, firstname, lastname, externalId, isLoanOfficer);
}
public static Staff createNew(final Office staffOffice, final String firstname, final String lastname, final String externalId,
final boolean isLoanOfficer) {
return new Staff(staffOffice, firstname, lastname, externalId, isLoanOfficer);
return new Staff(staffOffice, firstname, lastname, externalId, mobileNo, isLoanOfficer);
}
protected Staff() {
//
}
private Staff(final Office staffOffice, final String firstname, final String lastname, final String externalId,
private Staff(final Office staffOffice, final String firstname, final String lastname, final String externalId, final String mobileNo,
final boolean isLoanOfficer) {
this.office = staffOffice;
this.firstname = StringUtils.defaultIfEmpty(firstname, null);
this.lastname = StringUtils.defaultIfEmpty(lastname, null);
this.externalId = externalId;
this.externalId = StringUtils.defaultIfEmpty(externalId, null);
this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
this.loanOfficer = isLoanOfficer;
deriveDisplayName(firstname);
}
@@ -141,6 +142,13 @@ public class Staff extends AbstractPersistable<Long> {
this.externalId = newValue;
}
final String mobileNoParamName = "mobileNo";
if (command.isChangeInStringParameterNamed(mobileNoParamName, this.mobileNo)) {
final String newValue = command.stringValueOfParameterNamed(mobileNoParamName);
actualChanges.put(mobileNoParamName, newValue);
this.mobileNo = StringUtils.defaultIfEmpty(newValue, null);
}
final String isLoanOfficerParamName = "isLoanOfficer";
if (command.isChangeInBooleanParameterNamed(isLoanOfficerParamName, this.loanOfficer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isLoanOfficerParamName);
@@ -179,6 +187,10 @@ public class Staff extends AbstractPersistable<Long> {
return this.displayName;
}
public String mobileNo() {
return this.mobileNo;
}
public Office office() {
return this.office;
}
@@ -19,6 +19,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.client.api.ClientApiConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -32,7 +33,7 @@ public final class StaffCommandFromApiJsonDeserializer {
* The parameters supported for this command.
*/
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("firstname", "lastname", "officeId", "externalId",
"isLoanOfficer"));
"mobileNo", "isLoanOfficer"));
private final FromJsonHelper fromApiJsonHelper;
@@ -61,6 +62,12 @@ public final class StaffCommandFromApiJsonDeserializer {
final String lastname = this.fromApiJsonHelper.extractStringNamed("lastname", element);
baseDataValidator.reset().parameter("lastname").value(lastname).notBlank().notExceedingLengthOf(50);
if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).ignoreIfNull()
.notExceedingLengthOf(50);
}
if (this.fromApiJsonHelper.parameterExists("isLoanOfficer", element)) {
final Boolean loanOfficerFlag = this.fromApiJsonHelper.extractBooleanNamed("isLoanOfficer", element);
baseDataValidator.reset().parameter("isLoanOfficer").value(loanOfficerFlag).notNull();
@@ -94,6 +101,11 @@ public final class StaffCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("lastname").value(lastname).notBlank().notExceedingLengthOf(50);
}
if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).notExceedingLengthOf(50);
}
if (this.fromApiJsonHelper.parameterExists("isLoanOfficer", element)) {
final Boolean loanOfficerFlag = this.fromApiJsonHelper.extractBooleanNamed("isLoanOfficer", element);
baseDataValidator.reset().parameter("isLoanOfficer").value(loanOfficerFlag).notNull();
@@ -38,7 +38,7 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
public String schema() {
return " s.id as id,s.office_id as officeId, o.name as officeName, s.firstname as firstname, s.lastname as lastname,"
+ " s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId from m_staff s "
+ " s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId, s.mobile_no as mobileNo from m_staff s "
+ " join m_office o on o.id = s.office_id";
}
@@ -53,8 +53,9 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
final String officeName = rs.getString("officeName");
final String externalId = rs.getString("externalId");
final String mobileNo = rs.getString("mobileNo");
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId);
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, mobileNo);
}
}
@@ -65,7 +66,8 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("s.id as id, s.office_id as officeId, ohierarchy.name as officeName,");
sqlBuilder.append("s.firstname as firstname, s.lastname as lastname,");
sqlBuilder.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId ");
sqlBuilder
.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer, s.external_id as externalId, s.mobile_no as mobileNo ");
sqlBuilder.append("from m_office o ");
sqlBuilder.append("join m_office ohierarchy on o.hierarchy like concat(ohierarchy.hierarchy, '%') ");
sqlBuilder.append("join m_staff s on s.office_id = ohierarchy.id ");
@@ -90,8 +92,9 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
final String officeName = rs.getString("officeName");
final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer");
final String externalId = rs.getString("externalId");
final String mobileNo = rs.getString("mobileNo");
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId);
return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer, externalId, mobileNo);
}
}
@@ -149,8 +152,6 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService {
public StaffData retrieveStaff(final Long staffId) {
try {
this.context.authenticatedUser();
final StaffMapper rm = new StaffMapper();
final String sql = "select " + rm.schema() + " where s.id = ?";
@@ -12,7 +12,6 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
@@ -32,16 +31,13 @@ public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePla
private final static Logger logger = LoggerFactory.getLogger(StaffWritePlatformServiceJpaRepositoryImpl.class);
private final PlatformSecurityContext context;
private final StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final StaffRepository staffRepository;
private final OfficeRepository officeRepository;
@Autowired
public StaffWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer, final StaffRepository staffRepository,
final OfficeRepository officeRepository) {
this.context = context;
public StaffWritePlatformServiceJpaRepositoryImpl(final StaffCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final StaffRepository staffRepository, final OfficeRepository officeRepository) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.staffRepository = staffRepository;
this.officeRepository = officeRepository;
@@ -52,8 +48,6 @@ public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePla
public CommandProcessingResult createStaff(final JsonCommand command) {
try {
this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForCreate(command.json());
final Long officeId = command.longValueOfParameterNamed("officeId");
@@ -80,8 +74,6 @@ public class StaffWritePlatformServiceJpaRepositoryImpl implements StaffWritePla
public CommandProcessingResult updateStaff(final Long staffId, final JsonCommand command) {
try {
this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForUpdate(command.json());
final Staff staffForUpdate = this.staffRepository.findOne(staffId);
@@ -23,11 +23,12 @@ public class AccountTransferDTO {
private final Integer fromTransferType;
private final Integer toTransferType;
private final Long chargeId;
private final Integer loanInstallmentNumber;
public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount,
final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId,
final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt,
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId) {
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId, Integer loanInstallmentNumber) {
this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount;
this.fromAccountType = fromAccountType;
@@ -41,6 +42,7 @@ public class AccountTransferDTO {
this.fromTransferType = fromTransferType;
this.toTransferType = toTransferType;
this.chargeId = chargeId;
this.loanInstallmentNumber = loanInstallmentNumber;
}
public LocalDate getTransactionDate() {
@@ -95,4 +97,9 @@ public class AccountTransferDTO {
return this.chargeId;
}
public Integer getLoanInstallmentNumber() {
return this.loanInstallmentNumber;
}
}
@@ -229,7 +229,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeChargePayment(toLoanAccount,
accountTransferDTO.getChargeId(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType());
accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType(), accountTransferDTO.getLoanInstallmentNumber());
final AccountTransfer transferTransaction = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO,
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
@@ -10,11 +10,12 @@ import java.util.Set;
public class CalendarConstants {
public static final String CALENDAR_RESOURCE_NAME = "calendar";
public static enum CALENDAR_SUPPORTED_PARAMETERS {
CALENDAR_ID("id"), ENTITY_TYPE("entityType"), ENTITY_ID("entityId"), TITLE("title"), DESCRIPTION("description"), LOCATION(
"location"), START_DATE("startDate"), END_DATE("endDate"), CREATED_DATE("createdDate"), DURATION("duration"), TYPE_ID(
"typeId"), REPEATING("repeating"), REMIND_BY_ID("remindById"), FIRST_REMINDER("firstReminder"), SECOND_REMINDER(
"secondReminder"), LOCALE("locale"), DATE_FORMAT("dateFormat"), REPEATS("repeats"), REPEATS_EVERY("repeatsEvery"), REPEATS_ON_DAY(
"secondReminder"), LOCALE("locale"), DATE_FORMAT("dateFormat"), FREQUENCY("frequency"), INTERVAL("interval"), REPEATS_ON_DAY(
"repeatsOnDay");
private final String value;
@@ -26,6 +26,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -54,10 +55,12 @@ public class CalendarsApiResource {
/**
* The set of parameters that are supported in response for {@link Calendar}
*/
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "entityId", "entityType", "title",
"description", "location", "startDate", "endDate", "duration", "type", "repeating", "recurrence", "remindBy", "firstReminder",
"secondReminder", "humanReadable", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername",
"lastUpdatedByUserId", "lastUpdatedByUsername", "recurringDates", "nextTenRecurringDates"));
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(
Arrays.asList("id", "entityId", "entityType", "title", "description", "location", "startDate", "endDate", "duration", "type",
"repeating", "recurrence", "frequency", "interval", "repeatsOnDay", "remindBy", "firstReminder", "secondReminder",
"humanReadable", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername", "lastUpdatedByUserId",
"lastUpdatedByUsername", "recurringDates", "nextTenRecurringDates", "entityTypeOptions", "calendarTypeOptions",
"remindByOptions", "frequencyOptions", "repeatsOnDayOptions"));
private final String resourceNameForPermissions = "CALENDAR";
private final PlatformSecurityContext context;
@@ -92,7 +95,12 @@ public class CalendarsApiResource {
CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
// Include recurring date details
calendarData = this.readPlatformService.generateRecurringDate(calendarData);
final boolean withHistory = true;
final LocalDate tillDate = null;
final Collection<LocalDate> recurringDates = this.readPlatformService.generateRecurringDates(calendarData, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.readPlatformService.generateNextTenRecurringDates(calendarData);
final LocalDate recentEligibleMeetingDate = null;
calendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (settings.isTemplate()) {
@@ -133,7 +141,7 @@ public class CalendarsApiResource {
CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(), calendarTypeOptions));
// Add recurring dates
calendarsData = this.readPlatformService.generateRecurringDates(calendarsData);
calendarsData = this.readPlatformService.updateWithRecurringDates(calendarsData);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS);
@@ -188,7 +196,7 @@ public class CalendarsApiResource {
@Path("{calendarId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteGLAccount(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
public String deleteCalendar(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
@PathParam("calendarId") final Long calendarId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCalendar(entityType, entityId, calendarId).build();
@@ -202,7 +210,10 @@ public class CalendarsApiResource {
final List<EnumOptionData> entityTypeOptions = this.dropdownReadPlatformService.retrieveCalendarEntityTypeOptions();
final List<EnumOptionData> calendarTypeOptions = this.dropdownReadPlatformService.retrieveCalendarTypeOptions();
final List<EnumOptionData> remindByOptions = this.dropdownReadPlatformService.retrieveCalendarRemindByOptions();
return new CalendarData(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions);
final List<EnumOptionData> frequencyOptions = this.dropdownReadPlatformService.retrieveCalendarFrequencyTypeOptions();
final List<EnumOptionData> repeatsOnDayOptions = this.dropdownReadPlatformService.retrieveCalendarWeekDaysTypeOptions();
return CalendarData.withTemplateOptions(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions,
repeatsOnDayOptions);
}
}
@@ -10,8 +10,10 @@ import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarRemindBy;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.domain.CalendarWeekDaysType;
import org.mifosplatform.portfolio.calendar.service.CalendarEnumerations;
/**
@@ -20,6 +22,7 @@ import org.mifosplatform.portfolio.calendar.service.CalendarEnumerations;
public class CalendarData {
private final Long id;
private final Long calendarInstanceId;
private final Long entityId;
private final EnumOptionData entityType;
private final String title;
@@ -31,6 +34,9 @@ public class CalendarData {
private final EnumOptionData type;
private final boolean repeating;
private final String recurrence;
private final EnumOptionData frequency;
private final Integer interval;
private final EnumOptionData repeatsOnDay;
private final EnumOptionData remindBy;
private final Integer firstReminder;
private final Integer secondReminder;
@@ -50,112 +56,61 @@ public class CalendarData {
final List<EnumOptionData> entityTypeOptions;
final List<EnumOptionData> calendarTypeOptions;
final List<EnumOptionData> remindByOptions;
final List<EnumOptionData> frequencyOptions;
final List<EnumOptionData> repeatsOnDayOptions;
public CalendarData(final Long id, final Long entityId, final EnumOptionData entityType, final String title, final String description,
final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final EnumOptionData type,
final boolean repeating, final String recurrence, final EnumOptionData remindBy, final Integer firstReminder,
final Integer secondReminder, final String humanReadable, final LocalDate recentEligibleMeetingDate,
final LocalDate createdDate, final LocalDate lastUpdatedDate, final Long createdByUserId, final String createdByUsername,
final Long lastUpdatedByUserId, final String lastUpdatedByUsername) {
public static CalendarData instance(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType, final String title,
final String description, final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration,
final EnumOptionData type, final boolean repeating, final String recurrence, final EnumOptionData frequency,
final Integer interval, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, final Integer firstReminder,
final Integer secondReminder, final String humanReadable, final LocalDate createdDate, final LocalDate lastUpdatedDate,
final Long createdByUserId, final String createdByUsername, final Long lastUpdatedByUserId, final String lastUpdatedByUsername) {
this.id = id;
this.entityId = entityId;
this.entityType = entityType;
this.title = title;
this.description = description;
this.location = location;
this.startDate = startDate;
this.endDate = endDate;
this.duration = duration;
this.type = type;
this.repeating = repeating;
this.recurrence = recurrence;
this.remindBy = remindBy;
this.firstReminder = firstReminder;
this.secondReminder = secondReminder;
this.entityTypeOptions = null;
this.calendarTypeOptions = null;
this.remindByOptions = null;
this.recurringDates = null;
this.nextTenRecurringDates = null;
this.humanReadable = humanReadable;
this.recentEligibleMeetingDate = recentEligibleMeetingDate;
final Collection<LocalDate> recurringDates = null;
final Collection<LocalDate> nextTenRecurringDates = null;
final LocalDate recentEligibleMeetingDate = null;
this.createdDate = createdDate;
this.lastUpdatedDate = lastUpdatedDate;
this.createdByUserId = createdByUserId;
this.createdByUsername = createdByUsername;
this.lastUpdatedByUserId = lastUpdatedByUserId;
this.lastUpdatedByUsername = lastUpdatedByUsername;
final List<EnumOptionData> entityTypeOptions = null;
final List<EnumOptionData> calendarTypeOptions = null;
final List<EnumOptionData> remindByOptions = null;
final List<EnumOptionData> frequencyOptions = null;
final List<EnumOptionData> repeatsOnDayOptions = null;
return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating,
recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates,
nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
frequencyOptions, repeatsOnDayOptions);
}
public CalendarData(final CalendarData calendarData, final List<EnumOptionData> entityTypeOptions,
final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions) {
this.id = calendarData.id;
this.entityId = calendarData.entityId;
this.entityType = calendarData.entityType;
this.title = calendarData.title;
this.description = calendarData.description;
this.location = calendarData.location;
this.startDate = calendarData.startDate;
this.endDate = calendarData.endDate;
this.duration = calendarData.duration;
this.type = calendarData.type;
this.repeating = calendarData.repeating;
this.recurrence = calendarData.recurrence;
this.remindBy = calendarData.remindBy;
this.firstReminder = calendarData.firstReminder;
this.secondReminder = calendarData.secondReminder;
this.entityTypeOptions = entityTypeOptions;
this.calendarTypeOptions = calendarTypeOptions;
this.remindByOptions = remindByOptions;
this.recurringDates = calendarData.recurringDates;
this.nextTenRecurringDates = calendarData.nextTenRecurringDates;
this.humanReadable = calendarData.humanReadable;
this.recentEligibleMeetingDate = calendarData.recentEligibleMeetingDate;
this.createdDate = calendarData.createdDate;
this.lastUpdatedDate = calendarData.lastUpdatedDate;
this.createdByUserId = calendarData.createdByUserId;
this.createdByUsername = calendarData.createdByUsername;
this.lastUpdatedByUserId = calendarData.lastUpdatedByUserId;
this.lastUpdatedByUsername = calendarData.lastUpdatedByUsername;
}
public CalendarData(final CalendarData calendarData, final Collection<LocalDate> recurringDates,
public static CalendarData withRecurringDates(final CalendarData calendarData, final Collection<LocalDate> recurringDates,
final Collection<LocalDate> nextTenRecurringDates, final LocalDate recentEligibleMeetingDate) {
this.id = calendarData.id;
this.entityId = calendarData.entityId;
this.entityType = calendarData.entityType;
this.title = calendarData.title;
this.description = calendarData.description;
this.location = calendarData.location;
this.startDate = calendarData.startDate;
this.endDate = calendarData.endDate;
this.duration = calendarData.duration;
this.type = calendarData.type;
this.repeating = calendarData.repeating;
this.recurrence = calendarData.recurrence;
this.remindBy = calendarData.remindBy;
this.firstReminder = calendarData.firstReminder;
this.secondReminder = calendarData.secondReminder;
this.entityTypeOptions = null;
this.calendarTypeOptions = null;
this.remindByOptions = null;
this.recurringDates = recurringDates;
this.nextTenRecurringDates = nextTenRecurringDates;
this.humanReadable = calendarData.humanReadable;
this.recentEligibleMeetingDate = recentEligibleMeetingDate;
this.createdDate = calendarData.createdDate;
this.lastUpdatedDate = calendarData.lastUpdatedDate;
this.createdByUserId = calendarData.createdByUserId;
this.createdByUsername = calendarData.createdByUsername;
this.lastUpdatedByUserId = calendarData.lastUpdatedByUserId;
this.lastUpdatedByUsername = calendarData.lastUpdatedByUsername;
return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title,
calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration,
calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, recurringDates,
nextTenRecurringDates, calendarData.humanReadable, recentEligibleMeetingDate, calendarData.createdDate,
calendarData.lastUpdatedDate, calendarData.createdByUserId, calendarData.createdByUsername,
calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername, calendarData.entityTypeOptions,
calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions,
calendarData.repeatsOnDayOptions);
}
public static CalendarData withRecentEligibleMeetingDate(final CalendarData calendarData, final LocalDate recentEligibleMeetingDate) {
return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title,
calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration,
calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, calendarData.recurringDates,
calendarData.nextTenRecurringDates, calendarData.humanReadable, recentEligibleMeetingDate, calendarData.createdDate,
calendarData.lastUpdatedDate, calendarData.createdByUserId, calendarData.createdByUsername,
calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername, calendarData.entityTypeOptions,
calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions,
calendarData.repeatsOnDayOptions);
}
public static CalendarData sensibleDefaultsForNewCalendarCreation() {
final Long id = null;
final Long calendarInstanceId = null;
final Long entityId = null;
final EnumOptionData entityType = null;
final String title = null;
@@ -167,12 +122,23 @@ public class CalendarData {
final EnumOptionData type = CalendarEnumerations.calendarType(CalendarType.COLLECTION);
final boolean repeating = false;
final String recurrence = null;
final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarFrequencyType.DAILY);
final Integer interval = new Integer(1);
final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.MO);
final EnumOptionData remindBy = CalendarEnumerations.calendarRemindBy(CalendarRemindBy.EMAIL);
final Integer firstReminder = new Integer(0);
final Integer secondReminder = new Integer(0);
final String humanReadable = null;
final Collection<LocalDate> recurringDates = null;
final Collection<LocalDate> nextTenRecurringDates = null;
final LocalDate recentEligibleMeetingDate = null;
final List<EnumOptionData> entityTypeOptions = null;
final List<EnumOptionData> calendarTypeOptions = null;
final List<EnumOptionData> remindByOptions = null;
final List<EnumOptionData> frequencyOptions = null;
final List<EnumOptionData> repeatsOnDayOptions = null;
final LocalDate createdDate = null;
final LocalDate lastUpdatedDate = null;
final Long createdByUserId = null;
@@ -180,9 +146,71 @@ public class CalendarData {
final Long lastUpdatedByUserId = null;
final String lastUpdatedByUsername = null;
return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating,
recurrence, remindBy, firstReminder, secondReminder, humanReadable, recentEligibleMeetingDate, createdDate,
lastUpdatedDate, createdByUserId, createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername);
return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating,
recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates,
nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
frequencyOptions, repeatsOnDayOptions);
}
public static CalendarData withTemplateOptions(final CalendarData calendarData, final List<EnumOptionData> entityTypeOptions,
final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions) {
return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title,
calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration,
calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder,
calendarData.recurringDates, calendarData.nextTenRecurringDates, calendarData.humanReadable,
calendarData.recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate,
calendarData.createdByUserId, calendarData.createdByUsername, calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername,
entityTypeOptions, calendarTypeOptions, remindByOptions, repeatsOptions, repeatsOnDayOptions);
}
private CalendarData(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType, final String title, final String description,
final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final EnumOptionData type,
final boolean repeating, final String recurrence, final EnumOptionData frequency, final Integer interval,
final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, final Integer firstReminder, final Integer secondReminder,
final Collection<LocalDate> recurringDates, final Collection<LocalDate> nextTenRecurringDates, final String humanReadable,
final LocalDate recentEligibleMeetingDate, final LocalDate createdDate, final LocalDate lastUpdatedDate,
final Long createdByUserId, final String createdByUsername, final Long lastUpdatedByUserId, final String lastUpdatedByUsername,
final List<EnumOptionData> entityTypeOptions, final List<EnumOptionData> calendarTypeOptions,
final List<EnumOptionData> remindByOptions, final List<EnumOptionData> repeatsOptions,
final List<EnumOptionData> repeatsOnDayOptions) {
this.id = id;
this.calendarInstanceId = calendarInstanceId;
this.entityId = entityId;
this.entityType = entityType;
this.title = title;
this.description = description;
this.location = location;
this.startDate = startDate;
this.endDate = endDate;
this.duration = duration;
this.type = type;
this.repeating = repeating;
this.recurrence = recurrence;
this.frequency = frequency;
this.interval = interval;
this.repeatsOnDay = repeatsOnDay;
this.remindBy = remindBy;
this.firstReminder = firstReminder;
this.secondReminder = secondReminder;
this.recurringDates = recurringDates;
this.nextTenRecurringDates = nextTenRecurringDates;
this.humanReadable = humanReadable;
this.recentEligibleMeetingDate = recentEligibleMeetingDate;
this.createdDate = createdDate;
this.lastUpdatedDate = lastUpdatedDate;
this.createdByUserId = createdByUserId;
this.createdByUsername = createdByUsername;
this.lastUpdatedByUserId = lastUpdatedByUserId;
this.lastUpdatedByUsername = lastUpdatedByUsername;
this.entityTypeOptions = entityTypeOptions;
this.calendarTypeOptions = calendarTypeOptions;
this.remindByOptions = remindByOptions;
this.frequencyOptions = repeatsOptions;
this.repeatsOnDayOptions = repeatsOnDayOptions;
}
public Long getId() {
@@ -265,4 +293,31 @@ public class CalendarData {
return this.humanReadable;
}
public Long getCalendarInstanceId() {
return this.calendarInstanceId;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (this.startDate.isBefore(compareDate) || this.startDate.equals(compareDate)) { return true; }
}
return false;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (this.endDate.isAfter(compareDate) || this.endDate.isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate){
if (isStartDateBeforeOrEqual(compareDate)){
if (this.endDate == null || isEndDateAfterOrEqual(compareDate)){
return true;
}
}
return false;
}
}
@@ -6,12 +6,21 @@
package org.mifosplatform.portfolio.calendar.domain;
import static org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_RESOURCE_NAME;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@@ -19,7 +28,11 @@ import javax.persistence.TemporalType;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.exception.CalendarDateException;
import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
@@ -68,6 +81,10 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "second_reminder", nullable = true)
private Integer secondReminder;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "calendar_id")
private Set<CalendarHistory> calendarHistory = new HashSet<CalendarHistory>();
protected Calendar() {
}
@@ -75,18 +92,29 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
public Calendar(final String title, final String description, final String location, final LocalDate startDate,
final LocalDate endDate, final Integer duration, final Integer typeId, final boolean repeating, final String recurrence,
final Integer remindById, final Integer firstReminder, final Integer secondReminder) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
final CalendarType calendarType = CalendarType.fromInt(typeId);
if (calendarType.isCollection() && !repeating) {
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue())
.failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.title = StringUtils.defaultIfEmpty(title, null);
this.description = StringUtils.defaultIfEmpty(description, null);
this.location = StringUtils.defaultIfEmpty(location, null);
if (null != startDate) {
this.startDate = startDate.toDateMidnight().toDate();
this.startDate = startDate.toDateTimeAtStartOfDay().toDate();
} else {
this.startDate = null;
}
if (null != endDate) {
this.endDate = endDate.toDateMidnight().toDate();
this.endDate = endDate.toDateTimeAtStartOfDay().toDate();
} else {
this.endDate = null;
}
@@ -153,17 +181,22 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
if (isStartDateBefore(newValue)) {
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (newValue.isBefore(currentDate)) {
final String defaultUserMessage = "New meeting effective from date cannot be in past";
throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newValue,
getStartDateLocalDate());
}else if (isStartDateAfter(newValue) && isStartDateBeforeOrEqual(currentDate)) {
//new meeting date should be on or after start date or current date
final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date";
throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newValue,
getStartDateLocalDate());
} else {
actualChanges.put(startDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput);
this.startDate = newValue.toDate();
} else {
// new meeting start date should be greater than existing
// meeting start date
final String defaultUserMessage = "New meeting start on or after date cannot be a date before existing meeting start date";
throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newValue,
getStartDateLocalDate());
}
}
@@ -211,8 +244,21 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
this.repeating = newValue;
}
//if repeating is false then update recurrence to NULL
if(!this.repeating) this.recurrence = null;
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
final CalendarType calendarType = CalendarType.fromInt(this.typeId);
if (calendarType.isCollection() && !this.repeating) {
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue())
.failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
final String newRecurrence = Calendar.constructRecurrence(command, this);
if (this.recurrence != null && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
// FIXME: AA - Is this restriction required only for collection type
// meetings or for all?.
@@ -319,9 +365,48 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
}
return endDateLocalDate;
}
public Set<CalendarHistory> history() {
return this.calendarHistory;
}
public boolean isStartDateBefore(final LocalDate newStartDate) {
if (this.startDate != null && newStartDate != null && getStartDateLocalDate().isBefore(newStartDate)) { return true; }
public boolean isStartDateBefore(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null && getStartDateLocalDate().isBefore(compareDate)) { return true; }
return false;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) { return true; }
}
return false;
}
public boolean isStartDateAfter(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null && getStartDateLocalDate().isAfter(compareDate)) { return true; }
return false;
}
public boolean isStartDateAfterOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isAfter(compareDate) || getStartDateLocalDate().isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate){
if (isStartDateBeforeOrEqual(compareDate)){
if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)){
return true;
}
}
return false;
}
@@ -337,23 +422,39 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
if (repeating) {
final StringBuilder recurrenceBuilder = new StringBuilder(200);
final String repeats = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS.getValue());
final Integer frequency = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue());
final CalendarFrequencyType frequencyType = CalendarFrequencyType.fromInt(frequency);
recurrenceBuilder.append("FREQ=");
recurrenceBuilder.append(repeats.toUpperCase());
final Integer repeatsEvery = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_EVERY.getValue());
if (repeatsEvery > 1) {
recurrenceBuilder.append(frequencyType.toString().toUpperCase());
final Integer interval = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue());
if (interval > 1) {
recurrenceBuilder.append(";INTERVAL=");
recurrenceBuilder.append(repeatsEvery);
recurrenceBuilder.append(interval);
}
if (repeats.equalsIgnoreCase("Weekly")) {
final String repeatsOnDay = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue());
if (repeatsOnDay != null && repeatsOnDay != "") {
if (frequencyType.isWeekly()) {
final Integer repeatsOnDay = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue());
final CalendarWeekDaysType weekDays = CalendarWeekDaysType.fromInt(repeatsOnDay);
if (!weekDays.isInvalid()) {
recurrenceBuilder.append(";BYDAY=");
recurrenceBuilder.append(repeatsOnDay);
recurrenceBuilder.append(weekDays.toString().toUpperCase());
}
}
return recurrenceBuilder.toString();
}
return "";
}
public boolean isValidRecurringDate(final LocalDate compareDate) {
if (isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(getRecurrence(), getStartDateLocalDate(),
compareDate); }
// validate with history details.
for (CalendarHistory history : history()) {
if (history.isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(history.getRecurrence(),
history.getStartDateLocalDate(), compareDate); }
}
return false;
}
}
@@ -82,4 +82,36 @@ public enum CalendarEntityType {
public static boolean isCenter(final String name) {
return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name) ? true : false;
}
public static boolean isLoan(final Integer value) {
return CalendarEntityType.LOANS.value.equals(value) ? true : false;
}
public static boolean isLoan(final String name) {
return CalendarEntityType.LOANS.name().equalsIgnoreCase(name) ? true : false;
}
public static boolean isClient(final Integer value) {
return CalendarEntityType.CLIENTS.value.equals(value) ? true : false;
}
public static boolean isClient(final String name) {
return CalendarEntityType.CLIENTS.name().equalsIgnoreCase(name) ? true : false;
}
public boolean isCenter() {
return this.value.equals(CalendarEntityType.CENTERS.getValue());
}
public boolean isGroup() {
return this.value.equals(CalendarEntityType.GROUPS.getValue());
}
public boolean isLoan() {
return this.value.equals(CalendarEntityType.LOANS.getValue());
}
public boolean isClient() {
return this.value.equals(CalendarEntityType.CLIENTS.getValue());
}
}
@@ -0,0 +1,87 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.calendar.domain;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.StringUtils;
public enum CalendarFrequencyType {
INVALID(0, "calendarFrequencyType.invalid"), DAILY(1, "calendarFrequencyType.daily"), WEEKLY(2, "calendarFrequencyType.weekly"), MONTHLY(
3, "calendarFrequencyType.monthly"), YEARLY(4, "calendarFrequencyType.yearly");
private final Integer value;
private final String code;
private CalendarFrequencyType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, CalendarFrequencyType> intToEnumMap = new HashMap<Integer, CalendarFrequencyType>();
private static int minValue = CalendarFrequencyType.DAILY.value;
private static int maxValue = CalendarFrequencyType.YEARLY.value;
static {
for (final CalendarFrequencyType type : CalendarFrequencyType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static CalendarFrequencyType fromInt(final int i) {
final CalendarFrequencyType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public static CalendarFrequencyType fromString(final String frequencyString) {
CalendarFrequencyType frequency = CalendarFrequencyType.INVALID;
if (StringUtils.isEmpty(frequencyString)) return frequency;
if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.DAILY.toString())) {
frequency = CalendarFrequencyType.DAILY;
} else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.WEEKLY.toString())) {
frequency = CalendarFrequencyType.WEEKLY;
} else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.MONTHLY.toString())) {
frequency = CalendarFrequencyType.MONTHLY;
} else if (frequencyString.equalsIgnoreCase(CalendarFrequencyType.YEARLY.toString())) {
frequency = CalendarFrequencyType.YEARLY;
}
return frequency;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
public boolean isWeekly(){
return this.value.equals(CalendarFrequencyType.WEEKLY.value);
}
public boolean isInvalid(){
return this.value.equals(CalendarFrequencyType.INVALID.value);
}
}
@@ -0,0 +1,134 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.calendar.domain;
import java.util.Date;
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 org.joda.time.LocalDate;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_calendar_history")
public class CalendarHistory extends AbstractPersistable<Long>{
@ManyToOne(optional = false)
@JoinColumn(name = "calendar_id", referencedColumnName = "id", nullable = false)
private Calendar calendar;
@Column(name = "title", length = 50, nullable = false)
private String title;
@Column(name = "description", length = 100, nullable = true)
private String description;
@Column(name = "location", length = 100, nullable = true)
private String location;
@Column(name = "start_date", nullable = false)
@Temporal(TemporalType.DATE)
private Date startDate;
@Column(name = "end_date", nullable = true)
@Temporal(TemporalType.DATE)
private Date endDate;
@Column(name = "duration", nullable = true)
private Integer duration;
@Column(name = "calendar_type_enum", nullable = false)
private Integer typeId;
@Column(name = "repeating", nullable = false)
private boolean repeating = false;
@Column(name = "recurrence", length = 100, nullable = true)
private String recurrence;
@Column(name = "remind_by_enum", nullable = true)
private Integer remindById;
@Column(name = "first_reminder", nullable = true)
private Integer firstReminder;
@Column(name = "second_reminder", nullable = true)
private Integer secondReminder;
protected CalendarHistory() {
}
public CalendarHistory(Calendar calendar, Date startDate) {
this.calendar = calendar;
this.title = calendar.getTitle();
this.description = calendar.getDescription();
this.location = calendar.getLocation();
this.startDate = startDate;
this.endDate = calendar.getStartDate();//Calendar start date become end date for history data.
this.duration = calendar.getDuration();
this.typeId = calendar.getTypeId();
this.repeating = calendar.isRepeating();
this.recurrence = calendar.getRecurrence();
this.remindById = calendar.getRemindById();
this.firstReminder = calendar.getFirstReminder();
this.secondReminder = calendar.getSecondReminder();
}
public String getRecurrence() {
return this.recurrence;
}
public LocalDate getStartDateLocalDate() {
LocalDate startDateLocalDate = null;
if (this.startDate != null) {
startDateLocalDate = LocalDate.fromDateFields(this.startDate);
}
return startDateLocalDate;
}
public LocalDate getEndDateLocalDate() {
LocalDate endDateLocalDate = null;
if (this.endDate != null) {
endDateLocalDate = LocalDate.fromDateFields(this.endDate);
}
return endDateLocalDate;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) { return true; }
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate){
if (isStartDateBeforeOrEqual(compareDate)){
if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)){
return true;
}
}
return false;
}
public void updateEndDate(Date historyCalEndDate){
this.endDate = historyCalEndDate;
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.calendar.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CalendarHistoryRepository extends JpaRepository<CalendarHistory, Long>, JpaSpecificationExecutor<CalendarHistory> {
}
@@ -66,4 +66,8 @@ public enum CalendarType {
public String toString() {
return name().toString();
}
public boolean isCollection(){
return this.value.equals(CalendarType.COLLECTION.value);
}
}
@@ -0,0 +1,91 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.calendar.domain;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.StringUtils;
public enum CalendarWeekDaysType {
INVALID(0, "calendarWeekDaysType.invalid"), MO(1, "calendarWeekDaysType.monday"), TU(2, "calendarWeekDaysType.tuesday"), WE(3,
"calendarWeekDaysType.wednesday"), TH(4, "calendarWeekDaysType.thursday"), FR(5, "calendarWeekDaysType.friday"), SA(6,
"calendarWeekDaysType.saturday"), SU(7, "calendarWeekDaysType.sunday");
private final Integer value;
private final String code;
private CalendarWeekDaysType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
private static final Map<Integer, CalendarWeekDaysType> intToEnumMap = new HashMap<Integer, CalendarWeekDaysType>();
private static int minValue = CalendarWeekDaysType.MO.value;
private static int maxValue = CalendarWeekDaysType.SU.value;
static {
for (final CalendarWeekDaysType type : CalendarWeekDaysType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static CalendarWeekDaysType fromInt(final int i) {
final CalendarWeekDaysType type = intToEnumMap.get(Integer.valueOf(i));
return type;
}
public static int getMinValue() {
return minValue;
}
public static int getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return name().toString();
}
public boolean isInvalid(){
return this.value.equals(CalendarWeekDaysType.INVALID.value);
}
public static CalendarWeekDaysType fromString(final String weekDayString) {
CalendarWeekDaysType weekDay = CalendarWeekDaysType.INVALID;
if (StringUtils.isEmpty(weekDayString)) return weekDay;
if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.MO.toString())) {
weekDay = CalendarWeekDaysType.MO;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.TU.toString())) {
weekDay = CalendarWeekDaysType.TU;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.WE.toString())) {
weekDay = CalendarWeekDaysType.WE;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.TH.toString())) {
weekDay = CalendarWeekDaysType.TH;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.FR.toString())) {
weekDay = CalendarWeekDaysType.FR;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.SA.toString())) {
weekDay = CalendarWeekDaysType.SA;
} else if (weekDayString.equalsIgnoreCase(CalendarWeekDaysType.SU.toString())) {
weekDay = CalendarWeekDaysType.SU;
}
return weekDay;
}
}
@@ -22,7 +22,9 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.command.CalendarCommand;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarRemindBy;
import org.mifosplatform.portfolio.calendar.domain.CalendarWeekDaysType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -141,22 +143,22 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue()).value(repeating).notNull();
if (repeating) {
final String repeats = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS.getValue()).value(repeats).notBlank()
.notExceedingLengthOf(20);
final Integer frequency = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue()).value(frequency).notBlank()
.inMinMaxRange(CalendarFrequencyType.getMinValue(), CalendarFrequencyType.getMaxValue());
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_EVERY.getValue(), element)) {
final Integer repeatsEvery = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.REPEATS_EVERY.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_EVERY.getValue()).value(repeatsEvery)
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element)) {
final Integer interval = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue()).value(interval)
.notNull().integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element)) {
final String repeatsOnDay = this.fromApiJsonHelper.extractStringNamed(
if (CalendarFrequencyType.fromInt(frequency).isWeekly()) {
final Integer repeatsOnDay = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue()).value(repeatsOnDay)
.notBlank().notNull();
.notBlank().inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue());
}
}
}
@@ -251,22 +253,23 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue()).value(repeating).notNull();
if (repeating) {
final String repeats = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS.getValue()).value(repeats).ignoreIfNull()
.notBlank().notExceedingLengthOf(20);
final Integer frequency = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.FREQUENCY.getValue()).value(frequency).notBlank()
.inMinMaxRange(CalendarFrequencyType.getMinValue(), CalendarFrequencyType.getMaxValue());
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_EVERY.getValue(), element)) {
final Integer repeatsEvery = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.REPEATS_EVERY.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_EVERY.getValue()).value(repeatsEvery)
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element)) {
final Integer interval = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.INTERVAL.getValue()).value(interval)
.notNull().integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element)) {
final String repeatsOnDay = this.fromApiJsonHelper.extractStringNamed(
final Integer repeatsOnDay = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue()).value(repeatsOnDay)
.notBlank();
.notBlank().inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue());
}
}
}
@@ -16,5 +16,9 @@ public interface CalendarDropdownReadPlatformService {
List<EnumOptionData> retrieveCalendarTypeOptions();
List<EnumOptionData> retrieveCalendarRemindByOptions();
List<EnumOptionData> retrieveCalendarFrequencyTypeOptions();
List<EnumOptionData> retrieveCalendarWeekDaysTypeOptions();
}
@@ -9,8 +9,10 @@ import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarRemindBy;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.domain.CalendarWeekDaysType;
import org.springframework.stereotype.Service;
@Service
@@ -31,4 +33,13 @@ public class CalendarDropdownReadPlatformServiceImpl implements CalendarDropdown
return CalendarEnumerations.calendarRemindBy(CalendarRemindBy.values());
}
@Override
public List<EnumOptionData> retrieveCalendarFrequencyTypeOptions() {
return CalendarEnumerations.calendarFrequencyType(CalendarFrequencyType.values());
}
@Override
public List<EnumOptionData> retrieveCalendarWeekDaysTypeOptions() {
return CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.values());
}
}
@@ -10,8 +10,10 @@ import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarRemindBy;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.domain.CalendarWeekDaysType;
public class CalendarEnumerations {
@@ -65,4 +67,42 @@ public class CalendarEnumerations {
}
return optionDatas;
}
public static EnumOptionData calendarFrequencyType(final int id) {
return calendarFrequencyType(CalendarFrequencyType.fromInt(id));
}
public static EnumOptionData calendarFrequencyType(final CalendarFrequencyType calendarFrequencyType) {
final EnumOptionData optionData = new EnumOptionData(calendarFrequencyType.getValue().longValue(), calendarFrequencyType.getCode(), calendarFrequencyType.toString());
return optionData;
}
public static List<EnumOptionData> calendarFrequencyType(final CalendarFrequencyType[] calendarFrequencyTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<EnumOptionData>();
for (final CalendarFrequencyType calendarFrequencyType : calendarFrequencyTypes) {
if(!calendarFrequencyType.isInvalid()){
optionDatas.add(calendarFrequencyType(calendarFrequencyType));
}
}
return optionDatas;
}
public static EnumOptionData calendarWeekDaysType(final int id) {
return calendarWeekDaysType(CalendarWeekDaysType.fromInt(id));
}
public static EnumOptionData calendarWeekDaysType(final CalendarWeekDaysType calendarWeekDaysType) {
final EnumOptionData optionData = new EnumOptionData(calendarWeekDaysType.getValue().longValue(), calendarWeekDaysType.getCode(), calendarWeekDaysType.toString());
return optionData;
}
public static List<EnumOptionData> calendarWeekDaysType(final CalendarWeekDaysType[] calendarWeekDaysTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<EnumOptionData>();
for (final CalendarWeekDaysType calendarWeekDaysType : calendarWeekDaysTypes) {
if(!calendarWeekDaysType.isInvalid()){
optionDatas.add(calendarWeekDaysType(calendarWeekDaysType));
}
}
return optionDatas;
}
}
@@ -10,6 +10,7 @@ import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
public interface CalendarReadPlatformService {
@@ -24,14 +25,16 @@ public interface CalendarReadPlatformService {
CalendarData retrieveNewCalendarDetails();
CalendarData generateRecurringDate(final CalendarData calendarData);
Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate);
CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate);
Collection<LocalDate> generateNextTenRecurringDates(final CalendarData calendarData);
Collection<CalendarData> generateRecurringDates(final Collection<CalendarData> calendarsData);
Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData);
CalendarData retrieveLoanCalendar(final Long loanId);
CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId);
LocalDate generateNextEligibleMeetingDateForCollection(CalendarData calendarData, MeetingData lastMeetingData);
}
@@ -20,6 +20,7 @@ import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -40,7 +41,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
private static final class CalendarDataMapper implements RowMapper<CalendarData> {
public String schema() {
return " select c.id as id, ci.entity_id as entityId, ci.entity_type_enum as entityTypeId, c.title as title, "
return " select c.id as id, ci.id as calendarInstanceId, ci.entity_id as entityId, ci.entity_type_enum as entityTypeId, c.title as title, "
+ " c.description as description, c.location as location, c.start_date as startDate, c.end_date as endDate, "
+ " c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, "
+ " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder, "
@@ -54,6 +55,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long calendarInstanceId = rs.getLong("calendarInstanceId");
final Long entityId = rs.getLong("entityId");
final Integer entityTypeId = rs.getInt("entityTypeId");
final EnumOptionData entityType = CalendarEnumerations.calendarEntityType(entityTypeId);
@@ -67,6 +69,9 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
final EnumOptionData type = CalendarEnumerations.calendarType(typeId);
final boolean repeating = rs.getBoolean("repeating");
final String recurrence = rs.getString("recurrence");
final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence));
final Integer interval = new Integer(CalendarUtils.getInterval(recurrence));
final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence));
final Integer remindById = rs.getInt("remindById");
EnumOptionData remindBy = null;
if (remindById != null && remindById != 0) {
@@ -86,11 +91,10 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
final Long lastUpdatedByUserId = rs.getLong("updatingUserId");
final String lastUpdatedByUserName = rs.getString("updatingUserName");
final LocalDate recentEligibleMeetingDate = null;
return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating,
recurrence, remindBy, firstReminder, secondReminder, humanReadable, recentEligibleMeetingDate, createdDate,
lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName);
return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate,
duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder,
humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId,
lastUpdatedByUserName);
}
}
@@ -183,49 +187,139 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
}
@Override
public CalendarData generateRecurringDate(final CalendarData calendarData) {
return generateRecurringDate(calendarData, null);
public Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate) {
final LocalDate fromDate = null;
Collection<LocalDate> recurringDates = generateRecurringDate(calendarData, fromDate, tillDate, -1);
if (withHistory) {
final Collection<CalendarData> calendarHistorys = this.retrieveCalendarsFromHistory(calendarData.getId());
for (CalendarData calendarHistory : calendarHistorys) {
recurringDates.addAll(generateRecurringDate(calendarHistory, fromDate, tillDate, -1));
}
}
return recurringDates;
}
@Override
public CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate) {
if (!calendarData.isRepeating()) { return calendarData; }
public Collection<LocalDate> generateNextTenRecurringDates(CalendarData calendarData) {
final LocalDate tillDate = null;
return generateRecurringDate(calendarData, DateUtils.getLocalDateOfTenant(), tillDate, 10);
}
private Collection<LocalDate> generateRecurringDate(final CalendarData calendarData, final LocalDate fromDate, final LocalDate tillDate, final int maxCount) {
if (!calendarData.isRepeating()) { return null; }
final String rrule = calendarData.getRecurrence();
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
final LocalDate seedDate = calendarData.getStartDate();
LocalDate endDate = calendarData.getEndDate();
LocalDate startDate = calendarData.getStartDate();
if (seedDate.isBefore(currentDate.minusYears(1))) {
startDate = currentDate.minusYears(1);
/**
* Start date or effective from date of calendar recurrence.
*/
final LocalDate seedDate = this.getSeedDate(calendarData.getStartDate());
/**
* periodStartDate date onwards recurring dates will be generated.
*/
final LocalDate periodStartDate = this.getPeriodStartDate(seedDate, calendarData.getStartDate(), fromDate);
/**
* till periodEndDate recurring dates will be generated.
*/
final LocalDate periodEndDate = this.getPeriodEndDate(calendarData.getEndDate(), tillDate);
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, periodStartDate, periodEndDate,
maxCount);
return recurringDates;
}
private LocalDate getSeedDate(LocalDate date) {
return date;
}
private LocalDate getPeriodStartDate(final LocalDate seedDate, final LocalDate recurrenceStartDate, final LocalDate fromDate) {
LocalDate periodStartDate = null;
if(fromDate != null) {
periodStartDate = fromDate;
}else {
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (seedDate.isBefore(currentDate.minusYears(1))) {
periodStartDate = currentDate.minusYears(1);
}else{
periodStartDate = recurrenceStartDate;
}
}
return periodStartDate;
}
private LocalDate getPeriodEndDate(LocalDate endDate, LocalDate tillDate) {
LocalDate periodEndDate = endDate;
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (tillDate != null) {
if (endDate != null) {
if (endDate.isAfter(tillDate)) {
endDate = tillDate;// to retrieve meeting dates till
// specified date (tillDate)
// to retrieve meeting dates tillspecified date (tillDate)
periodEndDate = tillDate;
}
} else {
endDate = tillDate;// end date is null then fetch meeting dates
// tillDate
// end date is null then fetch meeting dates tillDate
periodEndDate = tillDate;
}
} else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
endDate = currentDate.plusYears(1);
periodEndDate = currentDate.plusYears(1);
}
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, startDate, endDate, -1);
final Collection<LocalDate> nextTenRecurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, endDate);
final LocalDate recentEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(rrule, seedDate);
return new CalendarData(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
return periodEndDate;
}
@Override
public Collection<CalendarData> generateRecurringDates(final Collection<CalendarData> calendarsData) {
final Collection<CalendarData> recuCalendarsData = new ArrayList<CalendarData>();
public LocalDate generateNextEligibleMeetingDateForCollection(final CalendarData calendarData, final MeetingData lastMeetingData) {
final LocalDate lastMeetingDate = (lastMeetingData == null) ? null : lastMeetingData.getMeetingDate();
// get applicable calendar based on meeting date
CalendarData applicableCalendarData = calendarData;
LocalDate nextEligibleMeetingDate = null;
/**
* The next available meeting date for collection should be taken from
* application calendar for that time period. e.g. If the previous
* calendar details has weekly meeting starting from 1st of Oct 2013 on
* every Tuesday, then meeting dates for collection are 1,8,15,22,29..
*
* If meeting schedule has changed from Tuesday to Friday with effective
* from 15th of Oct (calendar update has made on 2nd of Oct) , then
* application should allow to generate collection sheet on 8th of Oct
* which is still on Tuesday and next collection sheet date should be on
* 18th of Oct as per current calendar
*/
if (lastMeetingDate != null && !calendarData.isBetweenStartAndEndDate(lastMeetingDate)
&& !calendarData.isBetweenStartAndEndDate(DateUtils.getLocalDateOfTenant())) {
applicableCalendarData = this.retrieveApplicableCalendarFromHistory(calendarData.getId(), lastMeetingDate);
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), lastMeetingDate);
}
/**
* If nextEligibleMeetingDate is on or after current calendar startdate
* then regenerate the nextEligible meeting date based on
*/
if (nextEligibleMeetingDate == null) {
final LocalDate seedDate = (lastMeetingDate != null) ? lastMeetingDate : calendarData.getStartDate();
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), seedDate);
} else if (calendarData.isBetweenStartAndEndDate(nextEligibleMeetingDate)) {
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(),
calendarData.getStartDate());
}
return nextEligibleMeetingDate;
}
@Override
public Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData) {
final Collection<CalendarData> recuCalendarsData = new ArrayList<CalendarData>();
final boolean withHistory = true;
final LocalDate tillDate = null;
for (final CalendarData calendarData : calendarsData) {
recuCalendarsData.add(generateRecurringDate(calendarData));
final Collection<LocalDate> recurringDates = this.generateRecurringDates(calendarData, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.generateNextTenRecurringDates(calendarData);
final LocalDate recentEligibleMeetingDate = null;
final CalendarData updatedCalendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates,
recentEligibleMeetingDate);
recuCalendarsData.add(updatedCalendarData);
}
return recuCalendarsData;
@@ -278,4 +372,83 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
return conditionSql;
}
private CalendarData retrieveApplicableCalendarFromHistory(Long calendarId, LocalDate compareDate) {
try {
final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper();
final String sql = rm.schema() + " where c.calendar_id = ? and date(?) between c.start_date and c.end_date limit 1";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarId, compareDate.toDate() });
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private Collection<CalendarData> retrieveCalendarsFromHistory(Long calendarId) {
try {
final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper();
final String sql = rm.schema() + " where c.calendar_id = ? ";
final Collection<CalendarData> calendars = this.jdbcTemplate.query(sql, rm, new Object[] { calendarId });
return calendars;
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private static final class CalendarDataFromHistoryMapper implements RowMapper<CalendarData> {
public String schema() {
return " select c.calendar_id as id, c.title as title, c.description as description, c.location as location, c.start_date as startDate, "
+ " c.end_date as endDate, c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, "
+ " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder "
+ " from m_calendar_history c ";
}
@Override
public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long calendarInstanceId = null;
final Long entityId = null;
final EnumOptionData entityType = null;
final String title = rs.getString("title");
final String description = rs.getString("description");
final String location = rs.getString("location");
final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
final Integer duration = rs.getInt("duration");
final Integer typeId = rs.getInt("typeId");
final EnumOptionData type = CalendarEnumerations.calendarType(typeId);
final boolean repeating = rs.getBoolean("repeating");
final String recurrence = rs.getString("recurrence");
final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence));
final Integer interval = new Integer(CalendarUtils.getInterval(recurrence));
final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence));
final Integer remindById = rs.getInt("remindById");
EnumOptionData remindBy = null;
if (remindById != null && remindById != 0) {
remindBy = CalendarEnumerations.calendarRemindBy(remindById);
}
final Integer firstReminder = rs.getInt("firstReminder");
final Integer secondReminder = rs.getInt("secondReminder");
String humanReadable = null;
if (startDate != null && recurrence != null) {
humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence);
}
final LocalDate createdDate = null;
final LocalDate lastUpdatedDate = null;
final Long createdByUserId = null;
final String createdByUserName = null;
final Long lastUpdatedByUserId = null;
final String lastUpdatedByUserName = null;
return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate,
duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder,
humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId,
lastUpdatedByUserName);
}
}
}
@@ -32,6 +32,8 @@ import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarWeekDaysType;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public class CalendarUtils {
@@ -284,6 +286,20 @@ public class CalendarUtils {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return recur.getInterval();
}
public static CalendarFrequencyType getFrequency(final String recurringRule){
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return CalendarFrequencyType.fromString(recur.getFrequency());
}
public static CalendarWeekDaysType getRepeatsOnDay(final String recurringRule){
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
final WeekDayList weekDays = recur.getDayList();
if(weekDays.isEmpty()) return CalendarWeekDaysType.INVALID;
//supports only one day
WeekDay weekDay = (WeekDay) weekDays.get(0);
return CalendarWeekDaysType.fromString(weekDay.getDay());
}
public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate,
final Integer loanRepaymentInterval, final String frequency) {
@@ -5,20 +5,40 @@
*/
package org.mifosplatform.portfolio.calendar.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarHistory;
import org.mifosplatform.portfolio.calendar.domain.CalendarHistoryRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.calendar.serialization.CalendarCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -28,29 +48,90 @@ import org.springframework.util.CollectionUtils;
public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWritePlatformService {
private final CalendarRepository calendarRepository;
private final CalendarHistoryRepository calendarHistoryRepository;
private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanWritePlatformService loanWritePlatformService;
private final ConfigurationDomainService configurationDomainService;
private final GroupRepositoryWrapper groupRepository;
private final LoanRepository loanRepository;
private final ClientRepositoryWrapper clientRepository;
@Autowired
public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository,
final CalendarHistoryRepository calendarHistoryRepository,
final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final CalendarInstanceRepository calendarInstanceRepository, final LoanWritePlatformService loanWritePlatformService,
final ConfigurationDomainService configurationDomainService) {
final ConfigurationDomainService configurationDomainService, final GroupRepositoryWrapper groupRepository,
final LoanRepository loanRepository, final ClientRepositoryWrapper clientRepository) {
this.calendarRepository = calendarRepository;
this.calendarHistoryRepository = calendarHistoryRepository;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.calendarInstanceRepository = calendarInstanceRepository;
this.loanWritePlatformService = loanWritePlatformService;
this.configurationDomainService = configurationDomainService;
this.groupRepository = groupRepository;
this.loanRepository = loanRepository;
this.clientRepository = clientRepository;
}
@Override
public CommandProcessingResult createCalendar(final JsonCommand command) {
this.fromApiJsonDeserializer.validateForCreate(command.json());
final Long entityId = command.getSupportedEntityId();
final CalendarEntityType entityType = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase());
LocalDate entityActivationDate = null;
Group centerOrGroup = null;
if (entityType.isCenter() || entityType.isGroup()) {
centerOrGroup = this.groupRepository.findOneWithNotFoundDetection(entityId);
entityActivationDate = centerOrGroup.getActivationLocalDate();
} else if (entityType.isLoan()) {
final Loan loan = this.loanRepository.findOne(entityId);
entityActivationDate = (loan.getApprovedOnDate() == null) ? loan.getSubmittedOnDate() : loan.getApprovedOnDate();
} else if (entityType.isLoan()) {
final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
entityActivationDate = client.getActivationLocalDate();
}
final Calendar newCalendar = Calendar.fromJson(command);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar");
if (entityActivationDate == null || newCalendar.getStartDateLocalDate().isBefore(entityActivationDate)) {
final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
String dateAsString = "";
if (entityActivationDate != null) dateAsString = formatter.print(entityActivationDate);
final String errorMessage = "cannot.be.before." + entityType.name().toLowerCase() + ".activation.date";
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(dateAsString)
.failWithCodeNoParameterAddedToErrorCode(errorMessage);
}
if (entityType.isCenter() || entityType.isGroup()) {
Long centerOrGroupId = entityId;
Integer centerOrGroupEntityTypeId = entityType.getValue();
if (entityType.isGroup()) {
@SuppressWarnings("null")
final Group parent = centerOrGroup.getParent();
if (parent != null) {
centerOrGroupId = parent.getId();
centerOrGroupEntityTypeId = CalendarEntityType.CENTERS.getValue();
}
}
final CalendarInstance collectionCalendarInstance = this.calendarInstanceRepository
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(centerOrGroupId, centerOrGroupEntityTypeId,
CalendarType.COLLECTION.getValue());
if (collectionCalendarInstance != null) {
final String errorMessage = "multiple.collection.calendar.not.supported";
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
this.calendarRepository.save(newCalendar);
final CalendarInstance newCalendarInstance = CalendarInstance.fromJson(newCalendar, command);
@@ -71,25 +152,30 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
final Long calendarId = command.entityId();
final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
final Date oldStartDate = calendarForUpdate.getStartDate();
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
//create calendar history before updating calendar
final CalendarHistory calendarHistory = new CalendarHistory(calendarForUpdate, oldStartDate);
final Map<String, Object> changes = calendarForUpdate.update(command);
if (!changes.isEmpty()) {
//update calendar history table only if there is a change in calendar start date.
if(currentDate.isAfter(new LocalDate(oldStartDate))){
final Date endDate = calendarForUpdate.getStartDateLocalDate().minusDays(1).toDate();
calendarHistory.updateEndDate(endDate);
this.calendarHistoryRepository.save(calendarHistory);
}
this.calendarRepository.saveAndFlush(calendarForUpdate);
if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled()) {
// In this approach loans following this meeting calendar will
// be affected immediately.
// get all calendar instances following this meeting calendar
// and calendar entity type is loan
if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled() && calendarForUpdate.isRepeating()) {
//fetch all loan calendar instances associated with modifying calendar.
final Collection<CalendarInstance> loanCalendarInstances = this.calendarInstanceRepository.findByCalendarIdAndEntityTypeId(
calendarId, CalendarEntityType.LOANS.getValue());
if (!CollectionUtils.isEmpty(loanCalendarInstances)) {
// update all loans which are following this meeting
// calendar
//update all loans associated with modifying calendar
this.loanWritePlatformService.applyMeetingDateChanges(calendarForUpdate, loanCalendarInstances);
//
}
}
}
@@ -29,7 +29,12 @@ public enum ChargeCalculationType {
return this.code;
}
public static Object[] validValues() {
public static Object[] validValuesForLoan() {
return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue(),
ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue(),ChargeCalculationType.PERCENT_OF_INTEREST.getValue() };
}
public static Object[] validValuesForSavings() {
return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue() };
}
@@ -56,6 +61,14 @@ public enum ChargeCalculationType {
return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue());
}
public boolean isPercentageOfAmountAndInterest() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue());
}
public boolean isPercentageOfInterest() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_INTEREST.getValue());
}
public boolean isFlat() {
return this.value.equals(ChargeCalculationType.FLAT.getValue());
}
@@ -63,4 +76,8 @@ public enum ChargeCalculationType {
public boolean isAllowedSavingsChargeCalculationType() {
return isFlat() || isPercentageOfAmount();
}
public boolean isPercentageBased(){
return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest();
}
}
@@ -16,7 +16,9 @@ public enum ChargeTimeType {
SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings
WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings
ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings
MONTHLY_FEE(7, "chargeTimeType.monthlyFee"); // only for savings
MONTHLY_FEE(7, "chargeTimeType.monthlyFee"), // only for savings
INSTALMENT_FEE(8, "chargeTimeType.instalmentFee"),// only for loan charges
OVERDUE_INSTALLMENT(9, "chargeTimeType.overdueInstallment"); // only for loan charges
private final Integer value;
private final String code;
@@ -35,7 +37,8 @@ public enum ChargeTimeType {
}
public static Object[] validLoanValues() {
return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue() };
return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
ChargeTimeType.INSTALMENT_FEE.getValue(),ChargeTimeType.OVERDUE_INSTALLMENT.getValue() };
}
public static Object[] validSavingsValues() {
@@ -69,6 +72,12 @@ public enum ChargeTimeType {
case 7:
chargeTimeType = MONTHLY_FEE;
break;
case 8:
chargeTimeType = INSTALMENT_FEE;
break;
case 9:
chargeTimeType = OVERDUE_INSTALLMENT;
break;
default:
chargeTimeType = INVALID;
break;
@@ -105,8 +114,16 @@ public enum ChargeTimeType {
return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue());
}
public boolean isInstalmentFee() {
return this.value.equals(ChargeTimeType.INSTALMENT_FEE.getValue());
}
public boolean isOverdueInstallment(){
return this.value.equals(ChargeTimeType.OVERDUE_INSTALLMENT.getValue());
}
public boolean isAllowedLoanChargeTime() {
return isTimeOfDisbursement() || isOnSpecifiedDueDate();
return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee() || isOverdueInstallment();
}
public boolean isAllowedSavingsChargeTime() {
@@ -11,7 +11,7 @@ public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRule
/*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_PAYED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, CHARGE_NOT_ACCOUNT_TRANSFER;
ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, CHARGE_NOT_ACCOUNT_TRANSFER,CHARGE_NOT_PAYABLE;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
@@ -20,7 +20,11 @@ public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRule
return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "This loan charge can be payed as the loan associated with it is currently inactive";
} else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) { return "This loan charge can be payed as the charge payment mode is not account transfer"; }
} else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
return "This loan charge can be payed as the charge payment mode is not account transfer";
}else if (name().toString().equalsIgnoreCase("CHARGE_NOT_PAYABLE")) {
return "This loan charge is not Payable through account transfer";
}
return name().toString();
}
@@ -32,7 +36,11 @@ public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRule
return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "error.msg.loan.charge.associated.loan.inactive";
} else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) { return "error.msg.loan.charge.payment.mode.not.account.transfer"; }
} else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
return "error.msg.loan.charge.payment.mode.not.account.transfer";
}else if (name().toString().equalsIgnoreCase("CHARGE_NOT_PAYABLE")) {
return "error.msg.loan.charge.payment.not.allowed.account.transfer";
}
return name().toString();
}
}
@@ -18,7 +18,9 @@ public class LoanChargeCannotBeUpdatedException extends AbstractPlatformDomainRu
return "This loan charge has been partially/completely paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be updated as the loan it is associated with is not in submitted and pending approval stage"; }
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) {
return "This charge cannot be updated as the loan it is associated with is not in submitted and pending approval stage";
}
return name().toString();
}
@@ -27,7 +29,9 @@ public class LoanChargeCannotBeUpdatedException extends AbstractPlatformDomainRu
return "error.msg.loan.charge.already.paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) {
return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage";
}
return name().toString();
}
}
@@ -11,14 +11,19 @@ public class LoanChargeCannotBeWaivedException extends AbstractPlatformDomainRul
/*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_WAIVED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE;
ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, WAIVE_NOT_ALLOWED_FOR_CHARGE;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "This loan charge has been completely paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { return "This loan charge can be waived as the loan associated with it is currently inactive"; }
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "This loan charge can be waived as the loan associated with it is currently inactive";
} else if(name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")){
return "This loan charge can be waived";
}
return name().toString();
}
@@ -27,7 +32,11 @@ public class LoanChargeCannotBeWaivedException extends AbstractPlatformDomainRul
return "error.msg.loan.charge.already.paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { return "error.msg.loan.charge.associated.loan.inactive"; }
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "error.msg.loan.charge.associated.loan.inactive";
} else if(name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) {
return "error.msg.loan.charge.waive.not.allowed";
}
return name().toString();
}
}
@@ -67,6 +67,9 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).isOneOfTheseValues(ChargeAppliesTo.validValues());
}
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeCalculationType", element);
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull();
final ChargeAppliesTo appliesTo = ChargeAppliesTo.fromInt(chargeAppliesTo);
if (appliesTo.isLoanCharge()) {
// loan applicable validation
@@ -81,6 +84,10 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
if (chargePaymentMode != null) {
baseDataValidator.reset().parameter("chargePaymentMode").value(chargePaymentMode).isOneOfTheseValues(ChargePaymentMode.validValues());
}
if (chargeCalculationType != null) {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).isOneOfTheseValues(ChargeCalculationType.validValuesForLoan());
}
} else if (appliesTo.isSavingsCharge()) {
// savings applicable validation
@@ -103,6 +110,10 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed("feeOnMonthDay", element);
baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
}
if (chargeCalculationType != null) {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).isOneOfTheseValues(ChargeCalculationType.validValuesForSavings());
}
}
final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
@@ -124,12 +135,6 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("active").value(active).notNull();
}
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeCalculationType", element);
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull();
if (chargeCalculationType != null) {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).isOneOfTheseValues(ChargeCalculationType.validValues());
}
if (this.fromApiJsonHelper.parameterExists("minCap", element)) {
final BigDecimal minCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minCap", element.getAsJsonObject());
baseDataValidator.reset().parameter("minCap").value(minCap).notNull().positiveAmount();
@@ -5,14 +5,6 @@
*/
package org.mifosplatform.portfolio.charge.service;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeCalculationType;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargePaymentMode;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeTimeType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.charge.domain.ChargeAppliesTo;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
@@ -20,6 +12,12 @@ import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.*;
@Service
public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownReadPlatformService {
@@ -27,9 +25,9 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
public List<EnumOptionData> retrieveCalculationTypes() {
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT)
// chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST),
// chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST)
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST)
);
}
@@ -64,12 +62,12 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
@Override
public List<EnumOptionData> retrieveLoanCalculationTypes() {
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT));
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST), chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST));
}
@Override
public List<EnumOptionData> retrieveLoanCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE));
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE),chargeTimeType(ChargeTimeType.INSTALMENT_FEE),chargeTimeType(ChargeTimeType.OVERDUE_INSTALLMENT));
}
@Override
@@ -43,6 +43,12 @@ public class ChargeEnumerations {
case MONTHLY_FEE:
optionData = new EnumOptionData(ChargeTimeType.MONTHLY_FEE.getValue().longValue(), ChargeTimeType.MONTHLY_FEE.getCode(), "Monthly Fee");
break;
case INSTALMENT_FEE:
optionData = new EnumOptionData(ChargeTimeType.INSTALMENT_FEE.getValue().longValue(), ChargeTimeType.INSTALMENT_FEE.getCode(), "Instalment Fee");
break;
case OVERDUE_INSTALLMENT:
optionData = new EnumOptionData(ChargeTimeType.OVERDUE_INSTALLMENT.getValue().longValue(),ChargeTimeType.OVERDUE_INSTALLMENT.getCode(),"overdue fees") ;
break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break;
@@ -25,6 +25,7 @@ public class ClientApiConstants {
public static final String groupIdParamName = "groupId";
public static final String accountNoParamName = "accountNo";
public static final String externalIdParamName = "externalId";
public static final String mobileNoParamName = "mobileNo";
public static final String firstnameParamName = "firstname";
public static final String middlenameParamName = "middlename";
public static final String lastnameParamName = "lastname";
@@ -36,6 +37,7 @@ public class ClientApiConstants {
public static final String staffIdParamName = "staffId";
public static final String closureDateParamName = "closureDate";
public static final String closureReasonIdParamName = "closureReasonId";
public static final String submittedOnDateParamName="submittedOnDate";
// response parameters
public static final String statusParamName = "status";
public static final String hierarchyParamName = "hierarchy";
@@ -53,12 +55,13 @@ public class ClientApiConstants {
public static final String staffOptionsParamName = "staffOptions";
public static final Set<String> CLIENT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, groupIdParamName, accountNoParamName, externalIdParamName, firstnameParamName, middlenameParamName,
lastnameParamName, fullnameParamName, officeIdParamName, activeParamName, activationDateParamName, staffIdParamName));
dateFormatParamName, groupIdParamName, accountNoParamName, externalIdParamName, mobileNoParamName, firstnameParamName,
middlenameParamName, lastnameParamName, fullnameParamName, officeIdParamName, activeParamName, activationDateParamName,
staffIdParamName,submittedOnDateParamName));
public static final Set<String> CLIENT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, accountNoParamName, externalIdParamName, firstnameParamName, middlenameParamName, lastnameParamName,
fullnameParamName, activeParamName, activationDateParamName, staffIdParamName));
dateFormatParamName, accountNoParamName, externalIdParamName, mobileNoParamName, firstnameParamName, middlenameParamName,
lastnameParamName, fullnameParamName, activeParamName, activationDateParamName, staffIdParamName));
/**
* These parameters will match the class level parameters of
@@ -66,7 +69,7 @@ public class ClientApiConstants {
* match those of request parameters.
*/
public static final Set<String> CLIENT_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, accountNoParamName,
externalIdParamName, statusParamName, activeParamName, activationDateParamName, firstnameParamName, middlenameParamName,
externalIdParamName, mobileNoParamName, statusParamName, activeParamName, activationDateParamName, firstnameParamName, middlenameParamName,
lastnameParamName, fullnameParamName, displayNameParamName, officeIdParamName, officeNameParamName, transferOfficeIdParamName,
trasnferOfficeNameParamName, hierarchyParamName, imageKeyParamName, imagePresentParamName, groupsParamName,
officeOptionsParamName, staffOptionsParamName));
@@ -135,6 +135,7 @@ public class ClientsApiResource {
clientData = ClientData.templateOnTop(clientData, templateData);
}
return this.toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
}
@@ -1,61 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.client.command;
import org.joda.time.LocalDate;
public class ClientCommand {
@SuppressWarnings("unused")
private final String accountNo;
private final String externalId;
private final String firstname;
private final String middlename;
private final String lastname;
private final String fullname;
private final Long officeId;
private final LocalDate joinedDate;
public ClientCommand(final String accountNo, final String externalId, final String firstname, final String middlename,
final String lastname, final String fullname, final Long officeId, final LocalDate joinedDate) {
this.accountNo = accountNo;
this.externalId = externalId;
this.firstname = firstname;
this.middlename = middlename;
this.lastname = lastname;
this.fullname = fullname;
this.officeId = officeId;
this.joinedDate = joinedDate;
}
public String getExternalId() {
return this.externalId;
}
public String getFirstname() {
return this.firstname;
}
public String getMiddlename() {
return this.middlename;
}
public String getLastname() {
return this.lastname;
}
public String getFullname() {
return this.fullname;
}
public Long getOfficeId() {
return this.officeId;
}
public LocalDate getJoiningDate() {
return this.joinedDate;
}
}
@@ -37,6 +37,7 @@ final public class ClientData implements Comparable<ClientData> {
private final String lastname;
private final String fullname;
private final String displayName;
private final String mobileNo;
private final Long officeId;
private final String officeName;
@@ -47,6 +48,7 @@ final public class ClientData implements Comparable<ClientData> {
private final Boolean imagePresent;
private final Long staffId;
private final String staffName;
private final ClientTimelineData timeline;
// associations
private final Collection<GroupGeneralData> groups;
@@ -59,25 +61,28 @@ final public class ClientData implements Comparable<ClientData> {
public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection<OfficeData> officeOptions,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> closureReasons) {
return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, null, null, joinedDate, null, null,
null, officeOptions, null, staffOptions, closureReasons);
return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, null, null, null, joinedDate, null,
null, null, officeOptions, null, staffOptions, closureReasons,null);
}
public static ClientData templateOnTop(final ClientData clientData, final ClientData templateData) {
return new ClientData(clientData.accountNo, clientData.status, clientData.officeId, clientData.officeName,
clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.activationDate,
clientData.imageId, clientData.staffId, clientData.staffName, templateData.officeOptions, clientData.groups,
templateData.staffOptions, null);
clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
clientData.activationDate, clientData.imageId, clientData.staffId, clientData.staffName, templateData.officeOptions,
clientData.groups, templateData.staffOptions, null,null);
}
public static ClientData setParentGroups(final ClientData clientData, final Collection<GroupGeneralData> parentGroups) {
return new ClientData(clientData.accountNo, clientData.status, clientData.officeId, clientData.officeName,
clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.activationDate,
clientData.imageId, clientData.staffId, clientData.staffName, clientData.officeOptions, parentGroups,
clientData.staffOptions, null);
clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
clientData.activationDate, clientData.imageId, clientData.staffId, clientData.staffName, clientData.officeOptions,
parentGroups, clientData.staffOptions, null,clientData.timeline);
}
public static ClientData clientIdentifier(final Long id, final String accountNo, final EnumOptionData status, final String firstname,
@@ -85,28 +90,31 @@ final public class ClientData implements Comparable<ClientData> {
final String officeName) {
return new ClientData(accountNo, status, officeId, officeName, null, null, id, firstname, middlename, lastname, fullname,
displayName, null, null, null, null, null, null, null, null, null);
displayName, null, null, null, null, null, null, null, null, null, null,null);
}
public static ClientData lookup(final Long id, final String displayName, final Long officeId, final String officeName) {
return new ClientData(null, null, officeId, officeName, null, null, id, null, null, null, null, displayName, null, null, null,
null, null, null, null, null, null);
null, null, null, null, null, null, null,null);
}
public static ClientData instance(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName,
final Long transferToOfficeId, final String transferToOfficeName, final Long id, final String firstname,
final String middlename, final String lastname, final String fullname, final String displayName, final String externalId,
final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName) {
final String mobileNo, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,final ClientTimelineData timeline) {
return new ClientData(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname, middlename,
lastname, fullname, displayName, externalId, activationDate, imageId, staffId, staffName, null, null, null, null);
lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName, null, null, null, null,timeline);
}
private ClientData(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName,
final Long transferToOfficeId, final String transferToOfficeName, final Long id, final String firstname,
final String middlename, final String lastname, final String fullname, final String displayName, final String externalId,
final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
final String mobileNo, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
final Collection<OfficeData> allowedOffices, final Collection<GroupGeneralData> groups,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> closureReasons) {
final Collection<StaffData> staffOptions, final Collection<CodeValueData> closureReasons,final ClientTimelineData timeline) {
this.accountNo = accountNo;
this.status = status;
if (status != null) {
@@ -125,6 +133,7 @@ final public class ClientData implements Comparable<ClientData> {
this.fullname = StringUtils.defaultIfEmpty(fullname, null);
this.displayName = StringUtils.defaultIfEmpty(displayName, null);
this.externalId = StringUtils.defaultIfEmpty(externalId, null);
this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
this.activationDate = activationDate;
this.imageId = imageId;
if (imageId != null) {
@@ -142,6 +151,8 @@ final public class ClientData implements Comparable<ClientData> {
this.officeOptions = allowedOffices;
this.staffOptions = staffOptions;
this.closureReasons = closureReasons;
this.timeline = timeline;
}
public Long id() {
@@ -168,12 +179,17 @@ final public class ClientData implements Comparable<ClientData> {
return this.imagePresent;
}
public ClientTimelineData getTimeline() {
return this.timeline;
}
@Override
public int compareTo(final ClientData obj) {
if (obj == null) { return -1; }
return new CompareToBuilder() //
.append(this.id, obj.id) //
.append(this.displayName, obj.displayName) //
.append(this.mobileNo, obj.mobileNo) //
.toComparison();
}
@@ -186,6 +202,7 @@ final public class ClientData implements Comparable<ClientData> {
return new EqualsBuilder() //
.append(this.id, rhs.id) //
.append(this.displayName, rhs.displayName) //
.append(this.mobileNo, rhs.mobileNo) //
.isEquals();
}
@@ -115,6 +115,12 @@ public final class ClientDataValidator {
.notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).ignoreIfNull()
.notExceedingLengthOf(50);
}
final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(ClientApiConstants.activeParamName, element);
if (active != null) {
if (active.booleanValue()) {
@@ -126,6 +132,12 @@ public final class ClientDataValidator {
baseDataValidator.reset().parameter(ClientApiConstants.activeParamName).value(active).trueOrFalseRequired(false);
}
if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.submittedOnDateParamName, element))
{
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(ClientApiConstants.submittedOnDateParamName, element);
baseDataValidator.reset().parameter(ClientApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -276,6 +288,12 @@ public final class ClientDataValidator {
baseDataValidator.reset().parameter(ClientApiConstants.externalIdParamName).value(externalId).notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(ClientApiConstants.mobileNoParamName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String mobileNo = this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.mobileNoParamName, element);
baseDataValidator.reset().parameter(ClientApiConstants.mobileNoParamName).value(mobileNo).notExceedingLengthOf(50);
}
final Boolean active = this.fromApiJsonHelper.extractBooleanNamed(ClientApiConstants.activeParamName, element);
if (active != null) {
if (active.booleanValue()) {
@@ -0,0 +1,60 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.client.data;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import java.math.BigDecimal;
/**
* Immutable data object represent the important time-line events of a loan
* application and loan.
*/
@SuppressWarnings("unused")
public class ClientTimelineData {
private final LocalDate submittedOnDate;
private final String submittedByUsername;
private final String submittedByFirstname;
private final String submittedByLastname;
private final LocalDate activatedOnDate;
private final String activatedByUsername;
private final String activatedByFirstname;
private final String activatedByLastname;
private final LocalDate closedOnDate;
private final String closedByUsername;
private final String closedByFirstname;
private final String closedByLastname;
public ClientTimelineData(final LocalDate submittedOnDate, final String submittedByUsername,
final String submittedByFirstname, final String submittedByLastname, final LocalDate activatedOnDate,
final String activatedByUsername, final String activatedByFirstname, final String activatedByLastname,
final LocalDate closedOnDate, final String closedByUsername, final String closedByFirstname,
final String closedByLastname) {
this.submittedOnDate = submittedOnDate;
this.submittedByUsername = submittedByUsername;
this.submittedByFirstname = submittedByFirstname;
this.submittedByLastname = submittedByLastname;
this.activatedOnDate = activatedOnDate;
this.activatedByUsername = activatedByUsername;
this.activatedByFirstname = activatedByFirstname;
this.activatedByLastname = activatedByLastname;
this.closedOnDate = closedOnDate;
this.closedByUsername = closedByUsername;
this.closedByFirstname = closedByFirstname;
this.closedByLastname = closedByLastname;
}
}
@@ -27,6 +27,7 @@ import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
@@ -44,10 +45,12 @@ import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.client.api.ClientApiConstants;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_client", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "account_no_UNIQUE") })
@Table(name = "m_client", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "account_no_UNIQUE"), //
@UniqueConstraint(columnNames = { "mobile_no" }, name = "mobile_no_UNIQUE") })
public final class Client extends AbstractPersistable<Long> {
@Column(name = "account_no", length = 20, unique = true, nullable = false)
@@ -94,6 +97,9 @@ public final class Client extends AbstractPersistable<Long> {
@Column(name = "display_name", length = 100, nullable = false)
private String displayName;
@Column(name = "mobile_no", length = 50, nullable = false, unique = true)
private String mobileNo;
@Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId;
@@ -117,10 +123,28 @@ public final class Client extends AbstractPersistable<Long> {
@Temporal(TemporalType.DATE)
private Date closureDate;
public static Client createNew(final Office clientOffice, final Group clientParentGroup, final Staff staff, final JsonCommand command) {
@ManyToOne(optional = true)
@JoinColumn(name = "closedon_userid", nullable = true)
private AppUser closeddBy;
@Column(name = "submittedon_date", nullable = true)
@Temporal(TemporalType.DATE)
private Date submittedOnDate;
@ManyToOne(optional = true)
@JoinColumn(name = "submittedon_userid", nullable = true)
private AppUser submittedBy;
@ManyToOne(optional = true)
@JoinColumn(name = "activatedon_userid", nullable = true)
private AppUser activatedBy;
public static Client createNew(final AppUser currentUser, final Office clientOffice, final Group clientParentGroup, final Staff staff,
final JsonCommand command) {
final String accountNo = command.stringValueOfParameterNamed(ClientApiConstants.accountNoParamName);
final String externalId = command.stringValueOfParameterNamed(ClientApiConstants.externalIdParamName);
final String mobileNo = command.stringValueOfParameterNamed(ClientApiConstants.mobileNoParamName);
final String firstname = command.stringValueOfParameterNamed(ClientApiConstants.firstnameParamName);
final String middlename = command.stringValueOfParameterNamed(ClientApiConstants.middlenameParamName);
@@ -141,23 +165,37 @@ public final class Client extends AbstractPersistable<Long> {
officeJoiningDate = activationDate;
}
return new Client(status, clientOffice, clientParentGroup, accountNo, firstname, middlename, lastname, fullname, activationDate,
officeJoiningDate, externalId, staff);
LocalDate submittedOnDate = new LocalDate();
if (active && submittedOnDate.isAfter(activationDate)) {
submittedOnDate = activationDate;
}
if (command.hasParameter(ClientApiConstants.submittedOnDateParamName)) {
submittedOnDate = command.localDateValueOfParameterNamed(ClientApiConstants.submittedOnDateParamName);
}
return new Client(currentUser, status, clientOffice, clientParentGroup, accountNo, firstname, middlename, lastname, fullname,
activationDate, officeJoiningDate, externalId, mobileNo, staff, submittedOnDate);
}
protected Client() {
//
}
private Client(final ClientStatus status, final Office office, final Group clientParentGroup, final String accountNo,
final String firstname, final String middlename, final String lastname, final String fullname, final LocalDate activationDate,
final LocalDate officeJoiningDate, final String externalId, final Staff staff) {
private Client(final AppUser currentUser, final ClientStatus status, final Office office, final Group clientParentGroup,
final String accountNo, final String firstname, final String middlename, final String lastname, final String fullname,
final LocalDate activationDate, final LocalDate officeJoiningDate, final String externalId, final String mobileNo,
final Staff staff, final LocalDate submittedOnDate) {
if (StringUtils.isBlank(accountNo)) {
this.accountNumber = new RandomPasswordGenerator(19).generate();
this.accountNumberRequiresAutoGeneration = true;
} else {
this.accountNumber = accountNo;
}
this.submittedOnDate = submittedOnDate.toDate();
this.submittedBy = currentUser;
this.status = status.getValue();
this.office = office;
if (StringUtils.isNotBlank(externalId)) {
@@ -165,11 +203,19 @@ public final class Client extends AbstractPersistable<Long> {
} else {
this.externalId = null;
}
if (StringUtils.isNotBlank(mobileNo)) {
this.mobileNo = mobileNo.trim();
} else {
this.mobileNo = null;
}
if (activationDate != null) {
this.activationDate = activationDate.toDateMidnight().toDate();
this.activationDate = activationDate.toDateTimeAtStartOfDay().toDate();
this.activatedBy = currentUser;
}
if (officeJoiningDate != null) {
this.officeJoiningDate = officeJoiningDate.toDateMidnight().toDate();
this.officeJoiningDate = officeJoiningDate.toDateTimeAtStartOfDay().toDate();
}
if (StringUtils.isNotBlank(firstname)) {
this.firstname = firstname.trim();
@@ -234,7 +280,8 @@ public final class Client extends AbstractPersistable<Long> {
this.accountNumberRequiresAutoGeneration = false;
}
public void activate(final DateTimeFormatter formatter, final LocalDate activationLocalDate) {
public void activate(final AppUser currentUser, final DateTimeFormatter formatter, final LocalDate activationLocalDate) {
if (isActive()) {
final String defaultUserMessage = "Cannot activate client. Client is already active.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.already.active", defaultUserMessage,
@@ -246,19 +293,8 @@ public final class Client extends AbstractPersistable<Long> {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (isDateInTheFuture(activationLocalDate)) {
final String defaultUserMessage = "Activation date cannot be in the future.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.activationDate.in.the.future",
defaultUserMessage, ClientApiConstants.activationDateParamName, activationLocalDate);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(dataValidationErrors);
}
this.activationDate = activationLocalDate.toDate();
this.activatedBy = currentUser;
this.officeJoiningDate = this.activationDate;
this.status = ClientStatus.ACTIVE.getValue();
@@ -323,6 +359,12 @@ public final class Client extends AbstractPersistable<Long> {
this.externalId = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInStringParameterNamed(ClientApiConstants.mobileNoParamName, this.mobileNo)) {
final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.mobileNoParamName);
actualChanges.put(ClientApiConstants.mobileNoParamName, newValue);
this.mobileNo = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInStringParameterNamed(ClientApiConstants.firstnameParamName, this.firstname)) {
final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.firstnameParamName);
actualChanges.put(ClientApiConstants.firstnameParamName, newValue);
@@ -398,7 +440,35 @@ public final class Client extends AbstractPersistable<Long> {
}
private void validateActivationDate(final List<ApiParameterError> dataValidationErrors) {
if (this.activationDate != null) {
if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) {
final String defaultUserMessage = "submitted date cannot be in the future.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.in.the.future",
defaultUserMessage, ClientApiConstants.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.clients.submittedOnDate.after.activation.date",
defaultUserMessage, ClientApiConstants.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.clients.activationDate.in.the.future",
defaultUserMessage, ClientApiConstants.activationDateParamName, getActivationLocalDate());
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null) {
if (this.office.isOpeningDateAfter(getActivationLocalDate())) {
final String defaultUserMessage = "Client activation date cannot be a date before the office opening date.";
final ApiParameterError error = ApiParameterError.parameterError(
@@ -431,6 +501,10 @@ public final class Client extends AbstractPersistable<Long> {
this.displayName = nameBuilder.toString();
}
public LocalDate getSubmittedOnDate() {
return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null);
}
public LocalDate getActivationLocalDate() {
LocalDate activationLocalDate = null;
if (this.activationDate != null) {
@@ -463,6 +537,14 @@ public final class Client extends AbstractPersistable<Long> {
return this.image;
}
public String mobileNo() {
return this.mobileNo;
}
public void setMobileNo(final String mobileNo) {
this.mobileNo = mobileNo;
}
public String getDisplayName() {
return this.displayName;
}
@@ -519,9 +601,10 @@ public final class Client extends AbstractPersistable<Long> {
return this.groups;
}
public void close(final CodeValue closureReason, final Date closureDate) {
public void close(final AppUser currentUser, final CodeValue closureReason, final Date closureDate) {
this.closureReason = closureReason;
this.closureDate = closureDate;
this.closeddBy = currentUser;
this.status = ClientStatus.CLOSED.getValue();
}
@@ -27,6 +27,7 @@ import org.mifosplatform.organisation.office.service.OfficeReadPlatformService;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.organisation.staff.service.StaffReadPlatformService;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.data.ClientTimelineData;
import org.mifosplatform.portfolio.client.domain.ClientEnumerations;
import org.mifosplatform.portfolio.client.domain.ClientStatus;
import org.mifosplatform.portfolio.client.exception.ClientNotFoundException;
@@ -93,7 +94,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
@Override
public Page<ClientData> retrieveAll(final SearchParameters searchParameters) {
final String userOfficeHierarchy = this.context.OfficeHierarchy();
final String userOfficeHierarchy = this.context.officeHierarchy();
final String underHierarchySearchString = userOfficeHierarchy + "%";
// if (searchParameters.isScopedByOfficeHierarchy()) {
@@ -183,8 +184,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
@Override
public ClientData retrieveOne(final Long clientId) {
try {
final AppUser currentUser = this.context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy();
final String hierarchy = this.context.officeHierarchy();
final String hierarchySearchString = hierarchy + "%";
final String sql = "select " + this.clientMapper.schema()
@@ -262,14 +262,34 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
sqlBuilder.append("c.transfer_to_office_id as transferToOfficeId, transferToOffice.name as transferToOfficeName, ");
sqlBuilder.append("c.firstname as firstname, c.middlename as middlename, c.lastname as lastname, ");
sqlBuilder.append("c.fullname as fullname, c.display_name as displayName, ");
sqlBuilder.append("c.mobile_no as mobileNo, ");
sqlBuilder.append("c.activation_date as activationDate, c.image_id as imageId, ");
sqlBuilder.append("c.staff_id as staffId, s.display_name as staffName ");
sqlBuilder.append("c.staff_id as staffId, s.display_name as staffName,");
sqlBuilder.append("c.submittedon_date as submittedOnDate, ");
sqlBuilder.append("sbu.username as submittedByUsername, ");
sqlBuilder.append("sbu.firstname as submittedByFirstname, ");
sqlBuilder.append("sbu.lastname as submittedByLastname, ");
sqlBuilder.append("c.closedon_date as closedOnDate, ");
sqlBuilder.append("clu.username as closedByUsername, ");
sqlBuilder.append("clu.firstname as closedByFirstname, ");
sqlBuilder.append("clu.lastname as closedByLastname, ");
sqlBuilder.append("acu.username as activatedByUsername, ");
sqlBuilder.append("acu.firstname as activatedByFirstname, ");
sqlBuilder.append("acu.lastname as activatedByLastname ");
sqlBuilder.append("from m_client c ");
sqlBuilder.append("join m_office o on o.id = c.office_id ");
sqlBuilder.append("join m_group_client pgc on pgc.client_id = c.id ");
sqlBuilder.append("left join m_staff s on s.id = c.staff_id ");
sqlBuilder.append("left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id ");
sqlBuilder.append("left join m_appuser sbu on sbu.id = c.submittedon_userid ");
sqlBuilder.append("left join m_appuser acu on acu.id = c.activatedon_userid ");
sqlBuilder.append("left join m_appuser clu on clu.id = c.closedon_userid ");
this.schema = sqlBuilder.toString();
}
@@ -298,13 +318,35 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
final String fullname = rs.getString("fullname");
final String displayName = rs.getString("displayName");
final String externalId = rs.getString("externalId");
final String mobileNo = rs.getString("mobileNo");
final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
final Long imageId = JdbcSupport.getLong(rs, "imageId");
final Long staffId = JdbcSupport.getLong(rs, "staffId");
final String staffName = rs.getString("staffName");
final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
final String closedByUsername = rs.getString("closedByUsername");
final String closedByFirstname = rs.getString("closedByFirstname");
final String closedByLastname = rs.getString("closedByLastname");
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final String submittedByUsername = rs.getString("submittedByUsername");
final String submittedByFirstname = rs.getString("submittedByFirstname");
final String submittedByLastname = rs.getString("submittedByLastname");
final String activatedByUsername = rs.getString("activatedByUsername");
final String activatedByFirstname = rs.getString("activatedByFirstname");
final String activatedByLastname = rs.getString("activatedByLastname");
final ClientTimelineData timeline = new ClientTimelineData (submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname,
activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername,
closedByFirstname, closedByLastname);
return ClientData.instance(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
middlename, lastname, fullname, displayName, externalId, activationDate, imageId, staffId, staffName);
middlename, lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName,timeline);
}
}
@@ -335,12 +377,35 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
builder.append("c.transfer_to_office_id as transferToOfficeId, transferToOffice.name as transferToOfficeName, ");
builder.append("c.firstname as firstname, c.middlename as middlename, c.lastname as lastname, ");
builder.append("c.fullname as fullname, c.display_name as displayName, ");
builder.append("c.mobile_no as mobileNo, ");
builder.append("c.submittedon_date as submittedOnDate, ");
builder.append("sbu.username as submittedByUsername, ");
builder.append("sbu.firstname as submittedByFirstname, ");
builder.append("sbu.lastname as submittedByLastname, ");
builder.append("c.closedon_date as closedOnDate, ");
builder.append("clu.username as closedByUsername, ");
builder.append("clu.firstname as closedByFirstname, ");
builder.append("clu.lastname as closedByLastname, ");
// builder.append("c.submittedon as submittedOnDate, ");
builder.append("acu.username as activatedByUsername, ");
builder.append("acu.firstname as activatedByFirstname, ");
builder.append("acu.lastname as activatedByLastname, ");
builder.append("c.activation_date as activationDate, c.image_id as imageId, ");
builder.append("c.staff_id as staffId, s.display_name as staffName ");
builder.append("from m_client c ");
builder.append("join m_office o on o.id = c.office_id ");
builder.append("left join m_staff s on s.id = c.staff_id ");
builder.append("left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id ");
builder.append("left join m_appuser sbu on sbu.id = c.submittedon_userid ");
builder.append("left join m_appuser acu on acu.id = c.activatedon_userid ");
builder.append("left join m_appuser clu on clu.id = c.closedon_userid ");
this.schema = builder.toString();
}
@@ -369,13 +434,33 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
final String fullname = rs.getString("fullname");
final String displayName = rs.getString("displayName");
final String externalId = rs.getString("externalId");
final String mobileNo = rs.getString("mobileNo");
final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate");
final Long imageId = JdbcSupport.getLong(rs, "imageId");
final Long staffId = JdbcSupport.getLong(rs, "staffId");
final String staffName = rs.getString("staffName");
final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
final String closedByUsername = rs.getString("closedByUsername");
final String closedByFirstname = rs.getString("closedByFirstname");
final String closedByLastname = rs.getString("closedByLastname");
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final String submittedByUsername = rs.getString("submittedByUsername");
final String submittedByFirstname = rs.getString("submittedByFirstname");
final String submittedByLastname = rs.getString("submittedByLastname");
final String activatedByUsername = rs.getString("activatedByUsername");
final String activatedByFirstname = rs.getString("activatedByFirstname");
final String activatedByLastname = rs.getString("activatedByLastname");
final ClientTimelineData timeline = new ClientTimelineData (submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname,
activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername,
closedByFirstname, closedByLastname);
return ClientData.instance(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
middlename, lastname, fullname, displayName, externalId, activationDate, imageId, staffId, staffName);
middlename, lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName,timeline);
}
}
@@ -44,6 +44,7 @@ import org.mifosplatform.portfolio.note.domain.Note;
import org.mifosplatform.portfolio.note.domain.NoteRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository;
import org.mifosplatform.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -136,7 +137,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
public CommandProcessingResult createClient(final JsonCommand command) {
try {
this.context.authenticatedUser();
final AppUser currentUser = this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForCreate(command.json());
@@ -159,7 +160,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, clientOffice.getHierarchy());
}
final Client newClient = Client.createNew(clientOffice, clientParentGroup, staff, command);
final Client newClient = Client.createNew(currentUser,clientOffice, clientParentGroup, staff, command);
this.clientRepository.save(newClient);
@@ -188,8 +189,6 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
public CommandProcessingResult updateClient(final Long clientId, final JsonCommand command) {
try {
this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForUpdate(command.json());
final Client clientForUpdate = this.clientRepository.findOneWithNotFoundDetection(clientId);
@@ -239,7 +238,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate");
client.activate(fmt, activationDate);
final AppUser currentUser = this.context.authenticatedUser();
client.activate(currentUser, fmt, activationDate);
this.clientRepository.saveAndFlush(client);
@@ -332,6 +332,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
@Override
public CommandProcessingResult closeClient(final Long clientId, final JsonCommand command) {
try {
final AppUser currentUser = this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateClose(command);
final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
@@ -389,7 +391,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
}
}
client.close(closureReason, closureDate.toDate());
client.close(currentUser,closureReason, closureDate.toDate());
this.clientRepository.saveAndFlush(client);
return new CommandProcessingResultBuilder() //
@@ -30,7 +30,6 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepositoryWrapper;
import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.collectionsheet.data.JLGClientData;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetFlatData;
@@ -304,9 +303,11 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final String transactionDateStr = df.format(transactionDate.toDate());
final Calendar calendar = this.calendarRepositoryWrapper.findOneWithNotFoundDetection(calendarId);
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), transactionDate)) { throw new NotValidRecurringDateException(
"collectionsheet", "The date '" + transactionDate + "' is not a valid meeting date.", transactionDate); }
// check if transaction against calendar effective from date
if (!calendar.isValidRecurringDate(transactionDate)) { throw new NotValidRecurringDateException("collectionsheet", "The date '"
+ transactionDate + "' is not a valid meeting date.", transactionDate); }
final AppUser currentUser = this.context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy();
final String officeHierarchy = hierarchy + "%";
@@ -316,7 +317,10 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final JLGCollectionSheetFaltDataMapper mapper = new JLGCollectionSheetFaltDataMapper();
final StringBuilder sql = new StringBuilder(mapper.collectionSheetSchema());
sql.append(" WHERE gp.id = :groupId GROUP BY gp.id ,cl.id , ln.id ORDER BY gp.id , cl.id , ln.id ");
sql.append(" WHERE gp.id = :groupId ")
.append(" and (gp.status_enum = 300 or (gp.status_enum = 600 and gp.closedon_date >= :dueDate)) ")
.append(" and (cl.status_enum = 300 or (cl.status_enum = 600 and cl.closedon_date >= :dueDate)) ")
.append(" GROUP BY gp.id ,cl.id , ln.id ORDER BY gp.id , cl.id , ln.id ");
// entityType should be center if it's within a center
final CalendarEntityType entityType = (group.isChildGroup()) ? CalendarEntityType.CENTERS : CalendarEntityType.GROUPS;
@@ -348,17 +352,19 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final JLGCollectionSheetFaltDataMapper mapper = new JLGCollectionSheetFaltDataMapper();
String sql = mapper.collectionSheetSchema();
sql += "WHERE gp.parent_id = :centerId GROUP BY gp.id ,cl.id , ln.id ORDER BY gp.id , cl.id , ln.id";
StringBuilder sql = new StringBuilder(mapper.collectionSheetSchema());
sql.append(" WHERE gp.parent_id = :centerId ")
.append(" and (gp.status_enum = 300 or (gp.status_enum = 600 and gp.closedon_date >= :dueDate)) ")
.append(" and (cl.status_enum = 300 or (cl.status_enum = 600 and cl.closedon_date >= :dueDate)) ")
.append(" GROUP BY gp.id ,cl.id , ln.id ORDER BY gp.id , cl.id , ln.id");
final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", dueDateStr)
.addValue("centerId", center.getId()).addValue("officeHierarchy", officeHierarchy)
.addValue("entityTypeId", CalendarEntityType.CENTERS.getValue());
final Collection<JLGCollectionSheetFlatData> collectionSheetFlatDatas = this.namedParameterjdbcTemplate.query(sql, namedParameters,
final Collection<JLGCollectionSheetFlatData> collectionSheetFlatDatas = this.namedParameterjdbcTemplate.query(sql.toString(), namedParameters,
mapper);
return buildJLGCollectionSheet(transactionDate, collectionSheetFlatDatas);
}
}
@@ -12,6 +12,7 @@ import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@@ -24,6 +25,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -48,6 +50,8 @@ import org.mifosplatform.portfolio.group.data.CenterData;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.group.service.CenterReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.mifosplatform.portfolio.meeting.service.MeetingReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -70,6 +74,7 @@ public class CentersApiResource {
private final FromJsonHelper fromJsonHelper;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final MeetingReadPlatformService meetingReadPlatformService;
@Autowired
public CentersApiResource(final PlatformSecurityContext context, final CenterReadPlatformService centerReadPlatformService,
@@ -79,7 +84,7 @@ public class CentersApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService) {
final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
this.context = context;
this.centerReadPlatformService = centerReadPlatformService;
this.centerApiJsonSerializer = centerApiJsonSerializer;
@@ -91,6 +96,7 @@ public class CentersApiResource {
this.fromJsonHelper = fromJsonHelper;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.meetingReadPlatformService = meetingReadPlatformService;
}
@GET
@@ -98,7 +104,8 @@ public class CentersApiResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTemplate(@Context final UriInfo uriInfo, @QueryParam("command") final String commandParam,
@QueryParam("officeId") final Long officeId) {
@QueryParam("officeId") final Long officeId,
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) {
this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
@@ -109,7 +116,7 @@ public class CentersApiResource {
GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
}
final CenterData template = this.centerReadPlatformService.retrieveTemplate(officeId);
final CenterData template = this.centerReadPlatformService.retrieveTemplate(officeId, staffInSelectedOfficeOnly);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.centerApiJsonSerializer.serialize(settings, template, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
@@ -138,7 +145,8 @@ public class CentersApiResource {
@Path("{centerId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@Context final UriInfo uriInfo, @PathParam("centerId") final Long centerId) {
public String retrieveOne(@Context final UriInfo uriInfo, @PathParam("centerId") final Long centerId,
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly) {
this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
@@ -148,7 +156,7 @@ public class CentersApiResource {
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
if (template) {
final CenterData templateCenter = this.centerReadPlatformService.retrieveTemplate(center.officeId());
final CenterData templateCenter = this.centerReadPlatformService.retrieveTemplate(center.officeId(), staffInSelectedOfficeOnly);
center = CenterData.withTemplate(templateCenter, center);
}
@@ -161,7 +169,13 @@ public class CentersApiResource {
collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(centerId,
CalendarEntityType.CENTERS.getValue());
if (collectionMeetingCalendar != null) {
collectionMeetingCalendar = this.calendarReadPlatformService.generateRecurringDate(collectionMeetingCalendar);
final boolean withHistory = true;
final LocalDate tillDate = null;
final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(collectionMeetingCalendar, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService.generateNextTenRecurringDates(collectionMeetingCalendar);
final MeetingData lastMeeting = this.meetingReadPlatformService.retrieveLastMeeting(collectionMeetingCalendar.getCalendarInstanceId());
final LocalDate recentEligibleMeetingDate = this.calendarReadPlatformService.generateNextEligibleMeetingDateForCollection(collectionMeetingCalendar, lastMeeting);
collectionMeetingCalendar = CalendarData.withRecurringDates(collectionMeetingCalendar, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
}
}
@@ -42,6 +42,8 @@ public class GroupingTypesApiConstants {
public static final String activationDateParamName = "activationDate";
public static final String groupMembersParamName = "groupMembers";
public static final String submittedOnDateParamName = "submittedOnDate";
// group parameters
public static final String centerIdParamName = "centerId";
public static final String clientMembersParamName = "clientMembers";
@@ -55,6 +57,7 @@ public class GroupingTypesApiConstants {
public static final String staffOptionsParamName = "staffOptions";
public static final String clientOptionsParamName = "clientOptions";
public static final String collectionMeetingCalendar = "collectionMeetingCalendar";
public static final String timeLine ="timeline";
// group close parameters
public static final String closureDateParamName = "closureDate";
@@ -62,11 +65,11 @@ public class GroupingTypesApiConstants {
public static final Set<String> CENTER_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, idParamName, nameParamName, externalIdParamName, officeIdParamName, staffIdParamName, activeParamName,
activationDateParamName, groupMembersParamName));
activationDateParamName, groupMembersParamName,submittedOnDateParamName));
public static final Set<String> GROUP_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName, dateFormatParamName,
idParamName, nameParamName, externalIdParamName, centerIdParamName, officeIdParamName, staffIdParamName, activeParamName,
activationDateParamName, clientMembersParamName, collectionMeetingCalendar));
activationDateParamName, clientMembersParamName, collectionMeetingCalendar,submittedOnDateParamName));
public static final Set<String> GROUP_ROLES_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(roleParamName,
clientIdParamName));
@@ -86,7 +89,7 @@ public class GroupingTypesApiConstants {
public static final Set<String> GROUP_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, nameParamName,
externalIdParamName, officeIdParamName, officeNameParamName, "parentId", "parentName", staffIdParamName, staffNameParamName,
hierarchyParamName, officeOptionsParamName, staffOptionsParamName, clientOptionsParamName));
hierarchyParamName, officeOptionsParamName, staffOptionsParamName, clientOptionsParamName,timeLine));
public static final Set<String> ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, activationDateParamName));
@@ -26,6 +26,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -55,6 +56,8 @@ import org.mifosplatform.portfolio.group.service.CenterReadPlatformService;
import org.mifosplatform.portfolio.group.service.GroupReadPlatformService;
import org.mifosplatform.portfolio.group.service.GroupRolesReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.mifosplatform.portfolio.meeting.service.MeetingReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -81,6 +84,7 @@ public class GroupsApiResource {
private final GroupRolesReadPlatformService groupRolesReadPlatformService;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final MeetingReadPlatformService meetingReadPlatformService;
@Autowired
public GroupsApiResource(final PlatformSecurityContext context, final GroupReadPlatformService groupReadPlatformService,
@@ -93,7 +97,7 @@ public class GroupsApiResource {
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final GroupRolesReadPlatformService groupRolesReadPlatformService,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService) {
final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
this.context = context;
this.groupReadPlatformService = groupReadPlatformService;
@@ -109,6 +113,7 @@ public class GroupsApiResource {
this.groupRolesReadPlatformService = groupRolesReadPlatformService;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.meetingReadPlatformService = meetingReadPlatformService;
}
@GET
@@ -216,7 +221,13 @@ public class GroupsApiResource {
CalendarEntityType.GROUPS.getValue());
}
if (collectionMeetingCalendar != null) {
collectionMeetingCalendar = this.calendarReadPlatformService.generateRecurringDate(collectionMeetingCalendar);
final boolean withHistory = true;
final LocalDate tillDate = null;
final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(collectionMeetingCalendar, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService.generateNextTenRecurringDates(collectionMeetingCalendar);
final MeetingData lastMeeting = this.meetingReadPlatformService.retrieveLastMeeting(collectionMeetingCalendar.getCalendarInstanceId());
final LocalDate recentEligibleMeetingDate = this.calendarReadPlatformService.generateNextEligibleMeetingDateForCollection(collectionMeetingCalendar, lastMeeting);
collectionMeetingCalendar = CalendarData.withRecurringDates(collectionMeetingCalendar, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
}
}
@@ -52,13 +52,14 @@ public class GroupGeneralData {
private final Collection<CodeValueData> availableRoles;
private final GroupRoleData selectedRole;
private final Collection<CodeValueData> closureReasons;
private final GroupTimelineData timeline;
public static GroupGeneralData lookup(final Long groupId, final String groupName) {
final Collection<ClientData> clientMembers = null;
final Collection<GroupRoleData> groupRoles = null;
final Collection<CodeValueData> closureReasons = null;
return new GroupGeneralData(groupId, groupName, null, null, null, null, null, null, null, null, null, null, clientMembers, null,
null, null, null, groupRoles, null, null, null, null, closureReasons);
null, null, null, groupRoles, null, null, null, null, closureReasons,null);
}
public static GroupGeneralData template(final Long officeId, final Long centerId, final String centerName, final Long staffId,
@@ -72,7 +73,7 @@ public class GroupGeneralData {
return new GroupGeneralData(null, null, null, null, null, officeId, null, centerId, centerName, staffId, staffName, null,
clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, null, null, null,
closureReasons);
closureReasons,null);
}
public static GroupGeneralData withTemplate(final GroupGeneralData templatedGrouping, final GroupGeneralData grouping) {
@@ -80,7 +81,7 @@ public class GroupGeneralData {
grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
grouping.hierarchy, grouping.clientMembers, templatedGrouping.centerOptions, templatedGrouping.officeOptions,
templatedGrouping.staffOptions, templatedGrouping.clientOptions, grouping.groupRoles, templatedGrouping.availableRoles,
grouping.selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar, grouping.closureReasons);
grouping.selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar, grouping.closureReasons,templatedGrouping.timeline);
}
public static GroupGeneralData withAssocations(final GroupGeneralData grouping, final Collection<ClientData> membersOfGroup,
@@ -90,12 +91,12 @@ public class GroupGeneralData {
grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
grouping.hierarchy, membersOfGroup, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions,
grouping.clientOptions, groupRoles, grouping.availableRoles, grouping.selectedRole, calendarsData,
collectionMeetingCalendar, grouping.closureReasons);
collectionMeetingCalendar, grouping.closureReasons,grouping.timeline);
}
public static GroupGeneralData instance(final Long id, final String name, final String externalId, final EnumOptionData status,
final LocalDate activationDate, final Long officeId, final String officeName, final Long centerId, final String centerName,
final Long staffId, final String staffName, final String hierarchy) {
final Long staffId, final String staffName, final String hierarchy,final GroupTimelineData timeline) {
final Collection<ClientData> clientMembers = null;
final Collection<CenterData> centerOptions = null;
@@ -111,7 +112,7 @@ public class GroupGeneralData {
return new GroupGeneralData(id, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId,
staffName, hierarchy, clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles,
role, calendarsData, collectionMeetingCalendar, closureReasons);
role, calendarsData, collectionMeetingCalendar, closureReasons,timeline);
}
private GroupGeneralData(final Long id, final String name, final String externalId, final EnumOptionData status,
@@ -121,7 +122,7 @@ public class GroupGeneralData {
final Collection<StaffData> staffOptions, final Collection<ClientData> clientOptions,
final Collection<GroupRoleData> groupRoles, final Collection<CodeValueData> availableRoles, final GroupRoleData role,
final Collection<CalendarData> calendarsData, final CalendarData collectionMeetingCalendar,
final Collection<CodeValueData> closureReasons) {
final Collection<CodeValueData> closureReasons,final GroupTimelineData timeline) {
this.id = id;
this.name = name;
this.externalId = externalId;
@@ -159,6 +160,7 @@ public class GroupGeneralData {
this.calendarsData = calendarsData;
this.collectionMeetingCalendar = collectionMeetingCalendar;
this.closureReasons = closureReasons;
this.timeline = timeline;
}
public Long getId() {
@@ -190,7 +192,7 @@ public class GroupGeneralData {
grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
grouping.hierarchy, grouping.clientMembers, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions,
grouping.clientOptions, grouping.groupRoles, grouping.availableRoles, selectedRole, grouping.calendarsData,
grouping.collectionMeetingCalendar, grouping.closureReasons);
grouping.collectionMeetingCalendar, grouping.closureReasons,null);
}
public static GroupGeneralData withClosureReasons(final Collection<CodeValueData> closureReasons) {
@@ -219,6 +221,6 @@ public class GroupGeneralData {
return new GroupGeneralData(id, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId,
staffName, hierarchy, clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles,
role, calendarsData, collectionMeetingCalendar, closureReasons);
role, calendarsData, collectionMeetingCalendar, closureReasons,null);
}
}
@@ -0,0 +1,56 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.group.data;
import org.joda.time.LocalDate;
/**
* Immutable data object represent the important time-line events of a loan
* application and loan.
*/
@SuppressWarnings("unused")
public class GroupTimelineData {
private final LocalDate submittedOnDate;
private final String submittedByUsername;
private final String submittedByFirstname;
private final String submittedByLastname;
private final LocalDate activatedOnDate;
private final String activatedByUsername;
private final String activatedByFirstname;
private final String activatedByLastname;
private final LocalDate closedOnDate;
private final String closedByUsername;
private final String closedByFirstname;
private final String closedByLastname;
public GroupTimelineData(final LocalDate submittedOnDate, final String submittedByUsername,
final String submittedByFirstname, final String submittedByLastname, final LocalDate activatedOnDate,
final String activatedByUsername, final String activatedByFirstname, final String activatedByLastname,
final LocalDate closedOnDate, final String closedByUsername, final String closedByFirstname,
final String closedByLastname) {
this.submittedOnDate = submittedOnDate;
this.submittedByUsername = submittedByUsername;
this.submittedByFirstname = submittedByFirstname;
this.submittedByLastname = submittedByLastname;
this.activatedOnDate = activatedOnDate;
this.activatedByUsername = activatedByUsername;
this.activatedByFirstname = activatedByFirstname;
this.activatedByLastname = activatedByLastname;
this.closedOnDate = closedOnDate;
this.closedByUsername = closedByUsername;
this.closedByFirstname = closedByFirstname;
this.closedByLastname = closedByLastname;
}
}

Some files were not shown because too many files have changed in this diff Show More