/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package net.rrm.ehour.timesheet.service; import com.google.common.collect.Lists; import net.rrm.ehour.audit.annot.NonAuditable; import net.rrm.ehour.data.DateRange; import net.rrm.ehour.domain.ProjectAssignment; import net.rrm.ehour.domain.TimesheetComment; import net.rrm.ehour.domain.TimesheetEntry; import net.rrm.ehour.domain.User; import net.rrm.ehour.exception.OverBudgetException; import net.rrm.ehour.mail.service.ProjectManagerNotifierService; import net.rrm.ehour.persistence.timesheet.dao.TimesheetCommentDao; import net.rrm.ehour.persistence.timesheet.dao.TimesheetDao; import net.rrm.ehour.project.status.ProjectAssignmentStatus; import net.rrm.ehour.project.status.ProjectAssignmentStatusService; import net.rrm.ehour.util.DomainUtil; import net.rrm.ehour.util.EhourConstants; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.joda.time.Interval; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import scala.collection.Seq; import java.util.*; @Service public class TimesheetPersistence implements IPersistTimesheet, IDeleteTimesheetEntry { private static final Logger LOGGER = Logger.getLogger(TimesheetPersistence.class); private TimesheetDao timesheetDAO; private TimesheetCommentDao timesheetCommentDAO; private ProjectAssignmentStatusService projectAssignmentStatusService; private ProjectManagerNotifierService projectManagerNotifierService; private TimesheetLockService timesheetLockService; private ApplicationContext context; @Autowired public TimesheetPersistence(TimesheetDao timesheetDAO, TimesheetCommentDao timesheetCommentDAO, ProjectAssignmentStatusService projectAssignmentStatusService, ProjectManagerNotifierService projectManagerNotifierService, TimesheetLockService timesheetLockService, ApplicationContext context) { this.timesheetDAO = timesheetDAO; this.timesheetCommentDAO = timesheetCommentDAO; this.projectAssignmentStatusService = projectAssignmentStatusService; this.projectManagerNotifierService = projectManagerNotifierService; this.timesheetLockService = timesheetLockService; this.context = context; } @Transactional public void deleteAllTimesheetDataForUser(User user) { timesheetCommentDAO.deleteCommentsForUser(user.getUserId()); if (user.getProjectAssignments() != null && !user.getProjectAssignments().isEmpty()) { timesheetDAO.deleteTimesheetEntries(DomainUtil.getIdsFromDomainObjects(user.getProjectAssignments())); } } @Transactional @Override public List<ProjectAssignmentStatus> persistTimesheetWeek(Collection<TimesheetEntry> timesheetEntries, TimesheetComment comment, DateRange weekRange, User forUser) { Map<ProjectAssignment, List<TimesheetEntry>> timesheetRows = getTimesheetAsRows(timesheetEntries); List<ProjectAssignmentStatus> errorStatusses = new ArrayList<>(); Seq<Interval> lockedDatesInRange = timesheetLockService.findLockedDatesInRange(weekRange.getDateStart(), weekRange.getDateEnd(), forUser); List<Date> lockedDates = TimesheetLockService$.MODULE$.intervalToJavaDates(lockedDatesInRange); for (Map.Entry<ProjectAssignment, List<TimesheetEntry>> entry : timesheetRows.entrySet()) { try { getTimesheetPersister().validateAndPersist(entry.getKey(), entry.getValue(), weekRange, lockedDates); } catch (OverBudgetException e) { errorStatusses.add(e.getStatus()); } } // only update the comment when // - the whole week is not locked // - comment is an update // - or the comment is empty boolean wholeWeekLocked = TimesheetLockService$.MODULE$.isRangeLocked(weekRange.getDateStart(), weekRange.getDateEnd(), lockedDatesInRange); if (!wholeWeekLocked && (!comment.getNewComment() || StringUtils.isNotBlank(comment.getComment()))) { timesheetCommentDAO.persist(comment); } return errorStatusses; } private IPersistTimesheet getTimesheetPersister() { return context.getBean(IPersistTimesheet.class); } private Map<ProjectAssignment, List<TimesheetEntry>> getTimesheetAsRows(Collection<TimesheetEntry> entries) { Map<ProjectAssignment, List<TimesheetEntry>> timesheetRows = new HashMap<>(); for (TimesheetEntry timesheetEntry : entries) { ProjectAssignment assignment = timesheetEntry.getEntryId().getProjectAssignment(); List<TimesheetEntry> assignmentEntries = (timesheetRows.containsKey(assignment)) ? timesheetRows.get(assignment) : new ArrayList<TimesheetEntry>(); assignmentEntries.add(timesheetEntry); timesheetRows.put(assignment, assignmentEntries); } return timesheetRows; } @Transactional(rollbackFor = OverBudgetException.class, propagation = Propagation.REQUIRES_NEW) @NonAuditable public void validateAndPersist(ProjectAssignment assignment, List<TimesheetEntry> entries, DateRange weekRange, List<Date> lockedDates) throws OverBudgetException { ProjectAssignmentStatus beforeStatus = projectAssignmentStatusService.getAssignmentStatus(assignment); boolean checkAfterStatus = beforeStatus.isValid(); try { persistEntries(assignment, entries, weekRange, !beforeStatus.isValid(), lockedDates); } catch (OverBudgetException obe) { // make sure it's retrown by checking the after status checkAfterStatus = true; } ProjectAssignmentStatus afterStatus = projectAssignmentStatusService.getAssignmentStatus(assignment); if (checkAfterStatus && !afterStatus.isValid()) { throw new OverBudgetException(afterStatus); } else if (!beforeStatus.equals(afterStatus) && canNotifyPm(assignment)) { notifyPm(assignment, afterStatus); } } private void persistEntries(ProjectAssignment assignment, List<TimesheetEntry> entries, DateRange weekRange, boolean onlyLessThanExisting, List<Date> lockedDates) throws OverBudgetException { List<TimesheetEntry> previousEntries = Lists.newArrayList(timesheetDAO.getTimesheetEntriesInRange(assignment, weekRange)); for (TimesheetEntry entry : entries) { if (!entry.getEntryId().getProjectAssignment().equals(assignment)) { LOGGER.error("Invalid entry in assignment list, skipping: " + entry); previousEntries.remove(entry); continue; } if (lockedDates.contains(entry.getEntryId().getEntryDate())) { LOGGER.error("Date is locked but still trying to update " + entry); previousEntries.remove(entry); continue; } if (!entry.isEmptyEntry()) { persistEntry(onlyLessThanExisting, entry, getEntry(previousEntries, entry)); previousEntries.remove(entry); } } removeOldEntries(previousEntries); } private void removeOldEntries(List<TimesheetEntry> previousEntries) { for (TimesheetEntry entry : previousEntries) { LOGGER.info("Removing old entry " + entry.toString()); timesheetDAO.delete(entry); } } private void persistEntry(boolean bookOnlyLessHoursThanExistingHours, TimesheetEntry newEntry, TimesheetEntry existingEntry) throws OverBudgetException { if (bookOnlyLessHoursThanExistingHours && (existingEntry == null || (newEntry.getHours().compareTo(existingEntry.getHours()) > 0))) { throw new OverBudgetException(); } newEntry.setUpdateDate(new Date()); if (existingEntry != null) { timesheetDAO.merge(newEntry); } else { timesheetDAO.persist(newEntry); } } private TimesheetEntry getEntry(List<TimesheetEntry> entries, TimesheetEntry entry) { int index = entries.indexOf(entry); if (index >= 0) { return entries.get(index); } else { return null; } } private void notifyPm(ProjectAssignment assignment, ProjectAssignmentStatus status) { TimesheetEntry entry; entry = timesheetDAO.getLatestTimesheetEntryForAssignment(assignment.getAssignmentId()); // over alloted - fixed if (assignment.getAssignmentType().getAssignmentTypeId() == EhourConstants.ASSIGNMENT_TIME_ALLOTTED_FIXED && status.getStatusses().contains(ProjectAssignmentStatus.Status.OVER_ALLOTTED)) { projectManagerNotifierService.mailPMFixedAllottedReached(status.getAggregate(), entry.getEntryId().getEntryDate(), assignment.getProject().getProjectManager()); } // over overrun - flex else if (assignment.getAssignmentType().getAssignmentTypeId() == EhourConstants.ASSIGNMENT_TIME_ALLOTTED_FLEX && status.getStatusses().contains(ProjectAssignmentStatus.Status.OVER_OVERRUN)) { projectManagerNotifierService.mailPMFlexOverrunReached(status.getAggregate(), entry.getEntryId().getEntryDate(), assignment.getProject().getProjectManager()); } // in overrun - flex else if (status.getStatusses().contains(ProjectAssignmentStatus.Status.IN_OVERRUN) && assignment.getAssignmentType().getAssignmentTypeId() == EhourConstants.ASSIGNMENT_TIME_ALLOTTED_FLEX) { projectManagerNotifierService.mailPMFlexAllottedReached(status.getAggregate(), entry.getEntryId().getEntryDate(), assignment.getProject().getProjectManager()); } } private boolean canNotifyPm(ProjectAssignment assignment) { return assignment.isNotifyPm() && assignment.getProject().getProjectManager() != null && assignment.getProject().getProjectManager().getEmail() != null; } }