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.
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
+