From 104f027b5d9051acbfc79c35cdc3d43ed8a9df70 Mon Sep 17 00:00:00 2001 From: joniles Date: Fri, 26 Jan 2024 13:43:38 +0000 Subject: [PATCH 01/18] WIP --- src/main/java/net/sf/mpxj/LocaleData.java | 22 +++++++++++ src/main/java/net/sf/mpxj/ResourceField.java | 22 +++++++++++ .../sf/mpxj/common/AssignmentFieldLists.java | 30 +++++++++++++++ .../net/sf/mpxj/common/MPPResourceField.java | 21 ++++++++++ .../sf/mpxj/common/ResourceFieldLists.java | 30 +++++++++++++++ .../mpxj/mpp/ResourceAssignmentFactory.java | 38 ++----------------- 6 files changed, 129 insertions(+), 34 deletions(-) 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/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/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/MPPResourceField.java b/src/main/java/net/sf/mpxj/common/MPPResourceField.java index c2b1e3c80c..7398315beb 100644 --- a/src/main/java/net/sf/mpxj/common/MPPResourceField.java +++ b/src/main/java/net/sf/mpxj/common/MPPResourceField.java @@ -337,42 +337,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/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/ResourceAssignmentFactory.java b/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java index bf9786789b..972c70f1ab 100644 --- a/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java +++ b/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java @@ -41,6 +41,7 @@ import net.sf.mpxj.TimephasedCost; 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; @@ -185,10 +186,10 @@ 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, baselineWorkNormaliser, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(TIMEPHASED_BASELINE_WORK[index])), !useRawTimephasedData)); - assignment.setTimephasedBaselineCost(index, timephasedFactory.getBaselineCost(assignment, baselineCostNormaliser, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(TIMEPHASED_BASELINE_COST[index])), !useRawTimephasedData)); + assignment.setTimephasedBaselineWork(index, timephasedFactory.getBaselineWork(calendar, assignment, baselineWorkNormaliser, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentFieldLists.TIMEPHASED_BASELINE_WORK[index])), !useRawTimephasedData)); + assignment.setTimephasedBaselineCost(index, timephasedFactory.getBaselineCost(assignment, baselineCostNormaliser, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentFieldLists.TIMEPHASED_BASELINE_COST[index])), !useRawTimephasedData)); } byte[] timephasedActualWorkData = assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentField.TIMEPHASED_ACTUAL_WORK)); @@ -398,36 +399,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); } From 88726d5622fc602a8b6919177f108d636a2af924 Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 29 Jan 2024 10:43:33 +0000 Subject: [PATCH 02/18] Alignment --- mkdocs/docs/field-guide.md | 20 ++++ mkdocs/docs/mpp-field-guide.md | 20 ++++ .../java/net/sf/mpxj/mpp/MPP12Reader.java | 103 +++++++++++++----- .../java/net/sf/mpxj/mpp/MPP14Reader.java | 2 +- 4 files changed, 114 insertions(+), 31 deletions(-) diff --git a/mkdocs/docs/field-guide.md b/mkdocs/docs/field-guide.md index 6e27531c2d..98208bcf85 100644 --- a/mkdocs/docs/field-guide.md +++ b/mkdocs/docs/field-guide.md @@ -797,6 +797,26 @@ 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| | | | | | | |✓| | | | | | | | | | | | | | |  ### 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..d7f556f871 100644 --- a/mkdocs/docs/mpp-field-guide.md +++ b/mkdocs/docs/mpp-field-guide.md @@ -702,6 +702,26 @@ 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| | |✓|✓ ### Custom Fields Field|MPP8|MPP9|MPP12|MPP14 diff --git a/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java b/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java index 8e44b2b882..975747f217 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,11 @@ 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 +1547,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 +1588,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 +1879,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 +1960,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..541ecafdf7 100644 --- a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java +++ b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java @@ -1526,7 +1526,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; From ac33f9a143774c274628e8000fadf4007381fc22 Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 29 Jan 2024 10:51:12 +0000 Subject: [PATCH 03/18] Alignment --- src/main/java/net/sf/mpxj/mpp/MPP12Reader.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java b/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java index 975747f217..18780cab75 100644 --- a/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java +++ b/src/main/java/net/sf/mpxj/mpp/MPP12Reader.java @@ -1530,8 +1530,6 @@ private void processResourceData() throws IOException resource.enableEvents(); - resource.setGUID(MPPUtility.getGUID(data2, 0)); - hyperlinkReader.read(resource, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.HYPERLINK_DATA))); resource.setID(Integer.valueOf(MPPUtility.getInt(data, fieldMap.getFixedDataOffset(ResourceField.ID)))); From a86ba98d7fabb4ce4ccfe9135a170e55fbf08dbe Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 29 Jan 2024 15:04:03 +0000 Subject: [PATCH 04/18] WIP --- .../mpxj/mpp/ResourceAssignmentFactory.java | 15 ++++++++++--- .../sf/mpxj/mpp/TimephasedDataFactory.java | 22 +++++++++---------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java b/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java index 972c70f1ab..83124e77f8 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; @@ -196,19 +197,27 @@ public void process(ProjectFile file, FieldMap fieldMap, FieldMap enterpriseCust 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); } diff --git a/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java b/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java index dd203b2cf1..23c1f83bec 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())) { From 2abb90d00a7ebe2437dd8452cfcb74e356a8b72b Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 29 Jan 2024 16:45:44 +0000 Subject: [PATCH 05/18] WIP --- src/main/java/net/sf/mpxj/Resource.java | 2 +- .../java/net/sf/mpxj/ResourceAssignment.java | 8 ++--- .../DefaultTimephasedCostContainer.java | 10 ++++-- .../DefaultTimephasedWorkContainer.java | 10 ++++-- .../net/sf/mpxj/common/MPPResourceField.java | 2 ++ .../java/net/sf/mpxj/mpp/MPP14Reader.java | 34 +++++++++++++++++-- .../mpxj/mpp/ResourceAssignmentFactory.java | 8 ++--- .../sf/mpxj/mpp/TimephasedDataFactory.java | 8 ++--- .../java/net/sf/mpxj/mspdi/MSPDIReader.java | 8 ++--- 9 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/main/java/net/sf/mpxj/Resource.java b/src/main/java/net/sf/mpxj/Resource.java index d4abb8cb0c..40eddd94ae 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. diff --git a/src/main/java/net/sf/mpxj/ResourceAssignment.java b/src/main/java/net/sf/mpxj/ResourceAssignment.java index 75197e6986..f2cd6bae75 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/common/DefaultTimephasedCostContainer.java b/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java index 810d74e7ca..444425b901 100644 --- a/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java +++ b/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java @@ -25,7 +25,9 @@ import java.util.List; +import net.sf.mpxj.ProjectCalendar; import net.sf.mpxj.ResourceAssignment; +import net.sf.mpxj.TimePeriodEntity; import net.sf.mpxj.TimephasedCost; import net.sf.mpxj.TimephasedCostContainer; @@ -42,8 +44,9 @@ public class DefaultTimephasedCostContainer implements TimephasedCostContainer * @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 assignment, TimephasedNormaliser normaliser, List data, boolean raw) { + m_calendar = calendar; m_data = data; m_raw = raw; m_assignment = assignment; @@ -57,7 +60,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_assignment, m_data); m_raw = false; } return m_data; @@ -73,8 +76,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_assignment; } diff --git a/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java b/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java index 673341e80b..6a162da58d 100644 --- a/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java +++ b/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java @@ -26,7 +26,9 @@ import java.util.ArrayList; import java.util.List; +import net.sf.mpxj.ProjectCalendar; import net.sf.mpxj.ResourceAssignment; +import net.sf.mpxj.TimePeriodEntity; import net.sf.mpxj.TimephasedWork; import net.sf.mpxj.TimephasedWorkContainer; @@ -43,8 +45,9 @@ public class DefaultTimephasedWorkContainer implements TimephasedWorkContainer * @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 assignment, TimephasedNormaliser normaliser, List data, boolean raw) { + m_calendar = calendar; m_data = data; m_raw = raw; m_assignment = assignment; @@ -79,7 +82,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_assignment, m_data); m_raw = false; } return m_data; @@ -100,8 +103,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_assignment; } diff --git a/src/main/java/net/sf/mpxj/common/MPPResourceField.java b/src/main/java/net/sf/mpxj/common/MPPResourceField.java index 7398315beb..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; diff --git a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java index 541ecafdf7..f88bcf40a8 100644 --- a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java +++ b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java @@ -37,11 +37,19 @@ import java.util.Set; import java.util.TreeMap; +import net.sf.mpxj.AssignmentField; import net.sf.mpxj.FieldTypeClass; +import net.sf.mpxj.TimephasedCost; +import net.sf.mpxj.TimephasedCostContainer; +import net.sf.mpxj.TimephasedWork; +import net.sf.mpxj.TimephasedWorkContainer; +import net.sf.mpxj.common.AssignmentFieldLists; import net.sf.mpxj.common.BooleanHelper; import net.sf.mpxj.common.InputStreamHelper; import net.sf.mpxj.common.LocalDateTimeHelper; import net.sf.mpxj.common.MicrosoftProjectConstants; +import net.sf.mpxj.common.ResourceFieldLists; +import net.sf.mpxj.common.TimephasedNormaliser; import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentInputStream; @@ -1495,6 +1503,10 @@ private void processResourceData() throws IOException FieldMap enterpriseCustomFieldMap = new FieldMap14(m_file); enterpriseCustomFieldMap.createEnterpriseCustomFieldMap(m_projectProps, FieldTypeClass.RESOURCE); + TimephasedDataFactory timephasedFactory = new TimephasedDataFactory(); + TimephasedNormaliser baselineWorkNormaliser = new MPPTimephasedBaselineWorkNormaliser(); + TimephasedNormaliser baselineCostNormaliser = new MPPTimephasedBaselineCostNormaliser(); + 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,8 +1515,8 @@ 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(rscVarData); + System.out.println(rscVarMeta.toString(fieldMap)); + System.out.println(rscVarData); //System.out.println(rscFixedMeta); //System.out.println(rscFixedData); //System.out.println(rscFixed2Meta); @@ -1651,6 +1663,24 @@ 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); + if (work != null) + { + System.out.println("Baseline " + index); + work.getData().forEach(System.out::println); + } +// TimephasedCostContainer cost = timephasedFactory.getBaselineCost(calendar, resource, baselineCostNormaliser, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceFieldLists.TIMEPHASED_BASELINE_COST[index])), true); + } + +// byte[] timephasedActualWorkData = rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.TIMEPHASED_ACTUAL_WORK)); +// byte[] timephasedWorkData = rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.TIMEPHASED_WORK)); +// byte[] timephasedActualOvertimeWorkData = rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.TIMEPHASED_ACTUAL_OVERTIME_WORK)); + m_eventManager.fireResourceReadEvent(resource); } } diff --git a/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java b/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java index 83124e77f8..08125ff941 100644 --- a/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java +++ b/src/main/java/net/sf/mpxj/mpp/ResourceAssignmentFactory.java @@ -190,7 +190,7 @@ public void process(ProjectFile file, FieldMap fieldMap, FieldMap enterpriseCust for (int index = 0; index < AssignmentFieldLists.TIMEPHASED_BASELINE_WORK.length; index++) { assignment.setTimephasedBaselineWork(index, timephasedFactory.getBaselineWork(calendar, assignment, baselineWorkNormaliser, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentFieldLists.TIMEPHASED_BASELINE_WORK[index])), !useRawTimephasedData)); - assignment.setTimephasedBaselineCost(index, timephasedFactory.getBaselineCost(assignment, baselineCostNormaliser, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentFieldLists.TIMEPHASED_BASELINE_COST[index])), !useRawTimephasedData)); + assignment.setTimephasedBaselineCost(index, timephasedFactory.getBaselineCost(calendar, assignment, baselineCostNormaliser, assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentFieldLists.TIMEPHASED_BASELINE_COST[index])), !useRawTimephasedData)); } byte[] timephasedActualWorkData = assnVarData.getByteArray(varDataId, fieldMap.getVarDataKey(AssignmentField.TIMEPHASED_ACTUAL_WORK)); @@ -231,9 +231,9 @@ public void process(ProjectFile file, FieldMap fieldMap, FieldMap enterpriseCust createTimephasedData(file, assignment, timephasedWork, timephasedActualWork); - assignment.setTimephasedWork(new DefaultTimephasedWorkContainer(assignment, normaliser, timephasedWork, !useRawTimephasedData)); - assignment.setTimephasedActualWork(new DefaultTimephasedWorkContainer(assignment, normaliser, timephasedActualWork, !useRawTimephasedData)); - assignment.setTimephasedActualOvertimeWork(new DefaultTimephasedWorkContainer(assignment, normaliser, timephasedActualOvertimeWork, !useRawTimephasedData)); + assignment.setTimephasedWork(new DefaultTimephasedWorkContainer(calendar, assignment, normaliser, timephasedWork, !useRawTimephasedData)); + assignment.setTimephasedActualWork(new DefaultTimephasedWorkContainer(calendar, assignment, normaliser, timephasedActualWork, !useRawTimephasedData)); + assignment.setTimephasedActualOvertimeWork(new DefaultTimephasedWorkContainer(calendar, assignment, normaliser, timephasedActualOvertimeWork, !useRawTimephasedData)); if (timephasedWorkData != null) { diff --git a/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java b/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java index 23c1f83bec..e35f6f91cf 100644 --- a/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java +++ b/src/main/java/net/sf/mpxj/mpp/TimephasedDataFactory.java @@ -288,7 +288,7 @@ public List getPlannedWork(ProjectCalendar calendar, TimePeriodE * @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 8cdd506188..97338de4e8 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java @@ -1983,8 +1983,8 @@ private void readAssignment(Project.Assignments.Assignment assignment, SplitTask raw = false; } - DefaultTimephasedWorkContainer timephasedCompleteData = new DefaultTimephasedWorkContainer(mpx, workNormaliser, timephasedComplete, raw); - DefaultTimephasedWorkContainer timephasedPlannedData = new DefaultTimephasedWorkContainer(mpx, workNormaliser, timephasedPlanned, raw); + DefaultTimephasedWorkContainer timephasedCompleteData = new DefaultTimephasedWorkContainer(calendar, mpx, workNormaliser, timephasedComplete, raw); + DefaultTimephasedWorkContainer timephasedPlannedData = new DefaultTimephasedWorkContainer(calendar, mpx, workNormaliser, timephasedPlanned, raw); mpx.setActualCost(DatatypeConverter.parseCurrency(assignment.getActualCost())); mpx.setActualFinish(assignment.getActualFinish()); @@ -2053,7 +2053,7 @@ private void readAssignment(Project.Assignments.Assignment assignment, SplitTask List timephasedData = readTimephasedWork(calendar, assignment, entry.getKey().intValue()); if (!timephasedData.isEmpty()) { - entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(mpx, workNormaliser, timephasedData, true)); + entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(calendar, mpx, workNormaliser, timephasedData, true)); } } @@ -2063,7 +2063,7 @@ private void readAssignment(Project.Assignments.Assignment assignment, SplitTask List timephasedData = readTimephasedCost(calendar, assignment, entry.getKey().intValue()); if (!timephasedData.isEmpty()) { - entry.getValue().apply(mpx, new DefaultTimephasedCostContainer(mpx, costNormaliser, timephasedData, true)); + entry.getValue().apply(mpx, new DefaultTimephasedCostContainer(calendar, mpx, costNormaliser, timephasedData, true)); } } From b517b57a9ade1a4935e90b80778ac757c3c2bdaa Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 29 Jan 2024 17:03:23 +0000 Subject: [PATCH 06/18] WIP --- src/main/java/net/sf/mpxj/mpp/MPP14Reader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java index f88bcf40a8..cd99ee89fb 100644 --- a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java +++ b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java @@ -1669,12 +1669,14 @@ private void processResourceData() throws IOException { 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); } -// TimephasedCostContainer cost = timephasedFactory.getBaselineCost(calendar, resource, baselineCostNormaliser, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceFieldLists.TIMEPHASED_BASELINE_COST[index])), true); } // byte[] timephasedActualWorkData = rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.TIMEPHASED_ACTUAL_WORK)); From c96437421a7133f2e601ccbe9b5da35ac841dbc7 Mon Sep 17 00:00:00 2001 From: joniles Date: Tue, 30 Jan 2024 17:21:17 +0000 Subject: [PATCH 07/18] WIP --- mkdocs/docs/field-guide.md | 2 + mkdocs/docs/mpp-field-guide.md | 2 + src/main/java/net/sf/mpxj/Resource.java | 51 ++++++++++ .../java/net/sf/mpxj/mpp/MPP14Reader.java | 38 ++++--- .../java/net/sf/mpxj/mspdi/MSPDIWriter.java | 98 ++++++++++++++----- 5 files changed, 146 insertions(+), 45 deletions(-) diff --git a/mkdocs/docs/field-guide.md b/mkdocs/docs/field-guide.md index 98208bcf85..b5ceeb110d 100644 --- a/mkdocs/docs/field-guide.md +++ b/mkdocs/docs/field-guide.md @@ -817,6 +817,8 @@ 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 d7f556f871..75fc7c86b4 100644 --- a/mkdocs/docs/mpp-field-guide.md +++ b/mkdocs/docs/mpp-field-guide.md @@ -722,6 +722,8 @@ 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/Resource.java b/src/main/java/net/sf/mpxj/Resource.java index 40eddd94ae..2bc2f4a264 100644 --- a/src/main/java/net/sf/mpxj/Resource.java +++ b/src/main/java/net/sf/mpxj/Resource.java @@ -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/mpp/MPP14Reader.java b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java index cd99ee89fb..764a775e17 100644 --- a/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java +++ b/src/main/java/net/sf/mpxj/mpp/MPP14Reader.java @@ -1515,8 +1515,8 @@ 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.toString(fieldMap)); - System.out.println(rscVarData); + //System.out.println(rscVarMeta.toString(fieldMap)); + //System.out.println(rscVarData); //System.out.println(rscFixedMeta); //System.out.println(rscFixedData); //System.out.println(rscFixed2Meta); @@ -1663,25 +1663,21 @@ 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); - } - } - -// byte[] timephasedActualWorkData = rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.TIMEPHASED_ACTUAL_WORK)); -// byte[] timephasedWorkData = rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.TIMEPHASED_WORK)); -// byte[] timephasedActualOvertimeWorkData = rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.TIMEPHASED_ACTUAL_OVERTIME_WORK)); +// 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); } diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java index c8f6c0d270..c87ea75cd0 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java @@ -1119,7 +1119,9 @@ private Project.Resources.Resource writeResource(Resource mpx) writeAvailability(xml, mpx); - return (xml); + writeResourceTimephasedData(xml, mpx); + + return xml; } /** @@ -2194,7 +2196,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(); @@ -2205,19 +2207,48 @@ 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++) + { + 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]); } } @@ -2253,17 +2284,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) { @@ -2498,7 +2518,7 @@ private void writeAssignmentTimephasedWorkData(BigInteger assignmentID, List list, List data, int type) + private void writeTimephasedCostData(BigInteger assignmentID, List list, List data, int type) { if (data == null) { @@ -2703,7 +2723,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, @@ -2718,7 +2738,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, @@ -2733,5 +2753,35 @@ 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())); } From b18072a809559a03f4c4b4d8183dd4e9428e39ce Mon Sep 17 00:00:00 2001 From: joniles Date: Fri, 2 Feb 2024 12:22:02 +0000 Subject: [PATCH 08/18] WIP --- .../mpxj/common/DefaultTimephasedCostContainer.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java b/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java index 444425b901..ad4997d891 100644 --- a/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java +++ b/src/main/java/net/sf/mpxj/common/DefaultTimephasedCostContainer.java @@ -26,7 +26,6 @@ import java.util.List; import net.sf.mpxj.ProjectCalendar; -import net.sf.mpxj.ResourceAssignment; import net.sf.mpxj.TimePeriodEntity; import net.sf.mpxj.TimephasedCost; import net.sf.mpxj.TimephasedCostContainer; @@ -39,17 +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(ProjectCalendar calendar, TimePeriodEntity 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; } @@ -60,7 +59,7 @@ public DefaultTimephasedCostContainer(ProjectCalendar calendar, TimePeriodEntity { if (m_raw) { - m_normaliser.normalise(m_calendar, m_assignment, m_data); + m_normaliser.normalise(m_calendar, m_parent, m_data); m_raw = false; } return m_data; @@ -80,5 +79,5 @@ public DefaultTimephasedCostContainer(ProjectCalendar calendar, TimePeriodEntity private final List m_data; private boolean m_raw; private final TimephasedNormaliser m_normaliser; - private final TimePeriodEntity m_assignment; + private final TimePeriodEntity m_parent; } From 7b7ff88f5d7f9b66826770df05cad4098bc40b91 Mon Sep 17 00:00:00 2001 From: joniles Date: Fri, 2 Feb 2024 12:23:16 +0000 Subject: [PATCH 09/18] WIP --- .../mpxj/common/DefaultTimephasedWorkContainer.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java b/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java index 6a162da58d..03b428d7ef 100644 --- a/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java +++ b/src/main/java/net/sf/mpxj/common/DefaultTimephasedWorkContainer.java @@ -27,7 +27,6 @@ import java.util.List; import net.sf.mpxj.ProjectCalendar; -import net.sf.mpxj.ResourceAssignment; import net.sf.mpxj.TimePeriodEntity; import net.sf.mpxj.TimephasedWork; import net.sf.mpxj.TimephasedWorkContainer; @@ -40,17 +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(ProjectCalendar calendar, TimePeriodEntity 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; } @@ -66,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) @@ -82,7 +81,7 @@ private DefaultTimephasedWorkContainer(DefaultTimephasedWorkContainer source, do { if (m_raw) { - m_normaliser.normalise(m_calendar, m_assignment, m_data); + m_normaliser.normalise(m_calendar, m_parent, m_data); m_raw = false; } return m_data; @@ -107,5 +106,5 @@ private DefaultTimephasedWorkContainer(DefaultTimephasedWorkContainer source, do private final List m_data; private boolean m_raw; private final TimephasedNormaliser m_normaliser; - private final TimePeriodEntity m_assignment; + private final TimePeriodEntity m_parent; } From f079124fffd56c08c32fb757adfa1716b07d8ca0 Mon Sep 17 00:00:00 2001 From: joniles Date: Fri, 2 Feb 2024 12:42:57 +0000 Subject: [PATCH 10/18] WIP --- .../net/sf/mpxj/common/NullNormaliser.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/net/sf/mpxj/common/NullNormaliser.java 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<>(); +} From 18c3226ae349dd46730cb2f8ccc68f34de74f705 Mon Sep 17 00:00:00 2001 From: joniles Date: Fri, 2 Feb 2024 14:47:14 +0000 Subject: [PATCH 11/18] WIP --- .../net/sf/mpxj/common/NewWorkNormaliser.java | 74 +++++++++++++++++++ .../java/net/sf/mpxj/mspdi/MSPDIReader.java | 4 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java 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..1d21e8114e --- /dev/null +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -0,0 +1,74 @@ +package net.sf.mpxj.common; +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; + +// +// TODO: why are the units changing? Consider logic for tidying up start/end dates - next work start etc +// +public class NewWorkNormaliser implements TimephasedNormaliser +{ + @Override public void normalise(ProjectCalendar calendar, TimePeriodEntity parent, List list) + { + int index = 0; + boolean lastItemIsStandard = false; + + while (index < list.size()) + { + TimephasedWork item = list.get(index); + 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)) + { + 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); + } + else + { + if (lastItemIsStandard) + { + TimephasedWork lastItem = list.get(index-1); + 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) + { + 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; + } + } + 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; + } + } + } + else + { + index++; + lastItemIsStandard = false; + } + } + } + + public static final NewWorkNormaliser INSTANCE = new NewWorkNormaliser(); +} diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java index 09223f6e34..d84f432d61 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java @@ -58,6 +58,8 @@ import net.sf.mpxj.common.DefaultTimephasedCostContainer; import net.sf.mpxj.common.LocalDateHelper; import net.sf.mpxj.common.LocalDateTimeHelper; +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; @@ -2046,7 +2048,7 @@ private void readAssignment(Project.Assignments.Assignment assignment) List timephasedData = readTimephasedWork(assignment, entry.getKey().intValue()); if (!timephasedData.isEmpty()) { - entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(calendar, mpx, MSPDITimephasedWorkNormaliser.INSTANCE, timephasedData, true)); + entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(m_projectFile.getBaselineCalendar(), mpx, NewWorkNormaliser.INSTANCE, timephasedData, true)); } } From fd577b2dba75fd43a99cd622a0a06808dfe6e4b4 Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 5 Feb 2024 10:12:01 +0000 Subject: [PATCH 12/18] WIP --- .../net/sf/mpxj/common/NewWorkNormaliser.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java index 1d21e8114e..31aca36406 100644 --- a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -1,4 +1,5 @@ package net.sf.mpxj.common; +import java.time.LocalDateTime; import java.util.List; import net.sf.mpxj.Duration; @@ -13,6 +14,22 @@ public class NewWorkNormaliser implements TimephasedNormaliser { @Override public void normalise(ProjectCalendar calendar, TimePeriodEntity parent, List list) + { + if (list == null) + { + return; + } + + if (list.size() > 1) + { + mergeDays(calendar, list); + } + + normaliseStarts(calendar, list); + normaliseFinishes(calendar, list); + } + + private void mergeDays(ProjectCalendar calendar, List list) { int index = 0; boolean lastItemIsStandard = false; @@ -70,5 +87,41 @@ public class NewWorkNormaliser implements TimephasedNormaliser } } + private void normaliseStarts(ProjectCalendar calendar, List list) + { + for (TimephasedWork item : list) + { + LocalDateTime nextWorkStart = calendar.getNextWorkStart(item.getStart()); + if (item.getStart().isEqual(nextWorkStart)) + { + continue; + } + + Duration calendarWork = calendar.getWork(nextWorkStart, item.getFinish(), item.getTotalAmount().getUnits()); + if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) + { + item.setStart(nextWorkStart); + } + } + } + + private void normaliseFinishes(ProjectCalendar calendar, List list) + { + for (TimephasedWork item : list) + { + LocalDateTime previousWorkFinish = calendar.getPreviousWorkFinish(item.getFinish()); + if (item.getStart().isEqual(previousWorkFinish)) + { + continue; + } + + 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(); } From 9b5039314bbccfc90b858c42c7d892079aad9af2 Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 5 Feb 2024 11:43:08 +0000 Subject: [PATCH 13/18] WIP --- src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java index 31aca36406..14e55b2b02 100644 --- a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -92,7 +92,7 @@ private void normaliseStarts(ProjectCalendar calendar, List list for (TimephasedWork item : list) { LocalDateTime nextWorkStart = calendar.getNextWorkStart(item.getStart()); - if (item.getStart().isEqual(nextWorkStart)) + if (item.getStart().isEqual(nextWorkStart) || nextWorkStart.isAfter(item.getFinish())) { continue; } @@ -110,7 +110,7 @@ private void normaliseFinishes(ProjectCalendar calendar, List li for (TimephasedWork item : list) { LocalDateTime previousWorkFinish = calendar.getPreviousWorkFinish(item.getFinish()); - if (item.getStart().isEqual(previousWorkFinish)) + if (item.getStart().isEqual(previousWorkFinish) || item.getStart().isAfter(previousWorkFinish)) { continue; } From 0eadd0fccca3a6776a21e2a4488005d00f1aec89 Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 5 Feb 2024 15:03:28 +0000 Subject: [PATCH 14/18] WIP --- mkdocs/docs/field-guide.md | 2 ++ src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mkdocs/docs/field-guide.md b/mkdocs/docs/field-guide.md index f9b7a3d9f1..b5ceeb110d 100644 --- a/mkdocs/docs/field-guide.md +++ b/mkdocs/docs/field-guide.md @@ -1008,6 +1008,7 @@ Assignment GUID| | | | | |✓| |✓| |✓| | | |✓|✓|✓| | | | Assignment Resource GUID| | | | | | | |✓| | | | | | | | | | | | | | |  Assignment Task GUID| | | | | | | |✓| | | | | | | | | | | | | | |  Assignment Units|✓|✓|✓| |✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓| | |✓|✓|✓ +BCWS| | | | | | | | | |✓| | | | | | | | | | | | |  Budget Cost| | | | | | | |✓| |✓| | | | | | | | | | | | |  Budget Work| | | | | | | |✓| |✓| | | | | | | | | | | | |  CV| | | | | | | | | |✓| | | | | | | | | | | | |  @@ -1053,6 +1054,7 @@ Resource Unique ID|✓|✓|✓| |✓|✓|✓|✓|✓|✓|✓|✓|✓|✓|✓| Response Pending| | | | | | |✓|✓| | | | | | | | | | | | | | |  Resume| | | | | | | |✓| |✓| | | | | | | |✓| | | | |  Role Unique ID| | | | | | | | | | | | | |✓|✓|✓| | | | | | |  +SV| | | | | | | | | |✓| | | | | | | | | | | | |  Start|✓| | | | | |✓|✓|✓|✓| | |✓|✓|✓|✓| |✓| | | | |  Start Variance| | | | | | |✓|✓| |✓| | | | | | | | | | | | |  Stop| | | | | | | |✓| |✓| | | | | | | |✓| | | | |  diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java index d84f432d61..73739d7cb9 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java @@ -421,6 +421,7 @@ private void readCalendars(Project project, HashMap } m_projectFile.setDefaultCalendar(defaultCalendar); + m_baselineCalendar = m_projectFile.getBaselineCalendar(); } /** @@ -446,7 +447,6 @@ private static void updateBaseCalendarNames(List timephasedData = readTimephasedWork(assignment, entry.getKey().intValue()); if (!timephasedData.isEmpty()) { - entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(m_projectFile.getBaselineCalendar(), mpx, NewWorkNormaliser.INSTANCE, timephasedData, true)); + entry.getValue().apply(mpx, new DefaultTimephasedWorkContainer(m_baselineCalendar, mpx, NewWorkNormaliser.INSTANCE, timephasedData, true)); } } @@ -2293,6 +2293,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 = { From 0721b99b91edae928133c76a268d8399b5f95d18 Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 5 Feb 2024 16:41:43 +0000 Subject: [PATCH 15/18] WIP --- src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java index 14e55b2b02..134508b656 100644 --- a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -8,9 +8,6 @@ import net.sf.mpxj.TimeUnit; import net.sf.mpxj.TimephasedWork; -// -// TODO: why are the units changing? Consider logic for tidying up start/end dates - next work start etc -// public class NewWorkNormaliser implements TimephasedNormaliser { @Override public void normalise(ProjectCalendar calendar, TimePeriodEntity parent, List list) From 09bdc5def59b936655fae8f97110999bc494c1a9 Mon Sep 17 00:00:00 2001 From: joniles Date: Mon, 5 Feb 2024 18:55:33 +0000 Subject: [PATCH 16/18] WIP --- .../net/sf/mpxj/common/NewCostNormaliser.java | 82 +++++++++++++++++++ .../net/sf/mpxj/common/NewWorkNormaliser.java | 21 ++++- .../java/net/sf/mpxj/mspdi/MSPDIReader.java | 2 + 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/sf/mpxj/common/NewCostNormaliser.java 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 index 134508b656..69bf6e6ac9 100644 --- a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -10,6 +10,10 @@ public class NewWorkNormaliser implements TimephasedNormaliser { + private NewWorkNormaliser() + { + + } @Override public void normalise(ProjectCalendar calendar, TimePeriodEntity parent, List list) { if (list == null) @@ -94,11 +98,15 @@ private void normaliseStarts(ProjectCalendar calendar, List list continue; } - Duration calendarWork = calendar.getWork(nextWorkStart, item.getFinish(), item.getTotalAmount().getUnits()); - if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) + if (item.getStart().isEqual(calendar.getPreviousWorkFinish(item.getStart()))) { item.setStart(nextWorkStart); } +// Duration calendarWork = calendar.getWork(nextWorkStart, item.getFinish(), item.getTotalAmount().getUnits()); +// if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) +// { +// item.setStart(nextWorkStart); +// } } } @@ -112,11 +120,16 @@ private void normaliseFinishes(ProjectCalendar calendar, List li continue; } - Duration calendarWork = calendar.getWork(item.getStart(), previousWorkFinish, item.getTotalAmount().getUnits()); - if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) + 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); +// } } } diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIReader.java index 73739d7cb9..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,7 @@ 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; @@ -2059,6 +2060,7 @@ private void readAssignment(Project.Assignments.Assignment assignment) if (!timephasedData.isEmpty()) { 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)); } } From 75b1e7eeada3d3cb664cd2fd1949cfd14ce6967b Mon Sep 17 00:00:00 2001 From: joniles Date: Wed, 7 Feb 2024 13:43:51 +0000 Subject: [PATCH 17/18] WIP --- src/main/java/net/sf/mpxj/TimephasedItem.java | 2 +- .../net/sf/mpxj/common/NewWorkNormaliser.java | 39 ++++++++++++------- .../java/net/sf/mpxj/mspdi/MSPDIWriter.java | 1 + 3 files changed, 28 insertions(+), 14 deletions(-) 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/NewWorkNormaliser.java b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java index 69bf6e6ac9..87ff3a870b 100644 --- a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -21,6 +21,10 @@ private NewWorkNormaliser() return; } + System.out.println("Initial Items"); + list.forEach(System.out::println); + System.out.println(); + if (list.size() > 1) { mergeDays(calendar, list); @@ -38,26 +42,32 @@ private void mergeDays(ProjectCalendar calendar, List list) 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; @@ -69,10 +79,12 @@ private void mergeDays(ProjectCalendar calendar, List list) // 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++; @@ -82,6 +94,7 @@ private void mergeDays(ProjectCalendar calendar, List list) } else { + System.out.println("Item work is not equal to calendar work, leaving unchanged " + item); index++; lastItemIsStandard = false; } @@ -98,15 +111,15 @@ private void normaliseStarts(ProjectCalendar calendar, List list continue; } - if (item.getStart().isEqual(calendar.getPreviousWorkFinish(item.getStart()))) - { - item.setStart(nextWorkStart); - } -// Duration calendarWork = calendar.getWork(nextWorkStart, item.getFinish(), item.getTotalAmount().getUnits()); -// if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) +// if (item.getStart().isEqual(calendar.getPreviousWorkFinish(item.getStart()))) // { // item.setStart(nextWorkStart); // } + Duration calendarWork = calendar.getWork(nextWorkStart, item.getFinish(), item.getTotalAmount().getUnits()); + if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) + { + item.setStart(nextWorkStart); + } } } @@ -120,16 +133,16 @@ private void normaliseFinishes(ProjectCalendar calendar, List li 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()) +// 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); + } } } diff --git a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java index deb93b8e4e..1c22e3eb5c 100644 --- a/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java +++ b/src/main/java/net/sf/mpxj/mspdi/MSPDIWriter.java @@ -2216,6 +2216,7 @@ private void writeAssignmentTimephasedData(ResourceAssignment mpx, Project.Assig // Write the baselines 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]); } From 8894fe039521985173df5972c60b1142335dc59e Mon Sep 17 00:00:00 2001 From: joniles Date: Wed, 7 Feb 2024 18:48:44 +0000 Subject: [PATCH 18/18] WIP --- .../net/sf/mpxj/common/NewWorkNormaliser.java | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java index 87ff3a870b..bfdc84d010 100644 --- a/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java +++ b/src/main/java/net/sf/mpxj/common/NewWorkNormaliser.java @@ -28,6 +28,11 @@ private NewWorkNormaliser() 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); @@ -103,23 +108,44 @@ private void mergeDays(ProjectCalendar calendar, List list) 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; } -// if (item.getStart().isEqual(calendar.getPreviousWorkFinish(item.getStart()))) -// { -// item.setStart(nextWorkStart); -// } - Duration calendarWork = calendar.getWork(nextWorkStart, item.getFinish(), item.getTotalAmount().getUnits()); - if (calendarWork.getDuration() == item.getTotalAmount().getDuration()) + + 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"); +// } } }