taskjuggler 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +30 -0
- data/README.rdoc +1 -0
- data/data/tjp.vim +13 -9
- data/lib/taskjuggler/Allocation.rb +2 -2
- data/lib/taskjuggler/AttributeBase.rb +10 -0
- data/lib/taskjuggler/Attributes.rb +2 -8
- data/lib/taskjuggler/Journal.rb +20 -19
- data/lib/taskjuggler/PTNProxy.rb +14 -1
- data/lib/taskjuggler/ProjectFileParser.rb +3 -0
- data/lib/taskjuggler/ProjectFileScanner.rb +54 -44
- data/lib/taskjuggler/PropertyList.rb +32 -0
- data/lib/taskjuggler/PropertyTreeNode.rb +4 -0
- data/lib/taskjuggler/ResourceScenario.rb +53 -20
- data/lib/taskjuggler/RichText/Document.rb +7 -5
- data/lib/taskjuggler/RichText/Scanner.rb +38 -38
- data/lib/taskjuggler/RichText/TOCEntry.rb +1 -1
- data/lib/taskjuggler/TaskScenario.rb +78 -62
- data/lib/taskjuggler/TextParser.rb +4 -3
- data/lib/taskjuggler/TextParser/MacroTable.rb +3 -1
- data/lib/taskjuggler/TextParser/Scanner.rb +73 -58
- data/lib/taskjuggler/Tj3Config.rb +1 -1
- data/lib/taskjuggler/TjpSyntaxRules.rb +215 -67
- data/lib/taskjuggler/apps/Tj3Client.rb +1 -1
- data/lib/taskjuggler/daemon/ReportServer.rb +8 -1
- data/lib/taskjuggler/reports/ExportRE.rb +46 -0
- data/lib/taskjuggler/reports/MspXmlRE.rb +409 -0
- data/lib/taskjuggler/reports/Report.rb +29 -4
- data/lib/taskjuggler/reports/ReportBase.rb +0 -8
- data/lib/taskjuggler/reports/TableReport.rb +13 -3
- data/lib/taskjuggler/reports/TaskListRE.rb +1 -0
- data/lib/taskjuggler/reports/TjpExportRE.rb +1 -1
- data/manual/Rich_Text_Attributes +2 -2
- data/manual/Tutorial +15 -11
- data/manual/html/Day_To_Day_Juggling.html +1 -1
- data/manual/html/Getting_Started.html +1 -1
- data/manual/html/How_To_Contribute.html +1 -1
- data/manual/html/Installation.html +1 -1
- data/manual/html/Intro.html +1 -1
- data/manual/html/Reporting_Bugs.html +1 -1
- data/manual/html/Rich_Text_Attributes.html +1 -1
- data/manual/html/Software.html +1 -1
- data/manual/html/TaskJuggler_2x_Migration.html +1 -1
- data/manual/html/TaskJuggler_Internals.html +1 -1
- data/manual/html/The_TaskJuggler_Syntax.html +1 -1
- data/manual/html/Tutorial.html +5 -2
- data/manual/html/account.html +1 -1
- data/manual/html/account.task.html +1 -1
- data/manual/html/accountprefix.html +1 -1
- data/manual/html/accountreport.html +9 -3
- data/manual/html/accountroot.html +1 -1
- data/manual/html/active.html +1 -1
- data/manual/html/adopt.task.html +3 -3
- data/manual/html/aggregate.html +1 -1
- data/manual/html/alert.html +1 -1
- data/manual/html/alertlevels.html +1 -1
- data/manual/html/allocate.html +5 -2
- data/manual/html/alphabet.html +1 -1
- data/manual/html/alternative.html +1 -1
- data/manual/html/author.html +1 -1
- data/manual/html/balance.html +1 -1
- data/manual/html/booking.resource.html +1 -1
- data/manual/html/booking.task.html +1 -1
- data/manual/html/caption.html +1 -1
- data/manual/html/cellcolor.column.html +1 -1
- data/manual/html/celltext.column.html +43 -4
- data/manual/html/center.html +1 -1
- data/manual/html/charge.html +1 -1
- data/manual/html/chargeset.html +2 -2
- data/manual/html/columnid.html +6 -6
- data/manual/html/columns.html +1 -1
- data/manual/html/complete.html +1 -1
- data/manual/html/copyright.html +1 -1
- data/manual/html/credits.html +1 -1
- data/manual/html/currency.html +1 -1
- data/manual/html/currencyformat.html +1 -1
- data/manual/html/dailymax.html +1 -1
- data/manual/html/dailymin.html +1 -1
- data/manual/html/dailyworkinghours.html +1 -1
- data/manual/html/date.extend.html +1 -1
- data/manual/html/date.html +2 -2
- data/manual/html/definitions.html +1 -1
- data/manual/html/depends.html +1 -1
- data/manual/html/details.html +1 -1
- data/manual/html/disabled.html +1 -1
- data/manual/html/duration.html +1 -1
- data/manual/html/efficiency.html +1 -1
- data/manual/html/effort.html +1 -1
- data/manual/html/email.html +1 -1
- data/manual/html/enabled.html +1 -1
- data/manual/html/end.column.html +1 -1
- data/manual/html/end.html +1 -1
- data/manual/html/end.limit.html +1 -1
- data/manual/html/end.report.html +9 -3
- data/manual/html/end.timesheet.html +1 -1
- data/manual/html/endcredit.html +1 -1
- data/manual/html/epilog.html +1 -1
- data/manual/html/export.html +73 -5
- data/manual/html/extend.html +1 -1
- data/manual/html/fail.html +1 -1
- data/manual/html/fdl.html +1 -1
- data/manual/html/flags.account.html +1 -1
- data/manual/html/flags.html +1 -1
- data/manual/html/flags.journalentry.html +1 -1
- data/manual/html/flags.report.html +1 -1
- data/manual/html/flags.resource.html +1 -1
- data/manual/html/flags.statussheet.html +1 -1
- data/manual/html/flags.task.html +1 -1
- data/manual/html/flags.timesheet.html +1 -1
- data/manual/html/fontcolor.column.html +1 -1
- data/manual/html/footer.html +1 -1
- data/manual/html/formats.export.html +72 -0
- data/manual/html/formats.html +3 -3
- data/manual/html/functions.html +3 -3
- data/manual/html/gapduration.html +2 -2
- data/manual/html/gaplength.html +1 -1
- data/manual/html/halign.center.html +1 -1
- data/manual/html/halign.column.html +1 -1
- data/manual/html/halign.left.html +1 -1
- data/manual/html/halign.right.html +1 -1
- data/manual/html/hasalert.html +1 -1
- data/manual/html/header.html +1 -1
- data/manual/html/headline.html +1 -1
- data/manual/html/height.html +1 -1
- data/manual/html/hideaccount.html +1 -1
- data/manual/html/hidejournalentry.html +1 -1
- data/manual/html/hidereport.html +1 -1
- data/manual/html/hideresource.html +1 -1
- data/manual/html/hidetask.html +1 -1
- data/manual/html/icalreport.html +1 -1
- data/manual/html/include.macro.html +1 -1
- data/manual/html/include.project.html +1 -1
- data/manual/html/include.properties.html +1 -1
- data/manual/html/index.html +1 -1
- data/manual/html/inherit.extend.html +1 -1
- data/manual/html/interval1.html +2 -2
- data/manual/html/interval2.html +2 -2
- data/manual/html/interval3.html +2 -2
- data/manual/html/interval4.html +2 -2
- data/manual/html/isactive.html +1 -1
- data/manual/html/ischildof.html +1 -1
- data/manual/html/isdependencyof.html +1 -1
- data/manual/html/isdutyof.html +1 -1
- data/manual/html/isfeatureof.html +1 -1
- data/manual/html/isleaf.html +1 -1
- data/manual/html/ismilestone.html +1 -1
- data/manual/html/isongoing.html +1 -1
- data/manual/html/isresource.html +1 -1
- data/manual/html/isresponsibilityof.html +1 -1
- data/manual/html/istask.html +1 -1
- data/manual/html/journalattributes.html +1 -1
- data/manual/html/journalentry.html +1 -1
- data/manual/html/journalmode.html +11 -3
- data/manual/html/leaveallowance.html +1 -1
- data/manual/html/leaves.html +1 -1
- data/manual/html/left.html +1 -1
- data/manual/html/length.html +1 -1
- data/manual/html/limits.allocate.html +1 -1
- data/manual/html/limits.html +1 -1
- data/manual/html/limits.resource.html +1 -1
- data/manual/html/limits.task.html +1 -1
- data/manual/html/listitem.column.html +1 -1
- data/manual/html/listtype.column.html +1 -1
- data/manual/html/loadunit.html +2 -2
- data/manual/html/logicalexpression.html +1 -1
- data/manual/html/logicalflagexpression.html +1 -1
- data/manual/html/macro.html +1 -1
- data/manual/html/managers.html +1 -1
- data/manual/html/mandatory.html +1 -1
- data/manual/html/maxend.html +1 -1
- data/manual/html/maximum.html +1 -1
- data/manual/html/maxstart.html +1 -1
- data/manual/html/milestone.html +1 -1
- data/manual/html/minend.html +1 -1
- data/manual/html/minimum.html +1 -1
- data/manual/html/minstart.html +1 -1
- data/manual/html/monthlymax.html +1 -1
- data/manual/html/monthlymin.html +1 -1
- data/manual/html/navbar.html +5 -1
- data/manual/html/navigator.html +1 -1
- data/manual/html/newtask.html +1 -1
- data/manual/html/nikureport.html +1 -1
- data/manual/html/note.task.html +1 -1
- data/manual/html/now.html +1 -1
- data/manual/html/numberformat.html +1 -1
- data/manual/html/onend.html +1 -1
- data/manual/html/onstart.html +1 -1
- data/manual/html/opennodes.html +1 -1
- data/manual/html/overtime.booking.html +1 -1
- data/manual/html/period.column.html +1 -1
- data/manual/html/period.limit.html +1 -1
- data/manual/html/period.report.html +1 -1
- data/manual/html/period.task.html +1 -1
- data/manual/html/persistent.html +1 -1
- data/manual/html/precedes.html +1 -1
- data/manual/html/priority.html +1 -1
- data/manual/html/priority.timesheet.html +1 -1
- data/manual/html/project.html +1 -1
- data/manual/html/projectid.html +1 -1
- data/manual/html/projectid.task.html +1 -1
- data/manual/html/projectids.html +1 -1
- data/manual/html/projection.html +1 -1
- data/manual/html/prolog.html +1 -1
- data/manual/html/properties.html +1 -1
- data/manual/html/purge.html +8 -5
- data/manual/html/rate.html +1 -1
- data/manual/html/rate.resource.html +1 -1
- data/manual/html/reference.extend.html +1 -1
- data/manual/html/remaining.html +1 -1
- data/manual/html/replace.html +1 -1
- data/manual/html/reportprefix.html +1 -1
- data/manual/html/resource.html +1 -1
- data/manual/html/resourceattributes.html +1 -1
- data/manual/html/resourceprefix.html +1 -1
- data/manual/html/resourcereport.html +9 -3
- data/manual/html/resourceroot.html +1 -1
- data/manual/html/resources.limit.html +1 -1
- data/manual/html/responsible.html +1 -1
- data/manual/html/richtext.extend.html +1 -1
- data/manual/html/right.html +1 -1
- data/manual/html/rollupaccount.html +1 -1
- data/manual/html/rollupresource.html +1 -1
- data/manual/html/rolluptask.html +1 -1
- data/manual/html/scale.column.html +1 -1
- data/manual/html/scenario.html +1 -1
- data/manual/html/scenario.ical.html +1 -1
- data/manual/html/scenarios.export.html +1 -1
- data/manual/html/scenarios.html +1 -1
- data/manual/html/scenariospecific.extend.html +1 -1
- data/manual/html/scheduled.html +2 -2
- data/manual/html/scheduling.html +1 -1
- data/manual/html/select.html +1 -1
- data/manual/html/selfcontained.html +1 -1
- data/manual/html/shift.allocate.html +1 -1
- data/manual/html/shift.html +1 -1
- data/manual/html/shift.resource.html +1 -1
- data/manual/html/shift.task.html +1 -1
- data/manual/html/shift.timesheet.html +1 -1
- data/manual/html/shifts.allocate.html +1 -1
- data/manual/html/shifts.resource.html +1 -1
- data/manual/html/shifts.task.html +1 -1
- data/manual/html/shorttimeformat.html +1 -1
- data/manual/html/sloppy.booking.html +1 -1
- data/manual/html/sloppy.projection.html +1 -1
- data/manual/html/sortaccounts.html +1 -1
- data/manual/html/sortjournalentries.html +1 -1
- data/manual/html/sortresources.html +1 -1
- data/manual/html/sorttasks.html +1 -1
- data/manual/html/start.column.html +1 -1
- data/manual/html/start.html +1 -1
- data/manual/html/start.limit.html +1 -1
- data/manual/html/start.report.html +1 -1
- data/manual/html/startcredit.html +1 -1
- data/manual/html/status.statussheet.html +1 -1
- data/manual/html/status.timesheet.html +1 -1
- data/manual/html/statussheet.html +1 -1
- data/manual/html/statussheetreport.html +1 -1
- data/manual/html/strict.projection.html +1 -1
- data/manual/html/summary.html +1 -1
- data/manual/html/supplement.html +1 -1
- data/manual/html/supplement.resource.html +1 -1
- data/manual/html/supplement.task.html +1 -1
- data/manual/html/tagfile.html +1 -1
- data/manual/html/task.html +1 -1
- data/manual/html/task.statussheet.html +1 -1
- data/manual/html/task.timesheet.html +1 -1
- data/manual/html/taskattributes.html +1 -1
- data/manual/html/taskprefix.html +1 -1
- data/manual/html/taskreport.html +9 -3
- data/manual/html/taskroot.export.html +104 -0
- data/manual/html/taskroot.html +3 -3
- data/manual/html/text.extend.html +3 -3
- data/manual/html/textreport.html +9 -3
- data/manual/html/timeformat.html +1 -1
- data/manual/html/timeoff.nikureport.html +1 -1
- data/manual/html/timesheet.html +1 -1
- data/manual/html/timesheetreport.html +1 -1
- data/manual/html/timezone.export.html +1 -1
- data/manual/html/timezone.html +1 -1
- data/manual/html/timezone.report.html +1 -1
- data/manual/html/timezone.shift.html +1 -1
- data/manual/html/timingresolution.html +1 -1
- data/manual/html/title.column.html +1 -1
- data/manual/html/title.html +1 -1
- data/manual/html/toc.html +203 -189
- data/manual/html/tooltip.column.html +1 -1
- data/manual/html/tracereport.html +9 -3
- data/manual/html/trackingscenario.html +1 -1
- data/manual/html/treelevel.html +1 -1
- data/manual/html/vacation.html +1 -1
- data/manual/html/vacation.resource.html +1 -1
- data/manual/html/vacation.shift.html +1 -1
- data/manual/html/warn.html +1 -1
- data/manual/html/weeklymax.html +1 -1
- data/manual/html/weeklymin.html +1 -1
- data/manual/html/weekstartsmonday.html +1 -1
- data/manual/html/weekstartssunday.html +1 -1
- data/manual/html/width.column.html +1 -1
- data/manual/html/width.html +1 -1
- data/manual/html/work.html +1 -1
- data/manual/html/workinghours.project.html +1 -1
- data/manual/html/workinghours.resource.html +1 -1
- data/manual/html/workinghours.shift.html +1 -1
- data/manual/html/yearlyworkingdays.html +1 -1
- data/test/TestSuite/CSV-Reports/project-1.tji +0 -6
- data/test/TestSuite/CSV-Reports/refs/alert.csv +0 -7
- data/test/TestSuite/CSV-Reports/refs/celltext.csv +0 -7
- data/test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv +0 -3
- data/test/TestSuite/CSV-Reports/refs/sortByTree.csv +0 -7
- data/test/TestSuite/CSV-Reports/refs/sortBy_duration.down.csv +0 -7
- data/test/TestSuite/CSV-Reports/refs/sortBy_effort.up.csv +0 -7
- data/test/TestSuite/CSV-Reports/refs/sortBy_plan.start.down.csv +0 -7
- data/test/TestSuite/CSV-Reports/refs/taskreport.csv +0 -7
- data/test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv +0 -18
- data/test/TestSuite/CSV-Reports/refs/weekly.csv +0 -1
- data/test/TestSuite/HTML-Reports/Alerts-2.tjp +46 -0
- data/test/TestSuite/Scheduler/Correct/purge.tjp +30 -0
- data/test/TestSuite/Syntax/Correct/Export.tjp +8 -2
- data/test/TestSuite/Syntax/Correct/tutorial.tjp +0 -1
- metadata +16 -10
- data/test/TestSuite/CSV-Reports/refs/taskcounter.csv +0 -9
|
@@ -149,7 +149,7 @@ EOT
|
|
|
149
149
|
'purposes.')) do |arg|
|
|
150
150
|
@unsafeMode = true
|
|
151
151
|
end
|
|
152
|
-
@opts.on('--format [FORMAT]', [ :csv, :html, :niku, :tjp ],
|
|
152
|
+
@opts.on('--format [FORMAT]', [ :csv, :html, :mspxml, :niku, :tjp ],
|
|
153
153
|
format('Request the report to be generated in the specified' +
|
|
154
154
|
'format. Use multiple options to request multiple ' +
|
|
155
155
|
'formats. Supported formats are csv, html, niku and ' +
|
|
@@ -31,6 +31,8 @@ class TaskJuggler
|
|
|
31
31
|
|
|
32
32
|
# A reference to the TaskJuggler object that holds the project data.
|
|
33
33
|
@tj = tj
|
|
34
|
+
# This is set to the ID(s) of the reports that should be generated.
|
|
35
|
+
@reportId = 'unknown'
|
|
34
36
|
|
|
35
37
|
@lastPing = TjTime.new
|
|
36
38
|
|
|
@@ -114,6 +116,7 @@ class TaskJuggler
|
|
|
114
116
|
def generateReport(id, regExpMode, formats, dynamicAttributes)
|
|
115
117
|
info('generating_report', "Generating report #{id}")
|
|
116
118
|
startTime = Time.now
|
|
119
|
+
@reportId = id
|
|
117
120
|
begin
|
|
118
121
|
if (ok = @tj.generateReport(id, regExpMode, formats, dynamicAttributes))
|
|
119
122
|
info('report_id_generated',
|
|
@@ -146,6 +149,7 @@ class TaskJuggler
|
|
|
146
149
|
|
|
147
150
|
def checkTimeSheet(sheet)
|
|
148
151
|
info('check_time_sheet', "Checking time sheet #{sheet}")
|
|
152
|
+
@reportId = 'timesheet'
|
|
149
153
|
begin
|
|
150
154
|
ok = @tj.checkTimeSheet(sheet)
|
|
151
155
|
debug('', "Time sheet #{sheet} is #{ok ? '' : 'not '}ok")
|
|
@@ -158,6 +162,7 @@ class TaskJuggler
|
|
|
158
162
|
|
|
159
163
|
def checkStatusSheet(sheet)
|
|
160
164
|
info('check_status_sheet', "Checking status sheet #{sheet}")
|
|
165
|
+
@reportId = 'statussheet'
|
|
161
166
|
begin
|
|
162
167
|
ok = @tj.checkStatusSheet(sheet)
|
|
163
168
|
debug('', "Status sheet #{sheet} is #{ok ? '' : 'not '}ok")
|
|
@@ -175,7 +180,9 @@ class TaskJuggler
|
|
|
175
180
|
loop do
|
|
176
181
|
if TjTime.new - @lastPing > 120
|
|
177
182
|
error('ps_heartbeat_lost',
|
|
178
|
-
|
|
183
|
+
"Report server (Project #{@tj.project['projectid']} " +
|
|
184
|
+
"report #{@reportId}) lost heartbeat " +
|
|
185
|
+
'from ProjectServer. Terminating.')
|
|
179
186
|
end
|
|
180
187
|
sleep 30
|
|
181
188
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby -w
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
#
|
|
4
|
+
# = ExportRE.rb -- The TaskJuggler III Project Management Software
|
|
5
|
+
#
|
|
6
|
+
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012
|
|
7
|
+
# by Chris Schlaeger <chris@linux.com>
|
|
8
|
+
#
|
|
9
|
+
# This program is free software; you can redistribute it and/or modify
|
|
10
|
+
# it under the terms of version 2 of the GNU General Public License as
|
|
11
|
+
# published by the Free Software Foundation.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
require 'taskjuggler/reports/ReportBase'
|
|
15
|
+
require 'taskjuggler/reports/TjpExportRE'
|
|
16
|
+
require 'taskjuggler/reports/MspXmlRE'
|
|
17
|
+
|
|
18
|
+
class TaskJuggler
|
|
19
|
+
|
|
20
|
+
# This specialization of ReportBase implements an export of the
|
|
21
|
+
# project data in the TJP syntax format.
|
|
22
|
+
class ExportRE < ReportBase
|
|
23
|
+
|
|
24
|
+
# Create a new object and set some default values.
|
|
25
|
+
def initialize(report)
|
|
26
|
+
super(report)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def generateIntermediateFormat
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Return the project data in TJP syntax format.
|
|
34
|
+
def to_tjp
|
|
35
|
+
TjpExportRE.new(@report).to_tjp
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return the project data in Microsoft Project XML format.
|
|
39
|
+
def to_mspxml
|
|
40
|
+
MspXmlRE.new(@report).to_mspxml
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
#!/usr/bin/env ruby -w
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
#
|
|
4
|
+
# = MspXmlRE.rb -- The TaskJuggler III Project Management Software
|
|
5
|
+
#
|
|
6
|
+
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012
|
|
7
|
+
# by Chris Schlaeger <chris@linux.com>
|
|
8
|
+
#
|
|
9
|
+
# This program is free software; you can redistribute it and/or modify
|
|
10
|
+
# it under the terms of version 2 of the GNU General Public License as
|
|
11
|
+
# published by the Free Software Foundation.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
require 'taskjuggler/reports/ReportBase'
|
|
15
|
+
|
|
16
|
+
class TaskJuggler
|
|
17
|
+
|
|
18
|
+
# This specialization of ReportBase implements an export of the
|
|
19
|
+
# project data into Microsoft Project XML format. Due to limitations of MS
|
|
20
|
+
# Project and this implementation, only a subset of core data is being
|
|
21
|
+
# exported. The exported data is already a scheduled project with full
|
|
22
|
+
# resource/task assignment data.
|
|
23
|
+
class MspXmlRE < ReportBase
|
|
24
|
+
|
|
25
|
+
# Create a new object and set some default values.
|
|
26
|
+
def initialize(report)
|
|
27
|
+
super(report)
|
|
28
|
+
@scenarioIdx = 0
|
|
29
|
+
# Hash to map calendar names to UIDs (numbers).
|
|
30
|
+
@calendarUIDs = {}
|
|
31
|
+
@timeformat = "%Y-%m-%dT%H:%M:%S"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generateIntermediateFormat
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return the project data in Microsoft Project XML format.
|
|
39
|
+
def to_mspxml
|
|
40
|
+
@query = @project.reportContexts.last.query.dup
|
|
41
|
+
|
|
42
|
+
# Prepare the resource list.
|
|
43
|
+
@resourceList = PropertyList.new(@project.resources)
|
|
44
|
+
@resourceList.setSorting(a('sortResources'))
|
|
45
|
+
@resourceList = filterResourceList(@resourceList, nil, a('hideResource'),
|
|
46
|
+
a('rollupResource'), a('openNodes'))
|
|
47
|
+
@resourceList.sort!
|
|
48
|
+
|
|
49
|
+
# Prepare the task list.
|
|
50
|
+
@taskList = PropertyList.new(@project.tasks)
|
|
51
|
+
@taskList.includeAdopted
|
|
52
|
+
@taskList.setSorting(a('sortTasks'))
|
|
53
|
+
@taskList = filterTaskList(@taskList, nil, a('hideTask'), a('rollupTask'),
|
|
54
|
+
a('openNodes'))
|
|
55
|
+
@taskList.sort!
|
|
56
|
+
@taskList.checkForDuplicates(@report.sourceFileInfo)
|
|
57
|
+
|
|
58
|
+
@file = XMLDocument.new
|
|
59
|
+
@file << XMLBlob.new('<?xml version="1.0" encoding="UTF-8" ' +
|
|
60
|
+
'standalone="yes"?>')
|
|
61
|
+
@file << XMLComment.new(<<"EOT"
|
|
62
|
+
Generated by #{AppConfig.softwareName} v#{AppConfig.version} on #{TjTime.new}
|
|
63
|
+
For more information about #{AppConfig.softwareName} see #{AppConfig.contact}.
|
|
64
|
+
Project: #{@project['name']}
|
|
65
|
+
Date: #{@project['now']}
|
|
66
|
+
EOT
|
|
67
|
+
)
|
|
68
|
+
@file << (project =
|
|
69
|
+
XMLElement.new('Project',
|
|
70
|
+
'xmlns' =>
|
|
71
|
+
'http://schemas.microsoft.com/project'))
|
|
72
|
+
|
|
73
|
+
calendars = generateProjectAttributes(project)
|
|
74
|
+
generateTasks(project)
|
|
75
|
+
generateResources(project, calendars)
|
|
76
|
+
generateAssignments(project)
|
|
77
|
+
|
|
78
|
+
@file.to_s
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def generateProjectAttributes(p)
|
|
84
|
+
p << XMLNamedText.new('14', 'SaveVersion')
|
|
85
|
+
p << XMLNamedText.new(@report.name + '.xml', 'Name')
|
|
86
|
+
p << XMLNamedText.new(TjTime.new.to_s(@timeformat), 'CreationDate')
|
|
87
|
+
p << XMLNamedText.new('1', 'ScheduleFromStart')
|
|
88
|
+
p << XMLNamedText.new(@project['start'].to_s(@timeformat),
|
|
89
|
+
'StartDate')
|
|
90
|
+
p << XMLNamedText.new(@project['end'].to_s(@timeformat),
|
|
91
|
+
'FinishDate')
|
|
92
|
+
p << XMLNamedText.new('09:00:00', 'DefaultStartTime')
|
|
93
|
+
p << XMLNamedText.new('17:00:00', 'DefaultFinishTime')
|
|
94
|
+
p << XMLNamedText.new('1', 'CalendarUID')
|
|
95
|
+
p << XMLNamedText.new((@project.dailyWorkingHours * 60).to_i.to_s,
|
|
96
|
+
'MinutesPerDay')
|
|
97
|
+
p << XMLNamedText.new((@project.weeklyWorkingDays *
|
|
98
|
+
@project.dailyWorkingHours * 60).to_i.to_s,
|
|
99
|
+
'MinutesPerWeek')
|
|
100
|
+
p << XMLNamedText.new((@project.yearlyWorkingDays / 12).to_s,
|
|
101
|
+
'DaysPerMonth')
|
|
102
|
+
p << XMLNamedText.new(@project['now'].to_s(@timeformat), 'CurrentDate')
|
|
103
|
+
p << XMLNamedText.new(@project['now'].to_s(@timeformat), 'StatusDate')
|
|
104
|
+
loadUnitsMap = {
|
|
105
|
+
:minutes => 1,
|
|
106
|
+
:hours => 2,
|
|
107
|
+
:days => 3,
|
|
108
|
+
:weeks => 4,
|
|
109
|
+
:months => 5,
|
|
110
|
+
:quarters => 5,
|
|
111
|
+
:years => 5,
|
|
112
|
+
:shortAuto => 3,
|
|
113
|
+
:longAuto => 3
|
|
114
|
+
}
|
|
115
|
+
p << XMLNamedText.new(loadUnitsMap[a('loadUnit')].to_s, 'WorkFormat')
|
|
116
|
+
p << XMLNamedText.new('1', 'NewTasksAreManual')
|
|
117
|
+
p << XMLNamedText.new('0', 'SpreadPercentComplete')
|
|
118
|
+
rate = (@project['rate'] / @project.dailyWorkingHours).to_s
|
|
119
|
+
p << XMLNamedText.new(rate, 'StandardRate')
|
|
120
|
+
p << XMLNamedText.new(rate, 'OvertimeRate')
|
|
121
|
+
p << XMLNamedText.new(@project['currency'], 'CurrencySymbol')
|
|
122
|
+
p << XMLNamedText.new(@project['currency'], 'CurrencyCode')
|
|
123
|
+
#p << XMLNamedText.new('0', 'MicrosoftProjectServerURL')
|
|
124
|
+
|
|
125
|
+
p << (calendars = XMLElement.new('Calendars'))
|
|
126
|
+
generateCalendar(calendars, @project['workinghours'], 'Standard')
|
|
127
|
+
|
|
128
|
+
calendars
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def generateTasks(project)
|
|
132
|
+
project << (tasks = XMLElement.new('Tasks'))
|
|
133
|
+
|
|
134
|
+
@taskList.each do |task|
|
|
135
|
+
generateTask(tasks, task)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def generateResources(project, calendars)
|
|
140
|
+
project << (resources = XMLElement.new('Resources'))
|
|
141
|
+
|
|
142
|
+
@resourceList.each do |resource|
|
|
143
|
+
generateResource(resources, resource, calendars)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def generateAssignments(project)
|
|
148
|
+
project << (assignments = XMLElement.new('Assignments'))
|
|
149
|
+
|
|
150
|
+
i = 0
|
|
151
|
+
@taskList.each do |task|
|
|
152
|
+
rollupTask = a('rollupTask')
|
|
153
|
+
@query.property = task
|
|
154
|
+
@query.scopeProperty = nil
|
|
155
|
+
# We only generate assignments for leaf tasks and rolled-up container
|
|
156
|
+
# tasks.
|
|
157
|
+
next if (task.container? && !(rollupTask && rollupTask.eval(@query)))
|
|
158
|
+
|
|
159
|
+
task.assignedResources(@scenarioIdx).each do |resource|
|
|
160
|
+
generateAssignment(assignments, task, resource, i)
|
|
161
|
+
i += 1
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def generateCalendar(calendars, workinghours, name)
|
|
167
|
+
calendars << (cal = XMLElement.new('Calendar'))
|
|
168
|
+
uid = @calendarUIDs.length.to_s
|
|
169
|
+
@calendarUIDs[name] = uid
|
|
170
|
+
cal << XMLNamedText.new(uid, 'UID')
|
|
171
|
+
cal << XMLNamedText.new(name, 'Name')
|
|
172
|
+
cal << XMLNamedText.new('1', 'IsBaseCalendar')
|
|
173
|
+
cal << XMLNamedText.new('-1', 'BaseCalendarUID')
|
|
174
|
+
|
|
175
|
+
cal << (weekdays = XMLElement.new('WeekDays'))
|
|
176
|
+
d = 1
|
|
177
|
+
workinghours.days.each do |day|
|
|
178
|
+
weekdays << (weekday = XMLElement.new('WeekDay'))
|
|
179
|
+
weekday << XMLNamedText.new(d.to_s, 'DayType')
|
|
180
|
+
d += 1
|
|
181
|
+
if day.empty?
|
|
182
|
+
weekday << XMLNamedText.new('0', 'DayWorking')
|
|
183
|
+
else
|
|
184
|
+
weekday << XMLNamedText.new('1', 'DayWorking')
|
|
185
|
+
weekday << (workingtimes = XMLElement.new('WorkingTimes'))
|
|
186
|
+
day.each do |iv|
|
|
187
|
+
workingtimes << (worktime = XMLElement.new('WorkingTime'))
|
|
188
|
+
worktime << XMLNamedText.new(daytime_to_s(iv[0]), 'FromTime')
|
|
189
|
+
worktime << XMLNamedText.new(daytime_to_s(iv[1]), 'ToTime')
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def generateTask(tasks, task)
|
|
196
|
+
@query.property = task
|
|
197
|
+
task.calcCompletion(@scenarioIdx)
|
|
198
|
+
percentComplete = task['complete', @scenarioIdx]
|
|
199
|
+
|
|
200
|
+
tasks << (t = XMLElement.new('Task'))
|
|
201
|
+
t << XMLNamedText.new(task.get('index').to_s, 'UID')
|
|
202
|
+
t << XMLNamedText.new(task.get('index').to_s, 'ID')
|
|
203
|
+
t << XMLNamedText.new('1', 'Active')
|
|
204
|
+
t << XMLNamedText.new('0', 'Type')
|
|
205
|
+
t << XMLNamedText.new('0', 'IsNull')
|
|
206
|
+
t << XMLNamedText.new(task.get('name'), 'Name')
|
|
207
|
+
t << XMLNamedText.new(task.get('bsi'), 'WBS')
|
|
208
|
+
t << XMLNamedText.new(task.get('bsi'), 'OutlineNumber')
|
|
209
|
+
t << XMLNamedText.new((task.level -
|
|
210
|
+
(a('taskroot') ? a('taskroot').level : 0)).to_s,
|
|
211
|
+
'OutlineLevel')
|
|
212
|
+
t << XMLNamedText.new(task['priority', @scenarioIdx].to_s, 'Priority')
|
|
213
|
+
t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat),
|
|
214
|
+
'Start')
|
|
215
|
+
t << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat),
|
|
216
|
+
'Finish')
|
|
217
|
+
t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat),
|
|
218
|
+
'ManualStart')
|
|
219
|
+
t << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat),
|
|
220
|
+
'ManualFinish')
|
|
221
|
+
t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat),
|
|
222
|
+
'ActualStart')
|
|
223
|
+
t << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat),
|
|
224
|
+
'ActualFinish')
|
|
225
|
+
t << XMLNamedText.new('2', 'ConstraintType')
|
|
226
|
+
t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat),
|
|
227
|
+
'ConstraintDate')
|
|
228
|
+
t << XMLNamedText.new('3', 'FixedCostAccrual')
|
|
229
|
+
if (note = task.get('note'))
|
|
230
|
+
t << XMLNamedText.new(note.to_s, 'Notes')
|
|
231
|
+
end
|
|
232
|
+
responsible = task['responsible', @scenarioIdx].
|
|
233
|
+
map { |r| r.name }.join(', ')
|
|
234
|
+
t << XMLNamedText.new(responsible, 'Contact') unless responsible.empty?
|
|
235
|
+
|
|
236
|
+
if task.container?
|
|
237
|
+
rollupTask = a('rollupTask')
|
|
238
|
+
t << XMLNamedText.new(rollupTask ? '1' : '0', 'Manual')
|
|
239
|
+
t << XMLNamedText.new(rollupTask && rollupTask.eval(@query) ? '0' : '1',
|
|
240
|
+
'Summary')
|
|
241
|
+
else
|
|
242
|
+
t << XMLNamedText.new('1', 'Manual')
|
|
243
|
+
t << XMLNamedText.new('0', 'Summary')
|
|
244
|
+
t << XMLNamedText.new('0', 'Estimated')
|
|
245
|
+
t << XMLNamedText.new('5', 'DurationFormat')
|
|
246
|
+
if task['milestone', @scenarioIdx]
|
|
247
|
+
t << XMLNamedText.new('1', 'Milestone')
|
|
248
|
+
else
|
|
249
|
+
t << XMLNamedText.new('0', 'Milestone')
|
|
250
|
+
t << XMLNamedText.new(percentComplete.to_i.to_s,
|
|
251
|
+
'PercentComplete')
|
|
252
|
+
t << XMLNamedText.new(percentComplete.to_i.to_s,
|
|
253
|
+
'PercentWorkComplete')
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
task['startpreds', @scenarioIdx].each do |dt, onEnd|
|
|
257
|
+
next unless @taskList.include?(dt)
|
|
258
|
+
next if task.parent &&
|
|
259
|
+
task.parent['startpreds', @scenarioIdx].include?([ dt, onEnd ])
|
|
260
|
+
t << (pl = XMLElement.new('PredecessorLink'))
|
|
261
|
+
pl << XMLNamedText.new(@taskList[dt].get('index').to_s,
|
|
262
|
+
'PredecessorUID')
|
|
263
|
+
pl << XMLNamedText.new(onEnd ? '1' : '3', 'Type')
|
|
264
|
+
end
|
|
265
|
+
task['endpreds', @scenarioIdx].each do |dt, onEnd|
|
|
266
|
+
next unless @taskList.include?(dt)
|
|
267
|
+
next if task.parent &&
|
|
268
|
+
task.parent['endpreds', @scenarioIdx].include?([ dt, onEnd ])
|
|
269
|
+
t << (pl = XMLElement.new('PredecessorLink'))
|
|
270
|
+
pl << XMLNamedText.new(@taskList[dt].get('index').to_s,
|
|
271
|
+
'PredecessorUID')
|
|
272
|
+
pl << XMLNamedText.new(onEnd ? '0' : '2', 'Type')
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def generateResource(resources, resource, calendars)
|
|
277
|
+
# MS Project can only deal with a flat resource list. We don't export
|
|
278
|
+
# resource groups.
|
|
279
|
+
return unless resource.leaf?
|
|
280
|
+
|
|
281
|
+
resources << (r = XMLElement.new('Resource'))
|
|
282
|
+
r << XMLNamedText.new(resource.get('index').to_s, 'UID')
|
|
283
|
+
# All TJ resources are people or equipment.
|
|
284
|
+
r << XMLNamedText.new('1', 'Type')
|
|
285
|
+
r << XMLNamedText.new(resource.name, 'Name')
|
|
286
|
+
r << XMLNamedText.new(resource.id, 'Initials')
|
|
287
|
+
# MS Project seems to use hourly rates, TJ daily rates.
|
|
288
|
+
rate = (resource['rate', @scenarioIdx] / @project.dailyWorkingHours).to_s
|
|
289
|
+
r << XMLNamedText.new(rate, 'StandardRate')
|
|
290
|
+
r << XMLNamedText.new(rate, 'OvertimeRate')
|
|
291
|
+
r << XMLNamedText.new(resource['efficiency', @scenarioIdx].to_s,
|
|
292
|
+
'MaxUnits')
|
|
293
|
+
if (email = resource.get('email'))
|
|
294
|
+
r << XMLNamedText.new(email, 'EmailAddress')
|
|
295
|
+
end
|
|
296
|
+
r << XMLNamedText.new(resource.parent.name, 'Group') if resource.parent
|
|
297
|
+
#if (code = resource.get('Code'))
|
|
298
|
+
# r << XMLNamedText.new(code, 'Code')
|
|
299
|
+
# r << XMLNamedText.new('1', 'IsEnterprise')
|
|
300
|
+
#end
|
|
301
|
+
#if (ntaccount = resource.get('NTAccount'))
|
|
302
|
+
# r << XMLNamedText.new(ntaccount, 'NTAccount')
|
|
303
|
+
#end
|
|
304
|
+
# Generate a calendar for this resource and assign it.
|
|
305
|
+
generateCalendar(calendars, resource['workinghours', @scenarioIdx],
|
|
306
|
+
"Calendar #{resource.name}")
|
|
307
|
+
r << XMLNamedText.new(@calendarUIDs["Calendar #{resource.name}"],
|
|
308
|
+
'CalendarUID')
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def generateAssignment(assignments, task, resource, uid)
|
|
312
|
+
assignments << (a = XMLElement.new('Assignment'))
|
|
313
|
+
a << XMLNamedText.new(uid.to_s, 'UID')
|
|
314
|
+
a << XMLNamedText.new(@taskList[task].get('index').to_s,
|
|
315
|
+
'TaskUID')
|
|
316
|
+
a << XMLNamedText.new(resource.get('index').to_s,
|
|
317
|
+
'ResourceUID')
|
|
318
|
+
a << XMLNamedText.new(resource['efficiency', @scenarioIdx].to_s,
|
|
319
|
+
'Units')
|
|
320
|
+
a << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat),
|
|
321
|
+
'start')
|
|
322
|
+
a << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat),
|
|
323
|
+
'finish')
|
|
324
|
+
a << XMLNamedText.new('100.0', 'Cost')
|
|
325
|
+
a << XMLNamedText.new('8', 'WorkContour')
|
|
326
|
+
# The PercentWorkComplete value must be 0. Otherwise the completed
|
|
327
|
+
# work of the assignments will be ignored.
|
|
328
|
+
a << XMLNamedText.new('0', 'PercentWorkComplete')
|
|
329
|
+
responsible = task['responsible', @scenarioIdx].
|
|
330
|
+
map { |r| r.name }.join(', ')
|
|
331
|
+
a << XMLNamedText.new(responsible, 'AssnOwner') unless responsible.empty?
|
|
332
|
+
|
|
333
|
+
# Setup the query for this task and resource.
|
|
334
|
+
@query.property = resource
|
|
335
|
+
@query.scopeProperty = task
|
|
336
|
+
@query.attributeId = 'effort'
|
|
337
|
+
@query.scenarioIdx = @scenarioIdx
|
|
338
|
+
@query.start = task['start', @scenarioIdx]
|
|
339
|
+
@query.end = task['end', @scenarioIdx]
|
|
340
|
+
@query.process
|
|
341
|
+
|
|
342
|
+
tStart = task['start', @scenarioIdx]
|
|
343
|
+
# We provide assignement data on a day-by-day basis. We report the work
|
|
344
|
+
# that happens each day from task start to task end.
|
|
345
|
+
tStart = tStart.midnight
|
|
346
|
+
tEnd = task['end', @scenarioIdx]
|
|
347
|
+
t = tStart
|
|
348
|
+
while t < tEnd
|
|
349
|
+
tn = t.sameTimeNextDay
|
|
350
|
+
# We need to make sure that the stored intervals are within the task
|
|
351
|
+
# and report boundaries. tn and tnc are corrected versions of t and tn
|
|
352
|
+
# that meet this criterium.
|
|
353
|
+
tc = t < task['start', @scenarioIdx] ? task['start', @scenarioIdx] : t
|
|
354
|
+
tc = tc < a('start') ? a('start') : tc
|
|
355
|
+
tnc = tn > task['end', @scenarioIdx] ? task['end', @scenarioIdx] : tn
|
|
356
|
+
tnc = tnc > a('end') ? a('end') : tnc
|
|
357
|
+
@query.start = tc
|
|
358
|
+
@query.end = tnc
|
|
359
|
+
@query.process
|
|
360
|
+
workSeconds = @query.to_num * @project.dailyWorkingHours * 3600
|
|
361
|
+
a << (td = XMLElement.new('TimephasedData'))
|
|
362
|
+
td << XMLNamedText.new(uid.to_s, 'UID')
|
|
363
|
+
# The Type must be 1 or MS Project will take forever to load the file.
|
|
364
|
+
td << XMLNamedText.new('1', 'Type')
|
|
365
|
+
td << XMLNamedText.new(tc.to_s(@timeformat), 'Start')
|
|
366
|
+
td << XMLNamedText.new((tnc - 1).to_s(@timeformat), 'Finish')
|
|
367
|
+
td << XMLNamedText.new('2', 'Unit')
|
|
368
|
+
td << XMLNamedText.new(durationToMsp(workSeconds), 'Value')
|
|
369
|
+
t = tn
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def findRolledUpParent(task)
|
|
374
|
+
return nil unless (rollupTask = a('rollupTask'))
|
|
375
|
+
|
|
376
|
+
hideTask = a('hideTask')
|
|
377
|
+
while task
|
|
378
|
+
@query.property = task
|
|
379
|
+
# We don't want to include any tasks that are explicitely hidden via
|
|
380
|
+
# 'hidetask'.
|
|
381
|
+
return nil if hideTask && hideTask.eval(@query)
|
|
382
|
+
|
|
383
|
+
return task if rollupTask.eval(@query) && @taskList.include?(task)
|
|
384
|
+
|
|
385
|
+
task = task.parent
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def durationToMsp(duration)
|
|
390
|
+
return '' if duration == 0
|
|
391
|
+
|
|
392
|
+
hours = (duration / (60 * 60)).to_i
|
|
393
|
+
minutes = ((duration - (hours * 60 * 60)) / 60).to_i
|
|
394
|
+
seconds = (duration % 60).to_i
|
|
395
|
+
|
|
396
|
+
"PT#{hours}H#{minutes}M#{seconds}S"
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def daytime_to_s(t)
|
|
400
|
+
h = (t / (60 * 60)).to_i
|
|
401
|
+
m = ((t - (h * 60 * 60)) / 60).to_i
|
|
402
|
+
s = (t % 60).to_i
|
|
403
|
+
sprintf('%02d:%02d:%02d', h, m, s)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
end
|
|
409
|
+
|