diff --git a/mkdocs/docs/field-guide.md b/mkdocs/docs/field-guide.md
index ad4bda1195..b512d37bc8 100644
--- a/mkdocs/docs/field-guide.md
+++ b/mkdocs/docs/field-guide.md
@@ -190,6 +190,7 @@ ACWP| | | | | | | |✓| | |✓| | | | | | | | | | | | |
Active|✓|✓|✓|✓|✓|✓|✓| |✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓
Activity Codes|✓|✓|✓|✓|✓|✓|✓| |✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓
Activity ID|✓| |✓| | | | | | | | |✓|✓| |✓|✓|✓| | |✓| |✓|✓|
+Activity Percent Complete|✓|✓| |✓|✓|✓| | |✓|✓|✓|✓|✓|✓|✓|✓|✓| | |✓| |✓|✓|
Activity Status| | | | | | | | | | | | | | |✓|✓|✓| | | | | | |
Activity Type|✓| |✓| | | | | | | | | |✓| |✓|✓|✓| | | | | | |
Actual Cost| | | | | | | |✓|✓|✓|✓| | | |✓|✓|✓| | |✓| | | |
diff --git a/mkdocs/docs/mpp-field-guide.md b/mkdocs/docs/mpp-field-guide.md
index d09d76427e..e4321bf827 100644
--- a/mkdocs/docs/mpp-field-guide.md
+++ b/mkdocs/docs/mpp-field-guide.md
@@ -149,6 +149,7 @@ Field|MPP8|MPP9|MPP12|MPP14
% Work Complete|✓|✓|✓|✓
Active|✓|✓|✓|✓
Activity Codes|✓|✓|✓|✓
+Activity Percent Complete|✓|✓|✓|✓
Actual Cost|✓|✓|✓|✓
Actual Duration|✓|✓|✓|✓
Actual Duration Units| |✓|✓|✓
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index cdacb13bb2..ac9e3f71fd 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -7,6 +7,7 @@
Added the `Task.getBaselineTask()` methods. For applications where a separate baseline schedule is present or a baseline has been manually added to the `ProjectFile` instance, these methods will allow you to access the underlying baseline task instance from the current task instance.
+ Added the Activity Percent Complete attribute to the `Task` class. The value of this attribute will be the Duration, Physical or Units percent complete value, based on the Percent Complete Type setting. This attribute is provided as a convenience to match the Activity Percent Complete type value shown in P6.
Fix CVE-2024-49771: Potential Path Traversal Vulnerability
diff --git a/src/main/java/net/sf/mpxj/LocaleData.java b/src/main/java/net/sf/mpxj/LocaleData.java
index cedebfae44..6b6a86c0c8 100644
--- a/src/main/java/net/sf/mpxj/LocaleData.java
+++ b/src/main/java/net/sf/mpxj/LocaleData.java
@@ -1058,6 +1058,7 @@ public static final String[] getStringArray(Locale locale, String key)
TASK_COLUMNS_ARRAY[TaskField.SHOW_START_TEXT.getValue()] = "Show Start Text";
TASK_COLUMNS_ARRAY[TaskField.SHOW_FINISH_TEXT.getValue()] = "Show Finish Text";
TASK_COLUMNS_ARRAY[TaskField.SHOW_DURATION_TEXT.getValue()] = "Show Duration Text";
+ TASK_COLUMNS_ARRAY[TaskField.ACTIVITY_PERCENT_COMPLETE.getValue()] = "Activity Percent Complete";
RESOURCE_COLUMNS_ARRAY[ResourceField.ID.getValue()] = "ID";
RESOURCE_COLUMNS_ARRAY[ResourceField.NAME.getValue()] = "Name";
diff --git a/src/main/java/net/sf/mpxj/Task.java b/src/main/java/net/sf/mpxj/Task.java
index 8f77225d51..1934deca08 100644
--- a/src/main/java/net/sf/mpxj/Task.java
+++ b/src/main/java/net/sf/mpxj/Task.java
@@ -5484,6 +5484,17 @@ public boolean getShowDurationText()
return (BooleanHelper.getBoolean((Boolean) get(TaskField.SHOW_DURATION_TEXT)));
}
+ /**
+ * This accessor method returns the percent complete value for this task
+ * as defined by the Percent Complete Type attribute.
+ *
+ * @return activity percent complete
+ */
+ public Number getActivityPercentComplete()
+ {
+ return (Number) get(TaskField.ACTIVITY_PERCENT_COMPLETE);
+ }
+
/**
* Retrieve the effective calendar for this task. If the task does not have
* a specific calendar associated with it, fall back to using the default calendar
@@ -5879,6 +5890,33 @@ private List calculateSuccessors()
return getParentFile().getRelations().getSuccessors(this);
}
+ private Number calculateActivityPercentComplete()
+ {
+ PercentCompleteType type = getPercentCompleteType();
+ if (type == null)
+ {
+ return getPercentageComplete();
+ }
+
+ switch(type)
+ {
+ case UNITS:
+ {
+ return getPercentageWorkComplete();
+ }
+
+ case PHYSICAL:
+ {
+ return getPhysicalPercentComplete();
+ }
+
+ default:
+ {
+ return getPercentageComplete();
+ }
+ }
+ }
+
/**
* Supply a default value for constraint type.
*
@@ -5982,6 +6020,7 @@ private Boolean defaultExpanded()
CALCULATED_FIELD_MAP.put(TaskField.EXTERNAL_PROJECT, Task::calculateExternalProject);
CALCULATED_FIELD_MAP.put(TaskField.PREDECESSORS, Task::calculatePredecessors);
CALCULATED_FIELD_MAP.put(TaskField.SUCCESSORS, Task::calculateSuccessors);
+ CALCULATED_FIELD_MAP.put(TaskField.ACTIVITY_PERCENT_COMPLETE, Task::calculateActivityPercentComplete);
CALCULATED_FIELD_MAP.put(TaskField.CONSTRAINT_TYPE, Task::defaultConstraintType);
CALCULATED_FIELD_MAP.put(TaskField.ACTIVE, Task::defaultActive);
CALCULATED_FIELD_MAP.put(TaskField.TYPE, Task::defaultType);
@@ -6010,5 +6049,6 @@ private Boolean defaultExpanded()
dependencies.calculatedField(TaskField.CRITICAL).dependsOn(TaskField.TOTAL_SLACK, TaskField.ACTUAL_FINISH);
dependencies.calculatedField(TaskField.COMPLETE_THROUGH).dependsOn(TaskField.DURATION, TaskField.ACTUAL_START, TaskField.PERCENT_COMPLETE);
dependencies.calculatedField(TaskField.EXTERNAL_PROJECT).dependsOn(TaskField.SUBPROJECT_FILE, TaskField.EXTERNAL_TASK);
+ dependencies.calculatedField(TaskField.ACTIVITY_PERCENT_COMPLETE).dependsOn(TaskField.PERCENT_COMPLETE_TYPE, TaskField.PERCENT_COMPLETE, TaskField.PERCENT_WORK_COMPLETE, TaskField.PHYSICAL_PERCENT_COMPLETE);
}
}
diff --git a/src/main/java/net/sf/mpxj/TaskField.java b/src/main/java/net/sf/mpxj/TaskField.java
index 25adba0c15..b43d51f246 100644
--- a/src/main/java/net/sf/mpxj/TaskField.java
+++ b/src/main/java/net/sf/mpxj/TaskField.java
@@ -1037,7 +1037,8 @@ public enum TaskField implements FieldType
REMAINING_WORK_NONLABOR(DataType.DURATION),
SHOW_START_TEXT(DataType.BOOLEAN),
SHOW_FINISH_TEXT(DataType.BOOLEAN),
- SHOW_DURATION_TEXT(DataType.BOOLEAN);
+ SHOW_DURATION_TEXT(DataType.BOOLEAN),
+ ACTIVITY_PERCENT_COMPLETE(DataType.PERCENTAGE);
/**
* Constructor.