taskjuggler 0.2.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (305) hide show
  1. data/CHANGELOG +29 -0
  2. data/data/css/tjreport.css +38 -4
  3. data/data/tjp.vim +421 -421
  4. data/examples/Fedora-20/reports.tji +0 -2
  5. data/examples/tutorial.tjp +0 -2
  6. data/lib/taskjuggler/Attributes.rb +14 -1
  7. data/lib/taskjuggler/Project.rb +13 -2
  8. data/lib/taskjuggler/ProjectFileScanner.rb +1 -1
  9. data/lib/taskjuggler/ResourceScenario.rb +38 -0
  10. data/lib/taskjuggler/SheetReceiver.rb +5 -3
  11. data/lib/taskjuggler/SimpleQueryExpander.rb +0 -1
  12. data/lib/taskjuggler/TaskScenario.rb +2 -2
  13. data/lib/taskjuggler/TextParser.rb +0 -2
  14. data/lib/taskjuggler/TextParser/Pattern.rb +1 -1
  15. data/lib/taskjuggler/TextParser/Scanner.rb +0 -2
  16. data/lib/taskjuggler/TimeSheets.rb +2 -1
  17. data/lib/taskjuggler/Tj3AppBase.rb +1 -1
  18. data/lib/taskjuggler/Tj3Config.rb +1 -1
  19. data/lib/taskjuggler/TjpSyntaxRules.rb +29 -14
  20. data/lib/taskjuggler/WorkingHours.rb +9 -6
  21. data/lib/taskjuggler/apps/Tj3.rb +1 -1
  22. data/lib/taskjuggler/reports/GanttLine.rb +1 -1
  23. data/lib/taskjuggler/reports/Navigator.rb +103 -81
  24. data/lib/taskjuggler/reports/NikuReport.rb +8 -5
  25. data/lib/taskjuggler/reports/Report.rb +10 -1
  26. data/lib/taskjuggler/reports/ReportTableCell.rb +1 -1
  27. data/lib/taskjuggler/reports/ResourceListRE.rb +1 -1
  28. data/lib/taskjuggler/reports/TableReport.rb +1 -1
  29. data/lib/taskjuggler/reports/TaskListRE.rb +1 -1
  30. data/lib/taskjuggler/reports/TjpExportRE.rb +31 -1
  31. data/manual/Installation +53 -7
  32. data/manual/Intro +36 -28
  33. data/manual/html/Day_To_Day_Juggling.html +1 -1
  34. data/manual/html/Getting_Started.html +1 -1
  35. data/manual/html/How_To_Contribute.html +1 -1
  36. data/manual/html/Installation.html +29 -8
  37. data/manual/html/Intro.html +32 -19
  38. data/manual/html/Reporting_Bugs.html +2 -2
  39. data/manual/html/Rich_Text_Attributes.html +1 -1
  40. data/manual/html/Software.html +1 -1
  41. data/manual/html/TaskJuggler_2x_Migration.html +3 -3
  42. data/manual/html/TaskJuggler_Internals.html +1 -1
  43. data/manual/html/The_TaskJuggler_Syntax.html +1 -1
  44. data/manual/html/Tutorial.html +1 -3
  45. data/manual/html/account.html +2 -2
  46. data/manual/html/account.task.html +1 -1
  47. data/manual/html/accountprefix.html +1 -1
  48. data/manual/html/active.html +1 -1
  49. data/manual/html/adopt.task.html +1 -1
  50. data/manual/html/alert level.html +1 -1
  51. data/manual/html/alert.html +1 -1
  52. data/manual/html/allocate.html +1 -1
  53. data/manual/html/alphabet.html +1 -1
  54. data/manual/html/alternative.html +1 -1
  55. data/manual/html/author.html +1 -1
  56. data/manual/html/balance.html +1 -1
  57. data/manual/html/booking.resource.html +2 -2
  58. data/manual/html/booking.task.html +1 -1
  59. data/manual/html/caption.html +1 -1
  60. data/manual/html/cellcolor.column.html +1 -1
  61. data/manual/html/celltext.column.html +1 -1
  62. data/manual/html/center.html +1 -1
  63. data/manual/html/charge.html +1 -1
  64. data/manual/html/chargeset.html +1 -1
  65. data/manual/html/columnid.html +2 -2
  66. data/manual/html/columns.html +1 -1
  67. data/manual/html/complete.html +1 -1
  68. data/manual/html/copyright.html +1 -1
  69. data/manual/html/credit.html +1 -1
  70. data/manual/html/css/tjreport.css +38 -4
  71. data/manual/html/currency.html +1 -1
  72. data/manual/html/currencyformat.html +2 -2
  73. data/manual/html/dailymax.html +1 -1
  74. data/manual/html/dailymin.html +1 -1
  75. data/manual/html/dailyworkinghours.html +1 -1
  76. data/manual/html/date.extend.html +1 -1
  77. data/manual/html/date.html +1 -1
  78. data/manual/html/definitions.html +10 -10
  79. data/manual/html/depends.html +1 -1
  80. data/manual/html/details.html +1 -1
  81. data/manual/html/disabled.html +1 -1
  82. data/manual/html/duration.html +1 -1
  83. data/manual/html/efficiency.html +2 -2
  84. data/manual/html/effort.html +1 -1
  85. data/manual/html/email.html +2 -2
  86. data/manual/html/enabled.html +1 -1
  87. data/manual/html/end.column.html +1 -1
  88. data/manual/html/end.html +1 -1
  89. data/manual/html/end.limit.html +2 -2
  90. data/manual/html/end.report.html +2 -2
  91. data/manual/html/end.timesheet.html +1 -1
  92. data/manual/html/endcredit.html +1 -1
  93. data/manual/html/epilog.html +1 -1
  94. data/manual/html/export.html +1 -1
  95. data/manual/html/extend.html +1 -1
  96. data/manual/html/fail.html +2 -2
  97. data/manual/html/fdl.html +1 -1
  98. data/manual/html/flags.account.html +1 -1
  99. data/manual/html/flags.html +1 -1
  100. data/manual/html/flags.journalentry.html +1 -1
  101. data/manual/html/flags.report.html +1 -1
  102. data/manual/html/flags.resource.html +2 -2
  103. data/manual/html/flags.statussheet.html +1 -1
  104. data/manual/html/flags.task.html +1 -1
  105. data/manual/html/flags.timesheet.html +1 -1
  106. data/manual/html/fontcolor.column.html +1 -1
  107. data/manual/html/footer.html +1 -1
  108. data/manual/html/formats.html +1 -1
  109. data/manual/html/functions.html +1 -1
  110. data/manual/html/gapduration.html +1 -1
  111. data/manual/html/gaplength.html +1 -1
  112. data/manual/html/halign.center.html +1 -1
  113. data/manual/html/halign.column.html +1 -1
  114. data/manual/html/halign.left.html +1 -1
  115. data/manual/html/halign.right.html +1 -1
  116. data/manual/html/hasalert.html +1 -1
  117. data/manual/html/header.html +1 -1
  118. data/manual/html/headline.html +1 -1
  119. data/manual/html/hidejournalentry.html +2 -2
  120. data/manual/html/hidereport.html +1 -1
  121. data/manual/html/hideresource.html +2 -2
  122. data/manual/html/hidetask.html +2 -2
  123. data/manual/html/icalreport.html +1 -1
  124. data/manual/html/include.macro.html +1 -1
  125. data/manual/html/include.project.html +1 -1
  126. data/manual/html/include.properties.html +1 -1
  127. data/manual/html/index.html +1 -1
  128. data/manual/html/inherit.extend.html +2 -2
  129. data/manual/html/interval1.html +1 -1
  130. data/manual/html/interval2.html +1 -1
  131. data/manual/html/interval3.html +1 -1
  132. data/manual/html/interval4.html +1 -1
  133. data/manual/html/isactive.html +1 -1
  134. data/manual/html/ischildof.html +1 -1
  135. data/manual/html/isdependencyof.html +1 -1
  136. data/manual/html/isdutyof.html +1 -1
  137. data/manual/html/isfeatureof.html +1 -1
  138. data/manual/html/isleaf.html +1 -1
  139. data/manual/html/ismilestone.html +1 -1
  140. data/manual/html/isongoing.html +1 -1
  141. data/manual/html/isresource.html +1 -1
  142. data/manual/html/istask.html +1 -1
  143. data/manual/html/journalattributes.html +14 -14
  144. data/manual/html/journalentry.html +2 -2
  145. data/manual/html/journalmode.html +1 -1
  146. data/manual/html/left.html +1 -1
  147. data/manual/html/length.html +1 -1
  148. data/manual/html/limits.allocate.html +1 -1
  149. data/manual/html/limits.html +1 -1
  150. data/manual/html/limits.resource.html +2 -2
  151. data/manual/html/limits.task.html +1 -1
  152. data/manual/html/listitem.column.html +1 -1
  153. data/manual/html/listtype.column.html +1 -1
  154. data/manual/html/loadunit.html +1 -1
  155. data/manual/html/logicalexpression.html +1 -1
  156. data/manual/html/logicalflagexpression.html +1 -1
  157. data/manual/html/macro.html +1 -1
  158. data/manual/html/managers.html +2 -2
  159. data/manual/html/mandatory.html +1 -1
  160. data/manual/html/maxend.html +1 -1
  161. data/manual/html/maximum.html +1 -1
  162. data/manual/html/maxstart.html +1 -1
  163. data/manual/html/milestone.html +1 -1
  164. data/manual/html/minend.html +1 -1
  165. data/manual/html/minimum.html +1 -1
  166. data/manual/html/minstart.html +1 -1
  167. data/manual/html/monthlymax.html +1 -1
  168. data/manual/html/monthlymin.html +1 -1
  169. data/manual/html/navbar.html +1 -1
  170. data/manual/html/navigator.html +1 -1
  171. data/manual/html/newtask.html +1 -1
  172. data/manual/html/nikureport.html +1 -1
  173. data/manual/html/note.task.html +1 -1
  174. data/manual/html/now.html +1 -1
  175. data/manual/html/numberformat.html +2 -2
  176. data/manual/html/onend.html +1 -1
  177. data/manual/html/onstart.html +1 -1
  178. data/manual/html/opennodes.html +1 -1
  179. data/manual/html/overtime.booking.html +2 -2
  180. data/manual/html/period.column.html +1 -1
  181. data/manual/html/period.limit.html +2 -2
  182. data/manual/html/period.report.html +2 -2
  183. data/manual/html/period.task.html +1 -1
  184. data/manual/html/persistent.html +1 -1
  185. data/manual/html/precedes.html +1 -1
  186. data/manual/html/priority.html +1 -1
  187. data/manual/html/priority.timesheet.html +1 -1
  188. data/manual/html/project.html +1 -1
  189. data/manual/html/projectid.html +1 -1
  190. data/manual/html/projectid.task.html +1 -1
  191. data/manual/html/projectids.html +1 -1
  192. data/manual/html/projection.html +1 -1
  193. data/manual/html/prolog.html +1 -1
  194. data/manual/html/properties.html +1 -1
  195. data/manual/html/purge.html +2 -2
  196. data/manual/html/rate.html +1 -1
  197. data/manual/html/rate.resource.html +2 -2
  198. data/manual/html/reference.extend.html +1 -1
  199. data/manual/html/remaining.html +1 -1
  200. data/manual/html/replace.html +1 -1
  201. data/manual/html/report.html +1 -1
  202. data/manual/html/reportprefix.html +1 -1
  203. data/manual/html/resource.html +2 -2
  204. data/manual/html/resourceattributes.html +6 -6
  205. data/manual/html/resourceprefix.html +1 -1
  206. data/manual/html/resourcereport.html +1 -1
  207. data/manual/html/resourceroot.html +1 -1
  208. data/manual/html/resources.limit.html +2 -2
  209. data/manual/html/responsible.html +1 -1
  210. data/manual/html/richtext.extend.html +1 -1
  211. data/manual/html/right.html +1 -1
  212. data/manual/html/rollupresource.html +2 -2
  213. data/manual/html/rolluptask.html +2 -2
  214. data/manual/html/scale.column.html +1 -1
  215. data/manual/html/scenario.html +2 -2
  216. data/manual/html/scenario.ical.html +1 -1
  217. data/manual/html/scenarios.export.html +1 -1
  218. data/manual/html/scenarios.html +1 -1
  219. data/manual/html/scenariospecific.extend.html +2 -2
  220. data/manual/html/scheduled.html +1 -1
  221. data/manual/html/scheduling.html +1 -1
  222. data/manual/html/select.html +1 -1
  223. data/manual/html/selfcontained.html +1 -1
  224. data/manual/html/shift.html +1 -1
  225. data/manual/html/shift.resource.html +5 -5
  226. data/manual/html/shift.task.html +4 -4
  227. data/manual/html/shift.timesheet.html +1 -1
  228. data/manual/html/shifts.allocate.html +1 -1
  229. data/manual/html/shifts.resource.html +5 -5
  230. data/manual/html/shifts.task.html +4 -4
  231. data/manual/html/shorttimeformat.html +1 -1
  232. data/manual/html/sloppy.booking.html +2 -2
  233. data/manual/html/sloppy.projection.html +1 -1
  234. data/manual/html/sortjournalentries.html +1 -1
  235. data/manual/html/sortresources.html +1 -1
  236. data/manual/html/sorttasks.html +1 -1
  237. data/manual/html/start.column.html +1 -1
  238. data/manual/html/start.html +1 -1
  239. data/manual/html/start.limit.html +2 -2
  240. data/manual/html/start.report.html +2 -2
  241. data/manual/html/startcredit.html +1 -1
  242. data/manual/html/status.statussheet.html +1 -1
  243. data/manual/html/status.timesheet.html +2 -2
  244. data/manual/html/statussheet.html +1 -1
  245. data/manual/html/statussheetreport.html +1 -1
  246. data/manual/html/strict.projection.html +1 -1
  247. data/manual/html/summary.html +1 -1
  248. data/manual/html/supplement.html +1 -1
  249. data/manual/html/supplement.resource.html +2 -2
  250. data/manual/html/supplement.task.html +1 -1
  251. data/manual/html/tagfile.html +8 -4
  252. data/manual/html/task.html +1 -1
  253. data/manual/html/task.statussheet.html +1 -1
  254. data/manual/html/task.timesheet.html +1 -1
  255. data/manual/html/taskattributes.html +22 -22
  256. data/manual/html/taskprefix.html +1 -1
  257. data/manual/html/taskreport.html +1 -1
  258. data/manual/html/taskroot.html +1 -1
  259. data/manual/html/text.extend.html +1 -1
  260. data/manual/html/textreport.html +1 -1
  261. data/manual/html/timeformat.html +2 -2
  262. data/manual/html/timeoff.nikureport.html +1 -1
  263. data/manual/html/timesheet.html +1 -1
  264. data/manual/html/timesheetreport.html +1 -1
  265. data/manual/html/timezone.export.html +1 -1
  266. data/manual/html/timezone.html +1 -1
  267. data/manual/html/timezone.report.html +1 -1
  268. data/manual/html/timezone.shift.html +1 -1
  269. data/manual/html/timingresolution.html +1 -1
  270. data/manual/html/title.column.html +1 -1
  271. data/manual/html/title.html +1 -1
  272. data/manual/html/toc.html +16 -12
  273. data/manual/html/tooltip.column.html +1 -1
  274. data/manual/html/trackingscenario.html +1 -1
  275. data/manual/html/treelevel.html +1 -1
  276. data/manual/html/vacation.html +1 -1
  277. data/manual/html/vacation.resource.html +2 -2
  278. data/manual/html/vacation.shift.html +1 -1
  279. data/manual/html/warn.html +2 -2
  280. data/manual/html/weeklymax.html +1 -1
  281. data/manual/html/weeklymin.html +1 -1
  282. data/manual/html/weekstartsmonday.html +1 -1
  283. data/manual/html/weekstartssunday.html +1 -1
  284. data/manual/html/width.column.html +1 -1
  285. data/manual/html/work.html +1 -1
  286. data/manual/html/workinghours.project.html +1 -1
  287. data/manual/html/workinghours.resource.html +2 -2
  288. data/manual/html/workinghours.shift.html +1 -1
  289. data/manual/html/yearlyworkingdays.html +1 -1
  290. data/tasks/changelog.rake +1 -0
  291. data/test/TestSuite/Export-Reports/refs/AutoID.tjp +18 -0
  292. data/test/TestSuite/Export-Reports/refs/Mandatory.tjp +2 -1
  293. data/test/TestSuite/Export-Reports/refs/Shift.tjp +49 -0
  294. data/test/TestSuite/HTML-Reports/Navigator.tjp +34 -0
  295. data/test/TestSuite/ReportGenerator/Correct/FTE.tjp +15 -0
  296. data/test/TestSuite/ReportGenerator/Correct/refs/FTE-1.csv +4 -0
  297. data/test/TestSuite/Syntax/Correct/Mandatory.tjp +1 -0
  298. data/test/TestSuite/Syntax/Correct/Shift.tjp +4 -0
  299. data/test/TestSuite/Syntax/Correct/tutorial.tjp +0 -2
  300. data/test/TestSuite/Syntax/Errors/invalid_file_name.tjp +9 -0
  301. data/test/test_CSVFile.rb +0 -1
  302. data/test/test_Journal.rb +5 -5
  303. data/test/test_LogicalExpression.rb +1 -1
  304. data/test/test_ShiftAssignments.rb +1 -1
  305. metadata +285 -299
