Class: TaskJuggler::NikuReport

Inherits:
ReportBase show all
Defined in:
lib/taskjuggler/reports/NikuReport.rb

Overview

The Niku report can be used to export resource allocation data for certain task groups in the Niku XOG format. This file can be read by the Clarity enterprise resource management software from Computer Associates. Since I don’t think this is a use case for many users, the implementation is somewhat of a hack. The report relies on 3 custom attributes that the user has to define in the project. Resources must be tagged with a ClarityRID and Tasks must have a ClarityPID and a ClarityPName. This file format works for our Clarity installation. I have no idea if it is even portable to other Clarity installations.

Instance Method Summary collapse

Methods inherited from ReportBase

#a, #filterAccountList, #filterResourceList, #filterTaskList

Constructor Details

#initialize(report) ⇒ NikuReport

Returns a new instance of NikuReport.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/taskjuggler/reports/NikuReport.rb', line 56

def initialize(report)
  super(report)

  # A Hash to store NikuProject objects by id
  @projects = {}

  # A Hash to map ClarityRID to Resource
  @resources = {}

  # Unallocated and vacation time during the report period for all
  # resources hashed by ClarityId. Values are in days.
  @resourcesFreeWork = {}

  # Resources total effort during the report period hashed by ClarityId
  @resourcesTotalEffort = {}

  @scenarioIdx = nil
end

Instance Method Details

#generateIntermediateFormatObject



75
76
77
78
79
80
81
82
83
# File 'lib/taskjuggler/reports/NikuReport.rb', line 75

def generateIntermediateFormat
  super

  @scenarioIdx = a('scenarios')[0]

  computeResourceTotals
  collectProjects
  computeProjectAllocations
end

#to_csvObject



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/taskjuggler/reports/NikuReport.rb', line 207

def to_csv
  table = []
  # Header line with project names
  table << (row = [])
  # First column is the resource name and ID.
  row << ""
  projectIds = @projects.keys.sort
  projectIds.each do |projectId|
    row << @projects[projectId].name
  end

  # Header line with project IDs
  table << (row = [])
  row << "Resource"
  projectIds.each do |projectId|
    row << projectId
  end

  @resourcesTotalEffort.keys.sort.each do |resourceId|
    # Add one line per resource.
    table << (row = [])
    row << "#{@resources[resourceId].name} (#{resourceId})"
    projectIds.each do |projectId|
      row << sum(projectId, resourceId)
    end
  end

  table
end

#to_htmlObject



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/taskjuggler/reports/NikuReport.rb', line 85

def to_html
  tableFrame = generateHtmlTableFrame

  tableFrame << (tr = XMLElement.new('tr'))
  tr << (td = XMLElement.new('td'))
  td << (table = XMLElement.new('table', 'class' => 'tj_table',
                                         'cellspacing' => '1'))

  # Table Header with two rows. First the project name, then the ID.
  table << (thead = XMLElement.new('thead'))
  thead << (tr = XMLElement.new('tr', 'class' => 'tabline'))
  # First line
  tr << htmlTabCell('Project', true, 'right')
  @projects.keys.sort.each do |projectId|
    # Don't include projects without allocations.
    next if projectTotal(projectId) <= 0.0
    name = @projects[projectId].name
    # To avoid exploding tables for long project names, we only show the
    # last 15 characters for those. We expect the last characters to be
    # more significant in those names than the first.
    name = '...' + name[-15..-1] if name.length > 15
    tr << htmlTabCell(name, true, 'center')
  end
  tr << htmlTabCell('', true)
  # Second line
  thead << (tr = XMLElement.new('tr', 'class' => 'tabline'))
  tr << htmlTabCell('Resource', true, 'left')
  @projects.keys.sort.each do |projectId|
    # Don't include projects without allocations.
    next if projectTotal(projectId) <= 0.0
    tr << htmlTabCell(projectId, true, 'center')
  end
  tr << htmlTabCell('Total', true, 'center')

  # The actual content. One line per resource.
  table << (tbody = XMLElement.new('tbody'))
  numberFormat = a('numberFormat')
  @resourcesTotalEffort.keys.sort.each do |resourceId|
    tbody << (tr = XMLElement.new('tr', 'class' => 'tabline'))
    tr << htmlTabCell("#{@resources[resourceId].name} (#{resourceId})",
                      true, 'left')

    @projects.keys.sort.each do |projectId|
      next if projectTotal(projectId) <= 0.0
      value = sum(projectId, resourceId)
      valStr = numberFormat.format(value)
      valStr = '' if valStr.to_f == 0.0
      tr << htmlTabCell(valStr)
    end

    tr << htmlTabCell(numberFormat.format(resourceTotal(resourceId)), true)
  end

  # Project totals
  tbody << (tr = XMLElement.new('tr', 'class' => 'tabline'))
  tr << htmlTabCell('Total', 'true', 'left')
  @projects.keys.sort.each do |projectId|
    next if (pTotal = projectTotal(projectId)) <= 0.0
    tr << htmlTabCell(numberFormat.format(pTotal), true, 'right')
  end
  tr << htmlTabCell(numberFormat.format(total()), true, 'right')
  tableFrame
end

#to_nikuObject



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/taskjuggler/reports/NikuReport.rb', line 149

def to_niku
  xml = XMLDocument.new
  xml << XMLComment.new(<<"EOT"
Generated by #{AppConfig.softwareName} v#{AppConfig.version} on #{TjTime.new}
For more information about #{AppConfig.softwareName} see #{AppConfig.contact}.
Project: #{@project['name']}
Date:    #{@project['now']}
EOT
                       )
  xml << (nikuDataBus =
          XMLElement.new('NikuDataBus',
                         'xmlns:xsi' =>
                         'http://www.w3.org/2001/XMLSchema-instance',
                         'xsi:noNamespaceSchemaLocation' =>
                         '../xsd/nikuxog_project.xsd'))
  nikuDataBus << XMLElement.new('Header', 'action' => 'write',
                                'externalSource' => 'NIKU',
                                'objectType' => 'project',
                                'version' => '7.5.0')
  nikuDataBus << (projects = XMLElement.new('Projects'))

  timeFormat = '%Y-%m-%dT%H:%M:%S'
  numberFormat = a('numberFormat')
  @projects.keys.sort.each do |projectId|
    prj = @projects[projectId]
    projects << (project =
                 XMLElement.new('Project',
                                'name' => prj.name,
                                'projectID' => prj.id))
    project << (resources = XMLElement.new('Resources'))
    # We iterate over all resources to ensure that all have an entry in
    # the Clarity database for all projects. This is done to work around a
    # limitation of Clarity with respect to filling time sheets with
    # assigned projects.
    @resources.keys.sort.each do |clarityRID|
      resources << (resource =
                    XMLElement.new('Resource',
                                   'resourceID' => clarityRID,
                                   'defaultAllocation' => '0'))
      resource << (allocCurve = XMLElement.new('AllocCurve'))
      sum = sum(prj.id, clarityRID)
      allocCurve << (XMLElement.new('Segment',
                                    'start' =>
                                    a('start').to_s(timeFormat),
                                    'finish' =>
                                    (a('end') - 1).to_s(timeFormat),
                                    'sum' => numberFormat.format(sum).to_s))
    end

    # The custom information section usually contains Clarity installation
    # specific parts. They are identical for each project section, so we
    # mis-use the title attribute to insert them as an XML blob.
    project << XMLBlob.new(a('title')) unless a('title').empty?
  end

  xml.to_s
end