diff --git a/mkdocs/docs/field-guide.md b/mkdocs/docs/field-guide.md index 6e27531c2d..b5ceeb110d 100644 --- a/mkdocs/docs/field-guide.md +++ b/mkdocs/docs/field-guide.md @@ -797,6 +797,28 @@ Baseline Budget Cost| | | | | | | |✓| | | | | | | | | | | | Baseline Budget Work| | | | | | | |✓| | | | | | | | | | | | | | |  Baseline Cost| | | | | | | |✓|✓|✓| | | | | | | | | | | | |  Baseline Work| | | | | | |✓|✓|✓|✓| | | | | | | | | | | | |  +Timephased Baseline1 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline1 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline2 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline2 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline3 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline3 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline4 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline4 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline5 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline5 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline6 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline6 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline7 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline7 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline8 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline8 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline9 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline9 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline10 Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline10 Work| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline Cost| | | | | | | |✓| | | | | | | | | | | | | | |  +Timephased Baseline Work| | | | | | | |✓| | | | | | | | | | | | | | |  ### Custom Fields Field|Asta (PP)|ConceptDraw PROJECT (CDP)|FastTrack (FTS)|GanttDesigner (GNT)|GanttProject (GAN)|Merlin (SQLITE)|Microsoft (MPD)|Microsoft (MPP)|Microsoft (MPX)|Microsoft (MSPDI)|P3 (BTRIEVE)|Phoenix (PPX)|Planner (XML)|Primavera (PMXML)|Primavera (SQLITE)|Primavera (XER)|Project Commander (PC)|ProjectLibre (POD)|SDEF (SDEF)|Sage (SCHEDULE_GRID)|SureTrak (STW)|Synchro (SP)|TurboProject (PEP) diff --git a/mkdocs/docs/mpp-field-guide.md b/mkdocs/docs/mpp-field-guide.md index 7e7e755f6b..75fc7c86b4 100644 --- a/mkdocs/docs/mpp-field-guide.md +++ b/mkdocs/docs/mpp-field-guide.md @@ -702,6 +702,28 @@ Baseline Budget Cost| | | |✓ Baseline Budget Work| | | |✓ Baseline Cost| |✓|✓|✓ Baseline Work|✓|✓|✓|✓ +Timephased Baseline1 Cost| |✓|✓|✓ +Timephased Baseline1 Work| |✓|✓|✓ +Timephased Baseline2 Cost| |✓| |✓ +Timephased Baseline2 Work| |✓| |✓ +Timephased Baseline3 Cost| |✓| |✓ +Timephased Baseline3 Work| |✓| |✓ +Timephased Baseline4 Cost| |✓| |✓ +Timephased Baseline4 Work| |✓| |✓ +Timephased Baseline5 Cost| |✓| |✓ +Timephased Baseline5 Work| |✓| |✓ +Timephased Baseline6 Cost| |✓| |✓ +Timephased Baseline6 Work| |✓| |✓ +Timephased Baseline7 Cost| |✓| |✓ +Timephased Baseline7 Work| |✓| |✓ +Timephased Baseline8 Cost| |✓| |✓ +Timephased Baseline8 Work| |✓| |✓ +Timephased Baseline9 Cost| |✓| |✓ +Timephased Baseline9 Work| |✓| |✓ +Timephased Baseline10 Cost| | |✓|✓ +Timephased Baseline10 Work| | |✓|✓ +Timephased Baseline Cost| |✓|✓|✓ +Timephased Baseline Work| |✓|✓|✓ ### Custom Fields Field|MPP8|MPP9|MPP12|MPP14 diff --git a/src/main/java/net/sf/mpxj/LocaleData.java b/src/main/java/net/sf/mpxj/LocaleData.java index eec9d0f6d2..74c65a97ad 100644 --- a/src/main/java/net/sf/mpxj/LocaleData.java +++ b/src/main/java/net/sf/mpxj/LocaleData.java @@ -1605,6 +1605,28 @@ public static final String[] getStringArray(Locale locale, String key) RESOURCE_COLUMNS_ARRAY[ResourceField.LOCATION_UNIQUE_ID.getValue()] = "Location Unique ID"; RESOURCE_COLUMNS_ARRAY[ResourceField.UNIT_OF_MEASURE_UNIQUE_ID.getValue()] = "Unit of Measure Unique ID"; RESOURCE_COLUMNS_ARRAY[ResourceField.DEFAULT_UNITS.getValue()] = "Default Units"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE_WORK.getValue()] = "Timephased Baseline Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE1_WORK.getValue()] = "Timephased Baseline1 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE2_WORK.getValue()] = "Timephased Baseline2 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE3_WORK.getValue()] = "Timephased Baseline3 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE4_WORK.getValue()] = "Timephased Baseline4 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE5_WORK.getValue()] = "Timephased Baseline5 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE6_WORK.getValue()] = "Timephased Baseline6 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE7_WORK.getValue()] = "Timephased Baseline7 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE8_WORK.getValue()] = "Timephased Baseline8 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE9_WORK.getValue()] = "Timephased Baseline9 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE10_WORK.getValue()] = "Timephased Baseline10 Work"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE_COST.getValue()] = "Timephased Baseline Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE1_COST.getValue()] = "Timephased Baseline1 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE2_COST.getValue()] = "Timephased Baseline2 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE3_COST.getValue()] = "Timephased Baseline3 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE4_COST.getValue()] = "Timephased Baseline4 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE5_COST.getValue()] = "Timephased Baseline5 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE6_COST.getValue()] = "Timephased Baseline6 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE7_COST.getValue()] = "Timephased Baseline7 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE8_COST.getValue()] = "Timephased Baseline8 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE9_COST.getValue()] = "Timephased Baseline9 Cost"; + RESOURCE_COLUMNS_ARRAY[ResourceField.TIMEPHASED_BASELINE10_COST.getValue()] = "Timephased Baseline10 Cost"; ASSIGNMENT_COLUMNS_ARRAY[AssignmentField.START.getValue()] = "Start"; ASSIGNMENT_COLUMNS_ARRAY[AssignmentField.ASSIGNMENT_UNITS.getValue()] = "Assignment Units"; diff --git a/src/main/java/net/sf/mpxj/Resource.java b/src/main/java/net/sf/mpxj/Resource.java index d4abb8cb0c..2bc2f4a264 100644 --- a/src/main/java/net/sf/mpxj/Resource.java +++ b/src/main/java/net/sf/mpxj/Resource.java @@ -45,7 +45,7 @@ /** * This class represents a resource used in a project. */ -public final class Resource extends AbstractFieldContainer implements Comparable, ProjectEntityWithID, ChildResourceContainer +public final class Resource extends AbstractFieldContainer implements Comparable, ProjectEntityWithID, ChildResourceContainer, TimePeriodEntity { /** * Default constructor. @@ -2625,6 +2625,54 @@ public void setUnitOfMeasure(UnitOfMeasure unitOfMeasure) setUnitOfMeasureUniqueID(unitOfMeasure == null ? null : unitOfMeasure.getUniqueID()); } + /** + * Set timephased baseline work. Note that index 0 represents "Baseline", + * index 1 represents "Baseline1" and so on. + * + * @param index baseline index + * @param data timephased data + */ + public void setTimephasedBaselineWork(int index, TimephasedWorkContainer data) + { + m_timephasedBaselineWork[index] = data; + } + + /** + * Set timephased baseline cost. Note that index 0 represents "Baseline", + * index 1 represents "Baseline1" and so on. + * + * @param index baseline index + * @param data timephased data + */ + public void setTimephasedBaselineCost(int index, TimephasedCostContainer data) + { + m_timephasedBaselineCost[index] = data; + } + + /** + * Retrieve timephased baseline work. Note that index 0 represents "Baseline", + * index 1 represents "Baseline1" and so on. + * + * @param index baseline index + * @return timephased work, or null if no baseline is present + */ + public List getTimephasedBaselineWork(int index) + { + return m_timephasedBaselineWork[index] == null ? null : m_timephasedBaselineWork[index].getData(); + } + + /** + * Retrieve timephased baseline cost. Note that index 0 represents "Baseline", + * index 1 represents "Baseline1" and so on. + * + * @param index baseline index + * @return timephased work, or null if no baseline is present + */ + public List getTimephasedBaselineCost(int index) + { + return m_timephasedBaselineCost[index] == null ? null : m_timephasedBaselineCost[index].getData(); + } + /** * Maps a field index to a ResourceField instance. * @@ -2903,6 +2951,9 @@ private LocalDateTime calculateAvailableTo() private final CostRateTable[] m_costRateTables; private final AvailabilityTable m_availability = new AvailabilityTable(); + private final TimephasedWorkContainer[] m_timephasedBaselineWork = new TimephasedWorkContainer[11]; + private final TimephasedCostContainer[] m_timephasedBaselineCost = new TimephasedCostContainer[11]; + private static final Set ALWAYS_CALCULATED_FIELDS = new HashSet<>(Arrays.asList(ResourceField.STANDARD_RATE, ResourceField.OVERTIME_RATE, ResourceField.COST_PER_USE, ResourceField.START, ResourceField.FINISH, ResourceField.MAX_UNITS, ResourceField.AVAILABLE_FROM, ResourceField.AVAILABLE_TO)); private static final Map> CALCULATED_FIELD_MAP = new HashMap<>(); diff --git a/src/main/java/net/sf/mpxj/ResourceAssignment.java b/src/main/java/net/sf/mpxj/ResourceAssignment.java index 45fe0fae94..f6a2eb8732 100644 --- a/src/main/java/net/sf/mpxj/ResourceAssignment.java +++ b/src/main/java/net/sf/mpxj/ResourceAssignment.java @@ -808,7 +808,7 @@ private TimephasedCostContainer getTimephasedCostSingleRate(List //just return an empty list if there is no timephased work passed in if (standardWorkList == null) { - return new DefaultTimephasedCostContainer(this, null, Collections.emptyList(), false); + return new DefaultTimephasedCostContainer(getEffectiveCalendar(), this, null, Collections.emptyList(), false); } List result = new ArrayList<>(); @@ -892,7 +892,7 @@ private TimephasedCostContainer getTimephasedCostSingleRate(List } - return new DefaultTimephasedCostContainer(this, null, result, false); + return new DefaultTimephasedCostContainer(getEffectiveCalendar(), this, null, result, false); } /** @@ -1010,7 +1010,7 @@ private TimephasedCostContainer getTimephasedCostFixedAmount() } } - return new DefaultTimephasedCostContainer(this, null, result, false); + return new DefaultTimephasedCostContainer(getEffectiveCalendar(), this, null, result, false); } /** @@ -1048,7 +1048,7 @@ private TimephasedCostContainer getTimephasedActualCostFixedAmount() } } - return new DefaultTimephasedCostContainer(this, null, result, false); + return new DefaultTimephasedCostContainer(getEffectiveCalendar(), this, null, result, false); } /** diff --git a/src/main/java/net/sf/mpxj/ResourceField.java b/src/main/java/net/sf/mpxj/ResourceField.java index 5213982499..d52fde6f1c 100644 --- a/src/main/java/net/sf/mpxj/ResourceField.java +++ b/src/main/java/net/sf/mpxj/ResourceField.java @@ -529,6 +529,28 @@ public enum ResourceField implements FieldType ENTERPRISE_DATA(DataType.BINARY), OVERTIME_RATE_UNITS(DataType.RATE_UNITS), STANDARD_RATE_UNITS(DataType.RATE_UNITS), + TIMEPHASED_BASELINE_WORK(DataType.BINARY), + TIMEPHASED_BASELINE1_WORK(DataType.BINARY), + TIMEPHASED_BASELINE2_WORK(DataType.BINARY), + TIMEPHASED_BASELINE3_WORK(DataType.BINARY), + TIMEPHASED_BASELINE4_WORK(DataType.BINARY), + TIMEPHASED_BASELINE5_WORK(DataType.BINARY), + TIMEPHASED_BASELINE6_WORK(DataType.BINARY), + TIMEPHASED_BASELINE7_WORK(DataType.BINARY), + TIMEPHASED_BASELINE8_WORK(DataType.BINARY), + TIMEPHASED_BASELINE9_WORK(DataType.BINARY), + TIMEPHASED_BASELINE10_WORK(DataType.BINARY), + TIMEPHASED_BASELINE_COST(DataType.BINARY), + TIMEPHASED_BASELINE1_COST(DataType.BINARY), + TIMEPHASED_BASELINE2_COST(DataType.BINARY), + TIMEPHASED_BASELINE3_COST(DataType.BINARY), + TIMEPHASED_BASELINE4_COST(DataType.BINARY), + TIMEPHASED_BASELINE5_COST(DataType.BINARY), + TIMEPHASED_BASELINE6_COST(DataType.BINARY), + TIMEPHASED_BASELINE7_COST(DataType.BINARY), + TIMEPHASED_BASELINE8_COST(DataType.BINARY), + TIMEPHASED_BASELINE9_COST(DataType.BINARY), + TIMEPHASED_BASELINE10_COST(DataType.BINARY), INDEX(DataType.INTEGER), HYPERLINK_SCREEN_TIP(DataType.STRING), diff --git a/src/main/java/net/sf/mpxj/TimephasedItem.java b/src/main/java/net/sf/mpxj/TimephasedItem.java index a138afd109..74dfc49325 100644 --- a/src/main/java/net/sf/mpxj/TimephasedItem.java +++ b/src/main/java/net/sf/mpxj/TimephasedItem.java @@ -134,7 +134,7 @@ public void setFinish(LocalDateTime finish) @Override public String toString() { - return "[TimephasedItem start=" + m_start + " totalAmount=" + m_totalAmount + " finish=" + m_finish + " amountPerDay=" + m_amountPerDay + " modified=" + m_modified + "]"; + return "[TimephasedItem start=" + m_start + " finish=" + m_finish + " totalAmount=" + m_totalAmount + "]"; } @SuppressWarnings("unchecked") @Override public boolean equals(Object o) diff --git a/src/main/java/net/sf/mpxj/common/AssignmentFieldLists.java b/src/main/java/net/sf/mpxj/common/AssignmentFieldLists.java index 3968d38935..ac6976eec9 100644 --- a/src/main/java/net/sf/mpxj/common/AssignmentFieldLists.java +++ b/src/main/java/net/sf/mpxj/common/AssignmentFieldLists.java @@ -505,6 +505,36 @@ public final class AssignmentFieldLists AssignmentField.ENTERPRISE_RESOURCE_OUTLINE_CODE29 }; + public static final AssignmentField[] TIMEPHASED_BASELINE_WORK = + { + AssignmentField.TIMEPHASED_BASELINE_WORK, + AssignmentField.TIMEPHASED_BASELINE1_WORK, + AssignmentField.TIMEPHASED_BASELINE2_WORK, + AssignmentField.TIMEPHASED_BASELINE3_WORK, + AssignmentField.TIMEPHASED_BASELINE4_WORK, + AssignmentField.TIMEPHASED_BASELINE5_WORK, + AssignmentField.TIMEPHASED_BASELINE6_WORK, + AssignmentField.TIMEPHASED_BASELINE7_WORK, + AssignmentField.TIMEPHASED_BASELINE8_WORK, + AssignmentField.TIMEPHASED_BASELINE9_WORK, + AssignmentField.TIMEPHASED_BASELINE10_WORK + }; + + public static final AssignmentField[] TIMEPHASED_BASELINE_COST = + { + AssignmentField.TIMEPHASED_BASELINE_COST, + AssignmentField.TIMEPHASED_BASELINE1_COST, + AssignmentField.TIMEPHASED_BASELINE2_COST, + AssignmentField.TIMEPHASED_BASELINE3_COST, + AssignmentField.TIMEPHASED_BASELINE4_COST, + AssignmentField.TIMEPHASED_BASELINE5_COST, + AssignmentField.TIMEPHASED_BASELINE6_COST, + AssignmentField.TIMEPHASED_BASELINE7_COST, + AssignmentField.TIMEPHASED_BASELINE8_COST, + AssignmentField.TIMEPHASED_BASELINE9_COST, + AssignmentField.TIMEPHASED_BASELINE10_COST + }; + public static final List CUSTOM_FIELDS = new ArrayList<>(); static { diff --git a/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java b/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java index 810d74e7ca..ad4997d891 100644 --- a/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java +++ b/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java @@ -25,7 +25,8 @@ import java.util.List; -import net.sf.mpxj.ResourceAssignment; +import net.sf.mpxj.ProjectCalendar; +import net.sf.mpxj.TimePeriodEntity; import net.sf.mpxj.TimephasedCost; import net.sf.mpxj.TimephasedCostContainer; @@ -37,16 +38,17 @@ public class DefaultTimephasedCostContainer implements TimephasedCostContainer /** * Constructor. * - * @param assignment resource assignment to which the timephased data relates + * @param parent entity to which the timephased data relates * @param normaliser normaliser used to process this data * @param data timephased data * @param raw flag indicating if this data is raw */ - public DefaultTimephasedCostContainer(ResourceAssignment assignment, TimephasedNormaliser normaliser, List data, boolean raw) + public DefaultTimephasedCostContainer(ProjectCalendar calendar, TimePeriodEntity parent, TimephasedNormaliser normaliser, List data, boolean raw) { + m_calendar = calendar; m_data = data; m_raw = raw; - m_assignment = assignment; + m_parent = parent; m_normaliser = normaliser; } @@ -57,7 +59,7 @@ public DefaultTimephasedCostContainer(ResourceAssignment assignment, TimephasedN { if (m_raw) { - m_normaliser.normalise(m_assignment.getEffectiveCalendar(), m_assignment, m_data); + m_normaliser.normalise(m_calendar, m_parent, m_data); m_raw = false; } return m_data; @@ -73,8 +75,9 @@ public DefaultTimephasedCostContainer(ResourceAssignment assignment, TimephasedN return !m_data.isEmpty(); } + private final ProjectCalendar m_calendar; private final List m_data; private boolean m_raw; private final TimephasedNormaliser m_normaliser; - private final ResourceAssignment m_assignment; + private final TimePeriodEntity m_parent; } diff --git a/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java b/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java index 673341e80b..03b428d7ef 100644 --- a/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java +++ b/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java @@ -26,7 +26,8 @@ import java.util.ArrayList; import java.util.List; -import net.sf.mpxj.ResourceAssignment; +import net.sf.mpxj.ProjectCalendar; +import net.sf.mpxj.TimePeriodEntity; import net.sf.mpxj.TimephasedWork; import net.sf.mpxj.TimephasedWorkContainer; @@ -38,16 +39,17 @@ public class DefaultTimephasedWorkContainer implements TimephasedWorkContainer /** * Constructor. * - * @param assignment resource assignment to which the timephased data relates + * @param parent entity to which the timephased data relates * @param normaliser normaliser used to process this data * @param data timephased data * @param raw flag indicating if this data is raw */ - public DefaultTimephasedWorkContainer(ResourceAssignment assignment, TimephasedNormaliser normaliser, List data, boolean raw) + public DefaultTimephasedWorkContainer(ProjectCalendar calendar, TimePeriodEntity parent, TimephasedNormaliser normaliser, List data, boolean raw) { + m_calendar = calendar; m_data = data; m_raw = raw; - m_assignment = assignment; + m_parent = parent; m_normaliser = normaliser; } @@ -63,7 +65,7 @@ private DefaultTimephasedWorkContainer(DefaultTimephasedWorkContainer source, do { m_data = new ArrayList<>(); m_raw = source.m_raw; - m_assignment = source.m_assignment; + m_parent = source.m_parent; m_normaliser = source.m_normaliser; for (TimephasedWork sourceItem : source.m_data) @@ -79,7 +81,7 @@ private DefaultTimephasedWorkContainer(DefaultTimephasedWorkContainer source, do { if (m_raw) { - m_normaliser.normalise(m_assignment.getEffectiveCalendar(), m_assignment, m_data); + m_normaliser.normalise(m_calendar, m_parent, m_data); m_raw = false; } return m_data; @@ -100,8 +102,9 @@ private DefaultTimephasedWorkContainer(DefaultTimephasedWorkContainer source, do return new DefaultTimephasedWorkContainer(this, perDayFactor, totalFactor); } + private ProjectCalendar m_calendar; private final List m_data; private boolean m_raw; private final TimephasedNormaliser m_normaliser; - private final ResourceAssignment m_assignment; + private final TimePeriodEntity m_parent; } diff --git a/src/main/java/net/sf/mpxj/common/MPPResourceField.java b/src/main/java/net/sf/mpxj/common/MPPResourceField.java index c2b1e3c80c..3db6718b84 100644 --- a/src/main/java/net/sf/mpxj/common/MPPResourceField.java +++ b/src/main/java/net/sf/mpxj/common/MPPResourceField.java @@ -147,6 +147,8 @@ public static int getID(FieldType value) FIELD_ARRAY[63] = ResourceField.COST_RATE_C; FIELD_ARRAY[64] = ResourceField.COST_RATE_D; FIELD_ARRAY[65] = ResourceField.COST_RATE_E; + FIELD_ARRAY[68] = ResourceField.TIMEPHASED_BASELINE_WORK; + FIELD_ARRAY[69] = ResourceField.TIMEPHASED_BASELINE_COST; FIELD_ARRAY[70] = ResourceField.STANDARD_RATE_UNITS; FIELD_ARRAY[71] = ResourceField.OVERTIME_RATE_UNITS; FIELD_ARRAY[86] = ResourceField.INDICATORS; @@ -337,42 +339,63 @@ public static int getID(FieldType value) FIELD_ARRAY[343] = ResourceField.BASELINE1_COST; FIELD_ARRAY[348] = ResourceField.BASELINE1_START; FIELD_ARRAY[349] = ResourceField.BASELINE1_FINISH; + FIELD_ARRAY[350] = ResourceField.TIMEPHASED_BASELINE1_WORK; + FIELD_ARRAY[351] = ResourceField.TIMEPHASED_BASELINE1_COST; FIELD_ARRAY[352] = ResourceField.BASELINE2_WORK; FIELD_ARRAY[353] = ResourceField.BASELINE2_COST; FIELD_ARRAY[358] = ResourceField.BASELINE2_START; FIELD_ARRAY[359] = ResourceField.BASELINE2_FINISH; + FIELD_ARRAY[360] = ResourceField.TIMEPHASED_BASELINE2_WORK; + FIELD_ARRAY[361] = ResourceField.TIMEPHASED_BASELINE2_COST; FIELD_ARRAY[362] = ResourceField.BASELINE3_WORK; FIELD_ARRAY[363] = ResourceField.BASELINE3_COST; FIELD_ARRAY[368] = ResourceField.BASELINE3_START; FIELD_ARRAY[369] = ResourceField.BASELINE3_FINISH; + FIELD_ARRAY[370] = ResourceField.TIMEPHASED_BASELINE3_WORK; + FIELD_ARRAY[371] = ResourceField.TIMEPHASED_BASELINE3_COST; FIELD_ARRAY[372] = ResourceField.BASELINE4_WORK; FIELD_ARRAY[373] = ResourceField.BASELINE4_COST; FIELD_ARRAY[378] = ResourceField.BASELINE4_START; FIELD_ARRAY[379] = ResourceField.BASELINE4_FINISH; + FIELD_ARRAY[380] = ResourceField.TIMEPHASED_BASELINE4_WORK; + FIELD_ARRAY[381] = ResourceField.TIMEPHASED_BASELINE4_COST; FIELD_ARRAY[382] = ResourceField.BASELINE5_WORK; FIELD_ARRAY[383] = ResourceField.BASELINE5_COST; FIELD_ARRAY[388] = ResourceField.BASELINE5_START; FIELD_ARRAY[389] = ResourceField.BASELINE5_FINISH; + FIELD_ARRAY[390] = ResourceField.TIMEPHASED_BASELINE5_WORK; + FIELD_ARRAY[391] = ResourceField.TIMEPHASED_BASELINE5_COST; FIELD_ARRAY[392] = ResourceField.BASELINE6_WORK; FIELD_ARRAY[393] = ResourceField.BASELINE6_COST; FIELD_ARRAY[398] = ResourceField.BASELINE6_START; FIELD_ARRAY[399] = ResourceField.BASELINE6_FINISH; + FIELD_ARRAY[400] = ResourceField.TIMEPHASED_BASELINE6_WORK; + FIELD_ARRAY[401] = ResourceField.TIMEPHASED_BASELINE6_COST; FIELD_ARRAY[402] = ResourceField.BASELINE7_WORK; FIELD_ARRAY[403] = ResourceField.BASELINE7_COST; FIELD_ARRAY[408] = ResourceField.BASELINE7_START; FIELD_ARRAY[409] = ResourceField.BASELINE7_FINISH; + FIELD_ARRAY[410] = ResourceField.TIMEPHASED_BASELINE7_WORK; + FIELD_ARRAY[411] = ResourceField.TIMEPHASED_BASELINE7_COST; FIELD_ARRAY[412] = ResourceField.BASELINE8_WORK; FIELD_ARRAY[413] = ResourceField.BASELINE8_COST; FIELD_ARRAY[418] = ResourceField.BASELINE8_START; FIELD_ARRAY[419] = ResourceField.BASELINE8_FINISH; + FIELD_ARRAY[420] = ResourceField.TIMEPHASED_BASELINE8_WORK; + FIELD_ARRAY[421] = ResourceField.TIMEPHASED_BASELINE8_COST; FIELD_ARRAY[422] = ResourceField.BASELINE9_WORK; FIELD_ARRAY[423] = ResourceField.BASELINE9_COST; FIELD_ARRAY[428] = ResourceField.BASELINE9_START; FIELD_ARRAY[429] = ResourceField.BASELINE9_FINISH; + FIELD_ARRAY[430] = ResourceField.TIMEPHASED_BASELINE9_WORK; + FIELD_ARRAY[431] = ResourceField.TIMEPHASED_BASELINE9_COST; FIELD_ARRAY[432] = ResourceField.BASELINE10_WORK; FIELD_ARRAY[433] = ResourceField.BASELINE10_COST; FIELD_ARRAY[438] = ResourceField.BASELINE10_START; FIELD_ARRAY[439] = ResourceField.BASELINE10_FINISH; + FIELD_ARRAY[440] = ResourceField.TIMEPHASED_BASELINE10_WORK; + FIELD_ARRAY[441] = ResourceField.TIMEPHASED_BASELINE10_COST; + FIELD_ARRAY[442] = ResourceField.TASK_OUTLINE_NUMBER; FIELD_ARRAY[443] = ResourceField.ENTERPRISE_UNIQUE_ID; FIELD_ARRAY[446] = ResourceField.ENTERPRISE_COST1; diff --git a/src/main/java/net/sf/mpxj/common/NewCostNormaliser.java b/src/main/java/net/sf/mpxj/common/NewCostNormaliser.java new file mode 100644 index 0000000000..924c0023d1 --- /dev/null +++ b/src/main/java/net/sf/mpxj/common/NewCostNormaliser.java @@ -0,0 +1,82 @@ +package net.sf.mpxj.common; +import java.time.LocalDateTime; +import java.util.List; + +import net.sf.mpxj.Duration; +import net.sf.mpxj.ProjectCalendar; +import net.sf.mpxj.TimePeriodEntity; +import net.sf.mpxj.TimeUnit; +import net.sf.mpxj.TimephasedCost; +import net.sf.mpxj.TimephasedWork; + +public class NewCostNormaliser implements TimephasedNormaliser +{ + private NewCostNormaliser() + { + + } + + @Override public void normalise(ProjectCalendar calendar, TimePeriodEntity parent, List list) + { + if (list == null) + { + return; + } + + if (list.size() > 1) + { + mergeDays(calendar, list); + } + } + + private void mergeDays(ProjectCalendar calendar, List list) + { + int index = 0; + double previousItemRate = Double.MIN_VALUE; + + while (index < list.size()) + { + TimephasedCost item = list.get(index); + double itemWork = calendar.getWork(item.getStart(), item.getFinish(), TimeUnit.HOURS).getDuration(); + double itemAmount = NumberHelper.getDouble(item.getTotalAmount()); + + if (itemAmount == 0 && itemWork == 0) + { + // there is zero work in this period, and zero cost in this item, + // so it provides us with no information. + list.remove(index); + continue; + } + + double itemRate = itemAmount / itemWork; + if (itemRate != previousItemRate) + { + // the rate for this item is different to the previous item, + // so we won't merge them. + previousItemRate = itemRate; + index++; + continue; + } + + TimephasedCost previousItem = list.get(index-1); + double combinedWork = calendar.getWork(previousItem.getStart(), item.getFinish(), TimeUnit.HOURS).getDuration(); + double combinedAmount = itemAmount + NumberHelper.getDouble(previousItem.getTotalAmount()); + double combinedRate = combinedAmount / combinedWork; + + if (itemRate != combinedRate) + { + // combining the two items doesn't give us the same rate as the two individual items, + // so we leave them separate. + index++; + continue; + } + + // We can combine the items + previousItem.setFinish(item.getFinish()); + previousItem.setTotalAmount(Double.valueOf(combinedAmount)); + list.remove(index); + } + } + + public static final NewCostNormaliser INSTANCE = new NewCostNormaliser(); +} \ No newline at end of file diff --git a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java new file mode 100644 index 0000000000..bfdc84d010 --- /dev/null +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -0,0 +1,176 @@ +package net.sf.mpxj.common; +import java.time.LocalDateTime; +import java.util.List; + +import net.sf.mpxj.Duration; +import net.sf.mpxj.ProjectCalendar; +import net.sf.mpxj.TimePeriodEntity; +import net.sf.mpxj.TimeUnit; +import net.sf.mpxj.TimephasedWork; + +public class NewWorkNormaliser implements TimephasedNormaliser +{ + private NewWorkNormaliser() + { + + } + @Override public void normalise(ProjectCalendar calendar, TimePeriodEntity parent, List list) + { + if (list == null) + { + return; + } + + System.out.println("Initial Items"); + list.forEach(System.out::println); + System.out.println(); + + if (list.size() > 1) + { + mergeDays(calendar, list); + + System.out.println(); + System.out.println("Merged Items"); + list.forEach(System.out::println); + System.out.println(); + } + + normaliseStarts(calendar, list); + normaliseFinishes(calendar, list); + } + + private void mergeDays(ProjectCalendar calendar, List list) + { + int index = 0; + boolean lastItemIsStandard = false; + + while (index < list.size()) + { + TimephasedWork item = list.get(index); + System.out.println("Processing: " + item); + + Duration itemWork = item.getTotalAmount() == null ? Duration.getInstance(0, TimeUnit.HOURS) : item.getTotalAmount(); + Duration calendarWork = calendar.getWork(item.getStart(), item.getFinish(), itemWork.getUnits()); + if (itemWork.equals(calendarWork)) + { + System.out.println("Item work equals calendar work"); + if (itemWork.getDuration() == 0.0) + { + // We have a zero duration item which agrees with the calendar, + // so we can remove it as it provides no useful information. + list.remove(index); + System.out.println("Removing: item and calendar have zero work: " + item); + } + else + { + if (lastItemIsStandard) + { + TimephasedWork lastItem = list.get(index-1); + System.out.println("Last item is standard: " + lastItem); + Duration lastItemWork = lastItem.getTotalAmount() == null ? Duration.getInstance(0, TimeUnit.HOURS) : lastItem.getTotalAmount().convertUnits(itemWork.getUnits(), calendar); + double combinedWork = itemWork.getDuration() + lastItemWork.getDuration(); + Duration combinedCalendarWork = calendar.getWork(lastItem.getStart(), item.getFinish(), itemWork.getUnits()); + if (combinedCalendarWork.getDuration() == combinedWork) + { + System.out.println("Combining: " + lastItem + " and " + item); + lastItem.setFinish(item.getFinish()); + lastItem.setTotalAmount(Duration.getInstance(combinedWork, itemWork.getUnits())); + lastItemIsStandard = true; + list.remove(index); + } + else + { + // We can't merge with the previous item, but this item is + // standard, so we leave it in the list and move forward. + index++; + lastItemIsStandard = true; + System.out.println("Leaving unchanged: " + item); + } + } + else + { + System.out.println("Last item is not standard, leaving unchanged: " + item); + // We can't merge with the previous item, but this item is + // standard, so we leave it in the list and move forward. + index++; + lastItemIsStandard = true; + } + } + } + else + { + System.out.println("Item work is not equal to calendar work, leaving unchanged " + item); + index++; + lastItemIsStandard = false; + } + } + } + + private void normaliseStarts(ProjectCalendar calendar, List list) + { + System.out.println("Normalising Starts"); + for (TimephasedWork item : list) + { + System.out.println("Item: " + item); + + LocalDateTime nextWorkStart = calendar.getNextWorkStart(item.getStart()); + if (item.getStart().isEqual(nextWorkStart) || nextWorkStart.isAfter(item.getFinish())) + { + if (item.getStart().isEqual(nextWorkStart)) + { + System.out.println("Item alreday at next work start"); + } + else + { + System.out.println("Next work strat after item finish"); + } + continue; + } + + + LocalDateTime previousWorkFinish = calendar.getPreviousWorkFinish(item.getStart()); + System.out.println("Item Start: " + item.getStart() + " prev work finish: " + previousWorkFinish + " next work start:" + nextWorkStart); + if (item.getStart().isEqual(previousWorkFinish)) + { + System.out.println("Updating"); + item.setStart(nextWorkStart); + } +// Duration calendarWork = calendar.getWork(nextWorkStart, item.getFinish(), item.getTotalAmount().getUnits()); +// System.out.println("Item Work:" + item.getTotalAmount() + " Calendar Work: " + calendarWork); +// if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) +// { +// System.out.println("Work matches, updating start"); +// item.setStart(nextWorkStart); +// } +// else +// { +// System.out.println("Work does not match"); +// } + } + } + + private void normaliseFinishes(ProjectCalendar calendar, List list) + { + for (TimephasedWork item : list) + { + LocalDateTime previousWorkFinish = calendar.getPreviousWorkFinish(item.getFinish()); + if (item.getStart().isEqual(previousWorkFinish) || item.getStart().isAfter(previousWorkFinish)) + { + continue; + } + +// if (item.getFinish().isEqual(calendar.getNextWorkStart(item.getFinish()))) +// { +// item.setFinish(previousWorkFinish); +// } + + Duration calendarWork = calendar.getWork(item.getStart(), previousWorkFinish, item.getTotalAmount().getUnits()); + if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) + { + item.setFinish(previousWorkFinish); + } + } + } + + public static final NewWorkNormaliser INSTANCE = new NewWorkNormaliser(); +} diff --git a/src/main/java/net/sf/mpxj/common/NullNormaliser.java b/src/main/java/net/sf/mpxj/common/NullNormaliser.java new file mode 100644 index 0000000000..ca88b22423 --- /dev/null +++ b/src/main/java/net/sf/mpxj/common/NullNormaliser.java @@ -0,0 +1,19 @@ +package net.sf.mpxj.common; +import java.util.List; + +import net.sf.mpxj.ProjectCalendar; +import net.sf.mpxj.TimePeriodEntity; +import net.sf.mpxj.TimephasedCost; +import net.sf.mpxj.TimephasedWork; + +public class NullNormaliser implements TimephasedNormaliser +{ + @Override public void normalise(ProjectCalendar calendar, TimePeriodEntity parent, List list) + { + + } + + public static final NullNormaliser NULL_WORK_NORMALISER = new NullNormaliser<>(); + + public static final NullNormaliser NULL_COST_NORMALISER = new NullNormaliser<>(); +} diff --git a/src/main/java/net/sf/mpxj/common/ResourceFieldLists.java b/src/main/java/net/sf/mpxj/common/ResourceFieldLists.java index 356cd7b725..551cc31312 100644 --- a/src/main/java/net/sf/mpxj/common/ResourceFieldLists.java +++ b/src/main/java/net/sf/mpxj/common/ResourceFieldLists.java @@ -458,6 +458,36 @@ public final class ResourceFieldLists ResourceField.BASELINE10_BUDGET_WORK }; + public static final ResourceField[] TIMEPHASED_BASELINE_WORK = + { + ResourceField.TIMEPHASED_BASELINE_WORK, + ResourceField.TIMEPHASED_BASELINE1_WORK, + ResourceField.TIMEPHASED_BASELINE2_WORK, + ResourceField.TIMEPHASED_BASELINE3_WORK, + ResourceField.TIMEPHASED_BASELINE4_WORK, + ResourceField.TIMEPHASED_BASELINE5_WORK, + ResourceField.TIMEPHASED_BASELINE6_WORK, + ResourceField.TIMEPHASED_BASELINE7_WORK, + ResourceField.TIMEPHASED_BASELINE8_WORK, + ResourceField.TIMEPHASED_BASELINE9_WORK, + ResourceField.TIMEPHASED_BASELINE10_WORK + }; + + public static final ResourceField[] TIMEPHASED_BASELINE_COST = + { + ResourceField.TIMEPHASED_BASELINE_COST, + ResourceField.TIMEPHASED_BASELINE1_COST, + ResourceField.TIMEPHASED_BASELINE2_COST, + ResourceField.TIMEPHASED_BASELINE3_COST, + ResourceField.TIMEPHASED_BASELINE4_COST, + ResourceField.TIMEPHASED_BASELINE5_COST, + ResourceField.TIMEPHASED_BASELINE6_COST, + ResourceField.TIMEPHASED_BASELINE7_COST, + ResourceField.TIMEPHASED_BASELINE8_COST, + ResourceField.TIMEPHASED_BASELINE9_COST, + ResourceField.TIMEPHASED_BASELINE10_COST + }; + public static final List CUSTOM_FIELDS = new ArrayList<>(); static { diff --git a/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java b/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java index 8e44b2b882..18780cab75 100644 --- a/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java +++ b/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java @@ -34,6 +34,7 @@ import java.util.Set; import java.util.TreeMap; +import net.sf.mpxj.FieldContainer; import net.sf.mpxj.FieldTypeClass; import net.sf.mpxj.common.InputStreamHelper; import net.sf.mpxj.common.LocalDateTimeHelper; @@ -244,7 +245,7 @@ private void processGraphicalIndicators() } /** - * Read sub project data from the file, and add it to a hash map + * Read subproject data from the file, and add it to a hash map * indexed by task ID. * * Project stores all subprojects that have ever been inserted into this project @@ -1463,17 +1464,19 @@ private void processResourceData() throws IOException FixedData rscFixedData = new FixedData(rscFixedMeta, m_inputStreamFactory.getInstance(rscDir, "FixedData")); FixedMeta rscFixed2Meta = new FixedMeta(new DocumentInputStream(((DocumentEntry) rscDir.getEntry("Fixed2Meta"))), 49); FixedData rscFixed2Data = new FixedData(rscFixed2Meta, m_inputStreamFactory.getInstance(rscDir, "Fixed2Data")); - Props12 props = new Props12(m_inputStreamFactory.getInstance(rscDir, "Props")); //System.out.println(rscVarMeta); //System.out.println(rscVarData); //System.out.println(rscFixedMeta); //System.out.println(rscFixedData); //System.out.println(rscFixed2Meta); //System.out.println(rscFixed2Data); - //System.out.println(props); // Process aliases - new CustomFieldReader12(m_file, props.getByteArray(RESOURCE_FIELD_NAME_ALIASES)).process(); + if (rscDir.hasEntry("Props")) + { + Props12 props = new Props12(m_inputStreamFactory.getInstance(rscDir, "Props")); + new CustomFieldReader12(m_file, props.getByteArray(RESOURCE_FIELD_NAME_ALIASES)).process(); + } TreeMap resourceMap = createResourceMap(fieldMap, rscFixedMeta, rscFixedData); Integer[] uniqueid = rscVarMeta.getUniqueIdentifierArray(); @@ -1483,6 +1486,20 @@ private void processResourceData() throws IOException Resource resource; HyperlinkReader hyperlinkReader = new HyperlinkReader(); + // + // Select the correct metadata locations depending on + // which version of Microsoft project generated this file + // + MppBitFlag[] metaDataBitFlags; + MppBitFlag[] metaData2BitFlags; + int resourceTypeOffset; + int resourceTypeMask; + + resourceTypeOffset = 9; + resourceTypeMask = 0x02; + metaDataBitFlags = RESOURCE_META_DATA_BIT_FLAGS; + metaData2BitFlags = RESOURCE_META_DATA2_BIT_FLAGS; + for (Integer id : uniqueid) { offset = resourceMap.get(id); @@ -1502,6 +1519,7 @@ private void processResourceData() throws IOException resource = m_file.addResource(); resource.disableEvents(); + fieldMap.populateContainer(FieldTypeClass.RESOURCE, resource, id, new byte[][] { data, @@ -1512,13 +1530,9 @@ private void processResourceData() throws IOException resource.enableEvents(); - resource.setBudget((metaData2[8] & 0x20) != 0); - - resource.setGUID(MPPUtility.getGUID(data2, 0)); - hyperlinkReader.read(resource, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.HYPERLINK_DATA))); - resource.setID(Integer.valueOf(MPPUtility.getInt(data, 4))); + resource.setID(Integer.valueOf(MPPUtility.getInt(data, fieldMap.getFixedDataOffset(ResourceField.ID)))); resource.setOutlineCode(1, getCustomFieldOutlineCodeValue(rscVarData, m_outlineCodeVarData, id, fieldMap.getVarDataKey(ResourceField.OUTLINE_CODE1_INDEX))); resource.setOutlineCode(2, getCustomFieldOutlineCodeValue(rscVarData, m_outlineCodeVarData, id, fieldMap.getVarDataKey(ResourceField.OUTLINE_CODE2_INDEX))); @@ -1531,29 +1545,11 @@ private void processResourceData() throws IOException resource.setOutlineCode(9, getCustomFieldOutlineCodeValue(rscVarData, m_outlineCodeVarData, id, fieldMap.getVarDataKey(ResourceField.OUTLINE_CODE9_INDEX))); resource.setOutlineCode(10, getCustomFieldOutlineCodeValue(rscVarData, m_outlineCodeVarData, id, fieldMap.getVarDataKey(ResourceField.OUTLINE_CODE10_INDEX))); - resource.setUniqueID(id); - metaData = rscFixedMeta.getByteArrayValue(offset.intValue()); - resource.setFlag(1, (metaData[28] & 0x40) != 0); - resource.setFlag(2, (metaData[28] & 0x80) != 0); - resource.setFlag(3, (metaData[29] & 0x01) != 0); - resource.setFlag(4, (metaData[29] & 0x02) != 0); - resource.setFlag(5, (metaData[29] & 0x04) != 0); - resource.setFlag(6, (metaData[29] & 0x08) != 0); - resource.setFlag(7, (metaData[29] & 0x10) != 0); - resource.setFlag(8, (metaData[29] & 0x20) != 0); - resource.setFlag(9, (metaData[29] & 0x40) != 0); - resource.setFlag(10, (metaData[28] & 0x20) != 0); - resource.setFlag(11, (metaData[29] & 0x80) != 0); - resource.setFlag(12, (metaData[30] & 0x01) != 0); - resource.setFlag(13, (metaData[30] & 0x02) != 0); - resource.setFlag(14, (metaData[30] & 0x04) != 0); - resource.setFlag(15, (metaData[30] & 0x08) != 0); - resource.setFlag(16, (metaData[30] & 0x10) != 0); - resource.setFlag(17, (metaData[30] & 0x20) != 0); - resource.setFlag(18, (metaData[30] & 0x40) != 0); - resource.setFlag(19, (metaData[30] & 0x80) != 0); - resource.setFlag(20, (metaData[31] & 0x01) != 0); + readBitFields(metaDataBitFlags, resource, metaData); + readBitFields(metaData2BitFlags, resource, metaData2); + + resource.setUniqueID(id); // // Configure the resource calendar @@ -1590,7 +1586,7 @@ private void processResourceData() throws IOException // // Process resource type // - if ((metaData[9] & 0x02) != 0) + if ((metaData[resourceTypeOffset] & resourceTypeMask) != 0) { resource.setType(ResourceType.WORK); } @@ -1881,6 +1877,22 @@ private String getCustomFieldOutlineCodeValue(Var2Data varData, Var2Data outline return result; } + /** + * Iterate through a set of bit field flags and set the value for each one + * in the supplied container. + * + * @param flags bit field flags + * @param container field container + * @param data source data + */ + private void readBitFields(MppBitFlag[] flags, FieldContainer container, byte[] data) + { + for (MppBitFlag flag : flags) + { + flag.setValue(container, data); + } + } + private MPPReader m_reader; private ProjectFile m_file; private EventManager m_eventManager; @@ -1946,4 +1958,33 @@ private String getCustomFieldOutlineCodeValue(Var2Data varData, Var2Data outline */ private static final Integer RESOURCE_FIELD_NAME_ALIASES = Integer.valueOf(71303169); private static final Integer TASK_FIELD_NAME_ALIASES = Integer.valueOf(71303169); + + private static final MppBitFlag[] RESOURCE_META_DATA2_BIT_FLAGS = + { + new MppBitFlag(ResourceField.BUDGET, 8, 0x20, Boolean.FALSE, Boolean.TRUE), + }; + + private static final MppBitFlag[] RESOURCE_META_DATA_BIT_FLAGS = + { + new MppBitFlag(ResourceField.FLAG10, 28, 0x0000020, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG1, 28, 0x0000040, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG2, 28, 0x0000080, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG3, 28, 0x0000100, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG4, 28, 0x0000200, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG5, 28, 0x0000400, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG6, 28, 0x0000800, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG7, 28, 0x0001000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG8, 28, 0x0002000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG9, 28, 0x0004000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG11, 28, 0x0008000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG12, 28, 0x0010000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG13, 28, 0x0020000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG14, 28, 0x0040000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG15, 28, 0x0080000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG16, 28, 0x0100000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG17, 28, 0x0200000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG18, 28, 0x0400000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG19, 28, 0x0800000, Boolean.FALSE, Boolean.TRUE), + new MppBitFlag(ResourceField.FLAG20, 28, 0x1000000, Boolean.FALSE, Boolean.TRUE) + }; } diff --git a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java index ccd667e43e..7eb024d606 100644 --- a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java +++ b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java @@ -1047,7 +1047,7 @@ private void processTaskData() throws IOException if (temp != null) { // Task with this id already exists... determine if this is the 'real' task by seeing - // if this task has some var data. This is sort of hokey, but it's the best method i have + // if this task has some var data. This is sort of hokey, but it's the best method I have // been able to see. if (!taskVarMeta.getUniqueIdentifierSet().contains(uniqueID)) { @@ -1495,6 +1495,8 @@ private void processResourceData() throws IOException FieldMap enterpriseCustomFieldMap = new FieldMap14(m_file); enterpriseCustomFieldMap.createEnterpriseCustomFieldMap(m_projectProps, FieldTypeClass.RESOURCE); + TimephasedDataFactory timephasedFactory = new TimephasedDataFactory(); + DirectoryEntry rscDir = (DirectoryEntry) m_projectDir.getEntry("TBkndRsc"); VarMeta rscVarMeta = new VarMeta12(new DocumentInputStream(((DocumentEntry) rscDir.getEntry("VarMeta")))); Var2Data rscVarData = new Var2Data(m_file, rscVarMeta, new DocumentInputStream(((DocumentEntry) rscDir.getEntry("Var2Data")))); @@ -1503,7 +1505,7 @@ private void processResourceData() throws IOException FixedMeta rscFixed2Meta = new FixedMeta(new DocumentInputStream(((DocumentEntry) rscDir.getEntry("Fixed2Meta"))), rscFixedData, 50, 51); FixedData rscFixed2Data = new FixedData(rscFixed2Meta, m_inputStreamFactory.getInstance(rscDir, "Fixed2Data")); - //System.out.println(rscVarMeta); + //System.out.println(rscVarMeta.toString(fieldMap)); //System.out.println(rscVarData); //System.out.println(rscFixedMeta); //System.out.println(rscFixedData); @@ -1526,7 +1528,7 @@ private void processResourceData() throws IOException HyperlinkReader hyperlinkReader = new HyperlinkReader(); // - // Select the correct meta data locations depending on + // Select the correct metadata locations depending on // which version of Microsoft project generated this file // MppBitFlag[] metaDataBitFlags; @@ -1651,6 +1653,22 @@ private void processResourceData() throws IOException } } +// ProjectCalendar calendar = resource.getCalendar(); +// System.out.println(resource); +// for (int index = 0; index < ResourceFieldLists.TIMEPHASED_BASELINE_WORK.length; index++) +// { +// +// TimephasedWorkContainer work = timephasedFactory.getBaselineWork(calendar, resource, baselineWorkNormaliser, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceFieldLists.TIMEPHASED_BASELINE_WORK[index])), true); +// TimephasedCostContainer cost = timephasedFactory.getBaselineCost(calendar, resource, baselineCostNormaliser, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceFieldLists.TIMEPHASED_BASELINE_COST[index])), true); +// +// if (work != null) +// { +// System.out.println("Baseline " + index); +// work.getData().forEach(System.out::println); +// cost.getData().forEach(System.out::println); +// } +// } + m_eventManager.fireResourceReadEvent(resource); } } @@ -1984,7 +2002,7 @@ private void readBitFields(MppBitFlag[] flags, FieldContainer container, byte[] private Map m_parentTasks; // Signals the end of the list of subproject task unique ids - //private static final int SUBPROJECT_LISTEND = 0x00000303; + //private static final int SUBPROJECT_LIST_END = 0x00000303; // Signals that the previous value was for the subproject task unique id private static final int SUBPROJECT_TASKUNIQUEID0 = 0x00000000; diff --git a/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java b/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java index c273db307d..dbb11b4c31 100644 --- a/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java +++ b/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java @@ -23,6 +23,7 @@ package net.sf.mpxj.mpp; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -40,6 +41,7 @@ import net.sf.mpxj.TimeUnit; import net.sf.mpxj.TimephasedWork; import net.sf.mpxj.WorkContour; +import net.sf.mpxj.common.AssignmentFieldLists; import net.sf.mpxj.common.DefaultTimephasedWorkContainer; import net.sf.mpxj.common.MicrosoftProjectConstants; import net.sf.mpxj.common.NumberHelper; @@ -179,29 +181,37 @@ public void process(ProjectFile file, FieldMap fieldMap, FieldMap enterpriseCust ResourceType resourceType = resource == null ? ResourceType.WORK : resource.getType(); ProjectCalendar calendar = assignment.getEffectiveCalendar(); - for (int index = 0; index < TIMEPHASED_BASELINE_WORK.length; index++) + for (int index = 0; index < AssignmentFieldLists.TIMEPHASED_BASELINE_WORK.length; index++) { - assignment.setTimephasedBaselineWork(index, timephasedFactory.getBaselineWork(calendar, assignment, MPPTimephasedBaselineWorkNormaliser.INSTANCE, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(TIMEPHASED_BASELINE_WORK[index])), !useRawTimephasedData)); - assignment.setTimephasedBaselineCost(index, timephasedFactory.getBaselineCost(assignment, MPPTimephasedBaselineCostNormaliser.INSTANCE, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(TIMEPHASED_BASELINE_COST[index])), !useRawTimephasedData)); + assignment.setTimephasedBaselineWork(index, timephasedFactory.getBaselineWork(calendar, assignment, MPPTimephasedBaselineWorkNormaliser.INSTANCE, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentFieldLists.TIMEPHASED_BASELINE_WORK[index])), !useRawTimephasedData)); + assignment.setTimephasedBaselineCost(index, timephasedFactory.getBaselineCost(calendar, assignment, MPPTimephasedBaselineCostNormaliser.INSTANCE, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentFieldLists.TIMEPHASED_BASELINE_COST[index])), !useRawTimephasedData)); } byte[] timephasedActualWorkData = assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentField.TIMEPHASED_ACTUAL_WORK)); byte[] timephasedWorkData = assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentField.TIMEPHASED_WORK)); byte[] timephasedActualOvertimeWorkData = assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentField.TIMEPHASED_ACTUAL_OVERTIME_WORK)); - List timephasedActualWork = timephasedFactory.getCompleteWork(calendar, assignment, timephasedActualWorkData); - List timephasedWork = timephasedFactory.getPlannedWork(calendar, assignment, timephasedWorkData, timephasedActualWork, resourceType); - List timephasedActualOvertimeWork = timephasedFactory.getCompleteWork(calendar, assignment, timephasedActualOvertimeWorkData); + List timephasedActualWork; + List timephasedWork; + List timephasedActualOvertimeWork; if (task.getDuration() == null || task.getDuration().getDuration() == 0) { // If we have a zero duration task, we'll set the assignment actual start and finish based on the task actual start and finish + timephasedActualWork = new ArrayList<>(); + timephasedWork = new ArrayList<>(); + timephasedActualOvertimeWork = new ArrayList<>(); + assignment.setActualStart(task.getActualStart() != null ? assignment.getStart() : null); assignment.setActualFinish(task.getActualFinish() != null ? assignment.getFinish() : null); } else { // We have a task with a duration, try to determine the assignment actual start and finish values + timephasedActualWork = timephasedFactory.getCompleteWork(calendar, assignment, timephasedActualWorkData); + timephasedWork = timephasedFactory.getPlannedWork(calendar, assignment, timephasedActualWork.isEmpty() ? assignment.getStart() : assignment.getResume(), timephasedWorkData, resourceType); + timephasedActualOvertimeWork = timephasedFactory.getCompleteWork(calendar, assignment, timephasedActualOvertimeWorkData); + assignment.setActualFinish((task.getActualStart() != null && assignment.getRemainingWork().getDuration() == 0 && resource != null) ? assignment.getFinish() : null); assignment.setActualStart(assignment.getActualFinish() != null || !timephasedActualWork.isEmpty() ? assignment.getStart() : null); } @@ -215,9 +225,9 @@ public void process(ProjectFile file, FieldMap fieldMap, FieldMap enterpriseCust createTimephasedData(file, assignment, timephasedWork, timephasedActualWork); - assignment.setTimephasedWork(new DefaultTimephasedWorkContainer(assignment, MPPTimephasedWorkNormaliser.INSTANCE, timephasedWork, !useRawTimephasedData)); - assignment.setTimephasedActualWork(new DefaultTimephasedWorkContainer(assignment, MPPTimephasedWorkNormaliser.INSTANCE, timephasedActualWork, !useRawTimephasedData)); - assignment.setTimephasedActualOvertimeWork(new DefaultTimephasedWorkContainer(assignment, MPPTimephasedWorkNormaliser.INSTANCE, timephasedActualOvertimeWork, !useRawTimephasedData)); + assignment.setTimephasedWork(new DefaultTimephasedWorkContainer(calendar, assignment, MPPTimephasedWorkNormaliser.INSTANCE, timephasedWork, !useRawTimephasedData)); + assignment.setTimephasedActualWork(new DefaultTimephasedWorkContainer(calendar, assignment, MPPTimephasedWorkNormaliser.INSTANCE, timephasedActualWork, !useRawTimephasedData)); + assignment.setTimephasedActualOvertimeWork(new DefaultTimephasedWorkContainer(calendar, assignment, MPPTimephasedWorkNormaliser.INSTANCE, timephasedActualOvertimeWork, !useRawTimephasedData)); if (timephasedWorkData != null) { @@ -392,36 +402,5 @@ private void createTimephasedData(ProjectFile file, ResourceAssignment assignmen new MppBitFlag(AssignmentField.TEAM_STATUS_PENDING, 8, 0x02000000, Boolean.FALSE, Boolean.TRUE), new MppBitFlag(AssignmentField.RESPONSE_PENDING, 8, 0x01000000, Boolean.FALSE, Boolean.TRUE) }; - - private static final AssignmentField[] TIMEPHASED_BASELINE_WORK = - { - AssignmentField.TIMEPHASED_BASELINE_WORK, - AssignmentField.TIMEPHASED_BASELINE1_WORK, - AssignmentField.TIMEPHASED_BASELINE2_WORK, - AssignmentField.TIMEPHASED_BASELINE3_WORK, - AssignmentField.TIMEPHASED_BASELINE4_WORK, - AssignmentField.TIMEPHASED_BASELINE5_WORK, - AssignmentField.TIMEPHASED_BASELINE6_WORK, - AssignmentField.TIMEPHASED_BASELINE7_WORK, - AssignmentField.TIMEPHASED_BASELINE8_WORK, - AssignmentField.TIMEPHASED_BASELINE9_WORK, - AssignmentField.TIMEPHASED_BASELINE10_WORK - }; - - private static final AssignmentField[] TIMEPHASED_BASELINE_COST = - { - AssignmentField.TIMEPHASED_BASELINE_COST, - AssignmentField.TIMEPHASED_BASELINE1_COST, - AssignmentField.TIMEPHASED_BASELINE2_COST, - AssignmentField.TIMEPHASED_BASELINE3_COST, - AssignmentField.TIMEPHASED_BASELINE4_COST, - AssignmentField.TIMEPHASED_BASELINE5_COST, - AssignmentField.TIMEPHASED_BASELINE6_COST, - AssignmentField.TIMEPHASED_BASELINE7_COST, - AssignmentField.TIMEPHASED_BASELINE8_COST, - AssignmentField.TIMEPHASED_BASELINE9_COST, - AssignmentField.TIMEPHASED_BASELINE10_COST - }; - private static final Duration DEFAULT_NORMALIZER_WORK_PER_DAY = Duration.getInstance(480, TimeUnit.MINUTES); } diff --git a/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java b/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java index dd203b2cf1..e35f6f91cf 100644 --- a/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java +++ b/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java @@ -32,6 +32,7 @@ import net.sf.mpxj.ProjectCalendar; import net.sf.mpxj.ResourceAssignment; import net.sf.mpxj.ResourceType; +import net.sf.mpxj.TimePeriodEntity; import net.sf.mpxj.TimeUnit; import net.sf.mpxj.TimephasedCost; import net.sf.mpxj.TimephasedCostContainer; @@ -58,10 +59,10 @@ final class TimephasedDataFactory * @param data completed work data block * @return list of TimephasedWork instances */ - public List getCompleteWork(ProjectCalendar calendar, ResourceAssignment resourceAssignment, byte[] data) + public List getCompleteWork(ProjectCalendar calendar, TimePeriodEntity resourceAssignment, byte[] data) { List list = new ArrayList<>(); - if (calendar == null || data == null || data.length <= 26 || MPPUtility.getShort(data, 0) == 0 || resourceAssignment.getTask().getDuration() == null || resourceAssignment.getTask().getDuration().getDuration() == 0) + if (calendar == null || data == null || data.length <= 26 || MPPUtility.getShort(data, 0) == 0) { return list; } @@ -158,15 +159,15 @@ public List getCompleteWork(ProjectCalendar calendar, ResourceAs * * @param calendar calendar on which date calculations are based * @param assignment resource assignment + * @param plannedWorkStart planned work start date * @param data planned work data block - * @param timephasedComplete list of complete work * @param resourceType resource type * @return list of TimephasedWork instances */ - public List getPlannedWork(ProjectCalendar calendar, ResourceAssignment assignment, byte[] data, List timephasedComplete, ResourceType resourceType) + public List getPlannedWork(ProjectCalendar calendar, TimePeriodEntity assignment, LocalDateTime plannedWorkStart, byte[] data, ResourceType resourceType) { List list = new ArrayList<>(); - if (data == null || data.length == 0 || assignment.getTask().getDuration() == null || assignment.getTask().getDuration().getDuration() == 0) + if (data == null || data.length == 0) { return list; } @@ -188,7 +189,7 @@ public List getPlannedWork(ProjectCalendar calendar, ResourceAss // MPPUtility.getDouble(data, 8); TimephasedWork work = new TimephasedWork(); - work.setStart(timephasedComplete.isEmpty() ? assignment.getStart() : assignment.getResume()); + work.setStart(plannedWorkStart); work.setFinish(assignment.getFinish()); work.setTotalAmount(totalWork); @@ -198,7 +199,6 @@ public List getPlannedWork(ProjectCalendar calendar, ResourceAss } else { - LocalDateTime offset = timephasedComplete.isEmpty() ? assignment.getStart() : assignment.getResume(); int index = 40; double previousCumulativeWork = 0; TimephasedWork previousAssignment = null; @@ -213,11 +213,11 @@ public List getPlannedWork(ProjectCalendar calendar, ResourceAss LocalDateTime start; if (blockDuration.getDuration() == 0) { - start = offset; + start = plannedWorkStart; } else { - start = calendar.getNextWorkStart(calendar.getDate(offset, blockDuration)); + start = calendar.getNextWorkStart(calendar.getDate(plannedWorkStart, blockDuration)); } double currentCumulativeWork = MPPUtility.getDouble(data, index + 4); @@ -242,7 +242,7 @@ public List getPlannedWork(ProjectCalendar calendar, ResourceAss if (previousAssignment != null) { - LocalDateTime finish = calendar.getDate(offset, blockDuration); + LocalDateTime finish = calendar.getDate(plannedWorkStart, blockDuration); previousAssignment.setFinish(finish); if (previousAssignment.getStart().equals(previousAssignment.getFinish())) { @@ -262,7 +262,7 @@ public List getPlannedWork(ProjectCalendar calendar, ResourceAss double time = MPPUtility.getInt(data, 24); time /= 80; Duration blockDuration = Duration.getInstance(time, TimeUnit.MINUTES); - LocalDateTime finish = calendar.getDate(offset, blockDuration); + LocalDateTime finish = calendar.getDate(plannedWorkStart, blockDuration); previousAssignment.setFinish(finish); if (previousAssignment.getStart().equals(previousAssignment.getFinish())) { @@ -288,7 +288,7 @@ public List getPlannedWork(ProjectCalendar calendar, ResourceAss * @param raw flag indicating if this data is to be treated as raw * @return timephased work */ - public TimephasedWorkContainer getBaselineWork(ProjectCalendar calendar, ResourceAssignment assignment, TimephasedNormaliser normaliser, byte[] data, boolean raw) + public TimephasedWorkContainer getBaselineWork(ProjectCalendar calendar, TimePeriodEntity assignment, TimephasedNormaliser normaliser, byte[] data, boolean raw) { if (data == null || data.length == 0) { @@ -364,7 +364,7 @@ public TimephasedWorkContainer getBaselineWork(ProjectCalendar calendar, Resourc calculateAmountPerDay(calendar, list); - return new DefaultTimephasedWorkContainer(assignment, normaliser, list, true); + return new DefaultTimephasedWorkContainer(calendar, assignment, normaliser, list, true); } /** @@ -378,7 +378,7 @@ public TimephasedWorkContainer getBaselineWork(ProjectCalendar calendar, Resourc * @param raw flag indicating if this data is to be treated as raw * @return timephased work */ - public TimephasedCostContainer getBaselineCost(ResourceAssignment assignment, TimephasedNormaliser normaliser, byte[] data, boolean raw) + public TimephasedCostContainer getBaselineCost(ProjectCalendar calendar, TimePeriodEntity assignment, TimephasedNormaliser normaliser, byte[] data, boolean raw) { TimephasedCostContainer result = null; if (data == null || data.length == 0) @@ -423,7 +423,7 @@ public TimephasedCostContainer getBaselineCost(ResourceAssignment assignment, Ti if (list != null) { - result = new DefaultTimephasedCostContainer(assignment, normaliser, list, raw); + result = new DefaultTimephasedCostContainer(calendar, assignment, normaliser, list, raw); } return result; diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java index df4b1819d4..28c9216dd9 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java @@ -58,6 +58,9 @@ import net.sf.mpxj.common.DefaultTimephasedCostContainer; import net.sf.mpxj.common.LocalDateHelper; import net.sf.mpxj.common.LocalDateTimeHelper; +import net.sf.mpxj.common.NewCostNormaliser; +import net.sf.mpxj.common.NewWorkNormaliser; +import net.sf.mpxj.common.NullNormaliser; import net.sf.mpxj.mpp.MPPTimephasedBaselineCostNormaliser; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -419,6 +422,7 @@ private void readCalendars(Project project, HashMap } m_projectFile.setDefaultCalendar(defaultCalendar); + m_baselineCalendar = m_projectFile.getBaselineCalendar(); } /** @@ -444,7 +448,6 @@ private static void updateBaseCalendarNames(List timephasedData = readTimephasedWork(assignment, entry.getKey().intValue()); if (!timephasedData.isEmpty()) { - entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(mpx, MSPDITimephasedWorkNormaliser.INSTANCE, timephasedData, true)); + entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(m_baselineCalendar, mpx, NewWorkNormaliser.INSTANCE, timephasedData, true)); } } @@ -2056,7 +2059,8 @@ private void readAssignment(Project.Assignments.Assignment assignment) List timephasedData = readTimephasedCost(assignment, entry.getKey().intValue()); if (!timephasedData.isEmpty()) { - entry.getValue().apply(mpx, new DefaultTimephasedCostContainer(mpx, MPPTimephasedBaselineCostNormaliser.INSTANCE, timephasedData, true)); + entry.getValue().apply(mpx, new DefaultTimephasedCostContainer(calendar, mpx, MPPTimephasedBaselineCostNormaliser.INSTANCE, timephasedData, true)); + //entry.getValue().apply(mpx, new DefaultTimephasedCostContainer(m_baselineCalendar, mpx, NewCostNormaliser.INSTANCE, timephasedData, true)); } } @@ -2291,6 +2295,7 @@ public boolean getIgnoreErrors() private Map m_lookupTableMap; private Map> m_customFieldValueItems; private Map m_externalProjects; + private ProjectCalendar m_baselineCalendar; private static final RecurrenceType[] RECURRENCE_TYPES = { diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java index 7ff94c979a..1c22e3eb5c 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java @@ -1121,7 +1121,9 @@ private Project.Resources.Resource writeResource(Resource mpx) writeAvailability(xml, mpx); - return (xml); + writeResourceTimephasedData(xml, mpx); + + return xml; } /** @@ -2196,7 +2198,7 @@ private void writeAssignmentTimephasedData(ResourceAssignment mpx, Project.Assig return; } - ProjectCalendar calendar = getCalendar(mpx); + ProjectCalendar calendar = mpx.getEffectiveCalendar(); List complete = mpx.getTimephasedActualWork(); List planned = mpx.getTimephasedWork(); List completeOvertime = mpx.getTimephasedActualOvertimeWork(); @@ -2207,19 +2209,49 @@ private void writeAssignmentTimephasedData(ResourceAssignment mpx, Project.Assig BigInteger assignmentID = xml.getUID(); List list = xml.getTimephasedData(); - writeAssignmentTimephasedWorkData(assignmentID, list, complete, 2); - writeAssignmentTimephasedWorkData(assignmentID, list, planned, 1); - writeAssignmentTimephasedWorkData(assignmentID, list, completeOvertime, 3); + writeTimephasedWorkData(assignmentID, list, complete, 2); + writeTimephasedWorkData(assignmentID, list, planned, 1); + writeTimephasedWorkData(assignmentID, list, completeOvertime, 3); // Write the baselines - for (int index = 0; index < TIMEPHASED_BASELINE_WORK_TYPES.length; index++) + for (int index = 0; index < ASSIGNMENT_TIMEPHASED_BASELINE_WORK_TYPES.length; index++) + { + System.out.println("BASELINE " + index); + writeTimephasedWorkData(assignmentID, list, splitDays(calendar, mpx.getTimephasedBaselineWork(index), null, null), ASSIGNMENT_TIMEPHASED_BASELINE_WORK_TYPES[index]); + } + + for (int index = 0; index < ASSIGNMENT_TIMEPHASED_BASELINE_COST_TYPES.length; index++) + { + writeTimephasedCostData(assignmentID, list, splitDays(calendar, mpx.getTimephasedBaselineCost(index)), ASSIGNMENT_TIMEPHASED_BASELINE_COST_TYPES[index]); + } + } + + /** + * Writes the timephased data for a resource. + * + * @param mpx MPXJ resource + * @param xml MSPDI resource + */ + private void writeResourceTimephasedData(Project.Resources.Resource xml, Resource mpx) + { + if (!m_writeTimephasedData) + { + return; + } + + ProjectCalendar calendar = mpx.getCalendar(); + + BigInteger resourceID = NumberHelper.getBigInteger(xml.getUID()); + List list = xml.getTimephasedData(); + + for (int index = 0; index < RESOURCE_TIMEPHASED_BASELINE_WORK_TYPES.length; index++) { - writeAssignmentTimephasedWorkData(assignmentID, list, splitDays(calendar, mpx.getTimephasedBaselineWork(index), null, null), TIMEPHASED_BASELINE_WORK_TYPES[index]); + writeTimephasedWorkData(resourceID, list, splitDays(calendar, mpx.getTimephasedBaselineWork(index), null, null), ASSIGNMENT_TIMEPHASED_BASELINE_WORK_TYPES[index]); } - for (int index = 0; index < TIMEPHASED_BASELINE_COST_TYPES.length; index++) + for (int index = 0; index < RESOURCE_TIMEPHASED_BASELINE_COST_TYPES.length; index++) { - writeAssignmentTimephasedCostData(assignmentID, list, splitDays(calendar, mpx.getTimephasedBaselineCost(index)), TIMEPHASED_BASELINE_COST_TYPES[index]); + writeTimephasedCostData(resourceID, list, splitDays(calendar, mpx.getTimephasedBaselineCost(index)), ASSIGNMENT_TIMEPHASED_BASELINE_COST_TYPES[index]); } } @@ -2255,17 +2287,6 @@ private List splitPlannedWork(ProjectCalendar calendar, List splitDays(ProjectCalendar calendar, List list, List data, int type) + private void writeTimephasedWorkData(BigInteger assignmentID, List list, List data, int type) { if (data == null) { @@ -2539,7 +2560,7 @@ private BigInteger timephasedDataPeriodUnit(TimephasedItem item) return TIMEPHASED_DATA_PERIOD_MINUTES; } - private void writeAssignmentTimephasedCostData(BigInteger assignmentID, List list, List data, int type) + private void writeTimephasedCostData(BigInteger assignmentID, List list, List data, int type) { if (data == null) { @@ -2744,7 +2765,7 @@ private String nullIfEmpty(String value) MAPPING_TARGET_CUSTOM_FIELDS.addAll(Arrays.asList(AssignmentFieldLists.CUSTOM_DURATION)); } - private static final int[] TIMEPHASED_BASELINE_WORK_TYPES = + private static final int[] ASSIGNMENT_TIMEPHASED_BASELINE_WORK_TYPES = { 4, 16, @@ -2759,7 +2780,7 @@ private String nullIfEmpty(String value) 70 }; - private static final int[] TIMEPHASED_BASELINE_COST_TYPES = + private static final int[] ASSIGNMENT_TIMEPHASED_BASELINE_COST_TYPES = { 5, 17, @@ -2774,6 +2795,36 @@ private String nullIfEmpty(String value) 71, }; + private static final int[] RESOURCE_TIMEPHASED_BASELINE_WORK_TYPES = + { + 7, + 20, + 26, + 32, + 38, + 44, + 50, + 56, + 62, + 68, + 74 + }; + + private static final int[] RESOURCE_TIMEPHASED_BASELINE_COST_TYPES = + { + 8, + 21, + 27, + 33, + 39, + 45, + 51, + 57, + 63, + 69, + 75 + }; + private static final Set TIME_UNIT_NAMES = new HashSet<>(Arrays.stream(TimeUnit.values()).map(u -> u.getName()).collect(Collectors.toList())); private static final BigInteger TIMEPHASED_DATA_PERIOD_YEARS = BigInteger.valueOf(8);