/* * 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-2012 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.orders.entities; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.validation.Valid; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotNull; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes; import org.libreplan.business.advance.entities.AdvanceType; import org.libreplan.business.advance.entities.DirectAdvanceAssignment; import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.common.Registry; import org.libreplan.business.common.entities.EntitySequence; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.externalcompanies.entities.CustomerCommunication; import org.libreplan.business.externalcompanies.entities.DeadlineCommunication; import org.libreplan.business.externalcompanies.entities.DeliverDateComparator; import org.libreplan.business.externalcompanies.entities.EndDateCommunication; import org.libreplan.business.externalcompanies.entities.EndDateCommunicationComparator; import org.libreplan.business.externalcompanies.entities.ExternalCompany; import org.libreplan.business.orders.daos.IOrderDAO; import org.libreplan.business.planner.entities.DayAssignment; import org.libreplan.business.planner.entities.DayAssignment.FilterType; import org.libreplan.business.planner.entities.Task; import org.libreplan.business.planner.entities.TaskElement; import org.libreplan.business.planner.entities.TaskGroup; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.scenarios.entities.OrderVersion; import org.libreplan.business.scenarios.entities.Scenario; import org.libreplan.business.templates.entities.OrderTemplate; import org.libreplan.business.users.entities.OrderAuthorization; import org.libreplan.business.util.deepcopy.DeepCopy; /** * It represents an {@link Order} with its related information. <br /> * * Every project in the application is an {@link Order}. * * @author Óscar González Fernández <ogonzalez@igalia.com> * @author Manuel Rego Casasnovas <rego@igalia.com> */ public class Order extends OrderLineGroup implements Comparable { private String responsible; private Boolean dependenciesConstraintsHavePriority; private BaseCalendar calendar; private Integer lastOrderElementSequenceCode = 0; private BigDecimal workBudget = BigDecimal.ZERO.setScale(2); private BigDecimal materialsBudget = BigDecimal.ZERO.setScale(2); private Integer totalHours = 0; private OrderStatusEnum state = OrderStatusEnum.getDefault(); private ExternalCompany customer; private String customerReference; private Map<Scenario, OrderVersion> scenarios = new HashMap<>(); private Set<OrderAuthorization> orderAuthorizations = new HashSet<>(); private CurrentVersionInfo currentVersionInfo; private Set<CustomerCommunication> customerCommunications = new HashSet<>(); @Valid private SortedSet<DeadlineCommunication> deliveringDates = new TreeSet<>(new DeliverDateComparator()); @Valid private SortedSet<EndDateCommunication> endDateCommunicationToCustomer = new TreeSet<>( new EndDateCommunicationComparator()); public enum SchedulingMode { FORWARD, BACKWARDS } private SchedulingMode schedulingMode = SchedulingMode.FORWARD; private boolean neededToRecalculateSumChargedEfforts = false; private boolean neededToRecalculateSumExpenses = false; private Integer hoursMargin; private Integer budgetMargin; public Order() { /** * Constructor for hibernate. Do not use! */ } public static Order create() { Order order = new Order(); order.setNewObject(true); return order; } public static Order createUnvalidated(String code) { Order order = create(new Order(), code); return order; } public static class CurrentVersionInfo { private final OrderVersion orderVersion; private final boolean modifyingTheOwnerScenario; static CurrentVersionInfo create(Scenario scenario, OrderVersion orderVersion) { return new CurrentVersionInfo(scenario, orderVersion); } private CurrentVersionInfo(Scenario scenario, OrderVersion orderVersion) { Validate.notNull(scenario); Validate.notNull(orderVersion); this.orderVersion = orderVersion; this.modifyingTheOwnerScenario = orderVersion.isOwnedBy(scenario); } public boolean isUsingTheOwnerScenario() { return modifyingTheOwnerScenario; } public OrderVersion getOrderVersion() { return orderVersion; } } public CurrentVersionInfo getCurrentVersionInfo() { if (currentVersionInfo == null) { throw new IllegalStateException( "Order#useSchedulingDataFor(Scenario scenario)" + " must have been called first in order to use" + " this method"); } return currentVersionInfo; } public void addOrderAuthorization(OrderAuthorization orderAuthorization) { orderAuthorization.setOrder(this); orderAuthorizations.add(orderAuthorization); } public Map<Scenario, OrderVersion> getScenarios() { return Collections.unmodifiableMap(scenarios); } public void useSchedulingDataFor(Scenario scenario) { useSchedulingDataFor(scenario, true); } public void useSchedulingDataFor(Scenario scenario, boolean recursive) { OrderVersion orderVersion = scenarios.get(scenario); currentVersionInfo = CurrentVersionInfo.create(scenario, orderVersion); useSchedulingDataFor(orderVersion, recursive); } @Override public void writeSchedulingDataChanges() { super.writeSchedulingDataChanges(); } public void writeSchedulingDataChangesTo(Scenario currentScenario, OrderVersion newOrderVersion) { setVersionForScenario(currentScenario, newOrderVersion); writeSchedulingDataChangesTo( deepCopyWithNeededReplaces(newOrderVersion), newOrderVersion); useSchedulingDataFor(currentScenario); removeSpuriousDayAssignments(currentScenario); } private DeepCopy deepCopyWithNeededReplaces( OrderVersion newOrderVersion) { DeepCopy result = new DeepCopy(); addNeededReplaces(result, newOrderVersion); return result; } public boolean isUsingTheOwnerScenario() { return getCurrentVersionInfo().isUsingTheOwnerScenario(); } /** * Should be public! * Used in orders/_edition.zul */ public BigDecimal getWorkBudget() { if (workBudget == null) { return BigDecimal.ZERO; } return workBudget; } public void setWorkBudget(BigDecimal workBudget) { if (workBudget == null) { workBudget = BigDecimal.ZERO.setScale(2); } this.workBudget = workBudget; } /** * Should be public! * Used in orders/_edition.zul */ public BigDecimal getMaterialsBudget() { if (materialsBudget == null) { return BigDecimal.ZERO; } return materialsBudget; } public void setMaterialsBudget(BigDecimal materialsBudget) { if (materialsBudget == null) { materialsBudget = BigDecimal.ZERO.setScale(2); } this.materialsBudget = materialsBudget; } /** * Should be public! * Used in orders/_edition.zul */ public BigDecimal getTotalManualBudget() { return getWorkBudget().add(getMaterialsBudget()); } public Integer getTotalHours() { return totalHours; } public void setTotalHours(Integer totalHours) { this.totalHours = totalHours; } public OrderStatusEnum getState() { return state; } public void setState(OrderStatusEnum state) { this.state = state; } public String getCustomerReference() { return this.customerReference; } public void setCustomerReference(String customerReference) { this.customerReference = customerReference; } public ExternalCompany getCustomer() { return this.customer; } public void setCustomer(ExternalCompany customer) { this.customer = customer; } public String getResponsible() { return responsible; } public void setResponsible(String responsible) { this.responsible = responsible; } @NotNull public SchedulingMode getSchedulingMode() { return schedulingMode; } public boolean isScheduleBackwards() { return getSchedulingMode() == SchedulingMode.BACKWARDS; } public void setSchedulingMode(SchedulingMode schedulingMode) { Validate.notNull(schedulingMode); this.schedulingMode = schedulingMode; } public boolean isDeadlineBeforeStart() { return getDeadline() != null && getDeadline().before(getInitDate()); } public List<OrderElement> getOrderElements() { return new ArrayList<OrderElement>(getChildren()); } public TaskGroup getAssociatedTaskElement() { return (TaskGroup) super.getAssociatedTaskElement(); } public List<TaskElement> getAllChildrenAssociatedTaskElements() { List<TaskElement> result = new ArrayList<TaskElement>(); for (OrderElement orderElement : getAllChildren()) { TaskElement taskElement = orderElement.getAssociatedTaskElement(); if ( taskElement != null ) { result.add(taskElement); } } Validate.noNullElements(result); return result; } public List<TaskElement> getAssociatedTasks() { ArrayList<TaskElement> result = new ArrayList<TaskElement>(); TaskGroup taskGroup = getAssociatedTaskElement(); if ( taskGroup != null ) { result.addAll(taskGroup.getChildren()); } return result; } public boolean isSomeTaskElementScheduled() { return isScheduled(); } @SuppressWarnings("unused") @AssertTrue(message = "the project must have a start date") private boolean isIfSchedulingModeIsForwardOrderMustHaveStartDateConstraint() { return getSchedulingMode() != SchedulingMode.FORWARD || getInitDate() != null; } @SuppressWarnings("unused") @AssertTrue(message = "the project must have a deadline") private boolean isIfSchedulingModeIsBackwardsOrderMustHaveDeadlineConstraint() { return getSchedulingMode() != SchedulingMode.BACKWARDS || getDeadline() != null; } @SuppressWarnings("unused") @AssertTrue(message = "deadline must be after start date") private boolean isDeadlineMustBeAfterStartConstraint() { return getInitDate() == null || !this.isDeadlineBeforeStart(); } @SuppressWarnings("unused") @AssertTrue(message = "At least one hours group is needed for each task") private boolean isAtLeastOneHoursGroupForEachOrderElementConstraint() { for (OrderElement orderElement : this.getOrderElements()) { if (!orderElement.checkAtLeastOneHoursGroup()) { return false; } } return true; } public List<DayAssignment> getDayAssignments(FilterType filter) { List<DayAssignment> dayAssignments = new ArrayList<DayAssignment>(); for (OrderElement orderElement : getAllOrderElements()) { Set<TaskElement> taskElements = orderElement.getTaskElements(); for (TaskElement taskElement : taskElements) { if (taskElement instanceof Task) { dayAssignments .addAll(taskElement.getDayAssignments(filter)); } } } return DayAssignment.filter(dayAssignments, filter); } public Set<Resource> getResources(FilterType filter) { Set<Resource> resources = new HashSet<Resource>(); for (DayAssignment dayAssignment : getDayAssignments(filter)) { resources.add(dayAssignment.getResource()); } return resources; } @Override protected boolean applyConstraintBasedOnInitOrEndDate(Task task, boolean scheduleBackwards) { // the initDate or the deadline of a order doesn't imply a start // constraint at a task return false; } public boolean getDependenciesConstraintsHavePriority() { return dependenciesConstraintsHavePriority != null && dependenciesConstraintsHavePriority; } public void setDependenciesConstraintsHavePriority( Boolean dependenciesConstraintsHavePriority) { this.dependenciesConstraintsHavePriority = dependenciesConstraintsHavePriority; } public void setCalendar(BaseCalendar calendar) { this.calendar = calendar; } @NotNull(message = "project calendar not specified") public BaseCalendar getCalendar() { return calendar; } public void incrementLastOrderElementSequenceCode() { if (this.lastOrderElementSequenceCode == null) { this.lastOrderElementSequenceCode = 0; } this.lastOrderElementSequenceCode++; } @NotNull(message = "last task sequence code not specified") public Integer getLastOrderElementSequenceCode() { return lastOrderElementSequenceCode; } @Override public Order getOrder() { return this; } @Override public OrderTemplate createTemplate() { return OrderTemplate.create(this); } public void generateOrderElementCodes(int numberOfDigits) { if (isCodeAutogenerated()) { for (OrderElement orderElement : this.getAllOrderElements()) { if ((orderElement.getCode() == null) || (orderElement.getCode().isEmpty()) || (!orderElement.getCode().startsWith(this.getCode()))) { this.incrementLastOrderElementSequenceCode(); String orderElementCode = EntitySequence.formatValue( numberOfDigits, this.getLastOrderElementSequenceCode()); orderElement.setCode(this.getCode() + EntitySequence.CODE_SEPARATOR_CHILDREN + orderElementCode); } if (orderElement instanceof OrderLine) { for (HoursGroup hoursGroup : orderElement.getHoursGroups()) { if ((hoursGroup.getCode() == null) || (hoursGroup.getCode().isEmpty()) || (!hoursGroup.getCode().startsWith( orderElement.getCode()))) { ((OrderLine) orderElement) .incrementLastHoursGroupSequenceCode(); String hoursGroupCode = EntitySequence.formatValue( numberOfDigits, ((OrderLine) orderElement) .getLastHoursGroupSequenceCode()); hoursGroup.setCode(orderElement.getCode() + EntitySequence.CODE_SEPARATOR_CHILDREN + hoursGroupCode); } } } } } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getId() == null || isNewObject()) ? super.hashCode() : getId().hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || isNewObject()) { return false; } if (!(obj instanceof Order)) { return false; } Order other = (Order) obj; if (getId() == null) { if (other.getId() != null) { return false; } } else if (!getId().equals(other.getId())) { return false; } return true; } public void setVersionForScenario(Scenario currentScenario, OrderVersion orderVersion) { scenarios.put(currentScenario, orderVersion); } /** * Disassociates this order and its children from the scenario * @param scenario * @return <code>null</code> if there is no order version for the scenario; * the order version associated to the supplied scenario */ public OrderVersion disassociateFrom(Scenario scenario) { OrderVersion existentVersion = scenarios.remove(scenario); if (existentVersion != null && !isVersionUsed(existentVersion)) { removeVersion(existentVersion); } return existentVersion; } public OrderVersion getOrderVersionFor(Scenario current) { return scenarios.get(current); } public void setOrderVersion(Scenario scenario, OrderVersion newOrderVersion) { scenarios.put(scenario, newOrderVersion); } public boolean hasNoVersions() { return scenarios.isEmpty(); } public boolean isVersionUsed(OrderVersion orderVersion) { for (OrderVersion each : getScenarios().values()) { if (each.getId().equals(orderVersion.getId())) { return true; } } return false; } @Override public OrderLine toLeaf() { throw new UnsupportedOperationException( "Order can not be converted to leaf"); } public DirectAdvanceAssignment getDirectAdvanceAssignmentOfTypeSubcontractor() { if (StringUtils.isBlank(getExternalCode())) { return null; } AdvanceType advanceType = PredefinedAdvancedTypes.SUBCONTRACTOR.getType(); return getAdvanceAssignmentByType(advanceType); } @AssertTrue(message = "project name is already being used") public boolean isProjectUniqueNameConstraint() { IOrderDAO orderDAO = Registry.getOrderDAO(); if (isNewObject()) { return !orderDAO.existsByNameAnotherTransaction(getName()); } else { try { Order o = orderDAO.findByNameAnotherTransaction(getName()); return o.getId().equals(getId()); } catch (InstanceNotFoundException e) { return true; } } } @Override public int compareTo(Object o) { return this.getName().compareToIgnoreCase(((Order) o).getName()); } public void setCustomerCommunications(Set<CustomerCommunication> customerCommunications) { this.customerCommunications = customerCommunications; } public Set<CustomerCommunication> getCustomerCommunications() { return customerCommunications; } public void setDeliveringDates(SortedSet<DeadlineCommunication> deliveringDates) { this.deliveringDates = deliveringDates; } public SortedSet<DeadlineCommunication> getDeliveringDates() { return deliveringDates; } public void setEndDateCommunicationToCustomer( SortedSet<EndDateCommunication> endDateCommunicationToCustomer) { this.endDateCommunicationToCustomer.clear(); this.endDateCommunicationToCustomer.addAll(endDateCommunicationToCustomer); } public SortedSet<EndDateCommunication> getEndDateCommunicationToCustomer() { return Collections.unmodifiableSortedSet(this.endDateCommunicationToCustomer); } public void updateFirstAskedEndDate(Date communicationDate) { if (this.endDateCommunicationToCustomer != null && !this.endDateCommunicationToCustomer.isEmpty()) { this.endDateCommunicationToCustomer.first().setCommunicationDate(communicationDate); } } public Date getLastAskedEndDate() { if (this.endDateCommunicationToCustomer != null && !this.endDateCommunicationToCustomer.isEmpty()) { return this.endDateCommunicationToCustomer.first().getEndDate(); } return null; } public EndDateCommunication getLastEndDateCommunicationToCustomer() { if (this.endDateCommunicationToCustomer != null && !this.endDateCommunicationToCustomer.isEmpty()) { return this.endDateCommunicationToCustomer.first(); } return null; } public void removeAskedEndDate(EndDateCommunication endDate) { this.endDateCommunicationToCustomer.remove(endDate); } public void addAskedEndDate(EndDateCommunication endDate) { this.endDateCommunicationToCustomer.add(endDate); } public void markAsNeededToRecalculateSumChargedEfforts() { neededToRecalculateSumChargedEfforts = true; } public boolean isNeededToRecalculateSumChargedEfforts() { return neededToRecalculateSumChargedEfforts; } public void markAsNeededToRecalculateSumExpenses() { neededToRecalculateSumExpenses = true; } public boolean isNeededToRecalculateSumExpenses() { return neededToRecalculateSumExpenses; } public Integer getHoursMargin() { return hoursMargin; } public void setHoursMargin(Integer hoursMargin) { this.hoursMargin = hoursMargin; } public Integer getBudgetMargin() { return budgetMargin; } public void setBudgetMargin(Integer budgetMargin) { this.budgetMargin = budgetMargin; } @Override public boolean isOrder() { return true; } @AssertTrue(message = "task code is repeated inside the project") public boolean isUniqueCodeInsideOrderConstraint() { List<String> codes = new ArrayList<>(); codes.add(getCode()); for (OrderElement child : getAllChildren()) { String childCode = child.getCode(); if (codes.contains(childCode)) { return false; } codes.add(childCode); } return true; } public void calculateAndSetTotalHours() { int result = 0; for (OrderElement orderElement : this.getChildren()) { result = result + orderElement.getWorkHours(); } this.setTotalHours(result); } }