This specialization of ReportBase implements a template generator for time sheets. The time sheet is structured using the TJP file syntax.
In the future we might want to generate other output than TJP synatx. So we generate an abstract version of the time sheet first. This abstract version has a TSResourceRecord for each resource and each of these records holds a TSTaskRecord for each assigned task.
# File lib/taskjuggler/reports/TimeSheetReport.rb, line 64 64: def generateIntermediateFormat 65: super 66: @current = collectRecords(a('start'), a('end')) 67: newEnd = a('end') + (a('end').to_i - a('start').to_i) 68: newEnd = @project['end'] if newEnd > @project['end'] 69: @future = collectRecords(a('end'), a('end') + (a('end') - a('start'))) 70: end
Generate a time sheet in TJP syntax format.
# File lib/taskjuggler/reports/TimeSheetReport.rb, line 73 73: def to_tjp 74: # This String will hold the result. 75: @file = # The status headline should be no more than 60 characters and may# not be empty! The status summary is optional and should be no# longer than one or two sentences of plain text. The details section# is also optional has no length limitation. You can use simple# markup in this section. It is recommended that you provide at# least a summary or a details section.# See http://www.taskjuggler.org/tj3/manual/timesheet.html for details.## --------8<--------8<-------- 76: 77: # Iterate over all the resources that we have TSResourceRecords for. 78: @current.each do |rr| 79: resource = rr.resource 80: # Generate the time sheet header 81: @file << "timesheet #{resource.fullId} " + 82: "#{a('start')} - #{a('end')} {\n\n" 83: 84: @file << " # Vacation time: #{rr.vacationPercent}%\n\n" 85: 86: if rr.tasks.empty? 87: # If there were no assignments, just write a comment. 88: @file << " # There were no planned tasks assignments for " + 89: "this period!\n\n" 90: else 91: rr.tasks.each do |tr| 92: task = tr.task 93: 94: @file << " # Task: #{task.name}\n" 95: @file << " task #{task.fullId} {\n" 96: #@file << " work #{tr.workDays * 97: # @project['dailyworkinghours']}h\n" 98: @file << " work #{tr.workPercent}%\n" 99: if tr.remaining 100: @file << " remaining #{tr.remaining}d\n" 101: else 102: @file << " end #{tr.endDate}\n" 103: end 104: c = tr.workDays > 1.0 ? '' : '# ' 105: @file << " #{c}status green \"Your headline here!\" {\n" + 106: " # summary -8<-\n" + 107: " # A summary text\n" + 108: " # ->8-\n" + 109: " # details -8<-\n" + 110: " # Some more details\n" + 111: " # ->8-\n" + 112: " # flags ...\n" + 113: " #{c}}\n" 114: @file << " }\n\n" 115: end 116: end 117: @file << # If you had unplanned tasks, uncomment and fill out the # following lines: # newtask new.task.id "A task title" { # work X% # remaining Y.Yd # status green "Your headline here!" { # summary -8<- # A summary text # ->8- # details -8<- # Some more details # ->8- # flags ... # } # } # You can use the following section to report personal notes. # status green "Your headline here!" { # summary -8<- # A summary text # ->8- # details -8<- # Some more details # ->8- # } 118: future = @future[@future.index { |r| r.resource == resource }] 119: if future && !future.tasks.empty? 120: @file << # # Your upcoming tasks for the next period # Please check them carefully and discuss any necessary # changes with your manager or project manager! # 121: future.tasks.each do |taskRecord| 122: @file << " # #{taskRecord.task.name}: #{taskRecord.workPercent}%\n" 123: end 124: @file << "\n" 125: else 126: @file << "\n # You have no future assignments for this project!\n" 127: end 128: @file << "}\n# -------->8-------->8--------\n\n" 129: end 130: @file 131: end
# File lib/taskjuggler/reports/TimeSheetReport.rb, line 177 177: def collectRecords(from, to) 178: # Prepare the resource list. 179: resourceList = PropertyList.new(@project.resources) 180: resourceList.setSorting(@report.get('sortResources')) 181: resourceList = filterResourceList(resourceList, nil, 182: @report.get('hideResource'), 183: @report.get('rollupResource'), 184: @report.get('openNodes')) 185: # Prepare a template for the Query we will use to get all the data. 186: scenarioIdx = a('scenarios')[0] 187: queryAttrs = { 'project' => @project, 188: 'scopeProperty' => nil, 189: 'scenarioIdx' => scenarioIdx, 190: 'loadUnit' => a('loadUnit'), 191: 'numberFormat' => a('numberFormat'), 192: 'timeFormat' => a('timeFormat'), 193: 'currencyFormat' => a('currencyFormat'), 194: 'start' => from, 'end' => to, 195: 'hideJournalEntry' => a('hideJournalEntry'), 196: 'costAccount' => a('costAccount'), 197: 'revenueAccount' => a('revenueAccount') } 198: resourceList.query = Query.new(queryAttrs) 199: resourceList.sort! 200: 201: # Prepare the task list. 202: taskList = PropertyList.new(@project.tasks) 203: taskList.setSorting(@report.get('sortTasks')) 204: taskList = filterTaskList(taskList, nil, @report.get('hideTask'), 205: @report.get('rollupTask'), 206: @report.get('openNodes')) 207: 208: records = [] 209: resourceList.each do |resource| 210: # Time sheets only make sense for leaf resources that actuall do work. 211: next unless resource.leaf? 212: 213: # Create a new TSResourceRecord for the resource. 214: records << (resourceRecord = TSResourceRecord.new(resource)) 215: 216: # Calculate the average working days per week (usually 5) 217: weeklyWorkingDays = @project['yearlyworkingdays'] / 52.1428 218: # Calculate the number of weeks in the report 219: weeksToReport = (to - from) / (60 * 60 * 24 * 7) 220: 221: # Get the vacation days for the resource for this period. 222: queryAttrs['property'] = resource 223: query = Query.new(queryAttrs) 224: query.attributeId = 'vacationdays' 225: query.start = from 226: query.end = to 227: query.process 228: resourceRecord.vacationHours = query.to_s 229: resourceRecord.vacationPercent = 230: (query.to_num / (weeksToReport * weeklyWorkingDays)) * 100.0 231: 232: 233: # Now we have to find all the task that the resource is allocated to 234: # during the report period. 235: assignedTaskList = filterTaskList(taskList, resource, 236: a('hideTask'), a('rollupTask'), 237: a('openNodes')) 238: queryAttrs['scopeProperty'] = resource 239: assignedTaskList.query = Query.new(queryAttrs) 240: assignedTaskList.sort! 241: 242: assignedTaskList.each do |task| 243: # Time sheet task records only make sense for leaf tasks. 244: reportIv = Interval.new(from, to) 245: taskIv = Interval.new(task['start', scenarioIdx], 246: task['end', scenarioIdx]) 247: next if !task.leaf? || !reportIv.overlaps?(taskIv) 248: 249: queryAttrs['property'] = task 250: query = Query.new(queryAttrs) 251: 252: # Get the allocated effort for the task for this period. 253: query.attributeId = 'effort' 254: query.start = from 255: query.end = to 256: query.process 257: # The Query.to_num of an effort always returns the value in days. 258: workDays = query.to_num 259: workPercent = (workDays / (weeksToReport * weeklyWorkingDays)) * 260: 100.0 261: 262: remaining = endDate = nil 263: if task['effort', scenarioIdx] > 0 264: # The task is an effort based task. 265: # Get the remaining effort for this task. 266: query.start = to 267: query.end = task['end', scenarioIdx] 268: query.loadUnit = :days 269: query.process 270: remaining = query.to_s 271: else 272: # The task is a duration task. 273: # Get the planned task end date. 274: endDate = task['end', scenarioIdx] 275: end 276: 277: # Put all data into a TSTaskRecord and push it into the resource 278: # record. 279: resourceRecord.tasks << 280: TSTaskRecord.new(task, workDays, workPercent, remaining, endDate) 281: end 282: end 283: 284: records 285: end
This utility function is used to indent multi-line attributes. All attributes should be filtered through this function. Attributes that contain line breaks will be indented properly. In addition to the indentation specified by indent all but the first line will be indented after the first word of the first line. The text may not end with a line break.
# File lib/taskjuggler/reports/TimeSheetReport.rb, line 293 293: def indentBlock(text, indent) 294: out = '' 295: firstSpace = 0 296: text.length.times do |i| 297: if firstSpace == 0 && text[i] == \ \# There must be a space after ? 298: firstSpace = i 299: end 300: out << text[i] 301: if text[i] == \n\ 302: out += ' ' * (indent + firstSpace) 303: end 304: end 305: out 306: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.