diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0cf79d6b97..202870f011 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -7,6 +7,7 @@ When writing PMXML files, improve handling of P6 schedules where activity code sequence numbers are missing. + Added an *experimental* feature to `MSPDIWriter` to allow the writer to generate timephased data when none is present. Disabled by default, call the `setGenerateMissingTimephasedData` and pass `true` to enable. Update the MPXJ ruby gem to allow access to calendar data. diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java index 90675bc6e4..dd2e3373d3 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java @@ -193,6 +193,30 @@ public boolean getWriteTimephasedData() return m_writeTimephasedData; } + /** + * Pass true to this method to enable an experimental feature where + * timephased data is generated for tasks with no timephased data present. + * NOTE: this feature is disabled by default. + * + * @param value true to enable timephased data generation + */ + public void setGenerateMissingTimephasedData(boolean value) + { + m_generateMissingTimephasedData = value; + } + + /** + * Returns true if the experimental feature to generate timephased data + * for tasks with no timephased data present is enabled. + * NOTE: this feature is disabled by default. + * + * @return true if feature enabled + */ + public boolean getGenerateMissingTimephasedData() + { + return m_generateMissingTimephasedData; + } + /** * Set the save version to use when generating an MSPDI file. * @@ -1961,6 +1985,14 @@ private void writeAssignments(Project project) double actualWork = (durationValue * percentComplete) / 100; double remainingWork = durationValue - actualWork; + if (m_generateMissingTimephasedData) + { + // I'm being conservative here... I'm sure there is no issue with including + // this in the MSPDI file, but asI've only added this for the "generate + // missing timephased data" feature, I'll keep it optional for now. + dummy.setActualStart(task.getActualStart()); + } + dummy.setResourceUniqueID(MicrosoftProjectConstants.ASSIGNMENT_NULL_RESOURCE_ID); dummy.setWork(duration); dummy.setActualWork(Duration.getInstance(actualWork, durationUnits)); @@ -2193,7 +2225,12 @@ private void writeAssignmentExtendedAttribute(List planned = mpx.getTimephasedWork(); List completeOvertime = mpx.getTimephasedActualOvertimeWork(); + if ((planned == null || planned.isEmpty()) && m_generateMissingTimephasedData) + { + planned = generateTimephasedPlannedWork(mpx); + } + + if ((complete == null || complete.isEmpty()) && m_generateMissingTimephasedData) + { + complete = generateTimephasedCompleteWork(mpx); + } + complete = splitCompleteWork(calendar, planned, complete); planned = splitPlannedWork(calendar, planned, complete); completeOvertime = splitDays(calendar, completeOvertime, null, null); @@ -2225,6 +2272,60 @@ private void writeAssignmentTimephasedData(ResourceAssignment mpx, Project.Assig } } + private List generateTimephasedPlannedWork(ResourceAssignment assignment) + { + if (assignment.getActualFinish() != null) + { + return null; + } + + LocalDateTime start; + ProjectCalendar calendar = assignment.getEffectiveCalendar(); + + if (assignment.getActualStart() == null) + { + start = assignment.getStart(); + } + else + { + start = calendar.getNextWorkStart(calendar.getDate(assignment.getActualStart(), assignment.getActualWork())); + } + + TimephasedWork work = new TimephasedWork(); + work.setStart(start); + work.setFinish(assignment.getFinish()); + work.setTotalAmount(assignment.getRemainingWork()); + work.setAmountPerDay(Duration.getInstance(calendar.getMinutesPerDay(), TimeUnit.MINUTES)); + return Collections.singletonList(work); + } + + private List generateTimephasedCompleteWork(ResourceAssignment assignment) + { + if (assignment.getActualStart() == null) + { + return null; + } + + LocalDateTime finish; + ProjectCalendar calendar = assignment.getEffectiveCalendar(); + + if (assignment.getActualFinish() == null) + { + finish = calendar.getDate(assignment.getActualStart(), assignment.getActualWork()); + } + else + { + finish = assignment.getActualFinish(); + } + + TimephasedWork work = new TimephasedWork(); + work.setStart(assignment.getActualStart()); + work.setFinish(finish); + work.setTotalAmount(assignment.getActualWork()); + work.setAmountPerDay(Duration.getInstance(calendar.getMinutesPerDay(), TimeUnit.MINUTES)); + return Collections.singletonList(work); + } + private List splitCompleteWork(ProjectCalendar calendar, List planned, List complete) { if (!m_splitTimephasedAsDays || complete == null) @@ -2716,6 +2817,8 @@ private String nullIfEmpty(String value) private boolean m_writeTimephasedData; + private boolean m_generateMissingTimephasedData; + private boolean m_sourceIsPrimavera; private SaveVersion m_saveVersion = SaveVersion.Project2016;