taskjuggler 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (320) hide show
  1. data/CHANGELOG +30 -0
  2. data/README.rdoc +1 -0
  3. data/data/tjp.vim +13 -9
  4. data/lib/taskjuggler/Allocation.rb +2 -2
  5. data/lib/taskjuggler/AttributeBase.rb +10 -0
  6. data/lib/taskjuggler/Attributes.rb +2 -8
  7. data/lib/taskjuggler/Journal.rb +20 -19
  8. data/lib/taskjuggler/PTNProxy.rb +14 -1
  9. data/lib/taskjuggler/ProjectFileParser.rb +3 -0
  10. data/lib/taskjuggler/ProjectFileScanner.rb +54 -44
  11. data/lib/taskjuggler/PropertyList.rb +32 -0
  12. data/lib/taskjuggler/PropertyTreeNode.rb +4 -0
  13. data/lib/taskjuggler/ResourceScenario.rb +53 -20
  14. data/lib/taskjuggler/RichText/Document.rb +7 -5
  15. data/lib/taskjuggler/RichText/Scanner.rb +38 -38
  16. data/lib/taskjuggler/RichText/TOCEntry.rb +1 -1
  17. data/lib/taskjuggler/TaskScenario.rb +78 -62
  18. data/lib/taskjuggler/TextParser.rb +4 -3
  19. data/lib/taskjuggler/TextParser/MacroTable.rb +3 -1
  20. data/lib/taskjuggler/TextParser/Scanner.rb +73 -58
  21. data/lib/taskjuggler/Tj3Config.rb +1 -1
  22. data/lib/taskjuggler/TjpSyntaxRules.rb +215 -67
  23. data/lib/taskjuggler/apps/Tj3Client.rb +1 -1
  24. data/lib/taskjuggler/daemon/ReportServer.rb +8 -1
  25. data/lib/taskjuggler/reports/ExportRE.rb +46 -0
  26. data/lib/taskjuggler/reports/MspXmlRE.rb +409 -0
  27. data/lib/taskjuggler/reports/Report.rb +29 -4
  28. data/lib/taskjuggler/reports/ReportBase.rb +0 -8
  29. data/lib/taskjuggler/reports/TableReport.rb +13 -3
  30. data/lib/taskjuggler/reports/TaskListRE.rb +1 -0
  31. data/lib/taskjuggler/reports/TjpExportRE.rb +1 -1
  32. data/manual/Rich_Text_Attributes +2 -2
  33. data/manual/Tutorial +15 -11
  34. data/manual/html/Day_To_Day_Juggling.html +1 -1
  35. data/manual/html/Getting_Started.html +1 -1
  36. data/manual/html/How_To_Contribute.html +1 -1
  37. data/manual/html/Installation.html +1 -1
  38. data/manual/html/Intro.html +1 -1
  39. data/manual/html/Reporting_Bugs.html +1 -1
  40. data/manual/html/Rich_Text_Attributes.html +1 -1
  41. data/manual/html/Software.html +1 -1
  42. data/manual/html/TaskJuggler_2x_Migration.html +1 -1
  43. data/manual/html/TaskJuggler_Internals.html +1 -1
  44. data/manual/html/The_TaskJuggler_Syntax.html +1 -1
  45. data/manual/html/Tutorial.html +5 -2
  46. data/manual/html/account.html +1 -1
  47. data/manual/html/account.task.html +1 -1
  48. data/manual/html/accountprefix.html +1 -1
  49. data/manual/html/accountreport.html +9 -3
  50. data/manual/html/accountroot.html +1 -1
  51. data/manual/html/active.html +1 -1
  52. data/manual/html/adopt.task.html +3 -3
  53. data/manual/html/aggregate.html +1 -1
  54. data/manual/html/alert.html +1 -1
  55. data/manual/html/alertlevels.html +1 -1
  56. data/manual/html/allocate.html +5 -2
  57. data/manual/html/alphabet.html +1 -1
  58. data/manual/html/alternative.html +1 -1
  59. data/manual/html/author.html +1 -1
  60. data/manual/html/balance.html +1 -1
  61. data/manual/html/booking.resource.html +1 -1
  62. data/manual/html/booking.task.html +1 -1
  63. data/manual/html/caption.html +1 -1
  64. data/manual/html/cellcolor.column.html +1 -1
  65. data/manual/html/celltext.column.html +43 -4
  66. data/manual/html/center.html +1 -1
  67. data/manual/html/charge.html +1 -1
  68. data/manual/html/chargeset.html +2 -2
  69. data/manual/html/columnid.html +6 -6
  70. data/manual/html/columns.html +1 -1
  71. data/manual/html/complete.html +1 -1
  72. data/manual/html/copyright.html +1 -1
  73. data/manual/html/credits.html +1 -1
  74. data/manual/html/currency.html +1 -1
  75. data/manual/html/currencyformat.html +1 -1
  76. data/manual/html/dailymax.html +1 -1
  77. data/manual/html/dailymin.html +1 -1
  78. data/manual/html/dailyworkinghours.html +1 -1
  79. data/manual/html/date.extend.html +1 -1
  80. data/manual/html/date.html +2 -2
  81. data/manual/html/definitions.html +1 -1
  82. data/manual/html/depends.html +1 -1
  83. data/manual/html/details.html +1 -1
  84. data/manual/html/disabled.html +1 -1
  85. data/manual/html/duration.html +1 -1
  86. data/manual/html/efficiency.html +1 -1
  87. data/manual/html/effort.html +1 -1
  88. data/manual/html/email.html +1 -1
  89. data/manual/html/enabled.html +1 -1
  90. data/manual/html/end.column.html +1 -1
  91. data/manual/html/end.html +1 -1
  92. data/manual/html/end.limit.html +1 -1
  93. data/manual/html/end.report.html +9 -3
  94. data/manual/html/end.timesheet.html +1 -1
  95. data/manual/html/endcredit.html +1 -1
  96. data/manual/html/epilog.html +1 -1
  97. data/manual/html/export.html +73 -5
  98. data/manual/html/extend.html +1 -1
  99. data/manual/html/fail.html +1 -1
  100. data/manual/html/fdl.html +1 -1
  101. data/manual/html/flags.account.html +1 -1
  102. data/manual/html/flags.html +1 -1
  103. data/manual/html/flags.journalentry.html +1 -1
  104. data/manual/html/flags.report.html +1 -1
  105. data/manual/html/flags.resource.html +1 -1
  106. data/manual/html/flags.statussheet.html +1 -1
  107. data/manual/html/flags.task.html +1 -1
  108. data/manual/html/flags.timesheet.html +1 -1
  109. data/manual/html/fontcolor.column.html +1 -1
  110. data/manual/html/footer.html +1 -1
  111. data/manual/html/formats.export.html +72 -0
  112. data/manual/html/formats.html +3 -3
  113. data/manual/html/functions.html +3 -3
  114. data/manual/html/gapduration.html +2 -2
  115. data/manual/html/gaplength.html +1 -1
  116. data/manual/html/halign.center.html +1 -1
  117. data/manual/html/halign.column.html +1 -1
  118. data/manual/html/halign.left.html +1 -1
  119. data/manual/html/halign.right.html +1 -1
  120. data/manual/html/hasalert.html +1 -1
  121. data/manual/html/header.html +1 -1
  122. data/manual/html/headline.html +1 -1
  123. data/manual/html/height.html +1 -1
  124. data/manual/html/hideaccount.html +1 -1
  125. data/manual/html/hidejournalentry.html +1 -1
  126. data/manual/html/hidereport.html +1 -1
  127. data/manual/html/hideresource.html +1 -1
  128. data/manual/html/hidetask.html +1 -1
  129. data/manual/html/icalreport.html +1 -1
  130. data/manual/html/include.macro.html +1 -1
  131. data/manual/html/include.project.html +1 -1
  132. data/manual/html/include.properties.html +1 -1
  133. data/manual/html/index.html +1 -1
  134. data/manual/html/inherit.extend.html +1 -1
  135. data/manual/html/interval1.html +2 -2
  136. data/manual/html/interval2.html +2 -2
  137. data/manual/html/interval3.html +2 -2
  138. data/manual/html/interval4.html +2 -2
  139. data/manual/html/isactive.html +1 -1
  140. data/manual/html/ischildof.html +1 -1
  141. data/manual/html/isdependencyof.html +1 -1
  142. data/manual/html/isdutyof.html +1 -1
  143. data/manual/html/isfeatureof.html +1 -1
  144. data/manual/html/isleaf.html +1 -1
  145. data/manual/html/ismilestone.html +1 -1
  146. data/manual/html/isongoing.html +1 -1
  147. data/manual/html/isresource.html +1 -1
  148. data/manual/html/isresponsibilityof.html +1 -1
  149. data/manual/html/istask.html +1 -1
  150. data/manual/html/journalattributes.html +1 -1
  151. data/manual/html/journalentry.html +1 -1
  152. data/manual/html/journalmode.html +11 -3
  153. data/manual/html/leaveallowance.html +1 -1
  154. data/manual/html/leaves.html +1 -1
  155. data/manual/html/left.html +1 -1
  156. data/manual/html/length.html +1 -1
  157. data/manual/html/limits.allocate.html +1 -1
  158. data/manual/html/limits.html +1 -1
  159. data/manual/html/limits.resource.html +1 -1
  160. data/manual/html/limits.task.html +1 -1
  161. data/manual/html/listitem.column.html +1 -1
  162. data/manual/html/listtype.column.html +1 -1
  163. data/manual/html/loadunit.html +2 -2
  164. data/manual/html/logicalexpression.html +1 -1
  165. data/manual/html/logicalflagexpression.html +1 -1
  166. data/manual/html/macro.html +1 -1
  167. data/manual/html/managers.html +1 -1
  168. data/manual/html/mandatory.html +1 -1
  169. data/manual/html/maxend.html +1 -1
  170. data/manual/html/maximum.html +1 -1
  171. data/manual/html/maxstart.html +1 -1
  172. data/manual/html/milestone.html +1 -1
  173. data/manual/html/minend.html +1 -1
  174. data/manual/html/minimum.html +1 -1
  175. data/manual/html/minstart.html +1 -1
  176. data/manual/html/monthlymax.html +1 -1
  177. data/manual/html/monthlymin.html +1 -1
  178. data/manual/html/navbar.html +5 -1
  179. data/manual/html/navigator.html +1 -1
  180. data/manual/html/newtask.html +1 -1
  181. data/manual/html/nikureport.html +1 -1
  182. data/manual/html/note.task.html +1 -1
  183. data/manual/html/now.html +1 -1
  184. data/manual/html/numberformat.html +1 -1
  185. data/manual/html/onend.html +1 -1
  186. data/manual/html/onstart.html +1 -1
  187. data/manual/html/opennodes.html +1 -1
  188. data/manual/html/overtime.booking.html +1 -1
  189. data/manual/html/period.column.html +1 -1
  190. data/manual/html/period.limit.html +1 -1
  191. data/manual/html/period.report.html +1 -1
  192. data/manual/html/period.task.html +1 -1
  193. data/manual/html/persistent.html +1 -1
  194. data/manual/html/precedes.html +1 -1
  195. data/manual/html/priority.html +1 -1
  196. data/manual/html/priority.timesheet.html +1 -1
  197. data/manual/html/project.html +1 -1
  198. data/manual/html/projectid.html +1 -1
  199. data/manual/html/projectid.task.html +1 -1
  200. data/manual/html/projectids.html +1 -1
  201. data/manual/html/projection.html +1 -1
  202. data/manual/html/prolog.html +1 -1
  203. data/manual/html/properties.html +1 -1
  204. data/manual/html/purge.html +8 -5
  205. data/manual/html/rate.html +1 -1
  206. data/manual/html/rate.resource.html +1 -1
  207. data/manual/html/reference.extend.html +1 -1
  208. data/manual/html/remaining.html +1 -1
  209. data/manual/html/replace.html +1 -1
  210. data/manual/html/reportprefix.html +1 -1
  211. data/manual/html/resource.html +1 -1
  212. data/manual/html/resourceattributes.html +1 -1
  213. data/manual/html/resourceprefix.html +1 -1
  214. data/manual/html/resourcereport.html +9 -3
  215. data/manual/html/resourceroot.html +1 -1
  216. data/manual/html/resources.limit.html +1 -1
  217. data/manual/html/responsible.html +1 -1
  218. data/manual/html/richtext.extend.html +1 -1
  219. data/manual/html/right.html +1 -1
  220. data/manual/html/rollupaccount.html +1 -1
  221. data/manual/html/rollupresource.html +1 -1
  222. data/manual/html/rolluptask.html +1 -1
  223. data/manual/html/scale.column.html +1 -1
  224. data/manual/html/scenario.html +1 -1
  225. data/manual/html/scenario.ical.html +1 -1
  226. data/manual/html/scenarios.export.html +1 -1
  227. data/manual/html/scenarios.html +1 -1
  228. data/manual/html/scenariospecific.extend.html +1 -1
  229. data/manual/html/scheduled.html +2 -2
  230. data/manual/html/scheduling.html +1 -1
  231. data/manual/html/select.html +1 -1
  232. data/manual/html/selfcontained.html +1 -1
  233. data/manual/html/shift.allocate.html +1 -1
  234. data/manual/html/shift.html +1 -1
  235. data/manual/html/shift.resource.html +1 -1
  236. data/manual/html/shift.task.html +1 -1
  237. data/manual/html/shift.timesheet.html +1 -1
  238. data/manual/html/shifts.allocate.html +1 -1
  239. data/manual/html/shifts.resource.html +1 -1
  240. data/manual/html/shifts.task.html +1 -1
  241. data/manual/html/shorttimeformat.html +1 -1
  242. data/manual/html/sloppy.booking.html +1 -1
  243. data/manual/html/sloppy.projection.html +1 -1
  244. data/manual/html/sortaccounts.html +1 -1
  245. data/manual/html/sortjournalentries.html +1 -1
  246. data/manual/html/sortresources.html +1 -1
  247. data/manual/html/sorttasks.html +1 -1
  248. data/manual/html/start.column.html +1 -1
  249. data/manual/html/start.html +1 -1
  250. data/manual/html/start.limit.html +1 -1
  251. data/manual/html/start.report.html +1 -1
  252. data/manual/html/startcredit.html +1 -1
  253. data/manual/html/status.statussheet.html +1 -1
  254. data/manual/html/status.timesheet.html +1 -1
  255. data/manual/html/statussheet.html +1 -1
  256. data/manual/html/statussheetreport.html +1 -1
  257. data/manual/html/strict.projection.html +1 -1
  258. data/manual/html/summary.html +1 -1
  259. data/manual/html/supplement.html +1 -1
  260. data/manual/html/supplement.resource.html +1 -1
  261. data/manual/html/supplement.task.html +1 -1
  262. data/manual/html/tagfile.html +1 -1
  263. data/manual/html/task.html +1 -1
  264. data/manual/html/task.statussheet.html +1 -1
  265. data/manual/html/task.timesheet.html +1 -1
  266. data/manual/html/taskattributes.html +1 -1
  267. data/manual/html/taskprefix.html +1 -1
  268. data/manual/html/taskreport.html +9 -3
  269. data/manual/html/taskroot.export.html +104 -0
  270. data/manual/html/taskroot.html +3 -3
  271. data/manual/html/text.extend.html +3 -3
  272. data/manual/html/textreport.html +9 -3
  273. data/manual/html/timeformat.html +1 -1
  274. data/manual/html/timeoff.nikureport.html +1 -1
  275. data/manual/html/timesheet.html +1 -1
  276. data/manual/html/timesheetreport.html +1 -1
  277. data/manual/html/timezone.export.html +1 -1
  278. data/manual/html/timezone.html +1 -1
  279. data/manual/html/timezone.report.html +1 -1
  280. data/manual/html/timezone.shift.html +1 -1
  281. data/manual/html/timingresolution.html +1 -1
  282. data/manual/html/title.column.html +1 -1
  283. data/manual/html/title.html +1 -1
  284. data/manual/html/toc.html +203 -189
  285. data/manual/html/tooltip.column.html +1 -1
  286. data/manual/html/tracereport.html +9 -3
  287. data/manual/html/trackingscenario.html +1 -1
  288. data/manual/html/treelevel.html +1 -1
  289. data/manual/html/vacation.html +1 -1
  290. data/manual/html/vacation.resource.html +1 -1
  291. data/manual/html/vacation.shift.html +1 -1
  292. data/manual/html/warn.html +1 -1
  293. data/manual/html/weeklymax.html +1 -1
  294. data/manual/html/weeklymin.html +1 -1
  295. data/manual/html/weekstartsmonday.html +1 -1
  296. data/manual/html/weekstartssunday.html +1 -1
  297. data/manual/html/width.column.html +1 -1
  298. data/manual/html/width.html +1 -1
  299. data/manual/html/work.html +1 -1
  300. data/manual/html/workinghours.project.html +1 -1
  301. data/manual/html/workinghours.resource.html +1 -1
  302. data/manual/html/workinghours.shift.html +1 -1
  303. data/manual/html/yearlyworkingdays.html +1 -1
  304. data/test/TestSuite/CSV-Reports/project-1.tji +0 -6
  305. data/test/TestSuite/CSV-Reports/refs/alert.csv +0 -7
  306. data/test/TestSuite/CSV-Reports/refs/celltext.csv +0 -7
  307. data/test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv +0 -3
  308. data/test/TestSuite/CSV-Reports/refs/sortByTree.csv +0 -7
  309. data/test/TestSuite/CSV-Reports/refs/sortBy_duration.down.csv +0 -7
  310. data/test/TestSuite/CSV-Reports/refs/sortBy_effort.up.csv +0 -7
  311. data/test/TestSuite/CSV-Reports/refs/sortBy_plan.start.down.csv +0 -7
  312. data/test/TestSuite/CSV-Reports/refs/taskreport.csv +0 -7
  313. data/test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv +0 -18
  314. data/test/TestSuite/CSV-Reports/refs/weekly.csv +0 -1
  315. data/test/TestSuite/HTML-Reports/Alerts-2.tjp +46 -0
  316. data/test/TestSuite/Scheduler/Correct/purge.tjp +30 -0
  317. data/test/TestSuite/Syntax/Correct/Export.tjp +8 -2
  318. data/test/TestSuite/Syntax/Correct/tutorial.tjp +0 -1
  319. metadata +16 -10
  320. 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
- 'Heartbeat from ProjectServer lost. Terminating.')
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
+