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;