@@ -43,9 +43,7 @@ navigator menu
43
43
  macro all_navbar [
44
44
  header -8<-
45
45
  == [[File:icons/fedoralogo.png|bottom]] ${content_title}-${major} Project Plan ==
46
- ----
47
46
  <[navigator id="menu"]>
48
- ----
49
47
  ->8-
50
48
  ]
51
49
  macro FilePrefix [${content}-${major}-]
@@ -315,9 +315,7 @@ macro TaskTip [
315
315
  textreport frame "" {
316
316
  header -8<-
317
317
  == Accounting Software Project ==
318
- ----
319
318
  <[navigator id="navbar"]>
320
- ----
321
319
  ->8-
322
320
  footer "----"
323
321
  textreport index "Overview" {
@@ -547,7 +547,20 @@ class TaskJuggler
547
547
  end
548
548
 
549
549
  def to_tjp
550
- 'This code is still missing!'
550
+ v = get
551
+ first = true
552
+ str = 'shifts '
553
+ v.assignments.each do |sa|
554
+ if first
555
+ first = false
556
+ else
557
+ str += ",\n"
558
+ end
559
+
560
+ str += "#{sa.shiftScenario.property.fullId} #{sa.interval}"
561
+ end
562
+
563
+ str
551
564
  end
552
565
 
553
566
  end
@@ -202,8 +202,6 @@ class TaskJuggler
202
202
  false, false, false, [] ],
203
203
  [ 'flags', 'Flags', FlagListAttribute,
204
204
  true, false, true, [] ],
205
- [ 'fte', 'FTE', FloatAttribute,
206
- false, false, true, 1.0 ],
207
205
  [ 'headcount', 'Headcount', FixnumAttribute,
208
206
  false, false, true, 1 ],
209
207
  [ 'index', 'Index', FixnumAttribute,
@@ -955,6 +953,19 @@ class TaskJuggler
955
953
  slotsToDays(slots)
956
954
  end
957
955
 
956
+ # Return the number of global working slots during the given time interval
957
+ # specified by _startIdx_ and _endIdx_. This method takes global vacations
958
+ # into account.
959
+ def getWorkSlots(startIdx, endIdx)
960
+ slots = 0
961
+ startIdx.upto(endIdx) do |idx|
962
+ slots += 1 unless @scoreboard[idx]
963
+ end
964
+
965
+ slots
966
+ end
967
+
968
+
958
969
  # Return true if for the date specified by the global scoreboard index
959
970
  # _sbIdx_ there is any resource that is available.
960
971
  def anyResourceAvailable?(sbIdx)
@@ -184,7 +184,7 @@ class TaskJuggler
184
184
  begin
185
185
  [ type, TjTime.new(match) ]
186
186
  rescue TjException => msg
187
- error('time_error', msg)
187
+ error('time_error', msg.message)
188
188
  end
189
189
  end
190
190
 
@@ -278,6 +278,27 @@ class TaskJuggler
278
278
  query.string = query.scaleLoad(work)
279
279
  end
280
280
 
281
+ # The the Full-time equivalent for the resource or group.
282
+ def query_fte(query)
283
+ fte = 0.0
284
+ if @property.container?
285
+ # Accumulate the FTEs of all sub-resources.
286
+ @property.kids.each do |resource|
287
+ resource.query_fte(@scenarioIdx, query)
288
+ fte += query.to_num
289
+ end
290
+ else
291
+ # TODO: Getting the globalWorkSlots is relatively expensive. We
292
+ # probably don't need to compute this for every resource.
293
+ globalWorkSlots = @project.getWorkSlots(query.startIdx, query.endIdx)
294
+ workSlots = getWorkSlots(query.startIdx, query.endIdx)
295
+ fte = workSlots.to_f / globalWorkSlots if globalWorkSlots > 0
296
+ end
297
+
298
+ query.sortable = query.numerical = fte
299
+ query.string = query.numberFormat.format(fte)
300
+ end
301
+
281
302
  # Get the rate of the resource.
282
303
  def query_rate(query)
283
304
  query.sortable = query.numerical = r = rate
@@ -557,6 +578,23 @@ class TaskJuggler
557
578
  bookedSlots
558
579
  end
559
580
 
581
+ # Count the number of slots betweend the _startIdx_ and _endIdx_ that can
582
+ # be used for work
583
+ def getWorkSlots(startIdx, endIdx)
584
+ return 0 if startIdx >= endIdx
585
+
586
+ initScoreboard unless @scoreboard
587
+
588
+ workSlots = 0
589
+ startIdx.upto(endIdx - 1) do |idx|
590
+ slot = @scoreboard[idx]
591
+ # We count free slots and assigned slots.
592
+ workSlots += 1 if slot.nil? || slot.is_a?(Task)
593
+ end
594
+
595
+ workSlots
596
+ end
597
+
560
598
  # Count the free slots between the start and end index.
561
599
  def getFreeSlots(startIdx, endIdx)
562
600
  return 0 if startIdx >= endIdx
@@ -146,14 +146,16 @@ EOT
146
146
 
147
147
  # Isolate the actual syntax from _sheet_ and process it.
148
148
  def processSheet(sheet)
149
- @sheet = @sheetWasAttached ? cutOut(sheet) : sheet
150
-
151
149
  begin
152
- @sheet = @sheet.forceUTF8Encoding
150
+ @sheet = sheet.forceUTF8Encoding
153
151
  rescue
154
152
  error($!)
155
153
  end
156
154
 
155
+ # If the sheet contains special cut markers, we extract only the content
156
+ # within those markers.
157
+ @sheet = cutOut(@sheet)
158
+
157
159
  # A valid sheet must have the poper header line.
158
160
  if @sheetHeader.match(@sheet)
159
161
  checkSignature(@sheet)
@@ -38,7 +38,6 @@ class TaskJuggler
38
38
  str = @inputStr.dup
39
39
  # Replace all occurences of <-name->.
40
40
  str.gsub!(/<-[a-zA-Z][_a-zA-Z]*->/) do |match|
41
- len = match.size
42
41
  attribute = match[2..-3]
43
42
  @query.attributeId = attribute
44
43
  @query.process
@@ -1761,8 +1761,8 @@ class TaskJuggler
1761
1761
  # Prevent overbooking when multiple resources are allocated and
1762
1762
  # available. If the task has allocation limits we need to make sure
1763
1763
  # that none of them is already exceeded.
1764
- break if @effort > 0 && @doneEffort >= @effort ||
1765
- !limitsOk?(@currentSlotIdx, resource)
1764
+ break if (@effort > 0 && r['efficiency', @scenarioIdx] > 0.0 &&
1765
+ @doneEffort >= @effort) || !limitsOk?(@currentSlotIdx, r)
1766
1766
 
1767
1767
  if r.book(@scenarioIdx, @currentSlotIdx, @property)
1768
1768
  # For effort based task we adjust the the start end (as defined by
@@ -318,8 +318,6 @@ class TaskJuggler
318
318
  # stores the result of the pattern and keeps the State that we
319
319
  # need to return to in case we jump to other patterns from this
320
320
  # pattern.
321
- function = state.index == state.pattern.tokens.length - 1 ?
322
- state.pattern.function : nil
323
321
  @stack.push(TextParser::StackElement.new(state.pattern.function,
324
322
  state))
325
323
  end
@@ -136,7 +136,7 @@ class TaskJuggler::TextParser
136
136
  end
137
137
 
138
138
  # The token descriptor tells us where the transition(s) need to go to.
139
- tokenType, tokenName = token = @tokens[destIndex]
139
+ tokenType, tokenName = @tokens[destIndex]
140
140
 
141
141
  case tokenType
142
142
  when :reference
@@ -70,8 +70,6 @@ class TaskJuggler::TextParser
70
70
  # Inject the String _text_ into the input stream at the current cursor
71
71
  # position.
72
72
  def injectText(text, callLength)
73
- # Current scanner position in _bytes_ form start
74
- pos = @scanner.pos
75
73
  # Remove the macro call from the end of the already parsed input.
76
74
  preCall = @scanner.pre_match[0..-(callLength + 1)]
77
75
  # Store the end position of the inserted macro in bytes.
@@ -95,7 +95,8 @@ class TaskJuggler
95
95
 
96
96
  if @work >= @timeSheet.daysToSlots(1) && @status.nil?
97
97
  error('ts_no_status_work',
98
- "You must specify a status for task #{taskId}.")
98
+ "You must specify a status for task #{taskId}. It was worked " +
99
+ "on for a day or more.")
99
100
  end
100
101
 
101
102
  if @status
@@ -80,7 +80,7 @@ class TaskJuggler
80
80
  files = @opts.parse(argv)
81
81
  rescue OptionParser::ParseError => msg
82
82
  puts @opts.to_s + "\n"
83
- error(msg, 2)
83
+ error(msg.message, 2)
84
84
  end
85
85
 
86
86
  files
@@ -14,7 +14,7 @@
14
14
  require 'taskjuggler/UTF8String'
15
15
  require 'taskjuggler/AppConfig'
16
16
 
17
- AppConfig.version = '0.2.2'
17
+ AppConfig.version = '3.0.0'
18
18
  AppConfig.packageName = 'taskjuggler'
19
19
  AppConfig.softwareName = 'TaskJuggler III'
20
20
  AppConfig.packageInfo = 'A Project Management Software'
@@ -1767,6 +1767,11 @@ EOT
1767
1767
  listRule('moreIntervals', '!intervalOrDate')
1768
1768
  end
1769
1769
 
1770
+ def rule_intervalOptional
1771
+ optional
1772
+ singlePattern('!interval')
1773
+ end
1774
+
1770
1775
  def rule_intervalsOptional
1771
1776
  optional
1772
1777
  singlePattern('!intervals')
@@ -3360,7 +3365,13 @@ EOT
3360
3365
  )
3361
3366
 
3362
3367
  singlePattern('_fte')
3363
- descr('The Full-Time-Equivalent of a resource or group')
3368
+ descr(<<'EOT'
3369
+ The Full-Time-Equivalent of a resource or group. This is the ratio of the
3370
+ resource working time and the global working time. Working time is defined by
3371
+ working hours and vacations. The FTE value can vary over time and is
3372
+ calculated for the report interval or the user specified interval.
3373
+ EOT
3374
+ )
3364
3375
 
3365
3376
  singlePattern('_headcount')
3366
3377
  descr('The headcount number of the resource or group')
@@ -4431,23 +4442,21 @@ EOT
4431
4442
  end
4432
4443
 
4433
4444
  def rule_shiftAssignment
4434
- pattern(%w( !shiftId !intervalsOptional ), lambda {
4445
+ pattern(%w( !shiftId !intervalOptional ), lambda {
4435
4446
  # Make sure we have a ShiftAssignment for the property.
4436
4447
  sa = @property['shifts', @scenarioIdx] || ShiftAssignments.new
4437
4448
  sa.project = @project
4438
4449
 
4439
4450
  if @val[1].nil?
4440
- intervals = [ TimeInterval.new(@project['start'], @project['end']) ]
4451
+ interval = TimeInterval.new(@project['start'], @project['end'])
4441
4452
  else
4442
- intervals = @val[1]
4453
+ interval = @val[1]
4443
4454
  end
4444
- intervals.each do |interval|
4445
- if !sa.addAssignment(ShiftAssignment.new(@val[0].scenario(@scenarioIdx),
4446
- interval))
4447
- error('shift_assignment_overlap',
4448
- 'Shifts may not overlap each other.',
4449
- @sourceFileInfo[0], @property)
4450
- end
4455
+ if !sa.addAssignment(ShiftAssignment.new(@val[0].scenario(@scenarioIdx),
4456
+ interval))
4457
+ error('shift_assignment_overlap',
4458
+ 'Shifts may not overlap each other.',
4459
+ @sourceFileInfo[0], @property)
4451
4460
  end
4452
4461
  # Set same value again to set the 'provided' state for the attribute.
4453
4462
  begin
@@ -4798,7 +4807,7 @@ EOT
4798
4807
 
4799
4808
  def rule_statusSheetHeader
4800
4809
  pattern(%w( _statussheet !resourceId !valIntervalOrDate ), lambda {
4801
- unless (scenarioIdx = @project['trackingScenarioIdx'])
4810
+ unless @project['trackingScenarioIdx']
4802
4811
  error('ss_no_tracking_scenario',
4803
4812
  'No trackingscenario defined.')
4804
4813
  end
@@ -4975,7 +4984,7 @@ EOT
4975
4984
  LogicalExpression.new(LogicalOperation.new(0)))
4976
4985
  report.set('sortResources', [ [ 'seqno', true, -1 ] ])
4977
4986
  })
4978
- arg(1, 'file name', <<'EOT'
4987
+ arg(2, 'file name', <<'EOT'
4979
4988
  The name of the tagfile to generate. You can leave it empty and it will
4980
4989
  default to the commonly used name ''''tags''''.
4981
4990
  EOT
@@ -6198,6 +6207,8 @@ EOT
6198
6207
  doc('status.timesheet', <<'EOT'
6199
6208
  The status attribute can be used to describe the current status of the task or
6200
6209
  resource. The content of the status messages is added to the project journal.
6210
+ The status section is optional for tasks that have been worked on less than
6211
+ one day during the report interval.
6201
6212
  EOT
6202
6213
  )
6203
6214
  arg(2, 'headline', <<'EOT'
@@ -6518,7 +6529,11 @@ EOT
6518
6529
  end
6519
6530
  end
6520
6531
  wh.timezone = @project['timezone']
6521
- 7.times { |i| wh.setWorkingHours(i, @val[2]) if @val[1][i] }
6532
+ begin
6533
+ 7.times { |i| wh.setWorkingHours(i, @val[2]) if @val[1][i] }
6534
+ rescue
6535
+ error('bad_workinghours', $!.message)
6536
+ end
6522
6537
  })
6523
6538
  end
6524
6539
 
@@ -112,9 +112,10 @@ class TaskJuggler
112
112
  raise "dayOfWeek out of range: #{dayOfWeek}"
113
113
  end
114
114
  intervals.each do |iv|
115
- if iv[0] < 0 || iv[0] >= 24 * 60 * 60 ||
116
- iv[1] < 0 || iv[1] >= 24 * 60 * 60
117
- raise "Time interval has illegal values: #{iv[0]} - #{iv[1]}"
115
+ if iv[0] < 0 || iv[0] > 24 * 60 * 60 ||
116
+ iv[1] < 0 || iv[1] > 24 * 60 * 60
117
+ raise "Time interval has illegal values: " +
118
+ "#{time_to_s(iv[0])} - #{time_to_s(iv[1])}"
118
119
  end
119
120
  if iv[0] >= iv[1]
120
121
  raise "Interval end time must be larger than start time"
@@ -180,9 +181,7 @@ class TaskJuggler
180
181
  else
181
182
  str += ', '
182
183
  end
183
- str += "#{iv[0] / 3600}:" +
184
- "#{iv[0] % 3600 == 0 ? '00' : iv[0] % 3600} - " +
185
- "#{iv[1] / 3600}:#{iv[1] % 3600 == 0 ? '00' : iv[1] % 3600}"
184
+ str += "#{time_to_s(iv[0])} - #{time_to_s(iv[0])}"
186
185
  end
187
186
  str += "\n" if day < 6
188
187
  end
@@ -191,6 +190,10 @@ class TaskJuggler
191
190
 
192
191
  private
193
192
 
193
+ def time_to_s(t)
194
+ "#{t >= 24 * 60 * 60 ? '24:00' : "#{t / 3600}:#{t % 3600}"}"
195
+ end
196
+
194
197
  def initScoreboard
195
198
  # The scoreboard is an Array of True/False values. It spans a certain
196
199
  # time period with one entry per time slot.
@@ -87,7 +87,7 @@ EOT
87
87
  begin
88
88
  @freezeDate = TjTime.new(arg).align(3600)
89
89
  rescue TjException => msg
90
- error("Invalid freeze date: #{msg}")
90
+ error("Invalid freeze date: #{msg.message}")
91
91
  end
92
92
  end
93
93
  @opts.on('--freezebytask',
@@ -351,7 +351,7 @@ class TaskJuggler
351
351
  title = ''
352
352
  end
353
353
  trigger['onclick'] = "TagToTip('ID#{trigger.object_id}', " +
354
- "TITLE, '#{title}')"
354
+ "TITLE, '#{title.gsub(/'/, '&apos;')}')"
355
355
  trigger['style'] += 'cursor:help; '
356
356
  hook = trigger unless hook
357
357
  hook << (ltDiv = XMLElement.new('div', 'class' => 'tj_tooltip_box',
@@ -30,48 +30,54 @@ class TaskJuggler
30
30
  @current = false
31
31
  end
32
32
 
33
- def to_html
33
+ def to_html(html = nil)
34
34
  first = true
35
- html = (div = XMLElement.new('div'))
36
35
 
37
- @elements.each do |element|
38
- next unless label
36
+ topLevel = html.nil?
37
+
38
+ # If we don't have a container yet, to put all the menus into, create one.
39
+ html ||= XMLElement.new('div', 'class' => 'navbar_container')
39
40
 
41
+ html << XMLElement.new('hr', 'class' => 'navbar_topruler') if topLevel
42
+
43
+ # Create a container for this (sub-)menu.
44
+ html << (div = XMLElement.new('div', 'class' => 'navbar'))
45
+
46
+ @elements.each do |element|
47
+ # Separate the menu entries by vertical bars. Prepend them for all but
48
+ # the first entry.
40
49
  if first
41
50
  first = false
42
51
  else
43
52
  div << XMLText.new('|')
44
53
  end
45
54
 
46
- url = element.url
47
- if !url
48
- nEl = element
49
- while nEl.elements[0]
50
- break if nEl.current
51
-
52
- if nEl.elements[0].url
53
- url = nEl.elements[0].url
54
- break
55
- end
56
- nEl = nEl.elements[0]
57
- end
58
- end
59
- if url && url != currentUrl
60
- div << (span = XMLElement.new('span', 'class' => 'navbar_other'))
61
- span << (a = XMLElement.new('a', 'href' => url))
62
- a << XMLText.new(element.label)
63
- else
55
+ if element.current
56
+ # The navbar entry is referencing this page. Highlight is as the
57
+ # currently selected page.
64
58
  div << (span = XMLElement.new('span',
65
59
  'class' => 'navbar_current'))
66
60
  span << XMLText.new(element.label)
61
+ else
62
+ # The navbar entry is refencing another page. Show the link to it.
63
+ div << (span = XMLElement.new('span', 'class' => 'navbar_other'))
64
+ span << (a = XMLElement.new('a', 'href' => element.url))
65
+ a << XMLText.new(element.label)
67
66
  end
68
67
  end
68
+
69
+ # Now see if the current menu entry is actually just holding another sub
70
+ # menu and generate that menue in another line after an HR.
69
71
  @elements.each do |element|
70
72
  if element.current && !element.elements.empty?
71
- html << XMLElement.new('hr', {}, true) unless first
72
- html << element.to_html
73
+ html << XMLElement.new('hr', 'class' => 'navbar_midruler') unless first
74
+ element.to_html(html)
75
+ break
73
76
  end
74
77
  end
78
+
79
+ html << XMLElement.new('hr', 'class' => 'navbar_bottomruler') if topLevel
80
+
75
81
  html
76
82
  end
77
83
 
@@ -85,27 +91,6 @@ class TaskJuggler
85
91
  end
86
92
  end
87
93
 
88
- # Store the URL for the current report. Since the URL entry in the root
89
- # node of the NavigatorElement tree is never used, we use it to store the
90
- # current URL there.
91
- def currentUrl=(url)
92
- root.url = url
93
- end
94
-
95
- # Get the URL of the current report from the root node.
96
- def currentUrl
97
- root.url
98
- end
99
-
100
- # Traverse the tree all the way to the top and return the root element.
101
- def root
102
- p = self
103
- while p.parent
104
- p = p.parent
105
- end
106
- p
107
- end
108
-
109
94
  end
110
95
 
111
96
  # A Navigator is an automatically generated menu to navigate a list of
@@ -115,67 +100,79 @@ class TaskJuggler
115
100
  class Navigator
116
101
 
117
102
  attr_reader :id
118
- attr_accessor :hideReport, :reportRoot
103
+ attr_accessor :hideReport
119
104
 
120
105
  def initialize(id, project)
121
106
  @id = id
122
107
  @project = project
123
108
  @hideReport = LogicalExpression.new(LogicalOperation.new(0))
124
- @reportRoot = nil
125
109
  @elements = []
126
110
  end
127
111
 
128
112
  # Generate an output format independant version of the navigator. This is
129
113
  # a tree of NavigatorElement objects.
130
- def generate(reports, reportRoot, parentElement)
131
- reportDef = @project.reportContexts.last.report
132
- raise "Report context missing" unless reportDef
133
-
134
- interactive = false
135
- reports.each do |report|
136
- # The outermost (top-level) report determines whether the report
137
- # should be rendered interactive or not.
138
- interactive = report.interactive? unless interactive
139
-
114
+ def generate(allReports, currentReports, reportDef, parentElement)
115
+ element = nextParentElement = nextParentReport = nil
116
+ currentReports.each do |report|
140
117
  hasURL = report.get('formats').include?(:html)
141
- # Only generate menu entries for reports that are not the reportRoot,
142
- # that are leaf reports and have an HTML output format.
143
- next if (report.parent != reportRoot) ||
144
- (report.leaf? && !hasURL)
118
+ # Only generate menu entries for container reports or leaf reports
119
+ # have a HTML output format.
120
+ next if (report.leaf? && !hasURL) || !allReports.include?(report)
145
121
 
122
+ # What label should be used for the menu entry? It's either the name
123
+ # of the report or the user specified title.
146
124
  label = report.get('title') || report.name
147
- # Determine the URL for this element.
148
- if hasURL
149
- if interactive
150
- url = "/taskjuggler?project=#{report.project['projectid']};" +
151
- "report=#{report.fullId}"
152
- else
153
- url = report.name + '.html'
154
- url = normalizeURL(url, reportDef.name)
155
- end
156
- end
125
+
126
+ url = findReportURL(report, allReports, reportDef)
127
+
128
+ # Now we have all data so we can create the actual menu entry.
157
129
  parentElement.elements <<
158
130
  (element = NavigatorElement.new(parentElement, label, url))
159
131
 
160
- if report == reportDef
161
- element.currentUrl = url
162
- # Mark this element and all its parents as current.
163
- nEl = element
164
- while nEl
165
- nEl.current = true
166
- nEl = nEl.parent
167
- end
132
+ # Check if 'report' matches the 'reportDef' report or is a child of
133
+ # it.
134
+ if reportDef == report || reportDef.isChildOf?(report)
135
+ nextParentReport = report
136
+ nextParentElement = element
137
+ element.current = true
168
138
  end
139
+ end
169
140
 
170
- generate(reports, report, element)
141
+ if nextParentReport && nextParentReport.container?
142
+ generate(allReports, nextParentReport.kids, reportDef,
143
+ nextParentElement)
171
144
  end
172
145
  end
173
146
 
174
147
  def to_html
148
+ # The the Report object that contains this Navigator.
149
+ reportDef ||= @project.reportContexts.last.report
150
+ raise "Report context missing" unless reportDef
151
+
152
+ # Compile a list of all reports that the user wants to include in the
153
+ # menu.
175
154
  reports = filterReports
176
155
  return nil if reports.empty?
177
156
 
178
- generate(reports, nil, content = NavigatorElement.new(nil))
157
+ # Make sure the report is actually in the filtered list.
158
+ unless reports.include?(reportDef)
159
+ @project.warning('nav_in_hidden_rep',
160
+ "Navigator requested for a report that is not " +
161
+ "included in the navigator list.",
162
+ reportDef.sourceFileInfo)
163
+ return nil
164
+ end
165
+
166
+ # Find the list of reports that become the top-level menu entries.
167
+ topLevelReports = [ reportDef ]
168
+ report = reportDef
169
+ while report.parent
170
+ report = report.parent
171
+ topLevelReports = report.kids
172
+ end
173
+
174
+ generate(reports, topLevelReports, reportDef,
175
+ content = NavigatorElement.new(nil))
179
176
  content.to_html
180
177
  end
181
178
 
@@ -208,6 +205,31 @@ class TaskJuggler
208
205
  url1
209
206
  end
210
207
 
208
+ # Find the URL to be used for the current Navigator menu entry.
209
+ def findReportURL(report, allReports, reportDef)
210
+ return nil unless allReports.include?(report)
211
+
212
+ if report.get('formats').include?(:html)
213
+ # The element references an HTML report. Point to it.
214
+ if @project.reportContexts.last.report.interactive?
215
+ url = "/taskjuggler?project=#{report.project['projectid']};" +
216
+ "report=#{report.fullId}"
217
+ else
218
+ url = report.name + '.html'
219
+ url = normalizeURL(url, reportDef.name)
220
+ end
221
+ return url
222
+ else
223
+ # The menu element is just a entry for another sub-menu. The the URL
224
+ # from the first kid of the report that has a URL.
225
+ report.kids.each do |r|
226
+ if (url = findReportURL(r, allReports, reportDef))
227
+ return url
228
+ end
229
+ end
230
+ end
231
+ end
232
+
211
233
  end
212
234
 
213
235
  end