taskjuggler 0.2.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +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
|