/* * 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.orders.entities; import static org.libreplan.business.i18n.I18nHelper._; import java.math.BigDecimal; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.validation.Valid; import javax.validation.constraints.NotNull; import org.apache.commons.lang3.Validate; import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.Registry; import org.libreplan.business.common.daos.IIntegrationEntityDAO; 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.resources.entities.Criterion; import org.libreplan.business.resources.entities.ResourceEnum; import org.libreplan.business.templates.entities.OrderLineTemplate; public class HoursGroup extends IntegrationEntity implements Cloneable, ICriterionRequirable { private ResourceEnum resourceType = ResourceEnum.WORKER; private Integer workingHours = 0; private BigDecimal percentage = new BigDecimal(0).setScale(2); private Boolean fixedPercentage = false; private Set<CriterionRequirement> criterionRequirements = new HashSet<>(); private OrderLine parentOrderLine; private OrderLineTemplate orderLineTemplate; private HoursGroup origin; protected CriterionRequirementOrderElementHandler criterionRequirementHandler = CriterionRequirementOrderElementHandler.getInstance(); /** * Constructor for hibernate. Do not use! */ public HoursGroup() {} private HoursGroup(OrderLine parentOrderLine) { this.parentOrderLine = parentOrderLine; String code = parentOrderLine.getCode(); this.setCode(code != null ? code : ""); this.setOrderLineTemplate(null); } private HoursGroup(OrderLineTemplate orderLineTemplate) { this.orderLineTemplate = orderLineTemplate; this.setParentOrderLine(null); } public static HoursGroup create(OrderLine parentOrderLine) { HoursGroup result = new HoursGroup(parentOrderLine); result.setNewObject(true); return result; } public static HoursGroup create(OrderLineTemplate orderLineTemplate) { HoursGroup result = new HoursGroup(orderLineTemplate); result.setNewObject(true); return result; } public static HoursGroup createUnvalidated(String code, ResourceEnum resourceType, Integer workingHours) { HoursGroup result = create(new HoursGroup()); result.setCode(code); result.setResourceType(resourceType); result.setWorkingHours(workingHours); return result; } /** * Returns a copy of hoursGroup, and sets parent as its parent. * * @param hoursGroup * @param parent * @return {@link HoursGroup} */ public static HoursGroup copyFrom(HoursGroup hoursGroup, OrderLineTemplate parent) { HoursGroup result = copyFrom(hoursGroup); result.setCriterionRequirements( copyDirectCriterionRequirements(result, hoursGroup.getDirectCriterionRequirement())); result.setOrderLineTemplate(parent); result.setParentOrderLine(null); return result; } private static Set<CriterionRequirement> copyDirectCriterionRequirements( HoursGroup hoursGroup, Collection<DirectCriterionRequirement> criterionRequirements) { Set<CriterionRequirement> result = new HashSet<>(); for (DirectCriterionRequirement each: criterionRequirements) { DirectCriterionRequirement newDirectCriterionRequirement = DirectCriterionRequirement.copyFrom(each, hoursGroup); newDirectCriterionRequirement.setHoursGroup(hoursGroup); result.add(newDirectCriterionRequirement); } return result; } public static HoursGroup copyFrom(HoursGroup hoursGroup, OrderLine parent) { HoursGroup result = copyFrom(hoursGroup); result.setCriterionRequirements( copyDirectCriterionRequirements(result, hoursGroup.getDirectCriterionRequirement())); result.setOrderLineTemplate(null); result.setParentOrderLine(parent); return result; } private static HoursGroup copyFrom(HoursGroup hoursGroup) { HoursGroup result = createUnvalidated( hoursGroup.getCode(), hoursGroup.getResourceType(), hoursGroup.getWorkingHours()); result.setCode(UUID.randomUUID().toString()); result.percentage = hoursGroup.getPercentage(); result.fixedPercentage = hoursGroup.isFixedPercentage(); result.origin = hoursGroup; return result; } public ResourceEnum getResourceType() { return resourceType; } public void setResourceType(ResourceEnum resource) { Validate.notNull(resource); this.resourceType = resource; } public void setWorkingHours(Integer workingHours) throws IllegalArgumentException { if ( (workingHours != null) && (workingHours < 0) ) { throw new IllegalArgumentException("Working hours should not be negative"); } if ( workingHours == null ) { workingHours = 0; } this.workingHours = workingHours; } @NotNull(message = "working hours not specified") public Integer getWorkingHours() { return workingHours; } /** * @param proportion * It's one based, instead of one hundred based * @throws IllegalArgumentException * if the new sum of percentages in the parent {@link OrderLine} * surpasses one */ public void setPercentage(BigDecimal proportion) throws IllegalArgumentException { BigDecimal oldPercentage = this.percentage; this.percentage = proportion; if ( !isPercentageValidForParent() ) { this.percentage = oldPercentage; throw new IllegalArgumentException(_("Total percentage should be less than 100%")); } } private boolean isPercentageValidForParent() { return (parentOrderLine != null) ? parentOrderLine.isPercentageValid() : orderLineTemplate.isPercentageValid(); } public BigDecimal getPercentage() { return percentage; } public void setFixedPercentage(Boolean fixedPercentage) { this.fixedPercentage = fixedPercentage; } public Boolean isFixedPercentage() { return this.fixedPercentage; } public void setCriterionRequirements(Set<CriterionRequirement> criterionRequirements) { this.criterionRequirements = criterionRequirements; } @Valid @Override public Set<CriterionRequirement> getCriterionRequirements() { return criterionRequirements; } public Set<Criterion> getValidCriterions() { Set<Criterion> criterions = new HashSet<>(); for (CriterionRequirement criterionRequirement : getDirectCriterionRequirement()) { criterions.add(criterionRequirement.getCriterion()); } for (IndirectCriterionRequirement requirement : getIndirectCriterionRequirement()) { if ( requirement.isValid() ) { criterions.add(requirement.getCriterion()); } } return Collections.unmodifiableSet(criterions); } @Override public void addCriterionRequirement(CriterionRequirement requirement) { if ( !isValidResourceType(requirement) ) { throw new IllegalStateException( "Criterion cannot be assigned to this Hours Group. Criterion Resource Type is of a different type"); } if ( existSameCriterionRequirement(requirement) ) { throw new IllegalStateException( "Criterion cannot be assigned to this Hours Group. Criterion already exist within Hours Group"); } requirement.setHoursGroup(this); criterionRequirements.add(requirement); } public boolean canAddCriterionRequirement(CriterionRequirement newRequirement) { return !((isValidResourceType(newRequirement)) && (!existSameCriterionRequirement(newRequirement))); } @Override public void removeCriterionRequirement(CriterionRequirement requirement) { criterionRequirements.remove(requirement); if ( requirement instanceof IndirectCriterionRequirement ) { ((IndirectCriterionRequirement) requirement).getParent().getChildren().remove(requirement); } requirement.setCriterion(null); requirement.setHoursGroup(null); requirement.setOrderElement(null); } public void setParentOrderLine(OrderLine parentOrderLine) { this.parentOrderLine = parentOrderLine; } public OrderLine getParentOrderLine() { return parentOrderLine; } public void updateMyCriterionRequirements() { Set<CriterionRequirement> requirementsParent = criterionRequirementHandler .getRequirementWithSameResourType(getCriterionRequirementsFromParent(), resourceType); Set<IndirectCriterionRequirement> currentIndirects = criterionRequirementHandler .getCurrentIndirectRequirements(getIndirectCriterionRequirement(), requirementsParent); criterionRequirementHandler.removeOldIndirects(this, currentIndirects); criterionRequirementHandler.addNewsIndirects(this, currentIndirects); } public void propagateIndirectCriterionRequirementsKeepingValid() { updateMyCriterionRequirements(); // Set valid value as original value for every indirect Map<Criterion, Boolean> mapCriterionToValid = createCriterionToValidMap(origin.getIndirectCriterionRequirement()); for (CriterionRequirement each : criterionRequirements) { if ( each instanceof IndirectCriterionRequirement ) { IndirectCriterionRequirement indirect = (IndirectCriterionRequirement) each; indirect.setValid(mapCriterionToValid.get(each.getCriterion())); } } } private Map<Criterion, Boolean> createCriterionToValidMap(Set<IndirectCriterionRequirement> indirects) { Map<Criterion, Boolean> result = new HashMap<>(); for (IndirectCriterionRequirement each : indirects) { result.put(each.getCriterion(), each.isValid()); } return result; } private Set<CriterionRequirement> getCriterionRequirementsFromParent() { return (parentOrderLine != null) ? parentOrderLine.getCriterionRequirements() : orderLineTemplate.getCriterionRequirements(); } public Set<IndirectCriterionRequirement> getIndirectCriterionRequirement() { Set<IndirectCriterionRequirement> list = new HashSet<>(); for (CriterionRequirement criterionRequirement : criterionRequirements ) { if ( criterionRequirement instanceof IndirectCriterionRequirement ) { list.add((IndirectCriterionRequirement) criterionRequirement); } } return list; } public Set<DirectCriterionRequirement> getDirectCriterionRequirement() { Set<DirectCriterionRequirement> list = new HashSet<>(); for (CriterionRequirement criterionRequirement : criterionRequirements ) { if ( criterionRequirement instanceof DirectCriterionRequirement ) { list.add((DirectCriterionRequirement) criterionRequirement); } } return list; } public boolean isValidResourceType(CriterionRequirement newRequirement) { ResourceEnum resourceTypeRequirement = newRequirement.getCriterion().getType().getResource(); return resourceType == null || (resourceType.equals(resourceTypeRequirement) || (resourceTypeRequirement.equals(ResourceEnum.getDefault()))); } /** * Duplicate of {@link HoursGroup#isValidResourceType(CriterionRequirement)}. * Needed because in my case I do not need to check equality with {@link ResourceEnum#getDefault()}. */ public boolean isValidResourceTypeChanged(CriterionRequirement newRequirement) { return resourceType == null || resourceType.equals(newRequirement.getCriterion().getType().getResource()); } boolean existSameCriterionRequirement(CriterionRequirement newRequirement) { Criterion criterion = newRequirement.getCriterion(); for (CriterionRequirement requirement : getCriterionRequirements()){ if ( requirement.getCriterion().equals(criterion) ) { return true; } } return false; } public OrderLineTemplate getOrderLineTemplate() { return orderLineTemplate; } public void setOrderLineTemplate(OrderLineTemplate orderLineTemplate) { this.orderLineTemplate = orderLineTemplate; } public HoursGroup getOrigin() { return origin; } public void setOrigin(HoursGroup origin) { this.origin = origin; } @Override protected IIntegrationEntityDAO<? extends IntegrationEntity> getIntegrationEntityDAO() { return Registry.getHoursGroupDAO(); } /** * The automatic checking of this constraint is avoided because it uses the wrong code property. */ @Override public boolean isUniqueCodeConstraint() { return true; } }