/* * This file is part of LibrePlan * * Copyright (C) 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.web.users.dashboard; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.hibernate.NonUniqueResultException; import org.joda.time.LocalDate; import org.libreplan.business.calendars.entities.ResourceCalendar; import org.libreplan.business.common.daos.IConfigurationDAO; import org.libreplan.business.common.daos.IEntitySequenceDAO; import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.costcategories.entities.TypeOfWorkHours; import org.libreplan.business.orders.daos.IOrderDAO; import org.libreplan.business.orders.daos.ISumChargedEffortDAO; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.planner.daos.IResourceAllocationDAO; import org.libreplan.business.planner.entities.SpecificResourceAllocation; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.scenarios.IScenarioManager; import org.libreplan.business.users.daos.IUserDAO; import org.libreplan.business.users.entities.User; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.IntraDayDate.PartialDay; import org.libreplan.business.workreports.daos.IWorkReportDAO; import org.libreplan.business.workreports.daos.IWorkReportLineDAO; import org.libreplan.business.workreports.daos.IWorkReportTypeDAO; import org.libreplan.business.workreports.entities.PredefinedWorkReportTypes; import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReportLine; import org.libreplan.business.workreports.entities.WorkReportType; import org.libreplan.web.UserUtil; import org.libreplan.web.calendars.BaseCalendarModel; import org.libreplan.web.common.Util; import org.libreplan.web.common.concurrentdetection.OnConcurrentModification; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * Model for creation/edition of a personal timesheet * * @author Manuel Rego Casasnovas <mrego@igalia.com> */ @Service @Scope(BeanDefinition.SCOPE_PROTOTYPE) @OnConcurrentModification(goToPage = "/myaccount/userDashboard.zul") public class PersonalTimesheetModel implements IPersonalTimesheetModel { private User user; private LocalDate date; private LocalDate firstDay; private LocalDate lastDay; private List<OrderElement> orderElements; private WorkReport workReport; private Map<LocalDate, EffortDuration> capacityMap; private boolean modified; private Map<OrderElement, Set<LocalDate>> modifiedMap; private boolean currentUser; private boolean otherReports; private Map<Long, EffortDuration> otherEffortPerOrderElement; private Map<LocalDate, EffortDuration> otherEffortPerDay; private PersonalTimesheetsPeriodicityEnum periodicity; @Autowired private IResourceAllocationDAO resourceAllocationDAO; @Autowired private IScenarioManager scenarioManager; @Autowired private IWorkReportDAO workReportDAO; @Autowired private IWorkReportTypeDAO workReportTypeDAO; @Autowired private ISumChargedEffortDAO sumChargedEffortDAO; @Autowired private IConfigurationDAO configurationDAO; @Autowired private IOrderDAO orderDAO; @Autowired private IEntitySequenceDAO entitySequenceDAO; @Autowired private IUserDAO userDAO; @Autowired private IWorkReportLineDAO workReportLineDAO; @Override @Transactional(readOnly = true) public void initCreateOrEdit(LocalDate date) { currentUser = true; user = UserUtil.getUserFromSession(); if (!user.isBound()) { throw new RuntimeException( "This page only can be used by users bound to a resource"); } initFields(date); } private void initFields(LocalDate date) { this.date = date; periodicity = getPersonalTimesheetsPeriodicity(); initDates(); initCapacityMap(); initWorkReport(); initOrderElements(); initOtherMaps(); modified = false; modifiedMap = new HashMap<OrderElement, Set<LocalDate>>(); } @Override @Transactional(readOnly = true) public void initCreateOrEdit(LocalDate date, Resource resource) { currentUser = false; try { user = userDAO.find(((Worker) resource).getUser().getId()); } catch (InstanceNotFoundException e) { throw new RuntimeException(e); } initFields(date); } private void initDates() { firstDay = periodicity.getStart(date); lastDay = periodicity.getEnd(date); } private void initCapacityMap() { forceLoad(getWorker().getCalendar()); capacityMap = new HashMap<LocalDate, EffortDuration>(); for (LocalDate day = firstDay; day.compareTo(lastDay) <= 0; day = day .plusDays(1)) { capacityMap.put( day, getWorker().getCalendar().getCapacityOn( PartialDay.wholeDay(day))); } } private void forceLoad(ResourceCalendar calendar) { BaseCalendarModel.forceLoadBaseCalendar(calendar); } private void initWorkReport() { // Get work report representing this personal timesheet workReport = workReportDAO.getPersonalTimesheetWorkReport( user.getWorker(), date, periodicity); if (workReport == null) { // If it doesn't exist yet create a new one workReport = WorkReport .create(getPersonalTimesheetsWorkReportType()); workReport .setCode(entitySequenceDAO .getNextEntityCodeWithoutTransaction(EntityNameEnum.WORK_REPORT)); workReport.setCodeAutogenerated(true); workReport.setResource(user.getWorker()); } else { forceLoad(workReport.getWorkReportLines()); } forceLoad(workReport.getWorkReportType()); } private void forceLoad(Set<WorkReportLine> workReportLines) { for (WorkReportLine line : workReportLines) { line.getOrderElement().getName(); } } private WorkReportType getPersonalTimesheetsWorkReportType() { try { WorkReportType workReportType = workReportTypeDAO .findUniqueByName(PredefinedWorkReportTypes.PERSONAL_TIMESHEETS .getName()); return workReportType; } catch (NonUniqueResultException e) { throw new RuntimeException(e); } catch (InstanceNotFoundException e) { throw new RuntimeException(e); } } private void forceLoad(WorkReportType workReportType) { workReportType.getLineFields().size(); workReportType.getWorkReportLabelTypeAssignments().size(); workReportType.getHeadingFields().size(); } private void initOrderElements() { List<SpecificResourceAllocation> resourceAllocations = resourceAllocationDAO .findSpecificAllocationsRelatedTo(scenarioManager.getCurrent(), UserDashboardUtil.getBoundResourceAsList(user), firstDay, lastDay); orderElements = new ArrayList<OrderElement>(); for (SpecificResourceAllocation each : resourceAllocations) { OrderElement orderElement = each.getTask().getOrderElement(); forceLoad(orderElement); orderElements.add(orderElement); } for (WorkReportLine each : workReport.getWorkReportLines()) { OrderElement orderElement = each.getOrderElement(); if (isNotInOrderElements(orderElement)) { forceLoad(orderElement); orderElements.add(orderElement); } } } private boolean isNotInOrderElements(OrderElement orderElement) { return !Util.contains(orderElements, orderElement); } private void forceLoad(OrderElement orderElement) { orderElement.getName(); if (orderElement.getParent() != null) { forceLoad(orderElement.getParent()); } } private void initOtherMaps() { List<WorkReportLine> workReportLines = workReportLineDAO .findByResourceFilteredByDateNotInWorkReport( getWorker(), firstDay.toDateTimeAtStartOfDay().toDate(), lastDay.toDateTimeAtStartOfDay().toDate(), workReport.isNewObject() ? null : workReport); otherReports = !workReportLines.isEmpty(); otherEffortPerOrderElement = new HashMap<Long, EffortDuration>(); otherEffortPerDay = new HashMap<LocalDate, EffortDuration>(); for (WorkReportLine line : workReportLines) { OrderElement orderElement = line.getOrderElement(); EffortDuration effort = line.getEffort(); LocalDate date = LocalDate.fromDateFields(line.getDate()); initMapKey(otherEffortPerOrderElement, orderElement.getId()); increaseMap(otherEffortPerOrderElement, orderElement.getId(), effort); initMapKey(otherEffortPerDay, date); increaseMap(otherEffortPerDay, date, effort); if (isNotInOrderElements(orderElement)) { forceLoad(orderElement); orderElements.add(orderElement); } } } private void initMapKey(Map<Long, EffortDuration> map, Long key) { if (map.get(key) == null) { map.put(key, EffortDuration.zero()); } } private void increaseMap(Map<Long, EffortDuration> map, Long key, EffortDuration valueToIncrease) { map.put(key, map.get(key).plus(valueToIncrease)); } private void initMapKey(Map<LocalDate, EffortDuration> map, LocalDate key) { if (map.get(key) == null) { map.put(key, EffortDuration.zero()); } } private void increaseMap(Map<LocalDate, EffortDuration> map, LocalDate key, EffortDuration valueToIncrease) { map.put(key, map.get(key).plus(valueToIncrease)); } @Override public LocalDate getDate() { return date; } @Override public LocalDate getFirstDay() { return firstDay; } @Override public LocalDate getLastDate() { return lastDay; } @Override public Worker getWorker() { return user.getWorker(); } @Override @Transactional(readOnly = true) public List<OrderElement> getOrderElements() { Collections.sort(orderElements, new Comparator<OrderElement>() { @Override public int compare(OrderElement o1, OrderElement o2) { Order order1 = getOrder(o1); Order order2 = getOrder(o2); int compareOrderName = order1.getName().compareTo( order2.getName()); if (compareOrderName != 0) { return compareOrderName; } return o1.getName().compareTo(o2.getName()); } }); return orderElements; } @Override public EffortDuration getEffortDuration(OrderElement orderElement, LocalDate date) { WorkReportLine workReportLine = getWorkReportLine(orderElement, date); if (workReportLine == null) { return null; } return workReportLine.getEffort(); } private WorkReportLine getWorkReportLine(OrderElement orderElement, LocalDate date) { for (WorkReportLine line : workReport.getWorkReportLines()) { if (line.getOrderElement().getId().equals(orderElement.getId()) && LocalDate.fromDateFields(line.getDate()).equals(date)) { return line; } } return null; } @Override @Transactional(readOnly = true) public void setEffortDuration(OrderElement orderElement, LocalDate date, EffortDuration effortDuration) { WorkReportLine workReportLine = getOrCreateWorkReportLine(orderElement, date); workReportLine.setEffort(effortDuration); modified = true; markAsModified(orderElement, date); } private WorkReportLine getOrCreateWorkReportLine(OrderElement orderElement, LocalDate date) { WorkReportLine workReportLine = getWorkReportLine(orderElement, date); if (workReportLine == null) { workReportLine = createWorkReportLine(orderElement, date); workReport.addWorkReportLine(workReportLine); } return workReportLine; } private void markAsModified(OrderElement orderElement, LocalDate date) { if (modifiedMap.get(orderElement) == null) { modifiedMap.put(orderElement, new HashSet<LocalDate>()); } modifiedMap.get(orderElement).add(date); } private WorkReportLine createWorkReportLine(OrderElement orderElement, LocalDate date) { WorkReportLine workReportLine = WorkReportLine.create(workReport); workReportLine.setCodeAutogenerated(true); workReportLine.setOrderElement(orderElement); workReportLine.setDate(date.toDateTimeAtStartOfDay().toDate()); workReportLine.setTypeOfWorkHours(getTypeOfWorkHours()); workReportLine.setEffort(EffortDuration.zero()); return workReportLine; } private TypeOfWorkHours getTypeOfWorkHours() { return configurationDAO.getConfiguration() .getPersonalTimesheetsTypeOfWorkHours(); } @Override @Transactional public void save() { if (workReport.getWorkReportLines().isEmpty() && workReport.isNewObject()) { // Do nothing. // A new work report if it doesn't have work report lines is not // saved as it will not be possible to find it later with // WorkReportDAO.getPersonalTimesheetWorkReport() method. } else { Set<WorkReportLine> deletedWorkReportLinesSet = removeWorkReportLinesWithEffortZero(); Set<OrderElement> orderElements = sumChargedEffortDAO .getOrderElementsToRecalculateTimsheetDates( workReport.getWorkReportLines(), deletedWorkReportLinesSet); sumChargedEffortDAO .updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(deletedWorkReportLinesSet); sumChargedEffortDAO .updateRelatedSumChargedEffortWithWorkReportLineSet(workReport .getWorkReportLines()); workReport.generateWorkReportLineCodes(entitySequenceDAO .getNumberOfDigitsCode(EntityNameEnum.WORK_REPORT)); workReportDAO.save(workReport); sumChargedEffortDAO.recalculateTimesheetData(orderElements); if (workReport.getWorkReportLines().isEmpty()) { try { workReportDAO.remove(workReport.getId()); } catch (InstanceNotFoundException e) { throw new RuntimeException(e); } } } resetModifiedFields(); } private Set<WorkReportLine> removeWorkReportLinesWithEffortZero() { Set<WorkReportLine> toRemove = new HashSet<WorkReportLine>(); for (WorkReportLine line : workReport.getWorkReportLines()) { if (line.getEffort().isZero()) { toRemove.add(line); } } for (WorkReportLine line : toRemove) { workReport.removeWorkReportLine(line); } return toRemove; } private void resetModifiedFields() { modified = false; modifiedMap = new HashMap<OrderElement, Set<LocalDate>>(); } @Override public void cancel() { user = null; date = null; orderElements = null; workReport = null; resetModifiedFields(); } @Override public EffortDuration getEffortDuration(OrderElement orderElement) { EffortDuration result = EffortDuration.zero(); for (WorkReportLine line : workReport.getWorkReportLines()) { if (line.getOrderElement().equals(orderElement)) { result = result.plus(line.getEffort()); } } return result; } @Override public EffortDuration getEffortDuration(LocalDate date) { EffortDuration result = EffortDuration.zero(); for (WorkReportLine line : workReport.getWorkReportLines()) { if (LocalDate.fromDateFields(line.getDate()).equals(date)) { result = result.plus(line.getEffort()); } } return result; } @Override public EffortDuration getTotalEffortDuration() { return workReport.getTotalEffortDuration(); } @Override public EffortDuration getResourceCapacity(LocalDate date) { return capacityMap.get(date); } @Override public void addOrderElement(OrderElement orderElement) { if (isNotInOrderElements(orderElement)) { orderElements.add(orderElement); } } @Override @Transactional(readOnly = true) public Order getOrder(OrderElement orderElement) { return orderDAO.loadOrderAvoidingProxyFor(orderElement); } @Override public boolean isModified() { return modified; } @Override @Transactional(readOnly = true) public boolean isFirstPeriod() { LocalDate activationDate = getWorker().getCalendar() .getFistCalendarAvailability().getStartDate(); return firstDay.equals(periodicity.getStart(activationDate)); } @Override @Transactional(readOnly = true) public boolean isLastPeriod() { return firstDay.equals(periodicity.getStart(new LocalDate() .plusMonths(1))); } @Override public boolean wasModified(OrderElement orderElement, LocalDate date) { Set<LocalDate> dates = modifiedMap.get(orderElement); return (dates != null) && dates.contains(date); } @Override public boolean isCurrentUser() { return currentUser; } @Override public boolean hasOtherReports() { return otherReports; } @Override public EffortDuration getOtherEffortDuration(OrderElement orderElement) { EffortDuration effort = otherEffortPerOrderElement.get(orderElement .getId()); return effort == null ? EffortDuration.zero() : effort; } @Override public EffortDuration getOtherEffortDuration(LocalDate date) { EffortDuration effort = otherEffortPerDay.get(date); return effort == null ? EffortDuration.zero() : effort; } @Override public EffortDuration getTotalOtherEffortDuration() { EffortDuration result = EffortDuration.zero(); for (EffortDuration effort : otherEffortPerOrderElement.values()) { result = result.plus(effort); } return result; } @Override @Transactional(readOnly = true) public PersonalTimesheetsPeriodicityEnum getPersonalTimesheetsPeriodicity() { return configurationDAO.getConfiguration() .getPersonalTimesheetsPeriodicity(); } @Override public String getTimesheetString() { return PersonalTimesheetDTO.toString(periodicity, date); } @Override public LocalDate getPrevious() { return periodicity.previous(date); } @Override public LocalDate getNext() { return periodicity.next(date); } @Override public Boolean isFinished(OrderElement orderElement, LocalDate date) { WorkReportLine workReportLine = getWorkReportLine(orderElement, date); if (workReportLine == null) { return false; } return workReportLine.isFinished(); } @Override public void setFinished(OrderElement orderElement, LocalDate date, Boolean finished) { WorkReportLine workReportLine = getOrCreateWorkReportLine(orderElement, date); workReportLine.setFinished(finished); modified = true; markAsModified(orderElement, date); } @Override public Boolean isFinished(OrderElement orderElement) { if (workReport.isFinished(orderElement)) { return true; } List<WorkReportLine> lines = workReportLineDAO .findFinishedByOrderElementNotInWorkReportAnotherTransaction( orderElement, workReport); return !lines.isEmpty(); } }