/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.criterion.Restrictions;
import org.joda.time.LocalDate;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IntegrationEntityDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.daos.CostCategoryDAO;
import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.externalcompanies.entities.ExternalCompany;
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.OrderStatusEnum;
import org.libreplan.business.orders.entities.SchedulingState;
import org.libreplan.business.planner.daos.ITaskSourceDAO;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.reports.dtos.CostExpenseSheetDTO;
import org.libreplan.business.reports.dtos.OrderCostsPerResourceDTO;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.users.daos.IOrderAuthorizationDAO;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.business.users.entities.OrderAuthorization;
import org.libreplan.business.users.entities.OrderAuthorizationType;
import org.libreplan.business.users.entities.User;
import org.libreplan.business.users.entities.UserRole;
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 Order}.
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
* @author Jacobo Aragunde Pérez <jaragunde@igalia.com>
*/
@Repository
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class OrderDAO extends IntegrationEntityDAO<Order> implements IOrderDAO {
@Autowired
private ITaskSourceDAO taskSourceDAO;
@Autowired
private ITypeOfWorkHoursDAO typeOfWorkHoursDAO;
@Autowired
private IOrderAuthorizationDAO orderAuthorizationDAO;
@Autowired
private IUserDAO userDAO;
@Autowired
private IAdHocTransactionService transactionService;
private String STATE_PARAMETER = "state";
@Override
public List<Order> getOrders() {
return list(Order.class);
}
@Override
public void remove(Long id) throws InstanceNotFoundException {
Order order = find(id);
OrderElementDAO.removeTaskSourcesFor(taskSourceDAO, order);
super.remove(id);
}
private boolean isOrderContained(Order order, List<Order> orders) {
for (Order each : orders) {
if (each.getId().equals(order.getId())) {
return true;
}
}
return false;
}
private boolean matchFilterCriterion(OrderElement orderElement, List<Criterion> criterions) {
if ((criterions != null) && (!criterions.isEmpty())) {
List<OrderElement> orderElements = new ArrayList<>();
orderElements.add(orderElement);
List<Task> tasks = this.getFilteredTask(orderElements, criterions);
return !tasks.isEmpty();
}
return true;
}
@Transactional(readOnly = true)
public List<OrderCostsPerResourceDTO> getOrderCostsPerResource(
List<Order> orders,
Date startingDate,
Date endingDate,
List<Criterion> criterions) {
String strQuery = "SELECT new org.libreplan.business.reports.dtos.OrderCostsPerResourceDTO(worker, wrl) " +
"FROM Worker worker, WorkReportLine wrl " +
"LEFT OUTER JOIN wrl.resource resource " +
"WHERE resource.id = worker.id ";
// Set date range
if (startingDate != null && endingDate != null) {
strQuery += "AND wrl.date BETWEEN :startingDate AND :endingDate ";
}
if (startingDate != null && endingDate == null) {
strQuery += "AND wrl.date >= :startingDate ";
}
if (startingDate == null && endingDate != null) {
strQuery += "AND wrl.date <= :endingDate ";
}
// Order by
strQuery += "ORDER BY worker.id, wrl.date";
Query query = getSession().createQuery(strQuery);
if (startingDate != null) {
query.setParameter("startingDate", startingDate);
}
if (endingDate != null) {
query.setParameter("endingDate", endingDate);
}
List<OrderCostsPerResourceDTO> list = query.list();
List<OrderCostsPerResourceDTO> filteredList = new ArrayList<>();
for (OrderCostsPerResourceDTO each : list) {
Order order = loadOrderAvoidingProxyFor(each.getOrderElement());
// Apply filtering
if (matchFilterCriterion(each.getOrderElement(), criterions) && isOrderContained(order, orders)) {
// Attach orderName value
each.setOrderName(order.getName());
each.setOrderCode(order.getCode());
// Attach calculated pricePerHour
BigDecimal pricePerHour = CostCategoryDAO.getPriceByResourceDateAndHourType(
each.getWorker(), new LocalDate(each.getDate()), each.getHoursTypeCode());
if (pricePerHour == null) {
for (TypeOfWorkHours defaultprice : typeOfWorkHoursDAO.list(TypeOfWorkHours.class)) {
if (defaultprice.getCode().equals(each.getHoursTypeCode())) {
pricePerHour = defaultprice.getDefaultPrice();
}
}
}
each.setCostPerHour(pricePerHour);
each.setCost(each.getCostPerHour().multiply(each.getNumHours()));
filteredList.add(each);
}
}
return filteredList;
}
@Override
public List<Order> getOrdersByReadAuthorization(User user) {
if (user.isInRole(UserRole.ROLE_SUPERUSER) ||
user.isInRole(UserRole.ROLE_READ_ALL_PROJECTS) ||
user.isInRole(UserRole.ROLE_EDIT_ALL_PROJECTS)) {
return getOrders();
}
else {
List<Order> orders = new ArrayList<>();
List<OrderAuthorization> authorizations = orderAuthorizationDAO.listByUserAndItsProfiles(user);
for(OrderAuthorization authorization : authorizations) {
if (authorization.getAuthorizationType() == OrderAuthorizationType.READ_AUTHORIZATION ||
authorization.getAuthorizationType() == OrderAuthorizationType.WRITE_AUTHORIZATION) {
Order order = authorization.getOrder();
if (!orders.contains(order)) {
// These lines forces the load of the basic attributes of the order
order.getName();
orders.add(order);
}
}
}
return orders;
}
}
private List<Order> getOrdersByReadAuthorizationBetweenDatesByLabelsCriteriaCustomerAndState(
User user,
Date startDate,
Date endDate,
List<Label> labels,
List<Criterion> criteria,
ExternalCompany customer,
OrderStatusEnum state) {
List<Long> ordersIdsFiltered = getOrdersIdsFiltered(user, labels, criteria, customer, state);
if (ordersIdsFiltered != null && ordersIdsFiltered.isEmpty()) {
return Collections.emptyList();
}
List<Long> ordersIdsByDates = getOrdersIdsByDates(startDate, endDate);
if (ordersIdsByDates != null && ordersIdsByDates.isEmpty()) {
return Collections.emptyList();
}
List<Long> ordersIdsUnscheduled = getOrdersIdsUnscheduled(startDate, endDate);
Criteria c = getSession().createCriteria(Order.class);
if (ordersIdsFiltered != null && ordersIdsByDates != null) {
org.hibernate.criterion.Criterion and = Restrictions.and(
Restrictions.in("id", ordersIdsFiltered),
Restrictions.in("id", ordersIdsByDates));
c.add(and);
} else {
if (ordersIdsFiltered != null) {
c.add(Restrictions.in("id", ordersIdsFiltered));
}
if (ordersIdsByDates != null) {
if (ordersIdsUnscheduled.isEmpty()) {
c.add(Restrictions.in("id", ordersIdsByDates));
} else {
c.add(Restrictions.or(
Restrictions.in("id", ordersIdsByDates),
Restrictions.in("id", ordersIdsUnscheduled)));
}
}
}
c.addOrder(org.hibernate.criterion.Order.desc("initDate"));
c.addOrder(org.hibernate.criterion.Order.asc("infoComponent.name"));
return c.list();
}
private List<Long> getOrdersIdsUnscheduled(Date startDate, Date endDate) {
String strQuery = "SELECT s.orderElement.id " +
"FROM SchedulingDataForVersion s " +
"WHERE s.schedulingStateType = :type";
Query query = getSession().createQuery(strQuery);
query.setParameter("type", SchedulingState.Type.NO_SCHEDULED);
List<Long> ordersIdsUnscheduled = query.list();
if (ordersIdsUnscheduled.isEmpty()) {
return Collections.emptyList();
}
String strQueryDates = "SELECT o.id " + "FROM Order o " + "WHERE o.id IN (:ids) ";
if (startDate != null) {
strQueryDates += "AND o.initDate >= :startDate ";
}
if (endDate != null) {
strQueryDates += "AND o.initDate <= :endDate ";
}
Query queryDates = getSession().createQuery(strQueryDates);
if (startDate != null) {
queryDates.setParameter("startDate", startDate);
}
if (endDate != null) {
queryDates.setParameter("endDate", endDate);
}
queryDates.setParameterList("ids", ordersIdsUnscheduled);
return queryDates.list();
}
/**
* If both params are <code>null</code> it returns <code>null</code>.
* Otherwise it filters the list of tasks to return the ones without parent between the dates.
*/
private List<Long> getOrdersIdsByDates(Date startDate, Date endDate) {
if (startDate == null && endDate == null) {
/* Don't replace null with Collections.emptyList(), as the prompt says (sometimes), because it breaks logic */
return null;
}
String strQuery = "SELECT t.taskSource.schedulingData.orderElement.id "
+ "FROM TaskElement t "
+ "WHERE t.parent IS NULL ";
if (endDate != null) {
strQuery += "AND t.startDate.date <= :endDate ";
}
if (startDate != null) {
strQuery += "AND t.endDate.date >= :startDate ";
}
Query query = getSession().createQuery(strQuery);
if (startDate != null) {
query.setParameter("startDate", LocalDate.fromDateFields(startDate));
}
if (endDate != null) {
query.setParameter("endDate", LocalDate.fromDateFields(endDate));
}
return query.list();
}
/**
* If user has permissions over all orders and not filters are passed it returns <code>null</code>.
* Otherwise, it returns the list of orders
* identifiers for which the user has read permissions and the filters pass.
*/
private List<Long> getOrdersIdsFiltered(User user,
List<Label> labels,
List<Criterion> criteria,
ExternalCompany customer,
OrderStatusEnum state) {
List<Long> ordersIdsByReadAuthorization = getOrdersIdsByReadAuthorization(user);
String strQuery = "SELECT o.id ";
strQuery += "FROM Order o ";
String where = "";
String whereFinal = "";
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(o.labels) ";
}
}
if (criteria != null && !criteria.isEmpty()) {
strQuery += "JOIN o.criterionRequirements cr ";
if (where.isEmpty()) {
where += "WHERE ";
} else {
where += "AND ";
}
where += "cr.criterion IN (:criteria) ";
where += "AND cr.class = DirectCriterionRequirement ";
whereFinal += "GROUP BY o.id ";
whereFinal += "HAVING count(o.id) = :criteriaSize ";
}
if (customer != null) {
if (where.isEmpty()) {
where += "WHERE ";
} else {
where += "AND ";
}
where += "o.customer = :customer ";
}
if (state != null) {
if (where.isEmpty()) {
where += "WHERE ";
} else {
where += "AND ";
}
where += "o.state = :state ";
}
// If not restrictions by labels, criteria, customer or state
if (where.isEmpty()) {
return ordersIdsByReadAuthorization;
}
if (ordersIdsByReadAuthorization != null && !ordersIdsByReadAuthorization.isEmpty()) {
if (where.isEmpty()) {
where += "WHERE ";
} else {
where += "AND ";
}
where += "o.id IN (:ids) ";
}
strQuery += where + whereFinal;
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());
}
if (customer != null) {
query.setParameter("customer", customer);
}
if (state != null) {
query.setParameter(STATE_PARAMETER, state);
}
if (ordersIdsByReadAuthorization != null && !ordersIdsByReadAuthorization.isEmpty()) {
query.setParameterList("ids", ordersIdsByReadAuthorization);
}
return query.list();
}
/**
* If user has permissions over all orders it returns <code>null</code>.
* Otherwise, it returns the list of orders identifiers for which the user has read permissions.
*/
private List<Long> getOrdersIdsByReadAuthorization(User user) {
if (user.isInRole(UserRole.ROLE_SUPERUSER) ||
user.isInRole(UserRole.ROLE_READ_ALL_PROJECTS) ||
user.isInRole(UserRole.ROLE_EDIT_ALL_PROJECTS)) {
return null;
} else {
String strQuery = "SELECT oa.order.id " +
"FROM OrderAuthorization oa " +
"WHERE oa.user = :user ";
if (!user.getProfiles().isEmpty()) {
strQuery += "OR oa.profile IN (:profiles) ";
}
Query query = getSession().createQuery(strQuery);
query.setParameter("user", user);
if (!user.getProfiles().isEmpty()) {
query.setParameterList("profiles", user.getProfiles());
}
return query.list();
}
}
@Override
public List<Order> getOrdersByWriteAuthorization(User user) {
if (user.isInRole(UserRole.ROLE_SUPERUSER) || user.isInRole(UserRole.ROLE_EDIT_ALL_PROJECTS)) {
return getOrders();
}
else {
List<Order> orders = new ArrayList<>();
List<OrderAuthorization> authorizations = orderAuthorizationDAO.listByUserAndItsProfiles(user);
for(OrderAuthorization authorization : authorizations) {
if (authorization.getAuthorizationType() == OrderAuthorizationType.WRITE_AUTHORIZATION) {
Order order = authorization.getOrder();
if (!orders.contains(order)) {
order.getName(); // this lines forces the load of the basic attributes of the order
orders.add(order);
}
}
}
return orders;
}
}
@Override
public List<Order> findAll() {
return getSession()
.createCriteria(getEntityClass())
.addOrder(org.hibernate.criterion.Order.asc("infoComponent.code"))
.list();
}
@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly = true)
public Order findByCode(String code) throws InstanceNotFoundException {
if (StringUtils.isBlank(code)) {
throw new InstanceNotFoundException(null, getEntityClass().getName());
}
Order entity = (Order) getSession()
.createCriteria(getEntityClass())
.add(Restrictions.eq("infoComponent.code", code.trim()).ignoreCase())
.uniqueResult();
if (entity == null) {
throw new InstanceNotFoundException(code, getEntityClass().getName());
} else {
return entity;
}
}
@Override
public List<Order> getOrdersByReadAuthorizationByScenario(String username, Scenario scenario) {
User user;
try {
user = userDAO.findByLoginName(username);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return existsInScenario(getOrdersByReadAuthorization(user), scenario);
}
@Override
public List<Order> getOrdersByReadAuthorizationBetweenDatesByLabelsCriteriaCustomerAndState(
String username,
Scenario scenario,
Date startDate,
Date endDate,
List<Label> labels,
List<Criterion> criteria,
ExternalCompany customer,
OrderStatusEnum state) {
User user;
try {
user = userDAO.findByLoginName(username);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return existsInScenario(getOrdersByReadAuthorizationBetweenDatesByLabelsCriteriaCustomerAndState(
user, startDate, endDate, labels, criteria, customer, state), scenario);
}
private List<Order> existsInScenario(List<Order> orders, Scenario scenario) {
List<Order> result = new ArrayList<>();
for (Order each : orders) {
if (scenario.contains(each)) {
result.add(each);
}
}
return result;
}
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public Order findByNameAnotherTransaction(String name) throws InstanceNotFoundException {
return findByName(name);
}
@SuppressWarnings("unchecked")
private Order findByName(String name) throws InstanceNotFoundException {
if (StringUtils.isBlank(name)) {
throw new InstanceNotFoundException(null, getEntityClass().getName());
}
Order order = (Order) getSession()
.createCriteria(getEntityClass())
.add(Restrictions.eq("infoComponent.name", name).ignoreCase())
.uniqueResult();
if (order == null) {
throw new InstanceNotFoundException(name, getEntityClass().getName());
} else {
return order;
}
}
@Override
public List<Order> getOrdersByScenario(Scenario scenario) {
return existsInScenario(getOrders(), scenario);
}
@Override
@Transactional(readOnly = true)
public List<Task> getFilteredTask(List<OrderElement> orderElements, List<Criterion> criterions) {
if (orderElements == null || orderElements.isEmpty()) {
return new ArrayList<>();
}
String strQuery = "SELECT taskSource.task " +
"FROM OrderElement orderElement, TaskSource taskSource, Task task " +
"LEFT OUTER JOIN taskSource.schedulingData.orderElement taskSourceOrderElement " +
"LEFT OUTER JOIN taskSource.task taskElement " +
"WHERE taskSourceOrderElement.id = orderElement.id " +
"AND taskElement.id = task.id AND orderElement IN (:orderElements) ";
// Set Criterions
if (criterions != null && !criterions.isEmpty()) {
strQuery += " AND (EXISTS (FROM task.resourceAllocations as allocation, GenericResourceAllocation as generic " +
" WHERE generic.id = allocation.id " +
" AND EXISTS( FROM generic.criterions criterion WHERE criterion IN (:criterions))))";
}
// Order by
strQuery += "ORDER BY task.name";
// Set parameters
Query query = getSession().createQuery(strQuery);
query.setParameterList("orderElements", orderElements);
if (criterions != null && !criterions.isEmpty()) {
query.setParameterList("criterions", Criterion.withAllDescendants(criterions));
}
return query.list();
}
@Override
public Order loadOrderAvoidingProxyFor(final OrderElement orderElement) {
return loadOrdersAvoidingProxyFor(Collections.singletonList(orderElement)).get(0);
}
@Override
public List<Order> loadOrdersAvoidingProxyFor(final List<OrderElement> orderElements) {
List<OrderElement> orders =
transactionService.runOnAnotherTransaction(new IOnTransaction<List<OrderElement>>() {
@Override
public List<OrderElement> execute() {
List<OrderElement> result = new ArrayList<>();
for (OrderElement each : orderElements) {
if (each.isNewObject()) {
result.add(each.getOrder());
} else {
result.add(orderFrom(each));
}
}
return result;
}
private OrderElement orderFrom(OrderElement initial) {
OrderElement current = initial;
OrderElement result = current;
while (current != null) {
result = current;
current = findParent(current);
}
return result;
}
private OrderElement findParent(OrderElement orderElement) {
Query query = getSession()
.createQuery("select e.parent from OrderElement e where e.id = :id")
.setParameter("id", orderElement.getId());
return (OrderElement) query.uniqueResult();
}
});
List<Order> result = new ArrayList<>();
for (OrderElement each : orders) {
if (each != null) {
result.add(findExistingEntity(each.getId()));
} else {
result.add(null);
}
}
return result;
}
@Override
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public boolean existsByNameAnotherTransaction(String name) {
try {
return findByName(name)!= null;
} catch (InstanceNotFoundException e) {
return false;
}
}
@Override
@SuppressWarnings("unchecked")
public List<Order> getActiveOrders() {
Criteria criteria = getSession().createCriteria(getEntityClass());
criteria.add(Restrictions.not(Restrictions.eq(STATE_PARAMETER, OrderStatusEnum.CANCELLED)));
criteria.add(Restrictions.not(Restrictions.eq(STATE_PARAMETER, OrderStatusEnum.STORED)));
return criteria.list();
}
@Override
public List<CostExpenseSheetDTO> getCostExpenseSheet(List<Order> orders,
Date startingDate,
Date endingDate,
List<Criterion> criterions) {
String strQuery = "SELECT new org.libreplan.business.reports.dtos.CostExpenseSheetDTO(expense) " +
"FROM OrderElement orderElement, ExpenseSheetLine expense " +
"LEFT OUTER JOIN expense.orderElement exp_ord " +
"WHERE orderElement.id = exp_ord.id ";
if (startingDate != null && endingDate != null) {
strQuery += "AND expense.date BETWEEN :startingDate AND :endingDate ";
}
if (startingDate != null && endingDate == null) {
strQuery += "AND expense.date >= :startingDate ";
}
if (startingDate == null && endingDate != null) {
strQuery += "AND expense.date <= :endingDate ";
}
// Order by date
strQuery += "ORDER BY expense.date";
Query query = getSession().createQuery(strQuery);
// Set parameters
if (startingDate != null) {
query.setParameter("startingDate", new LocalDate(startingDate));
}
if (endingDate != null) {
query.setParameter("endingDate", new LocalDate(endingDate));
}
List<CostExpenseSheetDTO> list = query.list();
List<CostExpenseSheetDTO> filteredList = new ArrayList<>();
for (CostExpenseSheetDTO each : list) {
Order order = loadOrderAvoidingProxyFor(each.getOrderElement());
// Apply filtering
if (matchFilterCriterion(each.getOrderElement(), criterions) && isOrderContained(order, orders)) {
each.setOrder(order);
filteredList.add(each);
}
}
return filteredList;
}
@Override
public List<Order> getOrdersWithNotEmptyCustomersReferences() {
return getSession()
.createCriteria(Order.class)
.add(Restrictions.isNotNull("customerReference"))
.add(Restrictions.ne("customerReference", ""))
.list();
}
}