/* * 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.daos; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.criterion.Restrictions; import org.libreplan.business.common.daos.IntegrationEntityDAO; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.expensesheet.daos.IExpenseSheetLineDAO; import org.libreplan.business.labels.entities.Label; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.SchedulingDataForVersion; import org.libreplan.business.orders.entities.TaskSource; import org.libreplan.business.planner.daos.ITaskSourceDAO; import org.libreplan.business.resources.entities.Criterion; import org.libreplan.business.templates.entities.OrderElementTemplate; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workreports.daos.IWorkReportDAO; import org.libreplan.business.workreports.daos.IWorkReportLineDAO; import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReportLine; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * DAO for {@link OrderElement}. * * @author Manuel Rego Casasnovas <mrego@igalia.com> * @author Diego Pino García <dpino@igalia.com> * @author Susana Montes Pedreira <smontes@wirelessgalicia.com> * @author Jacobo Aragunde Pérez <jaragunde@igalia.com> **/ @Repository @Scope(BeanDefinition.SCOPE_SINGLETON) public class OrderElementDAO extends IntegrationEntityDAO<OrderElement> implements IOrderElementDAO { @Autowired private IWorkReportLineDAO workReportLineDAO; @Autowired private IExpenseSheetLineDAO expenseSheetLineDAO; @Autowired private IWorkReportDAO workReportDAO; @Autowired private ITaskSourceDAO taskSourceDAO; @Override public List<OrderElement> findWithoutParent() { return getSession() .createCriteria(OrderElement.class) .add(Restrictions.isNull("parent")) .list(); } public List<OrderElement> findByCodeAndParent(OrderElement parent, String code) { Criteria c = getSession().createCriteria(OrderElement.class); c.add(Restrictions.eq("infoComponent.code", code)); if ( parent != null ) { c.add(Restrictions.eq("parent", parent)); } else { c.add(Restrictions.isNull("parent")); } return c.list(); } public OrderElement findUniqueByCodeAndParent(OrderElement parent, String code) throws InstanceNotFoundException { List<OrderElement> list = findByCodeAndParent(parent, code); if ( list.isEmpty() || list.size() > 1 ) { throw new InstanceNotFoundException(code, OrderElement.class.getName()); } return list.get(0); } @Override @Transactional(readOnly = true) public EffortDuration getAssignedDirectEffort(OrderElement orderElement) { List<WorkReportLine> listWRL = this.workReportLineDAO.findByOrderElement(orderElement); EffortDuration assignedDirectHours = EffortDuration.zero(); for (WorkReportLine aListWRL : listWRL) { assignedDirectHours = assignedDirectHours.plus(aListWRL.getEffort()); } return assignedDirectHours; } @Override @Transactional(readOnly = true) public BigDecimal getHoursAdvancePercentage(OrderElement orderElement) { boolean condition = orderElement.getSumChargedEffort() != null; final EffortDuration totalChargedEffort = condition ? orderElement.getSumChargedEffort().getTotalChargedEffort() : EffortDuration.zero(); BigDecimal assignedHours = totalChargedEffort.toHoursAsDecimalWithScale(2); BigDecimal estimatedHours = new BigDecimal(orderElement.getWorkHours()).setScale(2); return estimatedHours.compareTo(BigDecimal.ZERO) <= 0 ? BigDecimal.ZERO : assignedHours.divide(estimatedHours, RoundingMode.DOWN); } @Override public void remove(Long id) throws InstanceNotFoundException { OrderElement orderElement = find(id); removeTaskSourcesFor(this.taskSourceDAO, orderElement); for (WorkReport each : getWorkReportsPointingTo(orderElement)) { workReportDAO.remove(each.getId()); } super.remove(id); } public static void removeTaskSourcesFor(ITaskSourceDAO taskSourceDAO, OrderElement orderElement) throws InstanceNotFoundException { List<SchedulingDataForVersion> allVersions = orderElement.getSchedulingDataForVersionFromBottomToTop(); for (TaskSource each : taskSourcesFrom(allVersions)) { each.detachAssociatedTaskFromParent(); taskSourceDAO.remove(each.getId()); } } private static List<TaskSource> taskSourcesFrom(List<SchedulingDataForVersion> list) { List<TaskSource> result = new ArrayList<>(); for (SchedulingDataForVersion each : list) { if ( each.getTaskSource() != null ) { result.add(each.getTaskSource()); } } return result; } private Set<WorkReport> getWorkReportsPointingTo(OrderElement orderElement) { Set<WorkReport> result = new HashSet<>(); for (WorkReportLine each : workReportLineDAO.findByOrderElementAndChildren(orderElement)) { result.add(each.getWorkReport()); } return result; } @Override public List<OrderElement> findAll() { return getSession() .createCriteria(getEntityClass()) .addOrder(org.hibernate.criterion.Order.asc("infoComponent.code")) .list(); } @SuppressWarnings("unchecked") @Override @Transactional(readOnly = true) public OrderElement findByCode(String code) throws InstanceNotFoundException { if ( StringUtils.isBlank(code) ) { throw new InstanceNotFoundException(null, getEntityClass().getName()); } OrderElement entity = (OrderElement) getSession() .createCriteria(getEntityClass()) .add(Restrictions.eq("infoComponent.code", code.trim()).ignoreCase()) .uniqueResult(); if ( entity == null ) { throw new InstanceNotFoundException(code, getEntityClass().getName()); } else { return entity; } } public List<OrderElement> findByTemplate(OrderElementTemplate template) { return getSession() .createCriteria(OrderElement.class) .add(Restrictions.eq("template", template)) .list(); } @Override public OrderElement findUniqueByCode(String code) throws InstanceNotFoundException { Criteria c = getSession().createCriteria(OrderElement.class); c.add(Restrictions.eq("infoComponent.code", code)); OrderElement orderElement = (OrderElement) c.uniqueResult(); if ( orderElement == null ) { throw new InstanceNotFoundException(code, OrderElement.class.getName()); } else { return orderElement; } } @Override @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) public OrderElement findUniqueByCodeAnotherTransaction(String code) throws InstanceNotFoundException { return findUniqueByCode(code); } @Override @Transactional public List<OrderElement> getAll() { return list(OrderElement.class); } @Override public List<OrderElement> findOrderElementsWithExternalCode() { return getSession() .createCriteria(OrderElement.class) .add(Restrictions.isNotNull("externalCode")) .list(); } @SuppressWarnings("unchecked") @Override public OrderElement findByExternalCode(String code) throws InstanceNotFoundException { if ( StringUtils.isBlank(code) ) { throw new InstanceNotFoundException(null, getEntityClass().getName()); } OrderElement entity = (OrderElement) getSession() .createCriteria(OrderElement.class) .add(Restrictions.eq("externalCode", code.trim()).ignoreCase()) .uniqueResult(); if ( entity == null ) { throw new InstanceNotFoundException(code, getEntityClass().getName()); } else { return entity; } } /** * Methods to calculate statistics with the estimated hours and worked hours of a set of order elements. * * @param list * <{@link OrderElement}> */ public BigDecimal calculateAverageEstimatedHours(final List<OrderElement> list) { return average(new BigDecimal(list.size()), sumEstimatedHours(list)); } public EffortDuration calculateAverageWorkedHours(final List<OrderElement> list) { return list.isEmpty() ? EffortDuration.zero() : EffortDuration.average(sumWorkedHours(list), list.size()); } private BigDecimal average(BigDecimal divisor, BigDecimal sum) { BigDecimal average = new BigDecimal(0); if ( sum.compareTo(new BigDecimal(0)) > 0 ) { average = sum.divide(divisor, new MathContext(2, RoundingMode.HALF_UP)); } return average; } private BigDecimal sumEstimatedHours(final List<OrderElement> list) { BigDecimal sum = new BigDecimal(0); for (OrderElement orderElement : list) { sum = sum.add(new BigDecimal(orderElement.getWorkHours())); } return sum; } private EffortDuration sumWorkedHours(final List<OrderElement> list) { EffortDuration sum = EffortDuration.zero(); for (OrderElement orderElement : list) { sum = sum.plus(getAssignedDirectEffort(orderElement)); } return sum; } public BigDecimal calculateMaxEstimatedHours(final List<OrderElement> list) { BigDecimal max = new BigDecimal(0); if ( !list.isEmpty() ) { max = new BigDecimal(list.get(0).getWorkHours()); for (OrderElement orderElement : list) { BigDecimal value = new BigDecimal(orderElement.getWorkHours()); max = getMax(max, value); } } return max; } private BigDecimal getMax(BigDecimal valueA, BigDecimal valueB) { if ( valueA.compareTo(valueB) < 0 ) { return valueB; } else if ( valueA.compareTo(valueB) > 0 ) { return valueA; } return valueA; } private BigDecimal getMin(BigDecimal valueA, BigDecimal valueB) { if ( valueA.compareTo(valueB) > 0 ) { return valueB; } else if ( valueA.compareTo(valueB) < 0 ) { return valueA; } return valueA; } public BigDecimal calculateMinEstimatedHours(final List<OrderElement> list) { BigDecimal min = new BigDecimal(0); if ( !list.isEmpty() ) { min = new BigDecimal(list.get(0).getWorkHours()); for (OrderElement orderElement : list) { BigDecimal value = new BigDecimal(orderElement.getWorkHours()); min = getMin(min, value); } } return min; } @Override public EffortDuration calculateMaxWorkedHours(final List<OrderElement> list) { EffortDuration max = EffortDuration.zero(); if ( !list.isEmpty() ) { max = getAssignedDirectEffort(list.get(0)); for (OrderElement orderElement : list) { EffortDuration value = getAssignedDirectEffort(orderElement); max = EffortDuration.max(max, value); } } return max; } @Override public EffortDuration calculateMinWorkedHours(final List<OrderElement> list) { EffortDuration min = EffortDuration.zero(); if ( !list.isEmpty() ) { min = getAssignedDirectEffort(list.get(0)); for (OrderElement orderElement : list) { EffortDuration value = getAssignedDirectEffort(orderElement); min = EffortDuration.min(min, value); } } return min; } @Override public boolean isAlreadyInUse(OrderElement orderElement) { if ( orderElement.isNewObject() ) { return false; } boolean usedInWorkReports = !getSession() .createCriteria(WorkReport.class) .add(Restrictions.eq("orderElement", orderElement)).list().isEmpty(); boolean usedInWorkReportLines = !getSession() .createCriteria(WorkReportLine.class) .add(Restrictions.eq("orderElement", orderElement)).list().isEmpty(); return usedInWorkReports || usedInWorkReportLines; } @Override public boolean isAlreadyInUseThisOrAnyOfItsChildren(OrderElement orderElement) { if ( isAlreadyInUse(orderElement) ) { return true; } for (OrderElement child : orderElement.getChildren()) { if ( isAlreadyInUseThisOrAnyOfItsChildren(child) ) { return true; } } return false; } @SuppressWarnings("unchecked") @Override @Transactional(readOnly = true) public Set<String> getAllCodesExcluding(List<OrderElement> orderElements) { String strQuery = "SELECT order.infoComponent.code FROM OrderElement order "; final List<Long> ids = getNoEmptyIds(orderElements); if ( !ids.isEmpty() ) { strQuery += "WHERE order.id NOT IN (:ids)"; } Query query = getSession().createQuery(strQuery); if ( !ids.isEmpty() ) { query.setParameterList("ids", ids); } return new HashSet<>(query.list()); } private List<Long> getNoEmptyIds(List<OrderElement> orderElements) { List<Long> result = new ArrayList<>(); for (OrderElement each: orderElements) { final Long id = each.getId(); if ( id != null ) { result.add(id); } } return result; } @Override @Transactional(readOnly= true, propagation = Propagation.REQUIRES_NEW) public OrderElement findRepeatedOrderCodeInDB(OrderElement order) { final Map<String, OrderElement> orderElements = createMapByCode(getOrderAndAllChildren(order)); final Map<String, OrderElement> orderElementsInDB = createMapByCode(getAll()); boolean condition; for (String code : orderElements.keySet()) { OrderElement orderElement = orderElements.get(code); OrderElement orderElementInDB = orderElementsInDB.get(code); // There is an element in the DB with the same code and it's a different element in a different order condition = orderElementInDB != null && !orderElementInDB.getId().equals(orderElement.getId()) && !orderElementInDB.getOrder().getId().equals(orderElement.getOrder().getId()); if ( condition ) { return orderElement; } } return null; } private List<OrderElement> getOrderAndAllChildren(OrderElement order) { List<OrderElement> result = new ArrayList<>(); result.add(order); result.addAll(order.getAllChildren()); return result; } private Map<String, OrderElement> createMapByCode(List<OrderElement> orderElements) { Map<String, OrderElement> result = new HashMap<>(); for (OrderElement each: orderElements) { final String code = each.getCode(); result.put(code, each); } return result; } @Override public boolean hasImputedExpenseSheet(Long id) throws InstanceNotFoundException { return !expenseSheetLineDAO.findByOrderElement(find(id)).isEmpty(); } @Override public boolean hasImputedExpenseSheetThisOrAnyOfItsChildren(Long id) throws InstanceNotFoundException { return !expenseSheetLineDAO.findByOrderElementAndChildren(find(id)).isEmpty(); } @SuppressWarnings("unchecked") @Override public List<OrderElement> findByLabelsAndCriteria(Set<Label> labels, Set<Criterion> criteria) { String strQuery = "SELECT oe.id "; strQuery += "FROM OrderElement oe "; String where = ""; if ( labels != null && !labels.isEmpty() ) { for (int i = 0; i < labels.size(); i++) { if ( where.isEmpty() ) { where += "WHERE "; } else { where += "AND "; } where += ":label" + i + " IN elements(oe.labels) "; } } if ( criteria != null && !criteria.isEmpty() ) { strQuery += "JOIN oe.criterionRequirements cr "; if ( where.isEmpty() ) { where += "WHERE "; } else { where += "AND "; } where += "cr.criterion IN (:criteria) "; where += "AND cr.class = DirectCriterionRequirement "; where += "GROUP BY oe.id "; where += "HAVING count(oe.id) = :criteriaSize "; } strQuery += where; Query query = getSession().createQuery(strQuery); if ( labels != null && !labels.isEmpty() ) { int i = 0; for (Label label : labels) { query.setParameter("label" + i, label); i++; } } if ( criteria != null && !criteria.isEmpty() ) { query.setParameterList("criteria", criteria); query.setParameter("criteriaSize", (long) criteria.size()); } List<Long> orderElementsIds = query.list(); if ( orderElementsIds.isEmpty() ) { return Collections.emptyList(); } return getSession() .createQuery("FROM OrderElement oe WHERE oe.id IN (:ids) ORDER BY oe.infoComponent.code") .setParameterList("ids", orderElementsIds).list(); } @Override @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) public boolean existsByCodeInAnotherOrderAnotherTransaction(OrderElement orderElement) { return existsByCodeInAnotherOrder(orderElement); } private boolean existsByCodeInAnotherOrder(OrderElement orderElement) { try { return !areInTheSameOrder(orderElement, findUniqueByCode(orderElement.getCode())); } catch (InstanceNotFoundException e) { return false; } } private boolean areInTheSameOrder(OrderElement orderElement1, OrderElement orderElement2) { Order order1 = orderElement1.getOrder(); Order order2 = orderElement2.getOrder(); return !(order1 == null || order2 == null) && Objects.equals(order1.getId(), order2.getId()); } }