/*
* 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.
*
* 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.business.templates.entities;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.NonUniqueResultException;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.Valid;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.libreplan.business.advance.entities.AdvanceAssignmentTemplate;
import org.libreplan.business.advance.entities.DirectAdvanceAssignment;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.common.Registry;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.labels.entities.Label;
import org.libreplan.business.materials.entities.MaterialAssignment;
import org.libreplan.business.materials.entities.MaterialAssignmentTemplate;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.ICriterionRequirable;
import org.libreplan.business.orders.entities.InfoComponent;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLineGroup;
import org.libreplan.business.orders.entities.SchedulingState;
import org.libreplan.business.orders.entities.SchedulingState.Type;
import org.libreplan.business.qualityforms.entities.QualityForm;
import org.libreplan.business.requirements.entities.CriterionRequirement;
import org.libreplan.business.requirements.entities.DirectCriterionRequirement;
import org.libreplan.business.requirements.entities.IndirectCriterionRequirement;
import org.libreplan.business.templates.daos.IOrderElementTemplateDAO;
import org.libreplan.business.trees.ITreeNode;
import org.springframework.orm.hibernate5.HibernateOptimisticLockingFailureException;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public abstract class OrderElementTemplate
extends BaseEntity
implements ICriterionRequirable, ITreeNode<OrderElementTemplate> {
private static final Log LOG = LogFactory.getLog(OrderElementTemplate.class);
private SchedulingState.Type schedulingStateType;
private InfoComponent infoComponent;
private Integer startAsDaysFromBeginning;
private Integer deadlineAsDaysFromBeginning;
private OrderLineGroupTemplate parent;
private Set<CriterionRequirement> criterionRequirements = new HashSet<>();
private Set<MaterialAssignmentTemplate> materialAssignments = new HashSet<>();
private Set<Label> labels = new HashSet<>();
private Set<QualityForm> qualityForms = new HashSet<>();
private Set<AdvanceAssignmentTemplate> advanceAssignmentTemplates = new HashSet<>();
private SchedulingState schedulingState;
private OrderElement origin;
public static <T extends OrderElementTemplate> T create(T beingBuilt, OrderElement origin) {
InfoComponent infoComponentCopied = origin.getInfoComponent().copy();
Order order = origin.getOrder();
Days fromBeginningToStart = daysBetween(order.getInitDate(), origin.getInitDate());
Days fromBeginningToEnd = daysBetween(order.getInitDate(), origin.getDeadline());
beingBuilt.setMaterialAssignments(copyMaterialAssignmentsFrom(beingBuilt, origin.getMaterialAssignments()));
beingBuilt.setCriterionRequirements(
copyDirectCriterionRequirements(beingBuilt, origin.getDirectCriterionRequirement()));
beingBuilt.addLabels(origin.getLabels());
beingBuilt.setQualityForms(origin.getQualityForms());
beingBuilt.setAdvanceAssignmentTemplates(
copyDirectAdvanceAssignments(beingBuilt, origin.getDirectAdvanceAssignments()));
beingBuilt.setInfoComponent(infoComponentCopied);
beingBuilt.setSchedulingStateType(origin.getSchedulingStateType());
assignDates(beingBuilt, fromBeginningToStart, fromBeginningToEnd);
beingBuilt.setOrigin(origin);
return create(beingBuilt);
}
/**
* Copy only {@link DirectCriterionRequirement}.
*
* @param beingBuilt
* @param criterionRequirements
* @return {@link Set<CriterionRequirement>}
*/
private static Set<CriterionRequirement> copyDirectCriterionRequirements(
OrderElementTemplate beingBuilt, Collection<DirectCriterionRequirement> criterionRequirements) {
Set<CriterionRequirement> result = new HashSet<>();
for (DirectCriterionRequirement each: criterionRequirements) {
DirectCriterionRequirement newDirectCriterionRequirement =
DirectCriterionRequirement.copyFrom(each, beingBuilt);
result.add(newDirectCriterionRequirement);
}
return result;
}
private static Set<AdvanceAssignmentTemplate> copyDirectAdvanceAssignments(
OrderElementTemplate beingBuilt, Set<DirectAdvanceAssignment> directAdvanceAssignments) {
Set<AdvanceAssignmentTemplate> result = new HashSet<>();
for (DirectAdvanceAssignment each : directAdvanceAssignments) {
result.add(AdvanceAssignmentTemplate.convert(beingBuilt, each));
}
return result;
}
public static <T extends OrderElementTemplate> T createNew(T beingBuilt) {
beingBuilt.setInfoComponent(new InfoComponent());
assignDates(beingBuilt, null, null);
return create(beingBuilt);
}
private static Set<MaterialAssignmentTemplate> copyMaterialAssignmentsFrom(
OrderElementTemplate beingBuilt, Collection<? extends MaterialAssignment> assignments) {
Set<MaterialAssignmentTemplate> result = new HashSet<>();
for (MaterialAssignment each : assignments) {
result.add(MaterialAssignmentTemplate.copyFrom(each, beingBuilt));
}
return result;
}
private static void assignDates(
OrderElementTemplate beingBuilt, Days fromBeginningToStart, Days fromBeginningToEnd) {
Validate.isTrue(isNullOrPositive(fromBeginningToStart));
Validate.isTrue(isNullOrPositive(fromBeginningToEnd));
beingBuilt.startAsDaysFromBeginning = daysToInteger(fromBeginningToStart);
beingBuilt.deadlineAsDaysFromBeginning = daysToInteger(fromBeginningToEnd);
}
private static Days daysBetween(Date start, Date end) {
if ( start == null || end == null ) {
return null;
}
return Days.daysBetween(asDateTime(start), asDateTime(end));
}
private static DateTime asDateTime(Date date) {
return new DateTime(date);
}
private static boolean isNullOrPositive(Days days) {
return days == null || days.getDays() >= 0;
}
private static Integer daysToInteger(Days days) {
return days != null ? days.getDays() : null;
}
protected <T extends OrderElement> T setupElementParts(T orderElement) {
setupInfoComponent(orderElement);
setupDates(orderElement);
setupCriterionRequirements(orderElement);
setupMaterialAssignments(orderElement);
setupLabels(orderElement);
setupQualityForms(orderElement);
setupAdvances(orderElement);
return orderElement;
}
protected <T extends OrderElement> T setupSchedulingStateType(T orderElement) {
orderElement.initializeType(schedulingStateType);
return orderElement;
}
protected <T extends OrderElement> T setupVersioningInfo(OrderLineGroup parent, T orderElement) {
orderElement.useSchedulingDataFor(parent.getCurrentOrderVersion());
return orderElement;
}
private void setupInfoComponent(OrderElement orderElement) {
orderElement.setName(getName());
orderElement.setDescription(getDescription());
}
private void setupDates(OrderElement orderElement) {
Date orderInitDate = orderElement.getOrder().getInitDate();
if ( getStartAsDaysFromBeginning() != null ) {
orderElement.setInitDate(plusDays(orderInitDate, getStartAsDaysFromBeginning()));
}
if ( getDeadlineAsDaysFromBeginning() != null ) {
orderElement.setDeadline(plusDays(orderInitDate, getDeadlineAsDaysFromBeginning()));
}
}
private Date plusDays(Date date, Integer days) {
LocalDate localDate = new LocalDate(date);
return localDate.plusDays(days).toDateTimeAtStartOfDay().toDate();
}
private void setupCriterionRequirements(OrderElement orderElement) {
for (DirectCriterionRequirement each : getDirectCriterionRequirements()) {
if ( orderElement.canAddCriterionRequirement(each) ) {
orderElement.addCriterionRequirement(DirectCriterionRequirement.copyFrom(each, orderElement));
}
}
}
private void setupMaterialAssignments(OrderElement orderElement) {
for (MaterialAssignmentTemplate each : materialAssignments) {
orderElement.addMaterialAssignment(each.createAssignment(orderElement));
}
}
private void setupLabels(OrderElement orderElement) {
for (Label each : getLabels()) {
if ( orderElement.checkAncestorsNoOtherLabelRepeated(each) ) {
orderElement.addLabel(each);
}
}
}
private void setupQualityForms(OrderElement orderElement) {
for (QualityForm each : qualityForms) {
orderElement.addTaskQualityForm(each);
}
}
private void setupAdvances(OrderElement orderElement) {
for (AdvanceAssignmentTemplate each : advanceAssignmentTemplates) {
try {
orderElement.addAdvanceAssignment(each.createAdvanceAssignment(orderElement));
} catch (Exception e) {
String errorMessage = "error adding advance assignment to newly instantiated orderElement. Ignoring it";
LOG.warn(errorMessage, e);
}
}
}
public abstract OrderElement createElement(OrderLineGroup parent);
public SchedulingState getSchedulingState() {
if ( schedulingState == null ) {
schedulingState = SchedulingState.createSchedulingState(
getSchedulingStateType(),
getChildrenStates(),
newType -> schedulingStateType = newType);
}
return schedulingState;
}
private List<SchedulingState> getChildrenStates() {
List<SchedulingState> result = new ArrayList<>();
for (OrderElementTemplate each : getChildren()) {
result.add(each.getSchedulingState());
}
return result;
}
public SchedulingState.Type getSchedulingStateType() {
if ( schedulingStateType == null ) {
schedulingStateType = Type.NO_SCHEDULED;
}
return schedulingStateType;
}
protected void setSchedulingStateType(SchedulingState.Type schedulingStateType) {
this.schedulingStateType = schedulingStateType;
}
public OrderLineGroupTemplate getParent() {
return parent;
}
protected void setParent(OrderLineGroupTemplate parent) {
this.parent = parent;
}
protected InfoComponent getInfoComponent() {
if ( infoComponent == null ) {
infoComponent = new InfoComponent();
}
return infoComponent;
}
protected void setInfoComponent(InfoComponent infoComponent) {
this.infoComponent = infoComponent;
}
/**
* @return a description of the type or template this object is.
*/
public abstract String getType();
public abstract List<OrderElementTemplate> getChildrenTemplates();
@Min(0)
public Integer getDeadlineAsDaysFromBeginning() {
return deadlineAsDaysFromBeginning;
}
@Min(0)
public Integer getStartAsDaysFromBeginning() {
return startAsDaysFromBeginning;
}
public void setStartAsDaysFromBeginning(Integer days) {
this.startAsDaysFromBeginning = days;
}
public void setDeadlineAsDaysFromBeginning(Integer days) {
this.deadlineAsDaysFromBeginning = days;
}
public String getDescription() {
return getInfoComponent().getDescription();
}
public void setDescription(String description) {
getInfoComponent().setDescription(description);
}
@NotEmpty(message = "name not specified")
public String getName() {
return getInfoComponent().getName();
}
public void setName(String name) {
getInfoComponent().setName(name);
}
@Override
public OrderElementTemplate getThis() {
return this;
}
protected void copyTo(OrderElementTemplate result) {
result.setName(getName());
result.setDescription(getDescription());
result.setDeadlineAsDaysFromBeginning(getDeadlineAsDaysFromBeginning());
result.setStartAsDaysFromBeginning(getStartAsDaysFromBeginning());
}
@Valid
public Set<MaterialAssignmentTemplate> getMaterialAssignments() {
return Collections.unmodifiableSet(materialAssignments);
}
protected void setMaterialAssignments(Set<MaterialAssignmentTemplate> newMaterialAssignments) {
this.materialAssignments = newMaterialAssignments;
}
public void addMaterialAssignment(MaterialAssignmentTemplate materialAssignment) {
Validate.notNull(materialAssignment);
materialAssignments.add(materialAssignment);
}
public void removeMaterialAssignment(MaterialAssignmentTemplate materialAssignment) {
materialAssignments.remove(materialAssignment);
}
public BigDecimal getTotalMaterialAssignmentPrice() {
BigDecimal result = BigDecimal.ZERO;
for (MaterialAssignmentTemplate each : materialAssignments) {
result = result.add(each.getTotalPrice());
}
return result;
}
public BigDecimal getTotalMaterialAssignmentUnits() {
BigDecimal result = BigDecimal.ZERO;
for (MaterialAssignmentTemplate each : materialAssignments) {
if ( each.getUnits() != null ) {
result = result.add(each.getUnits());
}
}
return result;
}
public abstract boolean isLeaf();
@Valid
public Set<Label> getLabels() {
return Collections.unmodifiableSet(labels);
}
public void addLabel(Label label){
Validate.notNull(label);
this.labels.add(label);
}
public void addLabels(Collection<Label> labels){
Validate.notNull(labels);
this.labels.addAll(labels);
}
public void removeLabel(Label label) {
this.labels.remove(label);
}
public Set<QualityForm> getQualityForms() {
return Collections.unmodifiableSet(qualityForms);
}
protected void setQualityForms(Set<QualityForm> qualityForms) {
this.qualityForms = qualityForms;
}
public void addQualityForm(QualityForm qualityForm) {
qualityForms.add(qualityForm);
}
public void removeQualityForm(QualityForm qualityForm) {
qualityForms.remove(qualityForm);
}
@Valid
public Set<AdvanceAssignmentTemplate> getAdvanceAssignmentTemplates() {
return Collections.unmodifiableSet(advanceAssignmentTemplates);
}
protected void setAdvanceAssignmentTemplates(Set<AdvanceAssignmentTemplate> advanceAssignmentTemplates) {
this.advanceAssignmentTemplates = advanceAssignmentTemplates;
}
public boolean isRoot() {
return getParent() == null;
}
@AssertTrue(message = "template name is already in use")
public boolean isUniqueRootTemplateNameConstraint() {
if ( getParent() != null ) {
return true;
}
IOrderElementTemplateDAO orderElementTemplateDAO = Registry.getOrderElementTemplateDAO();
if ( isNewObject() ) {
return !orderElementTemplateDAO.existsRootByNameAnotherTransaction(this);
} else {
try {
OrderElementTemplate template = orderElementTemplateDAO.findUniqueRootByName(getName());
return template.getId().equals(getId());
} catch (InstanceNotFoundException e) {
return true;
}
catch (NonUniqueResultException e) {
return false;
} catch (HibernateOptimisticLockingFailureException e) {
return true;
}
}
}
public List<OrderElementTemplate> getAllChildren() {
List<OrderElementTemplate> children = getChildrenTemplates();
List<OrderElementTemplate> result = new ArrayList<>();
for (OrderElementTemplate orderElement : children) {
result.add(orderElement);
result.addAll(orderElement.getAllChildren());
}
return result;
}
@Valid
@Override
public Set<CriterionRequirement> getCriterionRequirements() {
return Collections.unmodifiableSet(criterionRequirements);
}
protected void setCriterionRequirements(Set<CriterionRequirement> criterionRequirements) {
this.criterionRequirements = criterionRequirements;
}
public abstract List<HoursGroup> getHoursGroups();
public abstract Integer getWorkHours();
/**
* Operations for manipulating CriterionRequirement.
*/
protected CriterionRequirementTemplateHandler criterionRequirementHandler =
CriterionRequirementTemplateHandler.getInstance();
public void setValidCriterionRequirement(IndirectCriterionRequirement requirement, boolean valid){
requirement.setValid(valid);
criterionRequirementHandler.propagateValidCriterionRequirement(this, requirement.getParent(), valid);
}
public void removeDirectCriterionRequirement(DirectCriterionRequirement criterionRequirement){
criterionRequirementHandler.propagateRemoveCriterionRequirement(this, criterionRequirement);
removeCriterionRequirement(criterionRequirement);
}
@Override
public void removeCriterionRequirement(CriterionRequirement requirement) {
criterionRequirements.remove(requirement);
if ( requirement instanceof IndirectCriterionRequirement ) {
((IndirectCriterionRequirement)requirement).getParent().getChildren().remove(requirement);
}
}
@Override
public void addCriterionRequirement(CriterionRequirement criterionRequirement) {
criterionRequirementHandler.addCriterionRequirement(this, criterionRequirement);
}
public void addDirectCriterionRequirement(CriterionRequirement criterionRequirement) {
criterionRequirementHandler.addDirectCriterionRequirement(this, criterionRequirement);
}
public void addIndirectCriterionRequirement(IndirectCriterionRequirement criterionRequirement) {
criterionRequirementHandler.addIndirectCriterionRequirement(this, criterionRequirement);
}
protected void basicAddCriterionRequirement(CriterionRequirement criterionRequirement) {
criterionRequirement.setOrderElementTemplate(this);
this.criterionRequirements.add(criterionRequirement);
}
public void updateCriterionRequirements() {
criterionRequirementHandler.updateMyCriterionRequirements(this);
criterionRequirementHandler.propagateUpdateCriterionRequirements(this);
}
public boolean canAddCriterionRequirement(DirectCriterionRequirement newRequirement) {
return criterionRequirementHandler.canAddCriterionRequirement(this, newRequirement);
}
protected Set<IndirectCriterionRequirement> getIndirectCriterionRequirement() {
return criterionRequirementHandler.getIndirectCriterionRequirement(criterionRequirements);
}
public Set<DirectCriterionRequirement> getDirectCriterionRequirements() {
return criterionRequirementHandler.getDirectCriterionRequirement(criterionRequirements);
}
public Order getOrder() {
return (parent != null) ? parent.getOrder() : null;
}
public OrderElement getOrigin() {
return origin;
}
public void setOrigin(OrderElement origin) {
this.origin = origin;
}
public abstract BigDecimal getBudget();
public abstract boolean isOrderTemplate();
@Override
public boolean isUpdatedFromTimesheets() {
return false;
}
@Override
public boolean isJiraIssue() {
return false;
}
}