taskjuggler 0.2.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +29 -0
- data/data/css/tjreport.css +38 -4
- data/data/tjp.vim +421 -421
- data/examples/Fedora-20/reports.tji +0 -2
- data/examples/tutorial.tjp +0 -2
- data/lib/taskjuggler/Attributes.rb +14 -1
- data/lib/taskjuggler/Project.rb +13 -2
- data/lib/taskjuggler/ProjectFileScanner.rb +1 -1
- data/lib/taskjuggler/ResourceScenario.rb +38 -0
- data/lib/taskjuggler/SheetReceiver.rb +5 -3
- data/lib/taskjuggler/SimpleQueryExpander.rb +0 -1
- data/lib/taskjuggler/TaskScenario.rb +2 -2
- data/lib/taskjuggler/TextParser.rb +0 -2
- data/lib/taskjuggler/TextParser/Pattern.rb +1 -1
- data/lib/taskjuggler/TextParser/Scanner.rb +0 -2
- data/lib/taskjuggler/TimeSheets.rb +2 -1
- data/lib/taskjuggler/Tj3AppBase.rb +1 -1
- data/lib/taskjuggler/Tj3Config.rb +1 -1
- data/lib/taskjuggler/TjpSyntaxRules.rb +29 -14
- data/lib/taskjuggler/WorkingHours.rb +9 -6
- data/lib/taskjuggler/apps/Tj3.rb +1 -1
- data/lib/taskjuggler/reports/GanttLine.rb +1 -1
- data/lib/taskjuggler/reports/Navigator.rb +103 -81
- data/lib/taskjuggler/reports/NikuReport.rb +8 -5
- data/lib/taskjuggler/reports/Report.rb +10 -1
- data/lib/taskjuggler/reports/ReportTableCell.rb +1 -1
- data/lib/taskjuggler/reports/ResourceListRE.rb +1 -1
- data/lib/taskjuggler/reports/TableReport.rb +1 -1
- data/lib/taskjuggler/reports/TaskListRE.rb +1 -1
- data/lib/taskjuggler/reports/TjpExportRE.rb +31 -1
- data/manual/Installation +53 -7
- data/manual/Intro +36 -28
- data/manual/html/Day_To_Day_Juggling.html +1 -1
- data/manual/html/Getting_Started.html +1 -1
- data/manual/html/How_To_Contribute.html +1 -1
- data/manual/html/Installation.html +29 -8
- data/manual/html/Intro.html +32 -19
- data/manual/html/Reporting_Bugs.html +2 -2
- data/manual/html/Rich_Text_Attributes.html +1 -1
- data/manual/html/Software.html +1 -1
- data/manual/html/TaskJuggler_2x_Migration.html +3 -3
- data/manual/html/TaskJuggler_Internals.html +1 -1
- data/manual/html/The_TaskJuggler_Syntax.html +1 -1
- data/manual/html/Tutorial.html +1 -3
- data/manual/html/account.html +2 -2
- data/manual/html/account.task.html +1 -1
- data/manual/html/accountprefix.html +1 -1
- data/manual/html/active.html +1 -1
- data/manual/html/adopt.task.html +1 -1
- data/manual/html/alert level.html +1 -1
- data/manual/html/alert.html +1 -1
- data/manual/html/allocate.html +1 -1
- data/manual/html/alphabet.html +1 -1
- data/manual/html/alternative.html +1 -1
- data/manual/html/author.html +1 -1
- data/manual/html/balance.html +1 -1
- data/manual/html/booking.resource.html +2 -2
- data/manual/html/booking.task.html +1 -1
- data/manual/html/caption.html +1 -1
- data/manual/html/cellcolor.column.html +1 -1
- data/manual/html/celltext.column.html +1 -1
- data/manual/html/center.html +1 -1
- data/manual/html/charge.html +1 -1
- data/manual/html/chargeset.html +1 -1
- data/manual/html/columnid.html +2 -2
- data/manual/html/columns.html +1 -1
- data/manual/html/complete.html +1 -1
- data/manual/html/copyright.html +1 -1
- data/manual/html/credit.html +1 -1
- data/manual/html/css/tjreport.css +38 -4
- data/manual/html/currency.html +1 -1
- data/manual/html/currencyformat.html +2 -2
- data/manual/html/dailymax.html +1 -1
- data/manual/html/dailymin.html +1 -1
- data/manual/html/dailyworkinghours.html +1 -1
- data/manual/html/date.extend.html +1 -1
- data/manual/html/date.html +1 -1
- data/manual/html/definitions.html +10 -10
- data/manual/html/depends.html +1 -1
- data/manual/html/details.html +1 -1
- data/manual/html/disabled.html +1 -1
- data/manual/html/duration.html +1 -1
- data/manual/html/efficiency.html +2 -2
- data/manual/html/effort.html +1 -1
- data/manual/html/email.html +2 -2
- data/manual/html/enabled.html +1 -1
- data/manual/html/end.column.html +1 -1
- data/manual/html/end.html +1 -1
- data/manual/html/end.limit.html +2 -2
- data/manual/html/end.report.html +2 -2
- data/manual/html/end.timesheet.html +1 -1
- data/manual/html/endcredit.html +1 -1
- data/manual/html/epilog.html +1 -1
- data/manual/html/export.html +1 -1
- data/manual/html/extend.html +1 -1
- data/manual/html/fail.html +2 -2
- data/manual/html/fdl.html +1 -1
- data/manual/html/flags.account.html +1 -1
- data/manual/html/flags.html +1 -1
- data/manual/html/flags.journalentry.html +1 -1
- data/manual/html/flags.report.html +1 -1
- data/manual/html/flags.resource.html +2 -2
- data/manual/html/flags.statussheet.html +1 -1
- data/manual/html/flags.task.html +1 -1
- data/manual/html/flags.timesheet.html +1 -1
- data/manual/html/fontcolor.column.html +1 -1
- data/manual/html/footer.html +1 -1
- data/manual/html/formats.html +1 -1
- data/manual/html/functions.html +1 -1
- data/manual/html/gapduration.html +1 -1
- data/manual/html/gaplength.html +1 -1
- data/manual/html/halign.center.html +1 -1
- data/manual/html/halign.column.html +1 -1
- data/manual/html/halign.left.html +1 -1
- data/manual/html/halign.right.html +1 -1
- data/manual/html/hasalert.html +1 -1
- data/manual/html/header.html +1 -1
- data/manual/html/headline.html +1 -1
- data/manual/html/hidejournalentry.html +2 -2
- data/manual/html/hidereport.html +1 -1
- data/manual/html/hideresource.html +2 -2
- data/manual/html/hidetask.html +2 -2
- data/manual/html/icalreport.html +1 -1
- data/manual/html/include.macro.html +1 -1
- data/manual/html/include.project.html +1 -1
- data/manual/html/include.properties.html +1 -1
- data/manual/html/index.html +1 -1
- data/manual/html/inherit.extend.html +2 -2
- data/manual/html/interval1.html +1 -1
- data/manual/html/interval2.html +1 -1
- data/manual/html/interval3.html +1 -1
- data/manual/html/interval4.html +1 -1
- data/manual/html/isactive.html +1 -1
- data/manual/html/ischildof.html +1 -1
- data/manual/html/isdependencyof.html +1 -1
- data/manual/html/isdutyof.html +1 -1
- data/manual/html/isfeatureof.html +1 -1
- data/manual/html/isleaf.html +1 -1
- data/manual/html/ismilestone.html +1 -1
- data/manual/html/isongoing.html +1 -1
- data/manual/html/isresource.html +1 -1
- data/manual/html/istask.html +1 -1
- data/manual/html/journalattributes.html +14 -14
- data/manual/html/journalentry.html +2 -2
- data/manual/html/journalmode.html +1 -1
- data/manual/html/left.html +1 -1
- data/manual/html/length.html +1 -1
- data/manual/html/limits.allocate.html +1 -1
- data/manual/html/limits.html +1 -1
- data/manual/html/limits.resource.html +2 -2
- data/manual/html/limits.task.html +1 -1
- data/manual/html/listitem.column.html +1 -1
- data/manual/html/listtype.column.html +1 -1
- data/manual/html/loadunit.html +1 -1
- data/manual/html/logicalexpression.html +1 -1
- data/manual/html/logicalflagexpression.html +1 -1
- data/manual/html/macro.html +1 -1
- data/manual/html/managers.html +2 -2
- data/manual/html/mandatory.html +1 -1
- data/manual/html/maxend.html +1 -1
- data/manual/html/maximum.html +1 -1
- data/manual/html/maxstart.html +1 -1
- data/manual/html/milestone.html +1 -1
- data/manual/html/minend.html +1 -1
- data/manual/html/minimum.html +1 -1
- data/manual/html/minstart.html +1 -1
- data/manual/html/monthlymax.html +1 -1
- data/manual/html/monthlymin.html +1 -1
- data/manual/html/navbar.html +1 -1
- data/manual/html/navigator.html +1 -1
- data/manual/html/newtask.html +1 -1
- data/manual/html/nikureport.html +1 -1
- data/manual/html/note.task.html +1 -1
- data/manual/html/now.html +1 -1
- data/manual/html/numberformat.html +2 -2
- data/manual/html/onend.html +1 -1
- data/manual/html/onstart.html +1 -1
- data/manual/html/opennodes.html +1 -1
- data/manual/html/overtime.booking.html +2 -2
- data/manual/html/period.column.html +1 -1
- data/manual/html/period.limit.html +2 -2
- data/manual/html/period.report.html +2 -2
- data/manual/html/period.task.html +1 -1
- data/manual/html/persistent.html +1 -1
- data/manual/html/precedes.html +1 -1
- data/manual/html/priority.html +1 -1
- data/manual/html/priority.timesheet.html +1 -1
- data/manual/html/project.html +1 -1
- data/manual/html/projectid.html +1 -1
- data/manual/html/projectid.task.html +1 -1
- data/manual/html/projectids.html +1 -1
- data/manual/html/projection.html +1 -1
- data/manual/html/prolog.html +1 -1
- data/manual/html/properties.html +1 -1
- data/manual/html/purge.html +2 -2
- data/manual/html/rate.html +1 -1
- data/manual/html/rate.resource.html +2 -2
- data/manual/html/reference.extend.html +1 -1
- data/manual/html/remaining.html +1 -1
- data/manual/html/replace.html +1 -1
- data/manual/html/report.html +1 -1
- data/manual/html/reportprefix.html +1 -1
- data/manual/html/resource.html +2 -2
- data/manual/html/resourceattributes.html +6 -6
- data/manual/html/resourceprefix.html +1 -1
- data/manual/html/resourcereport.html +1 -1
- data/manual/html/resourceroot.html +1 -1
- data/manual/html/resources.limit.html +2 -2
- data/manual/html/responsible.html +1 -1
- data/manual/html/richtext.extend.html +1 -1
- data/manual/html/right.html +1 -1
- data/manual/html/rollupresource.html +2 -2
- data/manual/html/rolluptask.html +2 -2
- data/manual/html/scale.column.html +1 -1
- data/manual/html/scenario.html +2 -2
- data/manual/html/scenario.ical.html +1 -1
- data/manual/html/scenarios.export.html +1 -1
- data/manual/html/scenarios.html +1 -1
- data/manual/html/scenariospecific.extend.html +2 -2
- data/manual/html/scheduled.html +1 -1
- data/manual/html/scheduling.html +1 -1
- data/manual/html/select.html +1 -1
- data/manual/html/selfcontained.html +1 -1
- data/manual/html/shift.html +1 -1
- data/manual/html/shift.resource.html +5 -5
- data/manual/html/shift.task.html +4 -4
- data/manual/html/shift.timesheet.html +1 -1
- data/manual/html/shifts.allocate.html +1 -1
- data/manual/html/shifts.resource.html +5 -5
- data/manual/html/shifts.task.html +4 -4
- data/manual/html/shorttimeformat.html +1 -1
- data/manual/html/sloppy.booking.html +2 -2
- data/manual/html/sloppy.projection.html +1 -1
- data/manual/html/sortjournalentries.html +1 -1
- data/manual/html/sortresources.html +1 -1
- data/manual/html/sorttasks.html +1 -1
- data/manual/html/start.column.html +1 -1
- data/manual/html/start.html +1 -1
- data/manual/html/start.limit.html +2 -2
- data/manual/html/start.report.html +2 -2
- data/manual/html/startcredit.html +1 -1
- data/manual/html/status.statussheet.html +1 -1
- data/manual/html/status.timesheet.html +2 -2
- data/manual/html/statussheet.html +1 -1
- data/manual/html/statussheetreport.html +1 -1
- data/manual/html/strict.projection.html +1 -1
- data/manual/html/summary.html +1 -1
- data/manual/html/supplement.html +1 -1
- data/manual/html/supplement.resource.html +2 -2
- data/manual/html/supplement.task.html +1 -1
- data/manual/html/tagfile.html +8 -4
- data/manual/html/task.html +1 -1
- data/manual/html/task.statussheet.html +1 -1
- data/manual/html/task.timesheet.html +1 -1
- data/manual/html/taskattributes.html +22 -22
- data/manual/html/taskprefix.html +1 -1
- data/manual/html/taskreport.html +1 -1
- data/manual/html/taskroot.html +1 -1
- data/manual/html/text.extend.html +1 -1
- data/manual/html/textreport.html +1 -1
- data/manual/html/timeformat.html +2 -2
- data/manual/html/timeoff.nikureport.html +1 -1
- data/manual/html/timesheet.html +1 -1
- data/manual/html/timesheetreport.html +1 -1
- data/manual/html/timezone.export.html +1 -1
- data/manual/html/timezone.html +1 -1
- data/manual/html/timezone.report.html +1 -1
- data/manual/html/timezone.shift.html +1 -1
- data/manual/html/timingresolution.html +1 -1
- data/manual/html/title.column.html +1 -1
- data/manual/html/title.html +1 -1
- data/manual/html/toc.html +16 -12
- data/manual/html/tooltip.column.html +1 -1
- data/manual/html/trackingscenario.html +1 -1
- data/manual/html/treelevel.html +1 -1
- data/manual/html/vacation.html +1 -1
- data/manual/html/vacation.resource.html +2 -2
- data/manual/html/vacation.shift.html +1 -1
- data/manual/html/warn.html +2 -2
- data/manual/html/weeklymax.html +1 -1
- data/manual/html/weeklymin.html +1 -1
- data/manual/html/weekstartsmonday.html +1 -1
- data/manual/html/weekstartssunday.html +1 -1
- data/manual/html/width.column.html +1 -1
- data/manual/html/work.html +1 -1
- data/manual/html/workinghours.project.html +1 -1
- data/manual/html/workinghours.resource.html +2 -2
- data/manual/html/workinghours.shift.html +1 -1
- data/manual/html/yearlyworkingdays.html +1 -1
- data/tasks/changelog.rake +1 -0
- data/test/TestSuite/Export-Reports/refs/AutoID.tjp +18 -0
- data/test/TestSuite/Export-Reports/refs/Mandatory.tjp +2 -1
- data/test/TestSuite/Export-Reports/refs/Shift.tjp +49 -0
- data/test/TestSuite/HTML-Reports/Navigator.tjp +34 -0
- data/test/TestSuite/ReportGenerator/Correct/FTE.tjp +15 -0
- data/test/TestSuite/ReportGenerator/Correct/refs/FTE-1.csv +4 -0
- data/test/TestSuite/Syntax/Correct/Mandatory.tjp +1 -0
- data/test/TestSuite/Syntax/Correct/Shift.tjp +4 -0
- data/test/TestSuite/Syntax/Correct/tutorial.tjp +0 -2
- data/test/TestSuite/Syntax/Errors/invalid_file_name.tjp +9 -0
- data/test/test_CSVFile.rb +0 -1
- data/test/test_Journal.rb +5 -5
- data/test/test_LogicalExpression.rb +1 -1
- data/test/test_ShiftAssignments.rb +1 -1
- metadata +285 -299
data/examples/tutorial.tjp
CHANGED
@@ -547,7 +547,20 @@ class TaskJuggler
|
|
547
547
|
end
|
548
548
|
|
549
549
|
def to_tjp
|
550
|
-
|
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
|
data/lib/taskjuggler/Project.rb
CHANGED
@@ -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)
|
@@ -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 =
|
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)
|
@@ -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 && @
|
1765
|
-
|
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 =
|
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
|
@@ -14,7 +14,7 @@
|
|
14
14
|
require 'taskjuggler/UTF8String'
|
15
15
|
require 'taskjuggler/AppConfig'
|
16
16
|
|
17
|
-
AppConfig.version = '0.
|
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('
|
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 !
|
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
|
-
|
4451
|
+
interval = TimeInterval.new(@project['start'], @project['end'])
|
4441
4452
|
else
|
4442
|
-
|
4453
|
+
interval = @val[1]
|
4443
4454
|
end
|
4444
|
-
|
4445
|
-
|
4446
|
-
|
4447
|
-
|
4448
|
-
|
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
|
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(
|
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
|
-
|
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]
|
116
|
-
iv[1] < 0 || iv[1]
|
117
|
-
raise "Time interval has illegal values:
|
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]
|
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.
|
data/lib/taskjuggler/apps/Tj3.rb
CHANGED
@@ -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(/'/, ''')}')"
|
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
|
-
|
38
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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',
|
72
|
-
|
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
|
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(
|
131
|
-
|
132
|
-
|
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
|
142
|
-
#
|
143
|
-
next if (report.
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
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
|
-
|
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
|