Java tutorial
/** * Genji Scrum Tool and Issue Tracker * Copyright (C) 2015 Steinbeis GmbH & Co. KG Task Management Solutions * <a href="http://www.trackplus.com">Genji Scrum Tool</a> * * 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* $Id:$ */ package com.aurel.track.exchange.msProject.exporter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import net.sf.mpxj.ConstraintType; import net.sf.mpxj.Day; import net.sf.mpxj.Duration; import net.sf.mpxj.ProjectCalendar; import net.sf.mpxj.ProjectCalendarHours; import net.sf.mpxj.ProjectFile; import net.sf.mpxj.Relation; import net.sf.mpxj.RelationType; import net.sf.mpxj.Resource; import net.sf.mpxj.ResourceAssignment; import net.sf.mpxj.Task; import net.sf.mpxj.TaskField; import net.sf.mpxj.TaskType; import net.sf.mpxj.TimeUnit; import net.sf.mpxj.mspdi.MSPDIWriter; import net.sf.mpxj.writer.ProjectWriter; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import org.joda.time.DateTime; import org.joda.time.Days; import com.aurel.track.accessControl.AccessBeans; import com.aurel.track.admin.customize.role.FieldsRestrictionsToRoleBL; import com.aurel.track.admin.project.ProjectAccountingTO; import com.aurel.track.admin.project.ProjectBL; import com.aurel.track.admin.user.person.PersonBL; import com.aurel.track.beans.TActualEstimatedBudgetBean; import com.aurel.track.beans.TComputedValuesBean; import com.aurel.track.beans.TCostBean; import com.aurel.track.beans.TMSProjectExchangeBean; import com.aurel.track.beans.TMSProjectTaskBean; import com.aurel.track.beans.TPersonBean; import com.aurel.track.beans.TProjectBean; import com.aurel.track.beans.TWorkItemBean; import com.aurel.track.beans.TWorkItemLinkBean; import com.aurel.track.dao.DAOFactory; import com.aurel.track.dao.MsProjectTaskDAO; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeBL; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeBL.MSPROJECT_TIME_UNITS; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDOMHelper; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.CONSTRAINT_TYPE; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.DAY_TYPE; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.DAY_WORKING; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.IS_BASE_CALENEDAR; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.LAG_FORMAT; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.PREDECESSOR_CROSS_PROJECT; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.TASK_SUMMARY_TYPE; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.TASK_TYPE; import com.aurel.track.exchange.msProject.exchange.MsProjectExchangeDataStoreBean.TIMEPHASEDDATA_UNIT; import com.aurel.track.exchange.msProject.importer.LinkLagBL; import com.aurel.track.fieldType.constants.BooleanFields; import com.aurel.track.fieldType.constants.SystemFields; import com.aurel.track.fieldType.runtime.base.LocalLookupContainer; import com.aurel.track.fieldType.runtime.base.LookupContainer; import com.aurel.track.fieldType.runtime.base.WBSComparable; import com.aurel.track.fieldType.runtime.system.text.SystemWBSRT; import com.aurel.track.item.ItemBL; import com.aurel.track.item.budgetCost.AccountingBL; import com.aurel.track.item.budgetCost.ExpenseBL; import com.aurel.track.item.budgetCost.RemainingPlanBL; import com.aurel.track.item.link.ItemLinkBL; import com.aurel.track.linkType.LinkTypeBL; import com.aurel.track.linkType.MsProjectLinkType; import com.aurel.track.report.execute.ComputedValuesLoaderBL; import com.aurel.track.resources.LocalizeUtil; import com.aurel.track.util.CalendarUtil; import com.aurel.track.util.DateTimeUtils; import com.aurel.track.util.EqualUtils; import com.aurel.track.util.GeneralUtils; import com.aurel.track.util.PropertiesHelper; public class MsProjectExporterBL { private static final Logger LOGGER = LogManager.getLogger(MsProjectExporterBL.class); private static MsProjectTaskDAO msProjectTaskDAO = DAOFactory.getFactory().getMsProjectTaskDAO(); private static boolean allowOvertimeBookings = false; private static double maxOvertimeHoursPerDay = 0; private static boolean allowOverdayBookings = true; private static boolean allowActualWorkOverlap = true; private static double EPSILON = Float.MIN_NORMAL; /** * Gets the import file info depending on whether a previous import file is * found * * @param projectOrReleaseID * @param locale * @return */ static String getImportFileInfo(Integer projectOrReleaseID, Locale locale) { Integer entityID; int entityType; if (projectOrReleaseID != null) { if (projectOrReleaseID.intValue() < 0) { entityID = Integer.valueOf(-projectOrReleaseID.intValue()); entityType = SystemFields.PROJECT; } else { entityID = projectOrReleaseID; entityType = SystemFields.RELEASE; } // initialize a new MsProjectImporterBean TMSProjectExchangeBean msProjectExchangeBean = MsProjectExchangeBL .getLastImportedMSProjectExchangeBean(entityID, entityType); if (msProjectExchangeBean == null) { return LocalizeUtil.getLocalizedTextFromApplicationResources( "admin.actions.exportMSProject.lbl.noPreviousImport", locale); } else { List<String> parameters = new LinkedList<String>(); parameters.add(msProjectExchangeBean.getFileName()); parameters.add( DateTimeUtils.getInstance().formatGUIDateTime(msProjectExchangeBean.getLastEdit(), locale)); if (msProjectExchangeBean.getChangedBy() != null) { TPersonBean lastImportedBy = LookupContainer .getPersonBean(msProjectExchangeBean.getChangedBy()); if (lastImportedBy != null) { parameters.add(lastImportedBy.getName()); } } return LocalizeUtil.getParametrizedString("admin.actions.exportMSProject.lbl.lastImportFile", parameters.toArray(), locale); } } return null; } /** * Loads the sizes from a property file * * @return */ private static void loadConfigFromFile() { Properties properties = new Properties(); InputStream inputStream = MsProjectExporterBL.class.getResourceAsStream("MsProjectExporter.properties"); try { properties.load(inputStream); } catch (IOException e) { LOGGER.warn("Loading the MsProjectExporter.properties from classpath failed with " + e.getMessage()); LOGGER.debug(ExceptionUtils.getStackTrace(e)); return; } try { allowOvertimeBookings = new Boolean(properties.getProperty("allowOvertimeBookings").trim()) .booleanValue(); } catch (Exception e) { LOGGER.warn("Loading the allowOvertimeBookings failed with " + e.getMessage()); LOGGER.debug(ExceptionUtils.getStackTrace(e)); } try { String maxOvertimeHoursPerDayStr = properties.getProperty("maxOvertimeHoursPerDay").trim(); if (maxOvertimeHoursPerDayStr != null && !"".equals(maxOvertimeHoursPerDayStr)) { maxOvertimeHoursPerDay = new Double(maxOvertimeHoursPerDayStr).doubleValue(); } } catch (Exception e) { LOGGER.info("Loading the maxOvertimeHours failed with " + e.getMessage()); LOGGER.debug(ExceptionUtils.getStackTrace(e)); } try { allowOverdayBookings = new Boolean(properties.getProperty("allowOverdayBookings").trim()) .booleanValue(); } catch (Exception e) { LOGGER.warn("Loading the allowOverdayBookings failed with " + e.getMessage()); LOGGER.debug(ExceptionUtils.getStackTrace(e)); } try { allowActualWorkOverlap = new Boolean(properties.getProperty("allowActualWorkOverlap").trim()) .booleanValue(); } catch (Exception e) { LOGGER.warn("Loading the allowActualWorkOverlap failed with " + e.getMessage()); LOGGER.debug(ExceptionUtils.getStackTrace(e)); } } /** * Export the MsProject data * * @param msProjectExchangeDataStoreBean * @param notClosed * @return */ public static ProjectFile export(MsProjectExchangeDataStoreBean msProjectExchangeDataStoreBean, boolean notClosed, Integer personID) { loadConfigFromFile(); Integer entityID = msProjectExchangeDataStoreBean.getEntityID(); int entityType = msProjectExchangeDataStoreBean.getEntityType(); Integer projectID = msProjectExchangeDataStoreBean.getProjectID(); Integer issueType = msProjectExchangeDataStoreBean.getIssueType(); TProjectBean projectBean = msProjectExchangeDataStoreBean.getProjectBean(); String name = msProjectExchangeDataStoreBean.getName(); ProjectFile project = msProjectExchangeDataStoreBean.getProject(); Integer defaultTaskType = PropertiesHelper.getIntegerProperty(projectBean.getMoreProps(), TProjectBean.MOREPPROPS.DEFAULT_TASK_TYPE); if (defaultTaskType == null) { defaultTaskType = project.getProjectHeader().getDefaultTaskType().getValue(); if (defaultTaskType == null) { defaultTaskType = Integer.valueOf(TASK_TYPE.FIXED_UNITS); } } Integer durationFormat = PropertiesHelper.getIntegerProperty(projectBean.getMoreProps(), TProjectBean.MOREPPROPS.DURATION_FORMAT); Double hoursPerWorkDay = ProjectBL.getHoursPerWorkingDay(projectBean.getObjectID()); Map<Integer, Integer> resourceUIDToPersonIDMap = msProjectExchangeDataStoreBean .getResourceUIDToPersonIDMap(); // int maxResourceID = getMaxInteger(resourceUIDToPersonIDMap.keySet()); // can be that the same Genji person is mapped for more than one // MSProject resource Map<Integer, List<Integer>> personIDToResourceUIDMap = GeneralUtils .getInvertedDuplicatedValuesMap(resourceUIDToPersonIDMap); /** * add/update the resources and calendars */ // get the potential resource persons, this // TODO get only the persons with no read restrictions for // TReportLayoutBean.PSEUDO_COLUMNS.MY_EXPENSE_TIME (old). List<TPersonBean> addExpenseRolePersons = PersonBL.getPersonsWithRightInProjectAndListType(projectID, issueType, AccessBeans.AccessFlagIndexes.MODIFYANYTASK, true, TPersonBean.PERSON_CATEGORY.ALL_PERSONS, null, true, true); List<TPersonBean> personsWithWork = PersonBL.getPersonsWithWorkInProject(entityID, entityType, issueType); List<Integer> addExpenseRolePersonIDs = GeneralUtils.createIntegerListFromBeanList(addExpenseRolePersons); List<Integer> personIDsWithWork = GeneralUtils.createIntegerListFromBeanList(personsWithWork); Set<Integer> personIDs = new HashSet<Integer>(); personIDs.addAll(addExpenseRolePersonIDs); personIDs.addAll(personIDsWithWork); /* * List<TPersonBean> personBeans = PersonBL.getPersonsByCategory( * personIDs, TPersonBean.PERSON_CATEGORY.ALL_DIRECT, false, true, * null); */ // TODO check // exportResourcesAndCalendars(personBeans, project, // personIDToResourceUIDMap,maxResourceID); /** * get the Genji side data */ // 'Task' type workItems already existing in track+ left joined with // TMSProjectTask (ordered by outlineNumber and created) List<TWorkItemBean> existingTrackPlusWorkItemsList = MsProjectExchangeBL.getTaskWorkItemsForExport(entityID, entityType, notClosed); Map<Integer, TWorkItemBean> existingTrackPlusWorkItemsMap = GeneralUtils .createMapFromList(existingTrackPlusWorkItemsList); int[] workItemIDs = GeneralUtils.createIntArrFromSet(existingTrackPlusWorkItemsMap.keySet()); LocalLookupContainer lookupContainer = ItemBL.getItemHierarchyContainer(existingTrackPlusWorkItemsList); Map<Integer, TMSProjectTaskBean> existingTrackPlusTasks = MsProjectExchangeBL .getMsProjectTasksForExport(entityID, entityType, notClosed); // the taskUID to workItemID mapping for existing tasks // the workItemID to taskUID mapping for existing tasks Map<Integer, Integer> workItemIDToTaskUIDMap = MsProjectExchangeBL .getWorkItemIDToTaskUIDMap(existingTrackPlusTasks); // the new workItems (without corresponding task elements) should be put // at the end // (after a database ASC ordering those with null values are the first // ones) List<TWorkItemBean> newWorkItemBeans = new ArrayList<TWorkItemBean>(); for (Iterator<TWorkItemBean> iterator = existingTrackPlusWorkItemsList.iterator(); iterator.hasNext();) { TWorkItemBean workItemBean = iterator.next(); if (!workItemIDToTaskUIDMap.containsKey(workItemBean.getObjectID())) { iterator.remove(); newWorkItemBeans.add(workItemBean); } } existingTrackPlusWorkItemsList.addAll(newWorkItemBeans); // last imported tasks List<Task> lastImportedMsProjectTasksList = msProjectExchangeDataStoreBean.getTasks(); Map<Integer, Task> lastImportedMsProjectTasksMap = MsProjectExchangeDOMHelper .createIntegerElementMapFromListForTasks(lastImportedMsProjectTasksList); // get the parent to children map Map<Integer, List<Integer>> parentToChildrenListMap = getParentToChildrenMap(existingTrackPlusWorkItemsList, existingTrackPlusTasks, workItemIDToTaskUIDMap); // get the last planned work for existing task workItems independently // of the date List<Integer> fieldIDs = new LinkedList<Integer>(); fieldIDs.add(FieldsRestrictionsToRoleBL.PSEUDO_COLUMNS.PLAN); List<TComputedValuesBean> plannedTimesList = ComputedValuesLoaderBL.loadByWorkItemKeys( existingTrackPlusWorkItemsList, AccessBeans.getFieldRestrictions(personID, existingTrackPlusWorkItemsList, fieldIDs, false), TComputedValuesBean.EFFORTTYPE.TIME, TComputedValuesBean.COMPUTEDVALUETYPE.PLAN, null, false, FieldsRestrictionsToRoleBL.PSEUDO_COLUMNS.PLAN); Map<Integer, TComputedValuesBean> plannedTimesMap = new HashMap<Integer, TComputedValuesBean>(); for (TComputedValuesBean computedValuesBean : plannedTimesList) { plannedTimesMap.put(computedValuesBean.getWorkitemKey(), computedValuesBean); } List<TCostBean> costBeanList = ExpenseBL.loadByWorkItemKeys(workItemIDs, null, null, null, null, true); Map<Integer, List<TCostBean>> costBeanMap = ExpenseBL.getCostBeansByWorkItemMap(costBeanList); // get the remaining work for existing task workItems Map<Integer, TActualEstimatedBudgetBean> remainingBudgetMap = RemainingPlanBL .loadRemainingBudgetMapByItemIDs(workItemIDs); // set project elements Date actualStartDate = getProjectActualStartDate(costBeanList); Date startDate = getProjectDate(existingTrackPlusWorkItemsList, true); if (actualStartDate != null) { if (startDate == null || startDate.after(actualStartDate)) { startDate = actualStartDate; } } String projectStartDate = MsProjectExchangeBL.formatDateTime(startDate); String projectEndDate = MsProjectExchangeBL .formatDateTime(getProjectDate(existingTrackPlusWorkItemsList, false)); DateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); if (projectStartDate == null || projectStartDate == "") { projectStartDate = dateTimeFormat.format(new Date()).toString(); } if (projectEndDate == null || projectEndDate == "") { projectEndDate = dateTimeFormat.format(new Date()).toString(); } setProjectElements(project, name, projectStartDate, projectEndDate, projectBean, defaultTaskType, durationFormat, hoursPerWorkDay); Set<Integer> personsHavingWork = getPersonsWithWork(costBeanList); Map<Integer, Resource> UIDBasedResourceElementsMap = getUIDBasedResources(project); Map<Integer, ProjectCalendar> UIDBasedCalendarElementsMap = getUIDBasedCalendars(project); NumberFormat numberFormatTimeSpan = new DecimalFormat("00"); /** * get the working times for base calendars */ Map<Integer, Map<String, List<String[]>>> calendarUIDBasedBaseCalendarExceptionWorkingTimes = new HashMap<Integer, Map<String, List<String[]>>>(); Map<Integer, Map<Integer, List<String[]>>> calendarUIDBasedBaseCalendarWeekDayWorkingTimes = new HashMap<Integer, Map<Integer, List<String[]>>>(); getUIDBasedBaseCalendarWorkingTimes(project, allowOvertimeBookings, maxOvertimeHoursPerDay, numberFormatTimeSpan, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes); /** * get the person specific working times */ Map<Integer, Integer> personIDToBaseCalendar = new HashMap<Integer, Integer>(); Map<Integer, Map<String, List<String[]>>> personBasedCalendarExceptionWorkingTimes = new HashMap<Integer, Map<String, List<String[]>>>(); Map<Integer, Map<Integer, List<String[]>>> personBasedCalendarWeekDayWorkingTimes = new HashMap<Integer, Map<Integer, List<String[]>>>(); getPersonIDBasedBaseCalendarWorkingTimes(personsHavingWork, personIDToResourceUIDMap, UIDBasedResourceElementsMap, UIDBasedCalendarElementsMap, allowOvertimeBookings, maxOvertimeHoursPerDay, numberFormatTimeSpan, personIDToBaseCalendar, personBasedCalendarExceptionWorkingTimes, personBasedCalendarWeekDayWorkingTimes); IDCounter idCounter = new IDCounter(); // initialize the taskUID with the maximal existing value idCounter.setTaskUID(getMaxInteger(existingTrackPlusTasks.keySet())); int currentLevel = -1; for (Iterator it = existingTrackPlusWorkItemsList.iterator(); it.hasNext();) { TWorkItemBean workItemBean = (TWorkItemBean) it.next(); Integer parentID = workItemBean.getSuperiorworkitem(); // first add those at the upper level (without task parents) if (parentID == null || !existingTrackPlusWorkItemsMap.containsKey(parentID)) listWorkItems(workItemBean, new OutlineStructure(++currentLevel), idCounter, existingTrackPlusWorkItemsMap, parentToChildrenListMap, project, workItemIDToTaskUIDMap, lastImportedMsProjectTasksMap, existingTrackPlusTasks, plannedTimesMap, costBeanMap, remainingBudgetMap, hoursPerWorkDay, defaultTaskType, durationFormat, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes, lookupContainer); } // remove the tasks not any more present in the track+ side but present // in the last imported file // (if a Task type workItem was closed or deleted or changed to other // issueType or mover to other project or... // the task should be removed from the last exported document together // with all assignments to that task) Set<Integer> removedTaskIDs = new HashSet<Integer>(); for (Iterator<Integer> itrLastImportedTask = lastImportedMsProjectTasksMap.keySet() .iterator(); itrLastImportedTask.hasNext();) { Integer taskID = itrLastImportedTask.next(); if (!existingTrackPlusTasks.containsKey(taskID)) { Task lastImportedTaskElement = lastImportedMsProjectTasksMap.get(taskID); if (MsProjectExchangeDOMHelper.UIDIsValidTask(lastImportedTaskElement)) { // TODO Possible probelem in old xml version it was: // lastImportedTaskElement.getParentNode().removeChild(lastImportedTaskElement); removedTaskIDs.add(taskID); } } } exportTaskDependencyLinks(project, workItemIDToTaskUIDMap); exportAssignments(project, existingTrackPlusWorkItemsMap, costBeanList, workItemIDToTaskUIDMap, personIDToResourceUIDMap, hoursPerWorkDay, removedTaskIDs, allowOvertimeBookings, maxOvertimeHoursPerDay, allowOverdayBookings, allowActualWorkOverlap, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes, personBasedCalendarExceptionWorkingTimes, personBasedCalendarWeekDayWorkingTimes, personIDToBaseCalendar); try { OutputStream out = new ByteArrayOutputStream(); ProjectWriter writer = new MSPDIWriter(); writer.write(msProjectExchangeDataStoreBean.getProject(), out); String content = out.toString(); MsProjectExchangeBL.saveMSProjectExportFile(msProjectExchangeDataStoreBean, content); } catch (Exception ex) { LOGGER.error(ex.getMessage()); } setTasksOrderByWbs(project); setTrackPlusAliasForIdMapping(project); updatingWorkBudget(project, costBeanList); return project; } private static void updatingWorkBudget(ProjectFile project, List<TCostBean> costBeanList) { Map<Integer, TCostBean> workItemIDToCostBean = new HashMap<Integer, TCostBean>(); int[] workItemKeys = new int[project.getAllTasks().size()]; int i = 0; for (TCostBean cBean : costBeanList) { workItemIDToCostBean.put(cBean.getWorkItemID(), cBean); } for (Task aTask : project.getAllTasks()) { Integer UIDTmp = (Integer) aTask.getFieldByAlias("trackPlusId"); if (UIDTmp == null) { UIDTmp = aTask.getUniqueID(); } if (workItemIDToCostBean.get(UIDTmp) != null) { aTask.setActualDuration(Duration.getInstance( workItemIDToCostBean.get(UIDTmp).getHours() / 8 * aTask.getResourceAssignments().size(), TimeUnit.HOURS)); } workItemKeys[i] = UIDTmp; i++; aTask.setActualStart(null); aTask.setActualFinish(null); // aTask.setActualDuration(Duration.getInstance(2, TimeUnit.HOURS)); } // BudgetDAO plannedDao = DAOFactory.getFactory().getBudgetDAO(); // List<TBudgetBean>budgetValuesList = // plannedDao.loadByWorkItemKeys(workItemKeys, null, false, null, null); // Map<Integer, TBudgetBean> workItemKeyToBudgetBean = new // HashMap<Integer, TBudgetBean>(); // for(TBudgetBean budget : budgetValuesList) { // workItemKeyToBudgetBean.put(budget.getWorkItemID(), budget); // } // for(Task aTask : project.getAllTasks()) { // Integer UIDTmp = (Integer)aTask.getFieldByAlias("trackPlusId"); // if(UIDTmp == null) { // UIDTmp = aTask.getUniqueID(); // } // if(workItemKeyToBudgetBean.get(UIDTmp) != null) { // aTask.setBu // } // } } private static void setTrackPlusAliasForIdMapping(ProjectFile project) { project.setTaskFieldAlias(TaskField.ENTERPRISE_NUMBER1, "trackPlusId"); for (Task task : project.getAllTasks()) { task.setFieldByAlias("trackPlusId", task.getUniqueID()); } } private static void setTasksOrderByWbs(ProjectFile project) { List<WBSComparable> comparableList = new LinkedList<WBSComparable>(); for (Task task : project.getAllTasks()) { WBSComparable wbsComparable = new WBSComparable(task.getOutlineNumber(), "\\.", task.getUniqueID()); comparableList.add(wbsComparable); } Collections.sort(comparableList); Map<Integer, Integer> tasksOrder = new HashMap<Integer, Integer>(); int i = 1; for (WBSComparable wbsComparable : comparableList) { tasksOrder.put(wbsComparable.getWorkItemID(), i); i++; } for (Task task : project.getAllTasks()) { task.setID(tasksOrder.get(task.getUniqueID())); } } /** * Import (add/update/delete) the task dependency links Update or delete a * link only if his lastEdit is before the MSProject import file creation * * @param msProjectImporterBean * @param UIDToWorkItemID * @param importCounts */ private static void exportTaskDependencyLinks(ProjectFile project, Map<Integer, Integer> workItemIDToTaskUIDMap) { List<Task> exportedTasksList = project.getAllTasks(); Map<Integer, Task> UIDBasedTasks = MsProjectExchangeDOMHelper .getSubelementBasedElementMapTask(exportedTasksList); Map<Integer, Map<Integer, Relation>> importedDependentToPredecessorLinksMap = MsProjectExchangeDOMHelper .getDependentPredecessorLinksMap(exportedTasksList); // get all msProject specific links from track+ MsProjectLinkType msProjectLinkType = MsProjectLinkType.getInstance(); List<TWorkItemLinkBean> existingTrackPlusWorkItemLinks = ItemLinkBL .loadUnidirectionalLinksByWorkItemsAndLinkTypes( GeneralUtils.createListFromSet(workItemIDToTaskUIDMap.keySet()), LinkTypeBL.getLinkTypesByPluginClass(msProjectLinkType), msProjectLinkType.getPossibleDirection()); Set<Integer> linkedItemIDs = new HashSet<Integer>(); for (TWorkItemLinkBean workItemLinkBean : existingTrackPlusWorkItemLinks) { Integer predLink = workItemLinkBean.getLinkPred(); if (predLink != null) { linkedItemIDs.add(predLink); } } List<TWorkItemBean> linkedItemBeans = ItemBL .loadByWorkItemKeys(GeneralUtils.createIntArrFromSet(linkedItemIDs)); Map<Integer, Integer> itemIDToProjectIDMap = new HashMap<Integer, Integer>(); Map<Integer, Double> projectWorkingHoursMap = new HashMap<Integer, Double>(); for (TWorkItemBean workItemBean : linkedItemBeans) { Integer workItemID = workItemBean.getObjectID(); Integer projectID = workItemBean.getProjectID(); itemIDToProjectIDMap.put(workItemID, projectID); Double hoursPerDay = projectWorkingHoursMap.get(projectID); if (hoursPerDay == null) { hoursPerDay = ProjectBL.getHoursPerWorkingDay(projectID); if (hoursPerDay == null) { hoursPerDay = AccountingBL.DEFAULTHOURSPERWORKINGDAY; } projectWorkingHoursMap.put(projectID, hoursPerDay); } } for (TWorkItemLinkBean workItemLinkBean : existingTrackPlusWorkItemLinks) { Integer predLink = workItemLinkBean.getLinkPred(); Integer succLink = workItemLinkBean.getLinkSucc(); Integer type = workItemLinkBean.getIntegerValue1(); Double hoursPerWorkday = null; Integer projectID = itemIDToProjectIDMap.get(predLink); if (projectID != null) { hoursPerWorkday = projectWorkingHoursMap.get(projectID); } if (hoursPerWorkday == null) { hoursPerWorkday = AccountingBL.DEFAULTHOURSPERWORKINGDAY; } // Double hoursPerWorkday = // MsProjectLinkTypeBL.getHoursPerWorkingDayForWorkItem(workItemLinkBean.getLinkPred()); Double convertedLinkLag = LinkLagBL.getUILinkLagFromMinutes(workItemLinkBean.getLinkLag(), workItemLinkBean.getLinkLagFormat(), hoursPerWorkday); // Integer dependentTaskUID = workItemIDToTaskUIDMap.get(predLink); Integer dependentTaskUID = predLink; // Integer predecessorUID = workItemIDToTaskUIDMap.get(succLink); Integer predecessorUID = succLink; if (dependentTaskUID != null && predecessorUID != null) { Map<Integer, Relation> predecessorLinksMap = importedDependentToPredecessorLinksMap .get(dependentTaskUID); Relation predecessorLinkElement = null; if (predecessorLinksMap != null && predecessorLinksMap.size() > 0) { // predecessorLink existed already in the previous import // get the predecessor element but also remove from map // because later all remaining predecessors // (representing links deleted from Genji) should be // removed from the document dom predecessorLinkElement = predecessorLinksMap.remove(predecessorUID); } if (predecessorLinkElement == null) { // new link Task dependentTaskElement = UIDBasedTasks.get(dependentTaskUID); if (dependentTaskElement != null) { // TODO This append is in the certain place? (child // order is important?) Integer msLagFormat = convertTrackLagToMsProject(workItemLinkBean.getLinkLagFormat()); if (msLagFormat != -1) { UIDBasedTasks.get(predecessorUID).addPredecessor(dependentTaskElement, RelationType.getInstance(type), Duration.getInstance(convertedLinkLag, TimeUnit.getInstance(msLagFormat))); } else { UIDBasedTasks.get(predecessorUID).addPredecessor(dependentTaskElement, RelationType.getInstance(type), Duration.getInstance(0, TimeUnit.MINUTES)); } } } // TODO The MPXJ API's Relation class doesn't have UniqueID // field. // MsProjectExchangeDOMHelper.setChildTextByName(predecessorLinkElement, // PREDECESSOR_ELEMENTS.PredecessorUID, // predecessorUID.toString(), true); // TODO Unused in MPXJ, because if the predecessorLinkElement != // null that means this is a previous import and the predecessor // is already set. /* * if (type != null) { * MsProjectExchangeDOMHelper.setChildTextByName * (predecessorLinkElement, COMMON_ELEMENTS.Type, * type.toString(), true); } */ Integer crossProject = Integer.valueOf(PREDECESSOR_CROSS_PROJECT.CROSS_PROJECT); if (workItemIDToTaskUIDMap.containsKey(succLink)) { // from MS Project point of view a Task is cross project // event if it is in another // release of the same project because it would mean another // MS Project file crossProject = Integer.valueOf(PREDECESSOR_CROSS_PROJECT.NOT_CROSS_PROJECT); } // TODO The MPXJ API Relation doesn't have cross project // attribute. /* * MsProjectExchangeDOMHelper.setChildTextByName( * predecessorLinkElement, PREDECESSOR_ELEMENTS.CrossProject, * crossProject.toString(), false); */ // TODO MPXJ Relation doesn't has setter methods for: link lag, // cross project, lag format. /* * if (linkLag!=null) { * MsProjectExchangeDOMHelper.setChildTextByName * (predecessorLinkElement, PREDECESSOR_ELEMENTS.LinkLag, * Integer.valueOf(linkLag.intValue()).toString(), true); * Element crossProjectNameElement = * MsProjectExchangeDOMHelper.getChildByName( * predecessorLinkElement, * PREDECESSOR_ELEMENTS.CrossProjectName); if * (crossProjectNameElement!=null) { * predecessorLinkElement.removeChild(crossProjectNameElement); * } * * } if (lagFormat!=null) { * MsProjectExchangeDOMHelper.setChildTextByName * (predecessorLinkElement, PREDECESSOR_ELEMENTS.LagFormat, * lagFormat.toString(), true); } */ } } // all links which remained in this map are present only in last // imported msProject file and not in track+: they will be removed if (importedDependentToPredecessorLinksMap.size() > 0) { Iterator<Integer> itrPredLinks = importedDependentToPredecessorLinksMap.keySet().iterator(); while (itrPredLinks.hasNext()) { Integer predLink = itrPredLinks.next(); Map<Integer, Relation> succMap = importedDependentToPredecessorLinksMap.get(predLink); if (succMap != null) { for (int i = 0; i < succMap.size(); i++) { // TODO Actually without remove the result is correct, // because it was a trick with building the xml, // relation removing unused in MPXJ. // Relation predecessorElement = succMap.get(i); // predecessorElement.getParentNode().removeChild(predecessorElement); } } } } } /** * Get the UID based calendars map * * @param document * @return */ public static Map<Integer, Resource> getUIDBasedResources(ProjectFile project) { List<Resource> resourceElementList = project.getAllResources(); return MsProjectExchangeDOMHelper.getSubelementBasedElementMapResource(resourceElementList); } /** * Get the UID based tasks * * @param document * @return */ private static Map<Integer, Task> getUIDBasedTasks(ProjectFile projectElement) { List<Task> calendars = projectElement.getAllTasks(); return MsProjectExchangeDOMHelper.getSubelementBasedElementMapTask(calendars); } /** * Get the UID based calendars map * * @param document * @return */ private static Map<Integer, ProjectCalendar> getUIDBasedCalendars(ProjectFile project) { List<ProjectCalendar> calendars = project.getCalendars(); return MsProjectExchangeDOMHelper.getSubelementBasedElementMap(calendars); } /** * Get the calendarUID based exceptionWorkingTimes and weekDayWorkingTimes * for base calendars * * @param document * @param calendarUIDBasedBaseCalendarExceptionWorkingTimes * @param calendarUIDBasedBaseCalendarWeekDayWorkingTimes */ private static void getUIDBasedBaseCalendarWorkingTimes(ProjectFile project, boolean allowOvertimeBookings, double maxOvertimeHoursPerDay, NumberFormat numberFormatTimeSpan, Map<Integer, Map<String, List<String[]>>> calendarUIDBasedBaseCalendarExceptionWorkingTimes, Map<Integer, Map<Integer, List<String[]>>> calendarUIDBasedBaseCalendarWeekDayWorkingTimes) { List<ProjectCalendar> calendars = project.getCalendars(); if (calendars != null) { for (int i = 0; i < calendars.size(); i++) { ProjectCalendar calendarElement = calendars.get(i); Integer UID = calendarElement.getUniqueID(); // TODO isBaseCalendar checking correct? // Integer isBaseCalendar = // MsProjectExchangeDOMHelper.getSubelementInteger(calendarElement, // CALENDAR_ELEMENTS.IsBaseCalendar); Integer isBaseCalendar; if (calendarElement.isDerived()) { isBaseCalendar = IS_BASE_CALENEDAR.NOT_BASE; } else { isBaseCalendar = IS_BASE_CALENEDAR.BASE; } if (isBaseCalendar != null && isBaseCalendar.intValue() == IS_BASE_CALENEDAR.BASE) { Map<String, List<String[]>> baseCalendarExceptionWorkingTimes = new HashMap<String, List<String[]>>(); Map<Integer, List<String[]>> baseCalendarWeekDayWorkingTimes = new HashMap<Integer, List<String[]>>(); loadPersonWorkingTimesForCalendar(calendarElement, baseCalendarExceptionWorkingTimes, baseCalendarWeekDayWorkingTimes, allowOvertimeBookings, maxOvertimeHoursPerDay, numberFormatTimeSpan); calendarUIDBasedBaseCalendarExceptionWorkingTimes.put(UID, baseCalendarExceptionWorkingTimes); calendarUIDBasedBaseCalendarWeekDayWorkingTimes.put(UID, baseCalendarWeekDayWorkingTimes); } } } } private static void getPersonIDBasedBaseCalendarWorkingTimes(Set<Integer> personsWithWork, Map<Integer, List<Integer>> personIDToResourceUIDMap, Map<Integer, Resource> UIDBasedResourceElementsMap, Map<Integer, ProjectCalendar> UIDBasedCalendarElementsMap, boolean allowOvertimeBookings, double maxOvertimeHoursPerDay, NumberFormat numberFormatTimeSpan, // output parameters Map<Integer, Integer> personIDToBaseCalendar, Map<Integer, Map<String, List<String[]>>> personBasedCalendarExceptionWorkingTimes, Map<Integer, Map<Integer, List<String[]>>> personBasedCalendarWeekDayWorkingTimes) { if (personsWithWork.size() > 0) { for (Integer personID : personsWithWork) { List<Integer> resourceUIDForPersonList = personIDToResourceUIDMap.get(personID); if (resourceUIDForPersonList == null || resourceUIDForPersonList.isEmpty()) { // unknown person, should never happen continue; } Integer resourceUID = resourceUIDForPersonList.get(0); Resource resourceElement = UIDBasedResourceElementsMap.get(resourceUID); if (resourceElement != null) { // TODO Resource element doesn't have getCalendarUID getter // method. Integer personCalendarUID = resourceElement.getUniqueID(); if (personCalendarUID != null) { ProjectCalendar personCalendarElement = UIDBasedCalendarElementsMap.get(personCalendarUID); Integer baseCalendarUID = personCalendarElement.getUniqueID(); personIDToBaseCalendar.put(personID, baseCalendarUID); Map<String, List<String[]>> personSpecificExceptionWorkingTimes = new HashMap<String, List<String[]>>(); Map<Integer, List<String[]>> personSpecificWeekDayWorkingTimes = new HashMap<Integer, List<String[]>>(); loadPersonWorkingTimesForCalendar(personCalendarElement, personSpecificExceptionWorkingTimes, personSpecificWeekDayWorkingTimes, allowOvertimeBookings, maxOvertimeHoursPerDay, numberFormatTimeSpan); personBasedCalendarExceptionWorkingTimes.put(personID, personSpecificExceptionWorkingTimes); personBasedCalendarWeekDayWorkingTimes.put(personID, personSpecificWeekDayWorkingTimes); } } } } } /** * Gets the most specific workingTime intervals for a specific day * * @param personSpecificExceptionWorkingTimes * @param personSpecificWeekDayWorkingTimes * @param baseCalendarExceptionWorkingTimes * @param baseCalendarWeekDayWorkingTimes * @param effortDate * @param allowOvertimeBookings * @return */ private static List<String[]> getWorkingTimeForDay( Map<String, List<String[]>> personSpecificExceptionWorkingTimes, Map<Integer, List<String[]>> personSpecificWeekDayWorkingTimes, Map<String, List<String[]>> baseCalendarExceptionWorkingTimes, Map<Integer, List<String[]>> baseCalendarWeekDayWorkingTimes, Date effortDate) { String effortDateStr = MsProjectExchangeBL.formatDate(effortDate); List<String[]> workingTimeList = null; if (personSpecificExceptionWorkingTimes != null) { workingTimeList = personSpecificExceptionWorkingTimes.get(effortDateStr); } if (workingTimeList == null) { if (baseCalendarExceptionWorkingTimes != null) { workingTimeList = baseCalendarExceptionWorkingTimes.get(effortDateStr); } if (workingTimeList == null) { Calendar calendar = Calendar.getInstance(); ; calendar.setTime(effortDate); Integer weekDay = Integer.valueOf(calendar.get(Calendar.DAY_OF_WEEK)); if (personSpecificWeekDayWorkingTimes != null) { workingTimeList = personSpecificWeekDayWorkingTimes.get(weekDay); } if (workingTimeList == null) { if (baseCalendarWeekDayWorkingTimes != null) { workingTimeList = baseCalendarWeekDayWorkingTimes.get(weekDay); } } } } return workingTimeList; } /** * Get the working periods for a calendar element: loads the exception days * map and the weekDay map with the corresponding workingTime periods * * @param calendarElement * @param exceptionWorkingTimes * @param weekDayWorkingTimes */ private static void loadPersonWorkingTimesForCalendar(ProjectCalendar calendarElement, Map<String, List<String[]>> exceptionWorkingTimes, Map<Integer, List<String[]>> weekDayWorkingTimes, boolean allowOvertimeBookings, double maxOvertimeHoursPerDay, NumberFormat numberFormatTimeSpan) { Calendar calendar = Calendar.getInstance(); if (calendarElement != null) { Day[] weekDays = Day.values(); if (weekDays != null && weekDays.length > 0) { for (int i = 0; i < weekDays.length; i++) { if (weekDays[i].getValue() == DAY_WORKING.WORKING) { ProjectCalendarHours hours = calendarElement.getCalendarHours(weekDays[i]); List<String[]> fromTimeToTimePairs = new ArrayList<String[]>(); if (hours != null) { for (int j = 0; j < hours.getRangeCount(); j++) { String fromTime = hours.getRange(j).getStart().toString(); String toTime = hours.getRange(j).getEnd().toString(); if (fromTime != null && toTime != null) { fromTimeToTimePairs.add(new String[] { fromTime, toTime }); } } } if (allowOvertimeBookings && fromTimeToTimePairs != null && fromTimeToTimePairs.size() > 0) { String[] lastWorkingPeriod = fromTimeToTimePairs.get(fromTimeToTimePairs.size() - 1); String lastWorkingPeriodEnd = lastWorkingPeriod[1]; String overtimeWorkingPeriodEnd = MsProjectExchangeBL.addHoursToCalendarTime( lastWorkingPeriodEnd, maxOvertimeHoursPerDay, numberFormatTimeSpan); // if official "homego" time is already midnight do // not add it again if (!overtimeWorkingPeriodEnd.equals(lastWorkingPeriodEnd)) { fromTimeToTimePairs .add(new String[] { lastWorkingPeriodEnd, overtimeWorkingPeriodEnd }); } } // exception date // TODO MPXJ returnes dates otherwise this part is // unuesd. // Element timePeriodElement = // MsProjectExchangeDOMHelper.getChildByName(weekDayElement, // WEEKDAY_ELEMENTS.TimePeriod); if (weekDays[i].getValue() == DAY_TYPE.Exception) {// && // timePeriodElement!=null) // { /* * Date fromDate = * MsProjectExchangeDOMHelper.getSubelementDate * (timePeriodElement, * TIMEPERIOD_ELEMENTS.FromDate); Date toDate = * MsProjectExchangeDOMHelper * .getSubelementDate(timePeriodElement, * TIMEPERIOD_ELEMENTS.ToDate); * calendar.setTime(fromDate); * CalendarUtil.clearTime(calendar); while * (calendar.getTime().before(toDate)) { * exceptionWorkingTimes * .put(MsProjectExchangeBL.formatDate * (calendar.getTime()), fromTimeToTimePairs); * calendar.add(Calendar.DATE, 1); } */ } else { // weekday type if (weekDays[i].getValue() > DAY_TYPE.Exception) { weekDayWorkingTimes.put(weekDays[i].getValue(), fromTimeToTimePairs); } } } } } } } private static Integer getBestResourceID(List<Integer> personIDs, Map<Integer, List<Integer>> personIDToResourceUIDMap) { if (personIDs != null && personIDToResourceUIDMap != null) { for (Iterator<Integer> iterator = personIDs.iterator(); iterator.hasNext();) { Integer personID = iterator.next(); if (personID != null) { List<Integer> resoureUIDList = personIDToResourceUIDMap.get(personID); if (resoureUIDList != null && resoureUIDList.size() > 0) { return resoureUIDList.get(0); } } } } return null; } /** * Export the assignments to actual work * * @param document * @param assignmentElementTemplate * @param timephasedDataElementTemplate * @param existingTrackPlusWorkItemsMap * @param allCostBeansList * @param workItemIDToTaskUIDMap * @param personIDToResourceUIDMap * @param hoursPerWorkday * @param removedTaskIDs * @param allowOvertimeBookings * @param maxOvertimeHoursPerDay * @param allowOverdayBookings * @param allowActualWorkOverlap * @param allowDayRestartIfOverlappedActualWork */ private static void exportAssignments(ProjectFile project, Map<Integer, TWorkItemBean> existingTrackPlusWorkItemsMap, List<TCostBean> allCostBeansList, Map<Integer, Integer> workItemIDToTaskUIDMap, Map<Integer, List<Integer>> personIDToResourceUIDMap, Double hoursPerWorkday, Set<Integer> removedTaskIDs, boolean allowOvertimeBookings, double maxOvertimeHoursPerDay, boolean allowOverdayBookings, boolean allowActualWorkOverlap, /* * boolean * allowDayRestartIfOverlappedActualWork * , */ Map<Integer, Map<String, List<String[]>>> calendarUIDBasedBaseCalendarExceptionWorkingTimes, Map<Integer, Map<Integer, List<String[]>>> calendarUIDBasedBaseCalendarWeekDayWorkingTimes, Map<Integer, Map<String, List<String[]>>> personBasedCalendarExceptionWorkingTimes, Map<Integer, Map<Integer, List<String[]>>> personBasedCalendarWeekDayWorkingTimes, Map<Integer, Integer> personIDToBaseCalendar) { // TODO unused MPXJ /* * Element assignmentsElement = * MsProjectExchangeDOMHelper.getChildByName(projectElement, * PROJECT_ELEMENTS.Assignments); Element assignmentsElement = * project.getAss() if (assignmentsElement==null) { assignmentsElement = * document.createElement(PROJECT_ELEMENTS.Assignments); * projectElement.appendChild(assignmentsElement); } */ // get last imported assignments // List<Element> importedAssignmentsList = // MsProjectExchangeDOMHelper.getElementList( // projectElement, PROJECT_ELEMENTS.Assignments, // PROJECT_SUBELEMENTS.Assignment); List<ResourceAssignment> importedAssignmentsList = project.getAllResourceAssignments(); /* * for(int i = 0; i < importedAssignmentsList.size(); i++) { * ResourceAssignment tmp = importedAssignmentsList.get(i); * tmp.setStart(tmp.getTask().getStart());; * tmp.setFinish(tmp.getTask().getFinish()); tmp.setActualStart(null); * tmp.setActualFinish(null); tmp.setActualWork(null); } */ if (removedTaskIDs != null && removedTaskIDs.size() > 0) { // remove the assignments associated with removed tasks for (int i = 0; i < importedAssignmentsList.size(); i++) { ResourceAssignment assignmentElement = importedAssignmentsList.get(i); Integer taskID = assignmentElement.getTaskUniqueID(); if (removedTaskIDs.contains(taskID)) { importedAssignmentsList.remove(i); } } } Map<Integer, ResourceAssignment> UIDBasedAssignmentsMap = MsProjectExchangeDOMHelper .getSubelementBasedElementMapAssignment(importedAssignmentsList); int maxAssignmentUID = getMaxInteger(UIDBasedAssignmentsMap.keySet()); Map<Integer, Map<Integer, ResourceAssignment>> importedTaskUIDToResourceUIDToAssignmentsMap = MsProjectExchangeDOMHelper .getAssignmentsMap(importedAssignmentsList); Map<Integer, Task> UIDBasedTaskElementMap = getUIDBasedTasks(project); String emptyTimeSpan = MsProjectExchangeBL.getTimeSpanFromDouble(0.0); // NumberFormat numberFormatTimeSpan = new DecimalFormat("00"); /** * Add empty assignment: if a workItem has no cost in Genji and it was * not imported before from MSPRoject a default assignment (with * actualWork=0 and work=total task budget and remainingWork=remaining * task budget) should still be added to the task, to assign the * workItem's responsible as resource to the task (otherwise the task * will have no duration) */ Map<Integer, List<TCostBean>> worksByWorkItemMap = getWorksByWorkItem(allCostBeansList); Iterator<Integer> itrExistingWorkItems = existingTrackPlusWorkItemsMap.keySet().iterator(); while (itrExistingWorkItems.hasNext()) { Integer workItemID = itrExistingWorkItems.next(); TWorkItemBean workItemBean = existingTrackPlusWorkItemsMap.get(workItemID); Integer taskUID = workItemIDToTaskUIDMap.get(workItemID); if (!worksByWorkItemMap.containsKey(workItemID) && !importedTaskUIDToResourceUIDToAssignmentsMap.containsKey(taskUID)) { // it has no actualWork and was not imported before: it will not // be processed in the "existing costs" loop below List<Integer> personList = new ArrayList<Integer>(); personList.add(workItemBean.getResponsibleID()); personList.add(workItemBean.getOwnerID()); personList.add(workItemBean.getOriginatorID()); Integer resourceID = getBestResourceID(personList, personIDToResourceUIDMap); if (resourceID != null && taskUID != null) { Task taskElement = UIDBasedTaskElementMap.get(taskUID); if (taskElement != null) { ResourceAssignment assignmentElement = new ResourceAssignment(project); ResourceAssignment existing = importedTaskUIDToResourceUIDToAssignmentsMap.get(taskUID) .get(resourceID); assignmentElement.setUniqueID(++maxAssignmentUID); assignmentElement.setTaskUniqueID(taskUID); assignmentElement.setResourceUniqueID(resourceID); // TODO set emptyTimeSpan string as duration not // possible in MPXJ // assignmentElement.setActualWork(Duration.emptyTimeSpan); // get the values directly from the task: the Work and // RemainingWork are probably equal // String work = // MsProjectExchangeDOMHelper.getSubelementText(taskElement, // COMMON_ELEMENTS.Work); if (importedTaskUIDToResourceUIDToAssignmentsMap.get(resourceID) != null) { assignmentElement.setWork(existing.getWork()); assignmentElement.setRemainingWork(existing.getRemainingWork()); assignmentElement.setStart(existing.getStart()); assignmentElement.setStart(existing.getFinish()); } } } } } /** * The actual booked calendar for a certain date for a certain user */ Map<Integer, Map<String, Calendar>> effortDateToCalBookedForPersonMap = new HashMap<Integer, Map<String, Calendar>>(); /** * The exhausted effort dates for a certain user */ Map<Integer, Map<String, Boolean>> effortDateExhaustedForPersonMap = new HashMap<Integer, Map<String, Boolean>>(); // loop through the existing costs in track+ based on workItemID and // personID Iterator<Integer> itrWorksByWorkItemIDMap = worksByWorkItemMap.keySet().iterator(); while (itrWorksByWorkItemIDMap.hasNext()) { Integer workItemID = itrWorksByWorkItemIDMap.next(); List<TCostBean> costBeansWorkItem = worksByWorkItemMap.get(workItemID); // get the workItem costs pro person Map<Integer, List<TCostBean>> costsByPerson = getCostsByPerson(costBeansWorkItem); Map<Integer, Double> personActualWorkMap = new HashMap<Integer, Double>(); for (Iterator<Integer> iterator = costsByPerson.keySet().iterator(); iterator.hasNext();) { Integer personID = iterator.next(); List<TCostBean> costs = costsByPerson.get(personID); personActualWorkMap.put(personID, getSumOfActualWorks(costs)); } Integer taskUID = workItemIDToTaskUIDMap.get(workItemID); if (taskUID != null) { Task taskElement = UIDBasedTaskElementMap.get(taskUID); if (taskElement != null) { Date taskActualStart = null; Date taskActualFinish = null; // all assignments for workItem: both last imported (even if // there is no actual work (costBean) associated) and new // ones Map<Integer, ResourceAssignment> resourceBasedAssignmentsForWorkItem = new HashMap<Integer, ResourceAssignment>(); Set<Integer> resoucesWithImportedRemainingWork = new HashSet<Integer>(); // iterate through costBeans for each person Iterator<Integer> itrPerson = costsByPerson.keySet().iterator(); while (itrPerson.hasNext()) { Integer personID = itrPerson.next(); List<TCostBean> costBeansPerson = costsByPerson.get(personID); List<Integer> resourceUIDForPersonList = personIDToResourceUIDMap.get(personID); Integer resourceUID = null; if (resourceUIDForPersonList == null || resourceUIDForPersonList.isEmpty()) { // unknown person, should never happen continue; } else { if (resourceUIDForPersonList.size() == 1) { // unambiguous person <-> resource: the // corresponding person was mapped only to this // resource resourceUID = resourceUIDForPersonList.get(0); } } ResourceAssignment assignmentElement = null; Map<Integer, ResourceAssignment> importedResourceUIDToAssignmentsMap = importedTaskUIDToResourceUIDToAssignmentsMap .get(taskUID); // last imported assignments for task exist if (importedResourceUIDToAssignmentsMap != null && importedResourceUIDToAssignmentsMap.size() > 0) { if (resourceUID == null && importedResourceUIDToAssignmentsMap.size() > 0) { // ambiguous: the same person was mapped for // more resources: // find the resourceID based on the available // assignments from the previous import: // get the first last imported resourceID found // which was mapped for the person. // (as best effort but still error prone) Iterator<Integer> itrResource = importedResourceUIDToAssignmentsMap.keySet() .iterator(); while (itrResource.hasNext()) { resourceUID = itrResource.next(); if (resourceUIDForPersonList.contains(resourceUID)) { break; } else { resourceUID = null; } } } // person had assignment to task both in the // previous import and in track+: remove it if found // to process them now // assignments not processed (removed) here should // be looped at the end for removing (probably their // costBeans were removed) assignmentElement = importedResourceUIDToAssignmentsMap.remove(resourceUID); } if (resourceUID == null) { // no previous import for this assignment, so there // is no way to uniquely identify the resourceID // for adding the assignment for, as best effort // take the first mapped for the person resourceUID = resourceUIDForPersonList.get(0); } // the timephased data will be calculated depending on // the unit Double unit = new Double(1.0); // default 1.0 = 100% boolean isNew = false; if (assignmentElement == null) { assignmentElement = new ResourceAssignment(project); // new assignment: the workItem has actual work // booked by a person but the previous // import file contains no assignments: actual // work(s) added in track+ exclusively since the // last import isNew = true; } else { // get the unit from the previously imported // assignment // unit = // MsProjectExchangeDOMHelper.getSubelementDouble(assignmentElement, // ASSIGNMENT_ELEMENTS.Units); unit = assignmentElement.getUnits().doubleValue(); if (unit == null || Math.abs(unit.doubleValue()) < EPSILON) { unit = new Double(1.0); } /* * if assignmentElement was already imported try to * preserve the remaining work pro person (taking * into account the corresponding actual work * difference). This counts by calculating the * duration of the task */ Double lastActualWorkPerson = assignmentElement.getActualWork().getDuration(); if (lastActualWorkPerson == null) { lastActualWorkPerson = new Double(0.0); } Double lastRemainingWorkPerson = assignmentElement.getRemainingWork().getDuration(); if (lastRemainingWorkPerson == null) { lastRemainingWorkPerson = new Double(0.0); } Double currentActualWorkPerson = personActualWorkMap.get(personID); if (currentActualWorkPerson == null) { currentActualWorkPerson = new Double(0.0); } double actualWorkDifference = currentActualWorkPerson.doubleValue() - lastActualWorkPerson.doubleValue(); double newRemainingWorkPerson = 0.0; if (lastRemainingWorkPerson.doubleValue() >= actualWorkDifference) { newRemainingWorkPerson = lastRemainingWorkPerson.doubleValue() - actualWorkDifference; } assignmentElement .setRemainingWork(Duration.getInstance(newRemainingWorkPerson, TimeUnit.HOURS)); } // because it is not trivial to combine two lists of // works for changes (last imported and actual from // Genji), // all imported ASSIGNMENT_ACTUAL_WORK and // ASSIGNMENT_REMAINING_WORK type // and without value timePhasedData elements will be // removed // (and later the ASSIGNMENT_ACTUAL_WORK elements from // track+ will be added again based on Genji costBeans) project.getAllResourceAssignments().remove(assignmentElement); resourceBasedAssignmentsForWorkItem.put(resourceUID, assignmentElement); if (isNew && costBeansPerson != null && costBeansPerson.size() > 0) { ResourceAssignment newAssignmentElement = new ResourceAssignment(project); newAssignmentElement.setUniqueID(++maxAssignmentUID); assignmentElement.setTaskUniqueID(taskUID); assignmentElement.setResourceUniqueID(resourceUID); } /** * add TimephasedData elements */ // if actualStart does not comply with the // timephasedData elements // then the Actual work may have the wrong value in // MSProject Date assignmentActualStart = null; Date assignmentActualFinish = null; int assignmentActualOvertimeHours = 0; int assignmentActualOverTimeMinutes = 0; // formatted effortDate mapped to calendar with with // actualized times for // a certain user to avoid double allocation for a user // in a certain time Map<String, Calendar> effortDateToCalBookedMap = null; Map<String, Boolean> effortDateExhaustedMap = null; if (!allowActualWorkOverlap) { effortDateToCalBookedMap = effortDateToCalBookedForPersonMap.get(personID); effortDateExhaustedMap = effortDateExhaustedForPersonMap.get(personID); } if (effortDateToCalBookedMap == null) { effortDateToCalBookedMap = new HashMap<String, Calendar>(); if (!allowActualWorkOverlap) { effortDateToCalBookedForPersonMap.put(personID, effortDateToCalBookedMap); } } // whether the date was exhausted by previous works if (effortDateExhaustedMap == null) { effortDateExhaustedMap = new HashMap<String, Boolean>(); if (!allowActualWorkOverlap) { effortDateExhaustedForPersonMap.put(personID, effortDateExhaustedMap); } } if (costBeansPerson != null && costBeansPerson.size() > 0) { // add the ASSIGNMENT_ACTUAL_WORK timephasedData // elements from Genji Date effortDate = null; Date previousEffortDate = null; for (TCostBean costBean : costBeansPerson) { if (effortDate != null) { // effort date is null at the beginning and // after by overbooked days if // allowOvertimeBookings=false // in previousEffortDate the last valid // (non-null) effortDate should be kept previousEffortDate = effortDate; } effortDate = costBean.getEffortdate(); if (previousEffortDate != null && effortDate != null && previousEffortDate.before(effortDate)) { // add empty timespans for the gaps between // actual work, // otherwise the gap is considered as // actually worked addEmptyIntervals(project, assignmentElement, previousEffortDate, effortDate, emptyTimeSpan, effortDateToCalBookedMap, effortDateExhaustedMap, personBasedCalendarExceptionWorkingTimes.get(personID), personBasedCalendarWeekDayWorkingTimes.get(personID), calendarUIDBasedBaseCalendarExceptionWorkingTimes .get(personIDToBaseCalendar.get(personID)), calendarUIDBasedBaseCalendarWeekDayWorkingTimes.get( personIDToBaseCalendar.get(personID)), allowOvertimeBookings); } Double hours = costBean.getHours(); if (effortDate != null && hours != null && hours.intValue() > 0.0) { // calculate the unit proportional values, // because in TimephasedData element the // Start and Finish // date fields are based on unit // proportional hours, but the Value field // is based on real hours // depending on Assignment element's Unit // sub-element Double unitHours = hours.doubleValue() / unit.doubleValue(); int unitProportionalHours = unitHours.intValue(); double unitDecimalHours = unitHours - unitProportionalHours; int unitProportionalMinutes = (int) Math.round(unitDecimalHours * 60); // date over-booked by previous costBeans effortDate = getNextValidDate(effortDate, effortDateExhaustedMap, personBasedCalendarExceptionWorkingTimes.get(personID), personBasedCalendarWeekDayWorkingTimes.get(personID), calendarUIDBasedBaseCalendarExceptionWorkingTimes .get(personIDToBaseCalendar.get(personID)), calendarUIDBasedBaseCalendarWeekDayWorkingTimes.get( personIDToBaseCalendar.get(personID)), allowOverdayBookings); if (effortDate == null) { if (allowOverdayBookings) { LOGGER.warn("No reasonable effort date found for person " + personID + " workItem " + workItemID + " costID " + costBean.getObjectID()); } else { LOGGER.warn("The work " + hours.doubleValue() + " for person " + personID + " workItem " + workItemID + " costID " + costBean.getObjectID() + " could not be added because the worktime for date " + MsProjectExchangeBL.formatDateTime(effortDate) + " is exhausted by previous works"); } continue;// to the next costBean } String effortDateStr = MsProjectExchangeBL.formatDate(effortDate); Calendar calToTime = Calendar.getInstance(); calToTime.setTime(effortDate); boolean jumpToNextWorkingTimeInterval = false; List<String[]> workingPeriods = getWorkingTimeForDay( personBasedCalendarExceptionWorkingTimes.get(personID), personBasedCalendarWeekDayWorkingTimes.get(personID), calendarUIDBasedBaseCalendarExceptionWorkingTimes .get(personIDToBaseCalendar.get(personID)), calendarUIDBasedBaseCalendarWeekDayWorkingTimes .get(personIDToBaseCalendar.get(personID)), effortDate); if (workingPeriods == null) { // should never happen, continue; } // else { int realHours = 0; int realMinutes = 0; boolean isOvertime = false; // the Start date of the timephasedData Date timePhasedDataStart = null; for (Iterator itrWorkPeriod = workingPeriods.iterator(); itrWorkPeriod .hasNext();) { // try each workingTime interval to see // where the work can be added String[] workingTime = (String[]) itrWorkPeriod.next(); String fromTime = workingTime[0]; String toTime = workingTime[1]; Map<String, Integer> timeUnitsMapFromTime = MsProjectExchangeBL .getTimeUnitsMapFromCalendarTime(fromTime); Map<String, Integer> timeUnitsMapToTime = MsProjectExchangeBL .getTimeUnitsMapFromCalendarTime(toTime); Integer previousHour = calToTime.get(Calendar.HOUR_OF_DAY); calToTime.set(Calendar.HOUR_OF_DAY, timeUnitsMapToTime.get(MSPROJECT_TIME_UNITS.HOUR)); calToTime.set(Calendar.MINUTE, timeUnitsMapToTime.get(MSPROJECT_TIME_UNITS.MINUTE)); Integer nextHour = calToTime.get(Calendar.HOUR_OF_DAY); if (!itrWorkPeriod.hasNext() && allowOvertimeBookings) { // midnight means the start of the // next day if (previousHour > nextHour) { // overtime over the midnight calToTime.add(Calendar.DATE, 1); } isOvertime = true; } else { isOvertime = false; } Calendar calBookedForPerson = effortDateToCalBookedMap.get(effortDateStr); if (calBookedForPerson == null) { calBookedForPerson = Calendar.getInstance(); calBookedForPerson.setTime(effortDate); calBookedForPerson.set(Calendar.HOUR_OF_DAY, timeUnitsMapFromTime.get(MSPROJECT_TIME_UNITS.HOUR)); calBookedForPerson.set(Calendar.MINUTE, timeUnitsMapFromTime.get(MSPROJECT_TIME_UNITS.MINUTE)); effortDateToCalBookedMap.put(effortDateStr, calBookedForPerson); } // jumped first time or from the // previous workingTime interval if (jumpToNextWorkingTimeInterval) { calBookedForPerson.set(Calendar.HOUR_OF_DAY, timeUnitsMapFromTime.get(MSPROJECT_TIME_UNITS.HOUR)); calBookedForPerson.set(Calendar.MINUTE, timeUnitsMapFromTime.get(MSPROJECT_TIME_UNITS.MINUTE)); jumpToNextWorkingTimeInterval = false; } if (calBookedForPerson.getTime().after(calToTime.getTime()) || calBookedForPerson.getTimeInMillis() == calToTime .getTimeInMillis()) { // try the next workingTime interval // because till this time // the user booked previous // costBeans LOGGER.debug("Period from " + MsProjectExchangeBL.formatDateTime(calToTime.getTime()) + " for personID " + personID + " workItemID " + workItemID + " costID " + costBean.getObjectID() + " added at date " + MsProjectExchangeBL.formatDateTime(effortDate) + " expense work " + hours.doubleValue() + " already booked by other costBeans."); if (calBookedForPerson.getTimeInMillis() == calToTime .getTimeInMillis()) { jumpToNextWorkingTimeInterval = true; } continue; // to the next working // time of the current // date } else { // available working time found if (timePhasedDataStart == null) { timePhasedDataStart = calBookedForPerson.getTime(); } long millisecondsLeftInInterval = calToTime.getTimeInMillis() - calBookedForPerson.getTimeInMillis(); if (millisecondsLeftInInterval == 0) { continue; // to the next working // time of the // current date } long diffHours = millisecondsLeftInInterval / (60 * 60 * 1000); long diffMinutes = (millisecondsLeftInInterval - diffHours * 60 * 60 * 1000) / (60 * 1000); if (unitProportionalHours < diffHours || (unitProportionalHours == diffHours && unitProportionalMinutes <= diffMinutes)) { // the current workingTime // interval is enough for work // to add calBookedForPerson.add(Calendar.HOUR_OF_DAY, unitProportionalHours); calBookedForPerson.add(Calendar.MINUTE, unitProportionalMinutes); Double unitPropRemainingHours = new Double(unitProportionalHours + (double) unitProportionalMinutes / 60); // add the real hours Double realHoursDouble = new Double( unitPropRemainingHours * unit.doubleValue()); realHours += realHoursDouble.intValue(); if (isOvertime) { assignmentActualOvertimeHours += realHoursDouble.intValue(); } double decimalHours = realHoursDouble - realHoursDouble.intValue(); realMinutes += (int) Math.round(decimalHours * 60); if (isOvertime) { assignmentActualOverTimeMinutes += (int) Math .round(decimalHours * 60); } addTimephasedDataElement(project, assignmentElement, timePhasedDataStart, calBookedForPerson.getTime(), MsProjectExchangeBL.getTimeSpanFromHoursAndMinutes( realHours, realMinutes)); if (assignmentActualStart == null || assignmentActualStart.after(timePhasedDataStart)) { assignmentActualStart = timePhasedDataStart; } if (assignmentActualFinish == null || assignmentActualFinish .before(calBookedForPerson.getTime())) { assignmentActualFinish = calBookedForPerson.getTime(); } LOGGER.debug("Added actual work for person " + personID + " workItem " + workItemID + " cost " + costBean.getObjectID() + " added at date " + MsProjectExchangeBL.formatDateTime(effortDate) + " start " + MsProjectExchangeBL.formatDateTime(timePhasedDataStart) + " finish " + MsProjectExchangeBL .formatDateTime(calBookedForPerson.getTime()) + " expense work " + hours.doubleValue() + " actual work " + realHours + ":" + realMinutes); if (!itrWorkPeriod.hasNext() && allowOverdayBookings && unitProportionalHours == diffHours && unitProportionalMinutes == diffMinutes) { // if terminated exactly at // midnight reset the // effortDateToEffortDateWithTime // to start again at the // first FromTime // effortDateToCalBookedForPersonMap.remove(effortDateStr); effortDateExhaustedMap.put(effortDateStr, Boolean.TRUE); } break; // successfully added for // this day, break to // continue with the // next CostBean } else { // the current workingTime // interval is not enough for // the entire work to add: // add only a part of the work // till the end of the current // interval int remainingHoursInWorkingTimePeriod = Long.valueOf(diffHours) .intValue(); int remainingMinutesInWorkingTimePeriod = Long.valueOf(diffMinutes) .intValue(); // book up to the end of the // current workingTime calBookedForPerson.setTime(calToTime.getTime()); jumpToNextWorkingTimeInterval = true; unitProportionalHours -= remainingHoursInWorkingTimePeriod; if (unitProportionalMinutes < remainingMinutesInWorkingTimePeriod) { unitProportionalMinutes += 60; unitProportionalHours--; } unitProportionalMinutes -= remainingMinutesInWorkingTimePeriod; // add only a part of the work // till the end of this // workingTime interval // add the real hours Double remainingHours = new Double(millisecondsLeftInInterval * unit.doubleValue() / (60 * 60 * 1000)); realHours += remainingHours.intValue(); if (isOvertime) { assignmentActualOvertimeHours += remainingHours.intValue(); } double decimalHours = remainingHours - remainingHours.intValue(); realMinutes += (int) Math.round(decimalHours * 60); if (isOvertime) { assignmentActualOverTimeMinutes += (int) Math .round(decimalHours * 60); } if (!itrWorkPeriod.hasNext()) { // the user consumed the // entire workDay and // overtime // till midnight but this is // still not enough // add the biggest possible // part of the work as best // effort addTimephasedDataElement(project, assignmentElement, timePhasedDataStart, calBookedForPerson.getTime(), MsProjectExchangeBL.getTimeSpanFromHoursAndMinutes( realHours, realMinutes)); if (assignmentActualStart == null || assignmentActualStart.after(timePhasedDataStart)) { assignmentActualStart = timePhasedDataStart; } if (assignmentActualFinish == null || assignmentActualFinish .before(calBookedForPerson.getTime())) { assignmentActualFinish = calBookedForPerson.getTime(); } LOGGER.debug("Added actual work part for person " + personID + " workItem " + workItemID + " cost " + costBean.getObjectID() + " start " + MsProjectExchangeBL .formatDateTime(timePhasedDataStart) + " finish " + MsProjectExchangeBL .formatDateTime(calBookedForPerson.getTime()) + " expense work " + hours.doubleValue() + " actual work " + realHours + ":" + realMinutes); // effortDateToCalBookedForPersonMap.remove(effortDateStr); effortDateExhaustedMap.put(effortDateStr, Boolean.TRUE); // force to initialize again // at the first FromTime calBookedForPerson = null; timePhasedDataStart = null; realHours = 0; realMinutes = 0; effortDate = getNextValidDate(effortDate, effortDateExhaustedMap, personBasedCalendarExceptionWorkingTimes.get(personID), personBasedCalendarWeekDayWorkingTimes.get(personID), calendarUIDBasedBaseCalendarExceptionWorkingTimes .get(personIDToBaseCalendar.get(personID)), calendarUIDBasedBaseCalendarWeekDayWorkingTimes .get(personIDToBaseCalendar.get(personID)), allowOverdayBookings); if (effortDate == null) { if (allowOvertimeBookings) { LOGGER.warn( "No reasonable effort date found for person " + personID + " workItem " + workItemID + " costID " + costBean.getObjectID()); } else { LOGGER.warn("The work " + unitProportionalHours + ":" + unitProportionalMinutes + " for person " + personID + " workItem " + workItemID + " costID " + costBean.getObjectID() + " could not be added because the worktime for date " + MsProjectExchangeBL.formatDateTime(effortDate) + " is exhausted"); } break; } effortDateStr = MsProjectExchangeBL.formatDate(effortDate); // calToTime is now midnight // (starting of the next // day). // set to the actual // effortDate day because // the // iterator for workPeriods // is started again with // this new date calToTime.setTime(effortDate); workingPeriods = getWorkingTimeForDay( personBasedCalendarExceptionWorkingTimes.get(personID), personBasedCalendarWeekDayWorkingTimes.get(personID), calendarUIDBasedBaseCalendarExceptionWorkingTimes .get(personIDToBaseCalendar.get(personID)), calendarUIDBasedBaseCalendarWeekDayWorkingTimes .get(personIDToBaseCalendar.get(personID)), effortDate); itrWorkPeriod = workingPeriods.iterator(); } } } } } } } // assignment level ActualStart, Start and ActualFinish // dates if (assignmentActualStart != null) { assignmentElement.setActualStart(assignmentActualStart); if (taskActualStart == null || taskActualStart.after(assignmentActualStart)) { taskActualStart = assignmentActualStart; } } // the Start should be not later as the ActualStart Date startElement = assignmentElement.getStart(); if (startElement != null) { Date startDate = assignmentElement.getStart(); if (startDate != null && assignmentActualStart != null && startDate.after(assignmentActualStart)) { assignmentElement.setStart(assignmentActualStart); } } if (assignmentActualFinish != null) { // it is actually finished only when remaining work // is 0 // so it might be removed later assignmentElement.setActualFinish(assignmentActualFinish); if (taskActualFinish == null || taskActualFinish.before(assignmentActualFinish)) { taskActualFinish = assignmentActualFinish; } } // do we have ActualOvertimeWork? if (assignmentActualOvertimeHours > 0 || assignmentActualOverTimeMinutes > 0) { String actualOverTimeHoursTimeSpan = MsProjectExchangeBL.getTimeSpanFromHoursAndMinutes( assignmentActualOvertimeHours, assignmentActualOverTimeMinutes); DateFormat df = new SimpleDateFormat(); Double actualOverTimeWork = assignmentElement.getActualOvertimeWork().getDuration(); if (actualOverTimeWork == null) { actualOverTimeWork = new Double(0); } Double overtimeWork = assignmentElement.getOvertimeWork().getDuration(); if (overtimeWork == null) { overtimeWork = new Double(0); } // the OvertimeWork will not be less than the // ActualOvertimeWork if (actualOverTimeWork.doubleValue() > overtimeWork.doubleValue()) { // TODO MPXJ, set setOvertimeWork check. try { Calendar myCal = Calendar.getInstance(); myCal.setTime(df.parse(actualOverTimeHoursTimeSpan)); int hours = myCal.get(Calendar.HOUR_OF_DAY); assignmentElement.setOvertimeWork(Duration.getInstance(hours, TimeUnit.HOURS)); } catch (Exception ex) { LOGGER.error("Date parse error: " + ex.getMessage()); } } } } // end person loop /** * actualize the remaining work and work for each assignment * of the current task it is important because the duration * is calculated based on the works assigned to each person * (for example if a user is set to 100% but no Work and * RemainingWork is set for him then it will not shorten the * duration as expected) */ // process those assignments of the current task which were // imported last time but // no TCostBean corresponding to person were found for them // consequently // they wern't processed (removed) above in the loop for // persons. // Probably the TCostBean was removed since the last import. // The assignment will not be deleted (that should be made // in MsProject) but the // actual work will be set to 0 (no TCostBean). We still // need the assignment because of the // remaining work: if we would remove the entire assignment // the task duration would increase // because the remaining work of this assignment should be // taken by other resources Map<Integer, ResourceAssignment> importedResourceUIDToAssignmentsMap = importedTaskUIDToResourceUIDToAssignmentsMap .get(taskUID); if (importedResourceUIDToAssignmentsMap != null && importedResourceUIDToAssignmentsMap.size() > 0) { Iterator<Integer> itrResource = importedResourceUIDToAssignmentsMap.keySet().iterator(); while (itrResource.hasNext()) { Integer resouceID = itrResource.next(); ResourceAssignment noCostAssignmentElement = importedResourceUIDToAssignmentsMap .remove(resouceID); // String emptyValue = // MsProjectExchangeBL.getTimeSpanFromDouble(0.0); // reset the ActualWork to 0 because there is no // costBean for this // TODO MPXJ // noCostAssignmentElement.setActualWork(emptyTimeSpan); resoucesWithImportedRemainingWork.add(resouceID); resourceBasedAssignmentsForWorkItem.put(resouceID, noCostAssignmentElement); // TODO MPXJ // removeTimephasedData(noCostAssignmentElement); } } /** * "normalize" the RemainingWork and Work for assignments */ // sum up the remaining work from each resource // (both new and imported assignments, but for new it is 0 // anyway) double allPersonsRemainingWork = 0.0; for (Map.Entry<Integer, ResourceAssignment> entry : resourceBasedAssignmentsForWorkItem .entrySet()) { // Integer resourceID = entry.getKey(); ResourceAssignment assignmentElement = entry.getValue(); Double remainingWork = assignmentElement.getRemainingWork().getDuration(); if (remainingWork != null) { allPersonsRemainingWork += remainingWork.doubleValue(); } } // remaining work for the entire task Double taskRemainingHours = taskElement.getRemainingWork().getDuration(); if (taskRemainingHours == null) { taskRemainingHours = new Double(0.0); } if (Math.abs(allPersonsRemainingWork - taskRemainingHours.doubleValue()) > 0.001) { // "normalization" needed if (allPersonsRemainingWork > taskRemainingHours.doubleValue()) { // the task level remaining work is less than the // sum if remaining work for assignments: // (probably was explicitly set in track+ to a // smaller value) // we must remove some person level remainingWork at // best effort double difference = allPersonsRemainingWork - taskRemainingHours.doubleValue(); for (Iterator itrAssignments = resourceBasedAssignmentsForWorkItem.keySet() .iterator(); itrAssignments.hasNext();) { Integer resourceID = (Integer) itrAssignments.next(); ResourceAssignment assignmentElement = resourceBasedAssignmentsForWorkItem .get(resourceID); Double remainingWork = assignmentElement.getRemainingWork().getDuration(); if (remainingWork != null && Math.abs(remainingWork.doubleValue()) > EPSILON) { if (difference > remainingWork.doubleValue()) { // TODO MPXJ set setRemainingWork // validation. assignmentElement.setRemainingWork( Duration.getInstance(remainingWork, TimeUnit.HOURS)); difference -= remainingWork.doubleValue(); } else { assignmentElement.setRemainingWork(Duration.getInstance( remainingWork.doubleValue() - difference, TimeUnit.HOURS)); break; } } } } else { // the difference should be added to one or more // person specific remainingWork at best effort: double difference = taskRemainingHours.doubleValue() - allPersonsRemainingWork; List<ResourceAssignment> newRemainingWorkElements = new ArrayList<ResourceAssignment>(); // see whether there are new assignments (those // which doesn't appear in the last import) // because for them the remainingWork should not be // preserved for (Iterator itrAssignments = resourceBasedAssignmentsForWorkItem.keySet() .iterator(); itrAssignments.hasNext();) { Integer resourceID = (Integer) itrAssignments.next(); if (!resoucesWithImportedRemainingWork.contains(resourceID)) { newRemainingWorkElements .add(resourceBasedAssignmentsForWorkItem.get(resourceID)); } } if (newRemainingWorkElements.isEmpty() && resourceBasedAssignmentsForWorkItem.size() > 0) { // no new assignment found, we must mess up a // "to be preserved" remainingWork of an // existing assignment ResourceAssignment assignmentElement = resourceBasedAssignmentsForWorkItem.values() .iterator().next(); double remainingWork = assignmentElement.getRemainingWork().getDuration(); assignmentElement.setRemainingWork( Duration.getInstance(remainingWork + difference, TimeUnit.HOURS)); } else { // new assignment(s) found: divide the remaining // work equally for each new assignment Double remainingWorkProPerson = difference / newRemainingWorkElements.size(); for (Iterator<ResourceAssignment> iterator = newRemainingWorkElements .iterator(); iterator.hasNext();) { ResourceAssignment assignmentElement = iterator.next(); assignmentElement.setRemainingWork( Duration.getInstance(remainingWorkProPerson, TimeUnit.HOURS)); } } } } /* * set the Works for each assignment: it is always the sum * of actual work and remaining work (doesn't matter the * value from track+ total budget) */ double taskActualOvertimeWork = 0.0; if (resourceBasedAssignmentsForWorkItem != null && resourceBasedAssignmentsForWorkItem.size() > 0) { for (Iterator itrAssignments = resourceBasedAssignmentsForWorkItem.keySet() .iterator(); itrAssignments.hasNext();) { Integer resourceID = (Integer) itrAssignments.next(); ResourceAssignment assignmentElement = resourceBasedAssignmentsForWorkItem .get(resourceID); Double remainingWorkHours = assignmentElement.getRemainingWork().getDuration(); if (remainingWorkHours != null && Math.abs(remainingWorkHours.doubleValue()) > EPSILON) { // if there is remaining work then remove the // actual finish date Date actualFinishElement = assignmentElement.getActualFinish(); if (actualFinishElement != null) { project.getAllResourceAssignments().remove(assignmentElement); } } // assignment level OvertimeWork, // ActualOvertimeWork, RegularWork Duration actualWorkDuration = assignmentElement.getActualWork(); Double actualWorkHours = null; if (actualWorkDuration != null) { actualWorkHours = actualWorkDuration.getDuration(); } if (actualWorkHours == null) { actualWorkHours = Double.valueOf(0); } Double work = remainingWorkHours + actualWorkHours; assignmentElement.setWork(Duration.getInstance(work, TimeUnit.HOURS)); Duration overtimeWorkDuration = assignmentElement.getOvertimeWork(); Double assignmentOvertimeWork = null; if (overtimeWorkDuration != null) { assignmentOvertimeWork = overtimeWorkDuration.getDuration(); } if (assignmentOvertimeWork == null) { assignmentOvertimeWork = new Double(0); } /* * Double assignmentActualOvertimeWork = * assignmentElement.getActualWork().getDuration(); * if (assignmentActualOvertimeWork==null) { * assignmentActualOvertimeWork = new Double(0); } * taskActualOvertimeWork += * assignmentActualOvertimeWork.doubleValue(); */ taskActualOvertimeWork += actualWorkHours; assignmentElement.setRegularWork( Duration.getInstance(work - assignmentOvertimeWork, TimeUnit.HOURS)); } } // task level OvertimeWork, ActualOvertimeWork, RegularWork Duration overtimeWorkDuration = taskElement.getOvertimeWork(); Double taskOvertimeWork = null; if (overtimeWorkDuration != null) { taskOvertimeWork = overtimeWorkDuration.getDuration(); } if (taskOvertimeWork == null) { taskOvertimeWork = new Double(0); } // the OvertimeWork will not be less than the // ActualOvertimeWork if (taskActualOvertimeWork > taskOvertimeWork.doubleValue()) { taskElement.setOvertimeWork(Duration.getInstance(taskActualOvertimeWork, TimeUnit.HOURS)); taskOvertimeWork = taskActualOvertimeWork; } if (taskActualOvertimeWork > 0.0) { // taskElement.setActualOvertimeWork(Duration.getInstance(taskActualOvertimeWork, // TimeUnit.HOURS)); } Double work = taskElement.getWork().getDuration(); if (work == null) { work = new Double(0); } taskElement.setRegularWork(Duration .getInstance(work.doubleValue() - taskOvertimeWork.doubleValue(), TimeUnit.HOURS)); // task level ActualStart, Start and ActualFinish dates if (taskActualStart != null) { } Date startElement = taskElement.getStart(); if (startElement != null) { Date startDate = taskElement.getStart(); if (startDate != null && taskActualStart != null && startDate.after(taskActualStart)) { // taskElement.setStart(taskActualStart); } } if (taskActualFinish != null && taskRemainingHours != null && Math.abs(taskRemainingHours.doubleValue()) < 0.0001) { taskElement.setActualFinish(taskActualFinish); } } } } // assignments which still remained are probably from tasks which // doesn't have actualWork. // The actual work should be set to 0 (no TCostBean) anyway for each // one, but ideally none of them should // be deleted (the assignment is still needed because of their remaining // work assigned to a person). // If the remainingWork is changed for the workItem then we would have // the evenly distribute the difference // among the previously imported assignments' remainingWorks. This is // too complicated, instead we assign // the task's entire remainingWork to the first person and delete the // assignments for the other persons if (importedTaskUIDToResourceUIDToAssignmentsMap.size() > 0) { // TODO Parsing correctness validation for (Integer key : importedTaskUIDToResourceUIDToAssignmentsMap.keySet()) { Integer taskUID = key; Task taskElement = UIDBasedTaskElementMap.get(taskUID); if (taskElement != null && taskElement.getRemainingWork() != null) { Double taskRemainingWork = taskElement.getRemainingWork().getDuration(); Map<Integer, ResourceAssignment> resourceUIDToAssignmentsMap = importedTaskUIDToResourceUIDToAssignmentsMap .get(taskUID); if (resourceUIDToAssignmentsMap != null) { boolean first = true; for (Integer key2 : resourceUIDToAssignmentsMap.keySet()) { Integer resourceID = resourceUIDToAssignmentsMap.get(key2).getUniqueID(); // only the real resourceIDs are important if (first) { // TODO assignmentElement REMAINING_WORK field // missing first = false; ResourceAssignment assignmentElement = resourceUIDToAssignmentsMap.get(resourceID); // assignmentElement.getTim // reset the ActualWork to 0 because there is no // costBean for this // removeTimephasedData(assignmentElement); // TODO MPXJ! // assignmentElement.setActualWork(Duration.getInstance(emptyTimeSpan, // TimeUnit.HOURS)); // work = actualWork + remainingWork // so if we set actual work to emptyTimeSpan we // should make work=remainingWork, otherwise the // file doesn't open // TODO MPXJ!! // assignmentElement.setRemainingWork(Duration.getInstance(taskRemainingWork, // TimeUnit.HOURS)); // MsProjectExchangeDOMHelper.setChildTextByName(assignmentElement, // COMMON_ELEMENTS.Work, taskRemainingWork, // true); // TODO remove if statement MPXJ if (assignmentElement != null) { assignmentElement .setWork(Duration.getInstance(taskRemainingWork, TimeUnit.HOURS)); } } else { ResourceAssignment assignmentElement = resourceUIDToAssignmentsMap.get(resourceID); project.getAllResourceAssignments().remove(assignmentElement); // reset the ActualWork to 0 because there is no // costBean for this } } } } } } } /** * Adds timephasedData entries between two actual works (costBeans) on * different days to fill the gap with empty time spans, otherwise the gap * is considered as actually worked * * @param document * @param assignmentElement * @param timephasedDataElementTemplate * @param previousEffortDate * @param effortDate * @param emptyTimeSpan * @param effortDateToCalBookedForPersonMap * @param effortDateExhaustedMap * @param personBasedCalendarExceptionWorkingTimes * @param personBasedCalendarWeekDayWorkingTimes * @param calendarUIDBasedBaseCalendarExceptionWorkingTimes * @param calendarUIDBasedBaseCalendarWeekDayWorkingTimes */ private static void addEmptyIntervals(ProjectFile project, ResourceAssignment assignmentElement, Date previousEffortDate, Date effortDate, String emptyTimeSpan, Map<String, Calendar> effortDateToCalBookedForPersonMap, Map<String, Boolean> effortDateExhaustedMap, Map<String, List<String[]>> personBasedCalendarExceptionWorkingTimes, Map<Integer, List<String[]>> personBasedCalendarWeekDayWorkingTimes, Map<String, List<String[]>> calendarUIDBasedBaseCalendarExceptionWorkingTimes, Map<Integer, List<String[]>> calendarUIDBasedBaseCalendarWeekDayWorkingTimes, boolean allowOvertimeBookings) { if (previousEffortDate != null && effortDate != null && previousEffortDate.before(effortDate)) { // if not the first costBean (actualWork) executed by a person on a // workItem (previousEffortDate!=null) // and we have a gap (previousEffortDate.before(effortDate)) String previousEffortDateStr = MsProjectExchangeBL.formatDate(previousEffortDate); Calendar previousEffortDateCalendar = effortDateToCalBookedForPersonMap.get(previousEffortDateStr); if (previousEffortDateCalendar != null) { while (previousEffortDateCalendar.getTime().before(effortDate)) { // by first loop may be the same day (if at // previousEffortDateCalendar // there is still some empty period // previousEffortDate==nextEmptyEffortDate) Date nextEmptyEffortDate = getNextValidDate(previousEffortDateCalendar.getTime(), effortDateExhaustedMap, personBasedCalendarExceptionWorkingTimes, personBasedCalendarWeekDayWorkingTimes, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes, true); if (nextEmptyEffortDate != null && nextEmptyEffortDate.before(effortDate)) { // we are still before effortDate List<String[]> workingPeriods = getWorkingTimeForDay( personBasedCalendarExceptionWorkingTimes, personBasedCalendarWeekDayWorkingTimes, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes, effortDate); if (workingPeriods != null && workingPeriods.size() > 0) { // typically not null by the first loop: // add start of the timephasedData from the // remaining empty workPeriod for this day Calendar calNextEmptyEffortStart = effortDateToCalBookedForPersonMap .get(MsProjectExchangeBL.formatDate(nextEmptyEffortDate)); if (calNextEmptyEffortStart == null) { // add start of the timephasedData from the // start of the day calNextEmptyEffortStart = Calendar.getInstance(); String[] workingTimeStart = workingPeriods.get(0); String fromTime = workingTimeStart[0]; Map<String, Integer> timeUnitsMapFromTime = MsProjectExchangeBL .getTimeUnitsMapFromCalendarTime(fromTime); calNextEmptyEffortStart.setTime(nextEmptyEffortDate); calNextEmptyEffortStart.set(Calendar.HOUR_OF_DAY, timeUnitsMapFromTime.get(MSPROJECT_TIME_UNITS.HOUR)); calNextEmptyEffortStart.set(Calendar.MINUTE, timeUnitsMapFromTime.get(MSPROJECT_TIME_UNITS.MINUTE)); } // add end of the timephasedData till the end of the // day String[] workingTimeEnd = null; if (allowOvertimeBookings && workingPeriods.size() > 1) { // if allowOvertimeBookings then the last period // is the overtimePeriod, so take the last // regular time period workingTimeEnd = workingPeriods.get(workingPeriods.size() - 2); } else { // the last regular time period workingTimeEnd = workingPeriods.get(workingPeriods.size() - 1); } String toTime = workingTimeEnd[1]; Map<String, Integer> timeUnitsMapToTime = MsProjectExchangeBL .getTimeUnitsMapFromCalendarTime(toTime); Calendar calNextEmptyEffortEnd = Calendar.getInstance(); calNextEmptyEffortEnd.setTime(nextEmptyEffortDate); calNextEmptyEffortEnd.set(Calendar.HOUR_OF_DAY, timeUnitsMapToTime.get(MSPROJECT_TIME_UNITS.HOUR)); calNextEmptyEffortEnd.set(Calendar.MINUTE, timeUnitsMapToTime.get(MSPROJECT_TIME_UNITS.MINUTE)); addTimephasedDataElement(project, assignmentElement, calNextEmptyEffortStart.getTime(), calNextEmptyEffortEnd.getTime(), emptyTimeSpan); } } else { // arrived at effortDate, the gap was filled break; } previousEffortDateCalendar.setTime(nextEmptyEffortDate); previousEffortDateCalendar.add(Calendar.DATE, 1); } } } } /** * Get the * * @param effortDate * @param effortDateExhaustedMap * @param personBasedCalendarExceptionWorkingTimes * @param personBasedCalendarWeekDayWorkingTimes * @param calendarUIDBasedBaseCalendarExceptionWorkingTimes * @param calendarUIDBasedBaseCalendarWeekDayWorkingTimes * @return */ private static Date getNextValidDate(Date effortDate, Map<String, Boolean> effortDateExhaustedMap, Map<String, List<String[]>> personBasedCalendarExceptionWorkingTimes, Map<Integer, List<String[]>> personBasedCalendarWeekDayWorkingTimes, Map<String, List<String[]>> calendarUIDBasedBaseCalendarExceptionWorkingTimes, Map<Integer, List<String[]>> calendarUIDBasedBaseCalendarWeekDayWorkingTimes, boolean allowOverdayBookings) { Calendar calendarNextDay = Calendar.getInstance(); calendarNextDay.setTime(effortDate); int nonWorkingDays = 0; Boolean dateExhausted = null; do { String effortDateStr = MsProjectExchangeBL.formatDate(effortDate); dateExhausted = effortDateExhaustedMap.get(effortDateStr); if (dateExhausted == null || !dateExhausted.booleanValue()) { // try to get the workPeriods for effortDate only if date was // not exhausted previously List<String[]> workingPeriods = getWorkingTimeForDay(personBasedCalendarExceptionWorkingTimes, personBasedCalendarWeekDayWorkingTimes, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes, effortDate); if (workingPeriods == null) { // weekend or holiday (exception day) nonWorkingDays++; if (nonWorkingDays > 60) { // not a single working day found in the next two months // probably none of the days is WorkingTime in a week // no should never happen, but should be tested to avoid // infinitely searching a working day return null; } else { // weekend or holiday (exception day) is counted as // dateExhausted dateExhausted = Boolean.TRUE; effortDateExhaustedMap.put(effortDateStr, dateExhausted); } } else { // working periods found return effortDate; } } if (allowOverdayBookings) { // can we try the next day? calendarNextDay.add(Calendar.DATE, 1); effortDate = calendarNextDay.getTime(); } else { return null; } } while (dateExhausted != null && dateExhausted.booleanValue()); return effortDate; } private static void addTimephasedDataElement(ProjectFile project, ResourceAssignment assignmentElement, Date startDate, Date finishDate, String timespan) { ResourceAssignment timephasedDataElement = new ResourceAssignment(project); // TODO ASSIGNMENT_ACTUAL_WORK field missing // MsProjectExchangeDOMHelper.setChildTextByName(timephasedDataElement, // COMMON_ELEMENTS.Type, // Integer.valueOf(TIMEPHASEDDATA_TYPE.ASSIGNMENT_ACTUAL_WORK).toString(), // true); timephasedDataElement.setUniqueID(assignmentElement.getUniqueID()); timephasedDataElement.setStart(startDate); timephasedDataElement.setFinish(finishDate); assignmentElement.setUnits(Integer.valueOf(TIMEPHASEDDATA_UNIT.HOURS)); // TODO set Value // MsProjectExchangeDOMHelper.setChildTextByName(timephasedDataElement, // TIMEPHASEDDATA_ELEMENTS.Value, timespan, true); } /** * Set the project elements * * @param rootElement * @param name * @param existingTrackPlusWorkItems * @param projectBean * @param defaultTaskType * @param durationFormat * @param hoursPerWorkDay */ private static void setProjectElements(ProjectFile project, String name, String projectStartDate, String projectEndDate, TProjectBean projectBean, Integer defaultTaskType, Integer durationFormat, Double hoursPerWorkDay) { Integer oldMinutesPerDay = PropertiesHelper.getIntegerProperty(projectBean.getMoreProps(), TProjectBean.MOREPPROPS.MINUTES_PER_DAY); Integer oldMinutesPerWeek = PropertiesHelper.getIntegerProperty(projectBean.getMoreProps(), TProjectBean.MOREPPROPS.MINUTES_PER_WEEK); if (oldMinutesPerDay == null) { oldMinutesPerDay = Integer.valueOf(8 * 60); } if (oldMinutesPerWeek == null) { oldMinutesPerWeek = Integer.valueOf(5 * 8 * 60); } Integer newMinutesPerDay = oldMinutesPerDay; Integer newMinutesPerWeek = oldMinutesPerWeek; if (hoursPerWorkDay != null && Math.abs(hoursPerWorkDay.doubleValue()) > EPSILON) { newMinutesPerDay = new Double(hoursPerWorkDay * 60).intValue(); if (EqualUtils.notEqual(newMinutesPerDay, oldMinutesPerDay)) { // the hoursPerWorkDay has been changed since the last project // import: // change also the minutes per week according to daysPerWeek double daysPerWeek = oldMinutesPerWeek / oldMinutesPerDay; newMinutesPerWeek = new Double(newMinutesPerDay * daysPerWeek).intValue(); } } Date date = new Date(); DateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); try { project.getProjectHeader().setName(name); project.getProjectHeader().setSubject(projectBean.getDescription()); Calendar cal = Calendar.getInstance(); cal.setTime(date); project.getProjectHeader().setCreationDate(cal.getTime()); project.getProjectHeader().setLastSaved(cal.getTime()); project.getProjectHeader().setStatusDate(dateTimeFormat.parse(projectStartDate)); project.getProjectHeader().setCurrencyCode("EUR"); project.getProjectHeader().setCurrentDate(cal.getTime()); project.getProjectHeader().setStartDate(dateTimeFormat.parse(projectStartDate)); project.getProjectHeader().setFinishDate(dateTimeFormat.parse(projectEndDate)); } catch (Exception ex) { LOGGER.error(ExceptionUtils.getStackTrace(ex)); LOGGER.error("Date parse error: " + ex.getMessage()); } if (defaultTaskType != null) { project.getProjectHeader().setDefaultTaskType(TaskType.getInstance(defaultTaskType)); } ProjectAccountingTO projectAccountingTO = ProjectBL.getProjectAccountingTO(projectBean.getObjectID()); String curencySymbol = projectAccountingTO.getCurrencySymbol();// projectBean.getCurrencySymbol(); if (curencySymbol != null) { project.getProjectHeader().setCurrencySymbol(curencySymbol); } if (newMinutesPerDay != null) { project.getProjectHeader().setMinutesPerDay(newMinutesPerDay); } if (newMinutesPerWeek != null) { project.getProjectHeader().setMinutesPerWeek(newMinutesPerWeek); } Integer daysPerMonth = PropertiesHelper.getIntegerProperty(projectBean.getMoreProps(), TProjectBean.MOREPPROPS.DAYS_PER_MONTH); if (daysPerMonth != null) { project.getProjectHeader().setDaysPerMonth(daysPerMonth); } // TODO Duration format field missing // if (durationFormat!=null) { // MsProjectExchangeDOMHelper.setChildTextByName(rootElement, // PROJECT_ELEMENTS.DurationFormat, durationFormat.toString(), false); // project.getProjectHeader().setDurat // } Integer weekStartDay = project.getProjectHeader().getWeekStartDay().getValue(); if (weekStartDay == null) { project.getProjectHeader().setWeekStartDay(Day.getInstance(Calendar.getInstance().getFirstDayOfWeek())); } } /** * This method recursively generates all task elements * * @param parentWorkItemBean * @param outlineStructure * @param workItemBeansMap * @param parentChildrenList * @param tasksElements * @param workItemIDToTaskUIDMap * @param lastImportedMsProjectTasksMap * @param existingTrackPlusTasks * @param totalBudgetBeanMap * @param costBeanMap * @param remainingBudgetMap * @param taskElementPattern * @param hoursPerWorkday * @param defaultTaskType * @param durationFormat */ private static void listWorkItems(TWorkItemBean parentWorkItemBean, OutlineStructure outlineStructure, IDCounter idCounter, Map<Integer, TWorkItemBean> workItemBeansMap, Map<Integer, List<Integer>> parentChildrenList, ProjectFile project, Map<Integer, Integer> workItemIDToTaskUIDMap, Map<Integer, Task> lastImportedMsProjectTasksMap, Map<Integer, TMSProjectTaskBean> existingTrackPlusTasks, // SortedMap<Integer, TBudgetBean> totalBudgetBeanMap, Map<Integer, TComputedValuesBean> plannedTimesMap, Map<Integer, List<TCostBean>> costBeanMap, Map<Integer, TActualEstimatedBudgetBean> remainingBudgetMap, Double hoursPerWorkday, Integer defaultTaskType, Integer durationFormat, Map<Integer, Map<String, List<String[]>>> calendarUIDBasedBaseCalendarExceptionWorkingTimes, Map<Integer, Map<Integer, List<String[]>>> calendarUIDBasedBaseCalendarWeekDayWorkingTimes, LocalLookupContainer localLookupContainer) { Integer workItemID = parentWorkItemBean.getObjectID(); Integer taskUID = workItemIDToTaskUIDMap.get(workItemID); Task taskElement = null; TMSProjectTaskBean msProjectTaskBean = null; boolean isNew = false; if (taskUID == null) { // newly added task (not imported previously and TMSProjectTaskBean // not even exists) isNew = true; } else { // taskUID (TMSProjectTaskBean) already exists msProjectTaskBean = existingTrackPlusTasks.get(taskUID); taskElement = lastImportedMsProjectTasksMap.get(taskUID); if (taskElement == null) { // but task doesn't exist in the last import file: it is a new // task but also a TMSProjectTaskBean was saved // (typically will be the case when by saving a new Task also a // TMSProjectTaskBean will be saved. Not yet the case.) taskElement = project.addTask(); isNew = true; } } List<Integer> childrenIDs = parentChildrenList.get(workItemID); boolean hasChildren = childrenIDs != null && childrenIDs.size() > 0; // saving the msProjectTaskBean if (msProjectTaskBean == null) { msProjectTaskBean = new TMSProjectTaskBean(); msProjectTaskBean.setWorkitem(workItemID); taskUID = idCounter.getTaskUID(); idCounter.setTaskUID(++taskUID); msProjectTaskBean.setUniqueID(taskUID); msProjectTaskBean.setTaskType(defaultTaskType); msProjectTaskBean.setConstraintType(CONSTRAINT_TYPE.AS_SOON_AS_POSSIBLE); msProjectTaskBean.setOutlineNumber(outlineStructure.getFullOutlineNumber()); if (hasChildren) { msProjectTaskBean.setSummary(BooleanFields.TRUE_VALUE); } // since none of the msProjectTaskBean attributes are modifiable at // UI a save is needed only if the bean is new msProjectTaskDAO.save(msProjectTaskBean); workItemIDToTaskUIDMap.put(workItemID, taskUID); } TWorkItemBean workItemBean = workItemBeansMap.get(workItemID); TComputedValuesBean computedValueBean = plannedTimesMap.get(workItemID); TActualEstimatedBudgetBean actualEstimatedBudgetBean = remainingBudgetMap.get(workItemID); List<TCostBean> workItemCosts = costBeanMap.get(workItemBean.getObjectID()); int taskID = idCounter.getTaskID(); idCounter.setTaskID(++taskID); addUpdateTask(project, taskElement, workItemBean, msProjectTaskBean, taskID, outlineStructure, computedValueBean, actualEstimatedBudgetBean, hoursPerWorkday, workItemCosts, hasChildren, defaultTaskType, durationFormat, isNew, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes, localLookupContainer); boolean firstChild = false; OutlineStructure newTaskLevel = null; if (childrenIDs != null && parentChildrenList.size() > 0) { for (Iterator iterator = childrenIDs.iterator(); iterator.hasNext();) { Integer childID = (Integer) iterator.next(); TWorkItemBean childWorkItemBean = workItemBeansMap.get(childID); if (firstChild == false) { newTaskLevel = outlineStructure.newLevel(); } listWorkItems(childWorkItemBean, newTaskLevel.copy(), idCounter, workItemBeansMap, parentChildrenList, project, workItemIDToTaskUIDMap, lastImportedMsProjectTasksMap, existingTrackPlusTasks, plannedTimesMap, costBeanMap, remainingBudgetMap, hoursPerWorkday, defaultTaskType, durationFormat, calendarUIDBasedBaseCalendarExceptionWorkingTimes, calendarUIDBasedBaseCalendarWeekDayWorkingTimes, localLookupContainer); firstChild = true; } } } /** * Get the parent to children map: order the children by outlineNumber * (Integer comparison not string comparison) * * @param existingTrackPlusWorkItemsList * @param existingTrackPlusTasksMap * @param workItemIDToTaskUIDMap * @return */ private static Map<Integer, List<Integer>> getParentToChildrenMap( List<TWorkItemBean> existingTrackPlusWorkItemsList, Map<Integer, TMSProjectTaskBean> existingTrackPlusTasksMap, Map<Integer, Integer> workItemIDToTaskUIDMap) { Map<Integer, List<Integer>> parentToChildrenListMap = new HashMap<Integer, List<Integer>>(); for (Iterator iterator = existingTrackPlusWorkItemsList.iterator(); iterator.hasNext();) { TWorkItemBean workItemBean = (TWorkItemBean) iterator.next(); Integer parentID = workItemBean.getSuperiorworkitem(); Integer childID = workItemBean.getObjectID(); List<Integer> children; if (parentID != null) { children = parentToChildrenListMap.get(parentID); if (children == null) { children = new ArrayList<Integer>(); parentToChildrenListMap.put(parentID, children); } children.add(childID); } } Comparator<Integer> outlineNumberComparator = new MsProjectExporterComparator(existingTrackPlusTasksMap, workItemIDToTaskUIDMap); for (Iterator<List<Integer>> iterator = parentToChildrenListMap.values().iterator(); iterator.hasNext();) { List<Integer> children = iterator.next(); Collections.sort(children, outlineNumberComparator); } return parentToChildrenListMap; } /** * Get the maximal taskUID value * * @param UIDs * @return */ private static int getMaxInteger(Collection<Integer> UIDs) { int maxUID = 0; for (Iterator iterator = UIDs.iterator(); iterator.hasNext();) { Integer UID = (Integer) iterator.next(); if (UID.intValue() > maxUID) { maxUID = UID; } } return maxUID; } /** * Gets the cost list by persons and workItems * * @param costBeanList * @return */ private static Map<Integer, List<TCostBean>> getWorksByWorkItem(List<TCostBean> costBeanList) { Map<Integer, List<TCostBean>> worksByWorkItem = new HashMap<Integer, List<TCostBean>>(); for (Iterator iterator = costBeanList.iterator(); iterator.hasNext();) { TCostBean costBean = (TCostBean) iterator.next(); Integer workItemID = costBean.getWorkitem(); List<TCostBean> works = worksByWorkItem.get(workItemID); if (works == null) { works = new ArrayList<TCostBean>(); worksByWorkItem.put(workItemID, works); } works.add(costBean); } return worksByWorkItem; } /** * Gets the cost list by persons and workItems * * @param costBeanList * @return */ private static Set<Integer> getPersonsWithWork(List<TCostBean> costBeanList) { Set<Integer> personsWithWork = new HashSet<Integer>(); for (Iterator iterator = costBeanList.iterator(); iterator.hasNext();) { TCostBean costBean = (TCostBean) iterator.next(); Integer personID = costBean.getPerson(); if (personID != null) { personsWithWork.add(personID); } } return personsWithWork; } /** * Get the earliest date * * @param existingWorkItems * @return */ private static Date getProjectDate(Collection<TWorkItemBean> existingWorkItems, boolean start) { Date extremeDate = null; for (Iterator<TWorkItemBean> it = existingWorkItems.iterator(); it.hasNext();) { TWorkItemBean workItemBean = it.next(); Date currentDate; if (start) { currentDate = workItemBean.getStartDate(); } else { currentDate = workItemBean.getEndDate(); } if (currentDate != null) { if (extremeDate == null) { extremeDate = currentDate; } else { if (start) { if (currentDate.before(extremeDate)) { extremeDate = currentDate; } } else { if (currentDate.after(extremeDate)) { extremeDate = currentDate; } } } } } return extremeDate; } /** * Get the earliest date * * @param existingWorkItems * @return */ private static Date getProjectActualStartDate(Collection<TCostBean> costBeanList) { Date extremeDate = null; for (Iterator<TCostBean> it = costBeanList.iterator(); it.hasNext();) { TCostBean costBean = it.next(); Date currentDate = costBean.getEffortdate(); if (currentDate != null) { if (extremeDate == null || currentDate.before(extremeDate)) { extremeDate = currentDate; } } } return extremeDate; } /** * Add or update the task * * @param taskElement * @param workItemBean * @param msProjectTaskBean * @param taskID * @param outlineStructure * @param budgetBean * @param actualEstimatedBudgetBean * @param hoursPerWorkday * @param workItemCosts * @param hasChildren * @param defaultTaskType * @param durationFormat * @param isNew */ private static void addUpdateTask(ProjectFile project, Task taskElement, TWorkItemBean workItemBean, TMSProjectTaskBean msProjectTaskBean, int taskID, OutlineStructure outlineStructure, TComputedValuesBean computedValueBean, TActualEstimatedBudgetBean actualEstimatedBudgetBean, Double hoursPerWorkday, List<TCostBean> workItemCosts, boolean hasChildren, Integer defaultTaskType, Integer durationFormat, boolean isNew, Map<Integer, Map<String, List<String[]>>> calendarUIDBasedBaseCalendarExceptionWorkingTimes, Map<Integer, Map<Integer, List<String[]>>> calendarUIDBasedBaseCalendarWeekDayWorkingTimes, LocalLookupContainer localLookupContainer) { outlineStructure.incCurrentLevel(); SystemWBSRT systemWbs = new SystemWBSRT(); if (msProjectTaskBean.getUniqueID() != null) { if (taskElement == null) { taskElement = project.addTask(); } // taskElement.setUniqueID( msProjectTaskBean.getUniqueID()); taskElement.setConstraintType(ConstraintType.getInstance(msProjectTaskBean.getConstraintType())); taskElement.setConstraintDate(msProjectTaskBean.getConstraintDate()); taskElement.setDeadline(msProjectTaskBean.getDeadline()); } // taskElement.setID(Integer.valueOf(taskID)); taskElement.setName(workItemBean.getSynopsis()); taskElement.setUniqueID(workItemBean.getObjectID()); if (isNew && defaultTaskType != null) { taskElement.setType(TaskType.getInstance(defaultTaskType)); } taskElement.setCreateDate(workItemBean.getCreated()); String wbs = systemWbs.getShowValue(SystemFields.WBS, null, workItemBean.getWBSOnLevel(), workItemBean.getObjectID(), localLookupContainer, null); taskElement.setOutlineNumber(wbs); // taskElement.setID(workItemBean.getIDNumber()); StringTokenizer stringTokenizer = new StringTokenizer(wbs, "."); int numberOfLevels = stringTokenizer.countTokens() - 1; if (numberOfLevels == 0) { taskElement.setOutlineLevel(1); } else { taskElement.setOutlineLevel(numberOfLevels + 1); } /* * if(msProjectTaskBean.getOutlineNumber() != null) { * taskElement.setOutlineNumber(msProjectTaskBean.getOutlineNumber()); * }else { // * taskElement.setOutlineLevel(outlineStructure.getAbsoluteLevel()); } */ if (workItemBean.getStartDate() != null && workItemBean.getEndDate() != null) { taskElement.setStart(workItemBean.getTopDownStartDate()); taskElement.setActualStart(workItemBean.getStartDate()); taskElement.setActualFinish(workItemBean.getEndDate()); /* * taskElement.setEarlyStart(workItemBean.getStartDate()); * taskElement.setLateStart(workItemBean.getStartDate()); * taskElement.setEarlyFinish(workItemBean.getEndDate()); * taskElement.setLateFinish(workItemBean.getEndDate()); */ taskElement.setFinish(workItemBean.getTopDownEndDate()); SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy"); if (formatter.format(workItemBean.getStartDate()) .compareTo(formatter.format(workItemBean.getEndDate())) == 0) { taskElement.setDuration(Duration.getInstance(1, TimeUnit.DAYS)); taskElement.setManualDuration(Duration.getInstance(1, TimeUnit.DAYS)); } else { int days = Days.daysBetween(new DateTime(workItemBean.getStartDate()), new DateTime(workItemBean.getEndDate())).getDays(); taskElement.setDuration(Duration.getInstance(days, TimeUnit.DAYS)); } } else if (workItemBean.getStartDate() != null && workItemBean.getEndDate() == null) { taskElement.setStart(workItemBean.getStartDate()); taskElement.setMilestone(true); } taskElement.setPercentageComplete(0); taskElement.setPercentageWorkComplete(0); taskElement.setNotes(workItemBean.getDescription()); // from budgetBean we need only the time unit because the // exported budget is the sum of actual work and remaining work if (computedValueBean != null) { // prepare this transformed values for hasChanged because the // MSProject work seems to be in hours TODO? /* * Double transformedHours = * AccountingBL.transformToTimeUnits(budgetBean.getEstimatedHours(), * hoursPerWorkday, budgetBean.getTimenit(), * AccountingBL.TIMEUNITS.HOURS); if (transformedHours!=null) { * plannedWorkHours = transformedHours.doubleValue(); } */ if (isNew && durationFormat == null) { // for new projects (without previous import the project level // durationFormat is null) if (AccountingBL.TIMEUNITS.HOURS.equals(computedValueBean.getMeasurementUnit())) { durationFormat = LAG_FORMAT.h; } else { durationFormat = LAG_FORMAT.d; } } } // TODO should we set the duration? // Element durationElement = // MsProjectExchangeDOMHelper.getChildByName(taskElement, // TASK_ELEMENTS.Duration); if (isNew && durationFormat != null) { // TODO setDuartionFormat method missing // MsProjectExchangeDOMHelper.setChildTextByName(taskElement, // TASK_ELEMENTS.DurationFormat, durationFormat.toString(), false); // taskElement.setDuration(Duration.getInstance(arg0, // durationFormat)); } int summary; if (hasChildren) { summary = TASK_SUMMARY_TYPE.SUMMARY; } else { summary = TASK_SUMMARY_TYPE.NOT_SUMMARY; } taskElement.setSummary(summary > 0 ? true : false); double actualHours = getSumOfActualWorks(workItemCosts); try { // taskElement.setActualDuration(Duration.getInstance(actualHours, // TimeUnit.HOURS)); } catch (Exception ex) { LOGGER.error("Date parse error: " + ex.getMessage()); } double remainingWorkHours = 0.0; if (actualEstimatedBudgetBean != null) { Double transformedHours = AccountingBL.transformToTimeUnits( actualEstimatedBudgetBean.getEstimatedHours(), hoursPerWorkday, actualEstimatedBudgetBean.getTimeUnit(), AccountingBL.TIMEUNITS.HOURS); if (transformedHours != null) { remainingWorkHours = transformedHours.doubleValue(); } } else { if (isNew && Math.abs(actualHours) < EPSILON && workItemBean.getStartDate() != null && workItemBean.getEndDate() != null) { // "simulate" a remaining value for issue in order to appear the // startDate and endDate in MSProject // as it was set in Genji although to work was set in Genji. Date dateFrom = workItemBean.getStartDate(); Calendar calendar = Calendar.getInstance(); calendar.setTime(dateFrom); CalendarUtil.clearTime(calendar); List<String[]> workingPeriods = null; while (calendar.getTime().before(workItemBean.getEndDate()) || calendar.getTime().getTime() == workItemBean.getEndDate().getTime()) { try { workingPeriods = getWorkingTimeForDay(null, null, calendarUIDBasedBaseCalendarExceptionWorkingTimes.values().iterator().next(), calendarUIDBasedBaseCalendarWeekDayWorkingTimes.values().iterator().next(), calendar.getTime()); } catch (Exception ex) { LOGGER.debug("Element missing: " + ex.getMessage()); } if (workingPeriods != null) { for (Iterator itrWorkPeriod = workingPeriods.iterator(); itrWorkPeriod.hasNext();) { // try each workingTime interval to see where the // work can be added String[] workingTime = (String[]) itrWorkPeriod.next(); String fromTime = workingTime[0]; String toTime = workingTime[1]; Map<String, Integer> timeUnitsMapFromTime = MsProjectExchangeBL .getTimeUnitsMapFromCalendarTime(fromTime); Map<String, Integer> timeUnitsMapToTime = MsProjectExchangeBL .getTimeUnitsMapFromCalendarTime(toTime); double hourDiff = MsProjectExchangeBL.getHoursDiff(timeUnitsMapFromTime, timeUnitsMapToTime); remainingWorkHours += hourDiff; } } calendar.add(Calendar.DATE, 1); } } } taskElement.setRemainingWork(Duration.getInstance(remainingWorkHours, TimeUnit.HOURS)); taskElement.setRemainingDuration(Duration.getInstance(remainingWorkHours, TimeUnit.HOURS)); // the planned work is forced to be the sum of actual work and actual // hours // (instead of the plannedWorkHours) because otherwise MSProject result // is not consistent taskElement.setWork(Duration.getInstance(remainingWorkHours + actualHours, TimeUnit.HOURS)); int days = Days .daysBetween(new DateTime(workItemBean.getStartDate()), new DateTime(workItemBean.getEndDate())) .getDays(); taskElement.setDuration(Duration.getInstance(days, TimeUnit.DAYS)); taskElement.setActualWork(null); // not really needed to update because in track+ UI they are not // modifiable: for new tasks // the msProjectTaskBean is new for existing tasks they can be taken // from the last imported file if (isNew && Math.abs(actualHours) < EPSILON) { if (workItemBean.getStartDate() != null) { msProjectTaskBean.setConstraintType(CONSTRAINT_TYPE.START_NO_EARLIER_THAN); msProjectTaskBean.setConstraintDate(workItemBean.getStartDate()); } else { if (workItemBean.getEndDate() != null) { msProjectTaskBean.setConstraintType(CONSTRAINT_TYPE.FINISH_NO_LATER_THAN); msProjectTaskBean.setConstraintDate(workItemBean.getEndDate()); } } } } /** * This method generates resource and calendar elements * * @param persons * @param document * @param resourceElementPattern * @param calendarElementPattern * @param resourceToPersonMappings */ /* * private static void exportResourcesAndCalendars(List<TPersonBean> * persons, ProjectFile project, Map<Integer, List<Integer>> * personIDToResourceUIDMap, int maxResourceUID) { //get the calendars from * the last import // Element calendarsElement = * MsProjectExchangeDOMHelper.getChildByName(projectElement, * PROJECT_ELEMENTS.Calendars); ProjectCalendar calendarsElement = * project.getCalendar(); * * if (calendarsElement==null) { ProjectCalendar newCalendar = new * ProjectCalendar(project); // calendarsElement = * document.createElement(PROJECT_ELEMENTS.Calendars); // * projectElement.appendChild(calendarsElement); } else { // * List<ProjectCalendar> existingCalendars = * MsProjectExchangeDOMHelper.getElementList( // projectElement, * PROJECT_ELEMENTS.Calendars, PROJECT_SUBELEMENTS.Calendar); * List<ProjectCalendar> existingCalendars = project.getCalendars(); * * //TODO Unused with MPXJ. } Map<Integer, ProjectCalendar> * lastImportedCalendarElementMap = getUIDBasedCalendars(project); * //MsProjectExchangeDOMHelper * .getSubelementBasedElementMap(lastImportedCalendarElementsList, * COMMON_ELEMENTS.UID); int maxCalendarUID = * getMaxInteger(lastImportedCalendarElementMap.keySet()); //get the * resources from the last import * * //TODO Unused with MPXJ ? * * Map<Integer, Resource> UIDBasedResourceElementsMap = * getUIDBasedResources(project); Resource resourceElement = null; * resourceElement = UIDBasedResourceElementsMap.get(Integer.valueOf(0)); if * (resourceElement==null) { resourceElement = project.addResource(); * resourceElement = addResourceElement(project, resourceElement,"", 0, 1); * } * * * int resourceID = 0; for(TPersonBean personBean: persons) { * resourceElement = null; String personName = * personBean.getSimpleFullName(); Integer personID = * personBean.getObjectID(); List<Integer> resourceUIDList = * personIDToResourceUIDMap.get(personID); if (resourceUIDList!=null && * resourceUIDList.size()>0) { //resource already existed by the last import * //get the first one for this person resourceElement = * UIDBasedResourceElementsMap.get(resourceUIDList.get(0)); } if * (resourceElement==null) { //resource is new resourceElement = * project.addResource(); resourceElement = addResourceElement(project, * resourceElement, personName, ++maxResourceUID, ++maxCalendarUID); //add * the new person to resource mapping, needed later by assignments * List<Integer> resourcesForPerson = * personIDToResourceUIDMap.get(personID); if (resourcesForPerson==null) { * resourcesForPerson = new ArrayList<Integer>(); * personIDToResourceUIDMap.put(personID, resourcesForPerson); } * resourcesForPerson.add(maxResourceUID); * UIDBasedResourceElementsMap.put(maxResourceUID, resourceElement); //add * also a calendar for the resource addCalendarElement(project, * calendarsElement, personName, maxCalendarUID); } //elements always * overwritten independently whether it existed already in a previous import * resourceElement.setUniqueID(++resourceID); * resourceElement.setNtAccount(personBean.getLoginName()); * resourceElement.setEmailAddress(personBean.getEmail()); * * } * * } */ /** * Creates and adds a resource element * * @param document * @param resourcesElement * @param resourceElementPattern * @param personName * @param resourceUID * @param calendarUID * @return */ public static Resource addResourceElement(ProjectFile project, Resource resourcesElement, String personName, int resourceUID, int calendarUID) { resourcesElement.setUniqueID(resourceUID); resourcesElement.setName(personName); // TODO for resourceElement setCalendarUID setter missing. // MsProjectExchangeDOMHelper.setChildTextByName(resourceElement, // RESOURCE_ELEMENTS.CalendarUID, // Integer.valueOf(calendarUID).toString(), true); return resourcesElement; } /** * Creates and adds a calendar element * * @param document * @param calendarsElement * @param calendarElementPattern * @param personBean * @param calendarUID */ public static void addCalendarElement(ProjectFile project, ProjectCalendar calendarsElement, String personName, int calendarUID) { if (calendarsElement == null) { calendarsElement = new ProjectCalendar(project); } calendarsElement.setUniqueID(calendarUID); calendarsElement.setName(personName); } /** * Gets the sum of the works from the list * * @param costBeanList * @return */ private static double getSumOfActualWorks(List<TCostBean> costBeanList) { double actualHours = 0.0; if (costBeanList != null) { Iterator<TCostBean> iterator = costBeanList.iterator(); while (iterator.hasNext()) { TCostBean costBean = iterator.next(); Double hours = costBean.getHours(); if (hours != null) { actualHours += hours.doubleValue(); } } } return actualHours; } /** * Gets the sum of the works from the list * * @param costBeanList * @return */ private static Map<Integer, List<TCostBean>> getCostsByPerson(List<TCostBean> costBeanList) { Map<Integer, List<TCostBean>> costsByPerson = new HashMap<Integer, List<TCostBean>>(); if (costBeanList != null) { Iterator<TCostBean> iterator = costBeanList.iterator(); while (iterator.hasNext()) { TCostBean costBean = iterator.next(); Integer person = costBean.getPerson(); if (person != null) { List<TCostBean> costs = costsByPerson.get(person); if (costs == null) { costs = new ArrayList<TCostBean>(); costsByPerson.put(person, costs); } costs.add(costBean); } } } return costsByPerson; } /** * This method convert Genji lag format to MPXJ lag format * * @param lag * @return */ public static Integer convertTrackLagToMsProject(Integer lag) { switch (lag) { // Day case MsProjectExchangeDataStoreBean.LAG_FORMAT.d: return 2; // elapsed day case MsProjectExchangeDataStoreBean.LAG_FORMAT.ed: return 9; // elapsed hours case MsProjectExchangeDataStoreBean.LAG_FORMAT.eh: return 8; // elapsed minutes case MsProjectExchangeDataStoreBean.LAG_FORMAT.em: return 7; // elapsed months case MsProjectExchangeDataStoreBean.LAG_FORMAT.emo: return 11; // elapsed percent case MsProjectExchangeDataStoreBean.LAG_FORMAT.ePERCENT: return 13; // elapsed weeks case MsProjectExchangeDataStoreBean.LAG_FORMAT.ew: return 10; // hours case MsProjectExchangeDataStoreBean.LAG_FORMAT.h: return 1; // minutes case MsProjectExchangeDataStoreBean.LAG_FORMAT.m: return 0; // months case MsProjectExchangeDataStoreBean.LAG_FORMAT.mo: return 4; // percent case MsProjectExchangeDataStoreBean.LAG_FORMAT.PERCENT: return 5; // weeks case MsProjectExchangeDataStoreBean.LAG_FORMAT.w: return 3; default: return -1; } } }