Merge pull request #356 from goutham-M/MIFOSX---76

holiday functionality to apply holidays for offices
This commit is contained in:
Vishwas Babu A J
2013-06-12 07:40:55 -07:00
16 changed files with 473 additions and 6 deletions
@@ -0,0 +1,22 @@
create table `m_holiday`(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`from_date` DATETIME NOT NULL,
`to_date` DATETIME NOT NULL,
`description` varchar(100) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `holiday_name` (`name`)
);
CREATE TABLE `m_holiday_office` (
`holiday_id` BIGINT(20) NOT NULL,
`office_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`holiday_id`, `office_id`),
INDEX `m_holiday_id_ibfk_1` (`holiday_id`),
INDEX `m_office_id_ibfk_2` (`office_id`),
CONSTRAINT `m_holiday_id_ibfk_1` FOREIGN KEY (`holiday_id`) REFERENCES `m_holiday` (`id`),
CONSTRAINT `m_office_id_ibfk_2` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'CREATE_HOLIDAY', 'HOLIDAY', 'CREATE', 0);
@@ -531,4 +531,8 @@ public class CommandWrapper {
public boolean isAccountingRuleResource() {
return this.entityName.equalsIgnoreCase("ACCOUNTINGRULE");
}
public boolean isHolidayResource() {
return this.entityName.equalsIgnoreCase("HOLIDAY");
}
}
@@ -1018,4 +1018,12 @@ public class CommandWrapperBuilder {
this.href = "/accountingrules/" + accountingRuleId;
return this;
}
public CommandWrapperBuilder createHoliday() {
this.actionName = "CREATE";
this.entityName = "HOLIDAY";
this.entityId = null;
this.href = "/holidays/template";
return this;
}
}
@@ -461,7 +461,12 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
throw new UnsupportedCommandException(wrapper.commandName());
}
}else {
} else if(wrapper.isHolidayResource()) {
if (wrapper.isCreate()) {
handler = applicationContext.getBean("createHolidayCommandHandler", NewCommandSourceHandler.class);
}
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -22,15 +22,16 @@ public class CommandProcessingResult {
private final Long savingsId;
private final Long resourceId;
private final String transactionId;
private final Long holidayId;
private final Map<String, Object> changes;
@SuppressWarnings("unused")
private final String resourceIdentifier;
public static CommandProcessingResult fromDetails(final Long commandId, final Long officeId, final Long groupId, final Long clientId,
final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final String transactionId,
final Map<String, Object> changes) {
final Long holidayId, final Map<String, Object> changes) {
return new CommandProcessingResult(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId,
transactionId, changes);
transactionId, holidayId, changes);
}
public static CommandProcessingResult commandOnlyResult(final Long commandId) {
@@ -78,11 +79,12 @@ public class CommandProcessingResult {
this.loanId = null;
this.savingsId = null;
this.transactionId = null;
this.holidayId = null;
this.changes = new HashMap<String, Object>();
}
private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId,
final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId,
final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId, final Long holidayId,
final Map<String, Object> changesOnly) {
this.commandId = commandId;
this.officeId = officeId;
@@ -94,6 +96,7 @@ public class CommandProcessingResult {
this.resourceId = resourceId;
this.changes = changesOnly;
this.transactionId = transactionId;
this.holidayId = holidayId;
}
private CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map<String, Object> changesOnly) {
@@ -109,6 +112,7 @@ public class CommandProcessingResult {
this.loanId = null;
this.savingsId = null;
this.transactionId = null;
this.holidayId = null;
this.commandId = commandId;
this.changes = changesOnly;
}
@@ -22,11 +22,12 @@ public class CommandProcessingResultBuilder {
private String resourceIdentifier;
private Long entityId;
private String transactionId;
private Long holidayId;
private Map<String, Object> changes;
public CommandProcessingResult build() {
return CommandProcessingResult.fromDetails(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId,
transactionId, changes);
transactionId, holidayId, changes);
}
public CommandProcessingResultBuilder withCommandId(final Long withCommandId) {
@@ -78,4 +79,9 @@ public class CommandProcessingResultBuilder {
this.transactionId = withTransactionId;
return this;
}
public CommandProcessingResultBuilder withHolidayId(final Long withHolidayId) {
this.holidayId = withHolidayId;
return this;
}
}
@@ -0,0 +1,24 @@
package org.mifosplatform.organisation.holiday.api;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class HolidayApiConstants {
public static final String HOLIDAY_RESOURCE_NAME = "holiday";
// general
public static final String localeParamName = "locale";
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 Set<String> HOLIDAY_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, name, fromDate, toDate, description, offices));
}
@@ -0,0 +1,45 @@
package org.mifosplatform.organisation.holiday.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/holidays")
@Component
@Scope("singleton")
public class HolidaysApiResource {
private final DefaultToApiJsonSerializer<Object> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public HolidaysApiResource(final DefaultToApiJsonSerializer<Object> toApiJsonSerializer, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String createNewHoliday(final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createHoliday().withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
}
@@ -0,0 +1,85 @@
package org.mifosplatform.organisation.holiday.data;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
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.organisation.holiday.api.HolidayApiConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
@Component
public class HolidayDataValidator {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public HolidayDataValidator(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();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, HolidayApiConstants.HOLIDAY_CREATE_REQUEST_DATA_PARAMETERS);
final JsonElement element = fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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).notBlank().notNull().notExceedingLengthOf(100);
final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.fromDate, element);
baseDataValidator.reset().parameter(HolidayApiConstants.fromDate).value(fromDate).notNull();
final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDate, element);
baseDataValidator.reset().parameter(HolidayApiConstants.toDate).value(toDate).notNull();
Set<Long> offices = null;
final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (topLevelJsonElement.has(HolidayApiConstants.offices)&&topLevelJsonElement.get(HolidayApiConstants.offices).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.offices).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("officeId", officeElement);
baseDataValidator.reset().parameter(HolidayApiConstants.offices).value(officeId).notNull();
offices.add(officeId);
}
}
}
baseDataValidator.reset().parameter(HolidayApiConstants.offices).value(offices).notNull();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
//
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
}
@@ -0,0 +1,88 @@
/**
* 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.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
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.organisation.holiday.api.HolidayApiConstants;
import org.mifosplatform.organisation.office.domain.Office;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_holiday", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "holiday_name") })
public class Holiday extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@Column(name = "name", unique = true, nullable = false, length = 100)
private String name;
@SuppressWarnings("unused")
@Column(name = "from_date", nullable = false)
@Temporal(TemporalType.DATE)
private Date fromDate;
@SuppressWarnings("unused")
@Column(name = "to_date", nullable = false)
@Temporal(TemporalType.DATE)
private Date toDate;
@SuppressWarnings("unused")
@Column(name = "description", length = 100)
private String description;
@SuppressWarnings("unused")
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "m_holiday_office", joinColumns = @JoinColumn(name = "holiday_id"), inverseJoinColumns = @JoinColumn(name = "office_id"))
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 String description = command.stringValueOfParameterNamed(HolidayApiConstants.description);
return new Holiday(name, fromDate, toDate, description, offices);
}
private Holiday(final String name, final LocalDate fromDate, final LocalDate toDate, final String description, final Set<Office> offices) {
if (StringUtils.isNotBlank(name)) {
this.name = name.trim();
}
if (fromDate != null) {
this.fromDate = fromDate.toDate();
}
if (toDate != null) {
this.toDate = toDate.toDate();
}
if (StringUtils.isNotBlank(name)) {
this.description = description.trim();
} else {
this.description = null;
}
if (offices != null) {
this.offices = offices;
}
}
}
@@ -0,0 +1,15 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.organisation.holiday.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface HolidayRepository extends JpaRepository<Holiday, Long>,
JpaSpecificationExecutor<Holiday> {
// no added behaviour
}
@@ -0,0 +1,27 @@
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 CreateHolidayCommandHandler implements NewCommandSourceHandler {
private final HolidayWritePlatformService holidayWritePlatformService;
@Autowired
public CreateHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) {
this.holidayWritePlatformService = holidayWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.holidayWritePlatformService.createHoliday(command);
}
}
@@ -0,0 +1,5 @@
package org.mifosplatform.organisation.holiday.service;
public class HolidayReadPlatformServiceImpl {
}
@@ -0,0 +1,9 @@
package org.mifosplatform.organisation.holiday.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface HolidayWritePlatformService {
CommandProcessingResult createHoliday(JsonCommand command);
}
@@ -0,0 +1,120 @@
package org.mifosplatform.organisation.holiday.service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
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.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@Service
public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(HolidayWritePlatformServiceJpaRepositoryImpl.class);
private final HolidayDataValidator fromApiJsonDeserializer;
private final HolidayRepository holidayRepository;
private final PlatformSecurityContext context;
private final OfficeRepository officeRepository;
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer,
final HolidayRepository holidayRepository, final PlatformSecurityContext context, final OfficeRepository officeRepository,
final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.holidayRepository = holidayRepository;
this.context = context;
this.officeRepository = officeRepository;
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Transactional
@Override
public CommandProcessingResult createHoliday(final JsonCommand command) {
try {
this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForCreate(command.json());
validateToDateBeforeFromDate(command);
Set<Office> offices = null;
final JsonObject topLevelJsonElement = this.fromApiJsonHelper.parse(command.json()).getAsJsonObject();
if (topLevelJsonElement.has(HolidayApiConstants.offices) && topLevelJsonElement.get(HolidayApiConstants.offices).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.offices).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("officeId", officeElement);
final Office office = this.officeRepository.findOne(officeId);
if (office == null) { throw new OfficeNotFoundException(officeId); }
offices.add(office);
}
}
final Holiday holiday = Holiday.createNew(offices, command);
this.holidayRepository.save(holiday);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(holiday.getId())
.withHolidayId(holiday.getId()).build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve);
return CommandProcessingResult.empty();
}
}
private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("holiday_name")) {
final String name = command.stringValueOfParameterNamed("name");
throw new PlatformDataIntegrityException("error.msg.holiday.duplicate.name", "Holiday with name `" + name + "` already exists",
"name", name);
}
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
private void validateToDateBeforeFromDate(final JsonCommand command) {
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDate);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDate);
if (toDate.isBefore(fromDate)) {
final String defaultUserMessage = "ToDate date cannot be before the FromDate.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.holiday.toDate.cannot be.before.the.FromDate",
defaultUserMessage, "ToDate");
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
}
@@ -53,7 +53,7 @@ public class OfficesApiResource {
private final OfficeReadPlatformService readPlatformService;
private final DefaultToApiJsonSerializer<OfficeData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public OfficesApiResource(final PlatformSecurityContext context, final OfficeReadPlatformService readPlatformService,