/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* Copyright (C) 2011 WirelessGalicia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.ws.subcontract.impl;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hibernate.NonUniqueResultException;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.Registry;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.daos.IEntitySequenceDAO;
import org.libreplan.business.common.entities.EntityNameEnum;
import org.libreplan.business.common.entities.EntitySequence;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.externalcompanies.daos.ICustomerCommunicationDAO;
import org.libreplan.business.externalcompanies.daos.IExternalCompanyDAO;
import org.libreplan.business.externalcompanies.entities.CommunicationType;
import org.libreplan.business.externalcompanies.entities.CustomerCommunication;
import org.libreplan.business.externalcompanies.entities.DeadlineCommunication;
import org.libreplan.business.externalcompanies.entities.ExternalCompany;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderStatusEnum;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.orders.entities.TaskSource.IOptionalPersistence;
import org.libreplan.business.orders.entities.TaskSource.TaskSourceSynchronization;
import org.libreplan.business.planner.daos.ITaskSourceDAO;
import org.libreplan.business.scenarios.daos.IScenarioDAO;
import org.libreplan.business.scenarios.entities.OrderVersion;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.web.subcontract.UpdateDeliveringDateDTO;
import org.libreplan.ws.common.api.InstanceConstraintViolationsDTO;
import org.libreplan.ws.common.api.InstanceConstraintViolationsDTOId;
import org.libreplan.ws.common.api.InstanceConstraintViolationsListDTO;
import org.libreplan.ws.common.api.OrderDTO;
import org.libreplan.ws.common.api.OrderElementDTO;
import org.libreplan.ws.common.impl.ConfigurationOrderElementConverter;
import org.libreplan.ws.common.impl.ConstraintViolationConverter;
import org.libreplan.ws.common.impl.DateConverter;
import org.libreplan.ws.common.impl.OrderElementConverter;
import org.libreplan.ws.common.impl.Util;
import org.libreplan.ws.subcontract.api.ISubcontractService;
import org.libreplan.ws.subcontract.api.SubcontractedTaskDataDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* REST-based implementation of {@link ISubcontractService}.
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
*/
@Path("/subcontracting/subcontract/")
@Produces("application/xml")
@Service("subcontractServiceREST")
public class SubcontractServiceREST implements ISubcontractService {
@Autowired
private IOrderElementDAO orderElementDAO;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IExternalCompanyDAO externalCompanyDAO;
@Autowired
private IEntitySequenceDAO entitySequenceDAO;
@Autowired
private IScenarioDAO scenarioDAO;
@Autowired
private ICustomerCommunicationDAO customerCommunicationDAO;
@Autowired
private ITaskSourceDAO taskSourceDAO;
@Autowired
private IAdHocTransactionService adHocTransactionService;
static class ViolationError extends RuntimeException {
private final List<InstanceConstraintViolationsDTO> violations;
ViolationError(InstanceConstraintViolationsDTO... violations) {
Validate.noNullElements(violations);
this.violations = Arrays.asList(violations);
}
ViolationError(String code, String message) {
this(createConstraintViolationFor(code, message));
}
public List<InstanceConstraintViolationsDTO> getViolations() {
return violations;
}
public InstanceConstraintViolationsListDTO toViolationList() {
return new InstanceConstraintViolationsListDTO(violations);
}
}
@Override
@POST
@Path("update/")
@Consumes("application/xml")
public InstanceConstraintViolationsListDTO updateDeliveringDates(
final UpdateDeliveringDateDTO updateDeliveringDateDTO) {
try {
updateSubcontract(updateDeliveringDateDTO);
} catch (ViolationError e) {
return e.toViolationList();
}
return new InstanceConstraintViolationsListDTO();
}
private void updateSubcontract(final UpdateDeliveringDateDTO updateDeliveringDateDTO) {
if (StringUtils.isEmpty(updateDeliveringDateDTO.externalCompanyNif)) {
throw new ViolationError(updateDeliveringDateDTO.externalCompanyNif, "external company Nif not specified");
}
if (StringUtils.isEmpty(updateDeliveringDateDTO.externalCode)) {
throw new ViolationError(updateDeliveringDateDTO.externalCode, "external order code not specified");
}
if ((updateDeliveringDateDTO.deliverDate) == null) {
throw new ViolationError(updateDeliveringDateDTO.deliverDate.toString(),
"deliver date not specified");
}
final ExternalCompany externalCompany = adHocTransactionService
.runOnTransaction(new IOnTransaction<ExternalCompany>() {
@Override
public ExternalCompany execute() {
return findExternalCompanyFor(updateDeliveringDateDTO.externalCompanyNif);
}
});
if (!externalCompany.isClient()) {
throw new ViolationError(updateDeliveringDateDTO.externalCompanyNif,
"external company is not registered as client");
}
try {
adHocTransactionService
.runOnTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
updateDeliveringDateInOrder(updateDeliveringDateDTO);
return null;
}
});
} catch (ValidationException e) {
InstanceConstraintViolationsDTO violation = ConstraintViolationConverter
.toDTO(new InstanceConstraintViolationsDTOId(Long.valueOf(1),
updateDeliveringDateDTO.externalCompanyNif, OrderDTO.ENTITY_TYPE), e);
throw new ViolationError(violation);
}
}
private void updateDeliveringDateInOrder(UpdateDeliveringDateDTO updateDeliveringDateDTO){
try {
OrderElement orderElement = orderElementDAO.findByExternalCode(updateDeliveringDateDTO.externalCode);
if(orderElement != null && orderElement instanceof Order) {
Order order = (Order)orderElement;
Date newDeliverDate = DateConverter.toDate(updateDeliveringDateDTO.deliverDate);
DeadlineCommunication deadlineCommunication = DeadlineCommunication.create(new Date(), newDeliverDate);
order.getDeliveringDates().add(deadlineCommunication);
LocalDate newLocalDeliverDate = new LocalDate(newDeliverDate);
OrderVersion orderVersion = order.getOrderVersionFor(Registry.getScenarioManager().getCurrent());
order.useSchedulingDataFor(orderVersion);
if (order.getAssociatedTaskElement() != null) {
order.getAssociatedTaskElement().setDeadline(newLocalDeliverDate);
}
createCustomerCommunication(order, CommunicationType.UPDATE_DELIVERING_DATE);
orderElementDAO.save(order);
} else {
throw new ViolationError(
updateDeliveringDateDTO.customerReference,
"It do not exist any order with this reference");
}
} catch (InstanceNotFoundException e) {
throw new ViolationError(updateDeliveringDateDTO.customerReference,
"It do not exist any order with this reference");
}
}
@Override
@POST
@Path("create/")
@Consumes("application/xml")
public InstanceConstraintViolationsListDTO subcontract(final SubcontractedTaskDataDTO subcontractedTaskDataDTO) {
try {
doSubcontract(subcontractedTaskDataDTO);
} catch (ViolationError e) {
return e.toViolationList();
}
return new InstanceConstraintViolationsListDTO();
}
private void doSubcontract(final SubcontractedTaskDataDTO subcontractedTask) {
if (StringUtils.isEmpty(subcontractedTask.externalCompanyNif)) {
throw new ViolationError(subcontractedTask.subcontractedCode, "external company ID not specified");
}
final ExternalCompany externalCompany = adHocTransactionService.runOnTransaction(new IOnTransaction<ExternalCompany>() {
@Override
public ExternalCompany execute() {
return findExternalCompanyFor(subcontractedTask);
}
});
if (!externalCompany.isClient()) {
throw new ViolationError(subcontractedTask.externalCompanyNif,
"external company is not registered as client");
}
final OrderElementDTO orderElementDTO = subcontractedTask.orderElementDTO;
if (orderElementDTO == null) {
throw new ViolationError(subcontractedTask.subcontractedCode, "task not specified");
}
try {
adHocTransactionService.runOnTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
createOrder(subcontractedTask, externalCompany, orderElementDTO);
return null;
}
});
} catch (ValidationException e) {
InstanceConstraintViolationsDTO violation = ConstraintViolationConverter
.toDTO(new InstanceConstraintViolationsDTOId(1L, orderElementDTO.code, OrderDTO.ENTITY_TYPE), e);
throw new ViolationError(violation);
}
}
private void createOrder(final SubcontractedTaskDataDTO subcontractedTaskDataDTO,
final ExternalCompany externalCompany,
final OrderElementDTO orderElementDTO) {
Scenario current = Registry.getScenarioManager().getCurrent();
OrderVersion version = OrderVersion.createInitialVersion(current);
OrderElement orderElement = OrderElementConverter.toEntity(
version, orderElementDTO, ConfigurationOrderElementConverter.noAdvanceMeasurements());
Order order;
if (orderElement instanceof Order) {
order = (Order) orderElement;
order.setVersionForScenario(current, version);
order.useSchedulingDataFor(version);
order.setExternalCode(order.getCode());
} else {
order = wrapInOrder(current, version, orderElement);
}
addOrderToDerivedScenarios(current, version, order);
order.setCodeAutogenerated(true);
String code = entitySequenceDAO.getNextEntityCode(EntityNameEnum.ORDER);
if (code == null) {
throw new ViolationError(subcontractedTaskDataDTO.orderElementDTO.code,
"unable to generate the code for the new project, please try again later");
}
order.setCode(code);
generateCodes(order);
order.setState(OrderStatusEnum.OUTSOURCED);
if (subcontractedTaskDataDTO.workDescription != null) {
order.setName(subcontractedTaskDataDTO.workDescription);
}
order.setCustomer(externalCompany);
order.setCustomerReference(subcontractedTaskDataDTO.subcontractedCode);
order.setWorkBudget(subcontractedTaskDataDTO.subcontractPrice);
if (subcontractedTaskDataDTO.deliverDate != null) {
Date deliveryDate = DateConverter.toDate(subcontractedTaskDataDTO.deliverDate);
DeadlineCommunication deadlineCommunication = DeadlineCommunication.create(new Date(), deliveryDate);
order.getDeliveringDates().add(deadlineCommunication);
order.setDeadline(deliveryDate);
}
synchronizeWithSchedule(order, TaskSource.persistTaskSources(taskSourceDAO));
order.writeSchedulingDataChanges();
order.validate();
orderElementDAO.save(order);
/*
* Create the customer communication to a new subcontrating project.
*/
if(!StringUtils.isBlank(order.getExternalCode())) {
createCustomerCommunication(order, CommunicationType.NEW_PROJECT);
}
}
private void synchronizeWithSchedule(OrderElement orderElement, IOptionalPersistence persistence) {
List<TaskSourceSynchronization> synchronizationsNeeded = orderElement.calculateSynchronizationsNeeded();
for (TaskSourceSynchronization each : synchronizationsNeeded) {
each.apply(persistence);
}
}
private void addOrderToDerivedScenarios(Scenario currentScenario, OrderVersion orderVersion, Order order) {
List<Scenario> derivedScenarios = scenarioDAO.getDerivedScenarios(currentScenario);
for (Scenario scenario : derivedScenarios) {
scenario.addOrder(order, orderVersion);
}
}
private void generateCodes(Order order) {
EntitySequence entitySequence;
try {
entitySequence = entitySequenceDAO.getActiveEntitySequence(EntityNameEnum.ORDER);
int numberOfDigits = entitySequence.getNumberOfDigits();
order.generateOrderElementCodes(numberOfDigits);
} catch (NonUniqueResultException e) {
throw new ViolationError("", "There are several active project sequences");
} catch (InstanceNotFoundException e) {
throw new ViolationError("", "It does not exist any activated code sequence.");
}
}
private Order wrapInOrder(Scenario current, OrderVersion version, OrderElement orderElement) {
if (orderElement instanceof Order) {
return (Order) orderElement;
}
Order order = Order.create();
order.setVersionForScenario(current, version);
order.useSchedulingDataFor(version);
order.add(orderElement);
order.setName("Project from client");
order.setInitDate(orderElement.getInitDate());
order.setDeadline(orderElement.getDeadline());
order.setCalendar(getDefaultCalendar());
order.setExternalCode(orderElement.getCode());
return order;
}
private BaseCalendar getDefaultCalendar() {
return configurationDAO.getConfiguration().getDefaultCalendar();
}
private ExternalCompany findExternalCompanyFor(final SubcontractedTaskDataDTO subcontractedTask){
return findExternalCompanyFor(subcontractedTask.externalCompanyNif);
}
private ExternalCompany findExternalCompanyFor(final String externalCompanyNif) {
try {
return externalCompanyDAO.findUniqueByNif(externalCompanyNif);
} catch (InstanceNotFoundException e) {
throw new ViolationError(externalCompanyNif, "external company not found");
}
}
private static InstanceConstraintViolationsDTO createConstraintViolationFor(String code, String message) {
/* TODO resolve deprecated */
return InstanceConstraintViolationsDTO.create(Util.generateInstanceId(1, code), message);
}
private void createCustomerCommunication(Order order, CommunicationType type){
Date communicationDate = new Date();
Date deadline;
if (type.equals(CommunicationType.NEW_PROJECT)){
deadline = order.getDeadline();
} else {
deadline = order.getDeliveringDates().first().getDeliverDate();
}
CustomerCommunication customerCommunication = CustomerCommunication
.create(deadline, communicationDate, type, order);
customerCommunicationDAO.save(customerCommunication);
}
}