Commit f577edc5 authored by Project Open's avatar Project Open

- Moved TaskJuggler integration into a separate package

parent ac5577fe
# /packages/intranet-ganttproject/tcl/intranet-taskjuggler-procs.tcl
#
# Copyright (C) 2003 - 2009 ]project-open[
#
# All rights reserved. Please check
# http://www.project-open.com/license/ for details.
ad_library {
Integrate ]project-open[ tasks and resource assignations
with GanttProject and its data structure
@author frank.bergmann@project-open.com
}
# ----------------------------------------------------------------------
# TaskJuggler
# ---------------------------------------------------------------------
ad_proc -public im_taskjuggler_task_path {
project_id
} {
Returns a TJ "absolute" path for a task.
Example: "t1234.t2345.t3456"
} {
set parent_id [db_string super_project "select parent_id from im_projects where project_id = :project_id" -default ""]
if {"" != $parent_id} {
#set super_project_path [util_memoize [list im_taskjuggler_task_path $parent_id]]
set super_project_path [im_taskjuggler_task_path $parent_id]
if {"" != $super_project_path} { append super_project_path "." }
return "${super_project_path}t$project_id"
} else {
return ""
}
}
ad_proc -public im_taskjuggler_write_subtasks {
{-depth 0 }
{-default_start ""}
project_id
} {
Returns a TJ specification of the project's tasks
} {
ns_log Notice "im_taskjuggler_write_subtasks: pid=$project_id, depth=$depth, default_start=$default_start"
# Get sub-tasks in the right sort_order
set project_list [db_list sorted_query "
select
p.project_id
from
im_projects p,
acs_objects o
where
p.project_id = o.object_id
and parent_id = :project_id
-- and p.project_type_id = [im_project_type_task]
and p.project_status_id not in (
[im_project_status_deleted],
[im_project_status_closed]
)
order by sort_order
"]
set result ""
foreach project_id $project_list {
append result [im_taskjuggler_write_task -depth $depth -default_start $default_start $project_id]
}
return $result
}
ad_proc -public im_taskjuggler_write_task {
{-depth 0 }
{-default_start ""}
project_id
} {
Write out the information about one specific task and then call
a recursive routine to write out the stuff below the task.
} {
ns_log Notice "im_taskjuggler_write_task: pid=$project_id, depth=$depth, default_start=$default_start"
set org_project_id $project_id
set indent ""
for {set i 0} {$i < $depth} {incr i} { append indent "\t" }
incr depth
set tj ""
# Get everything about the project
if {![db_0or1row project_info "
select p.*,
t.*,
o.object_type,
p.start_date::date as start_date,
p.end_date::date as end_date,
g.*,
(select count(*) from im_projects sub_p where sub_p.parent_id = :project_id) as num_subtasks
from im_projects p
LEFT OUTER JOIN im_timesheet_tasks t ON (p.project_id = t.task_id)
LEFT OUTER JOIN im_gantt_projects g ON (p.project_id = g.project_id),
acs_objects o
where p.project_id = :project_id
and p.project_id = o.object_id
"]} {
ad_return_complaint 1 [lang::message::lookup "" intranet-ganttproject.Project_Not_Found "Didn't find project \#%project_id%"]
return
}
# --------------------------------------------------------------
# Massage values
if {"" == $priority} { set priority "1" }
if {"" == $start_date} { set start_date $default_start }
if {"" == $start_date} { set start_date [db_string today "select to_char(now(), 'YYYY-MM-DD')"] }
ns_log Notice "im_taskjuggler_write_task: pid=$project_id, project_name=$project_name, start_date=$start_date"
append tj "${indent}task t$org_project_id \"$project_name\" {\n"
# --------------------------------------------------------------
# Add dependencies to predecessors
set dependency_sql "
SELECT DISTINCT
ttd.task_id_two,
ttd.dependency_type_id
FROM im_timesheet_task_dependencies ttd,
im_projects p
WHERE ttd.task_id_two = p.project_id AND
task_id_one = :task_id AND
task_id_two <> :task_id
"
set dependency_ctr 0
db_foreach dependency $dependency_sql {
# ToDo: Verify that there is only end-to-start dependency types in TaskJuggler
set task_path [im_taskjuggler_task_path $task_id_two]
append tj "${indent}\tdepends $task_path # $task_id_two\n"
incr dependency_ctr
}
# Make tasks without dependency start at the start of the project
if {0 == $dependency_ctr} {
append tj "${indent}\tstart $default_start\n"
# Write the start command once for the topmost task
# if {0 != $num_subtasks} {
# }
}
# --------------------------------------------------------------
# Write out dependent tasks
set sub_tasks [im_taskjuggler_write_subtasks -depth $depth -default_start $default_start $org_project_id]
append tj $sub_tasks
# Write out effort and assignment information only for leaf tasks
if {0 == $num_subtasks} {
# --------------------------------------------------------------
# Planned units
if {"" != $planned_units} {
set effort_tj "effort $planned_units"
switch $uom_id {
321 {
# Day
append effort_tj "d"
}
320 {
# Hour
append effort_tj "h"
}
default {
ad_return_complaint 1 "Found invalid UoM for a Gantt task: $uom_id"
ad_script_abort
}
}
append tj "${indent}\t$effort_tj\n"
}
# --------------------------------------------------------------
# Allocations
set project_allocations_sql "
select
r.object_id_one AS task_id,
r.object_id_two AS user_id,
coalesce(bom.percentage, 100.0) as percentage
from acs_rels r,
im_biz_object_members bom
where
r.rel_id = bom.rel_id AND
r.object_id_one = :org_project_id
"
set allocation_ctr 0
db_foreach project_allocations $project_allocations_sql {
incr allocation_ctr
set allocation_hours [expr {$percentage * 8.0 / 100.0}]
if {$allocation_hours < 0.1 } {
# Ignore allocations below 1%.
append tj "${indent}\t\# WARNING: Ignoring assignment percentage of $percentage because it is below the TJ resolution\n"
continue
}
if {$allocation_hours < 1.0} {
# Tj allocation precision is 1h.
append tj "${indent}\t\# WARNING: Increasing allocation from $allocation_hours to 1.0h, because of TJ resolution\n"
set allocation_hours 1.0
}
append tj "${indent}\tallocate r$user_id { limits { dailymax ${allocation_hours}h } }\n"
}
if {0 == $allocation_ctr} {
append tj "${indent}\tallocate members\n"
}
# --------------------------------------------------------------
# Percent Completed
# Only add this to leaf tasks
if {"" == [string trim $sub_tasks]} {
if {"" != $percent_completed} {
append tj "${indent}\tcomplete $percent_completed\n"
}
}
}
# --------------------------------------------------------------
# Close the task
append tj "${indent}}\n"
return $tj
}
<master>
<property name="doc(title)">@page_title;literal@</property>
<property name="context">@context_bar;literal@</property>
<property name="main_navbar_label">projects</property>
<property name="sub_navbar">@sub_navbar;literal@</property>
<h1><%= [lang::message::lookup "" intranet-ganttproject.TaskJuggler_Schedule_Successfully_Imported "
TaskJuggler Schedule Successfully Imported
"] %></h1>
<p>
<%= [lang::message::lookup "" intranet-ganttproject.Schedule_Imported_msg "
Your TaskJuggler schedule has been successfully imported into \]project-open\[.<br>
The following Gantt diagram shows the results.
"] %>
</p>
@content;noquote@
# /packages/intranet-ganttproject/www/taskjuggler-import.tcl
#
# Copyright (C) 2003 - 2010 ]project-open[
#
# All rights reserved. Please check
# http://www.project-open.com/license/ for details.
# ---------------------------------------------------------------
# Page Contract
# ---------------------------------------------------------------
ad_page_contract {
Create a TaskJuggler .tpj file for scheduling
@author frank.bergmann@project-open.com
} {
project_id:integer
{return_url ""}
}
# ---------------------------------------------------------------
# Defaults & Security
# ---------------------------------------------------------------
set user_id [auth::require_login]
set page_title [lang::message::lookup "" intranet-ganttproject.TaskJuggler_Import "TaskJuggler Import"]
set context_bar [im_context_bar $page_title]
if {"" == $return_url} { set return_url [im_url_with_query] }
# ---------------------------------------------------------------
# Get information about the project
# ---------------------------------------------------------------
if {![db_0or1row project_info "
select g.*,
p.*,
p.project_id as main_project_id,
p.project_name as main_project_name,
p.start_date::date as project_start_date,
p.end_date::date as project_end_date,
c.company_name,
im_name_from_user_id(p.project_lead_id) as project_lead_name
from im_projects p left join im_gantt_projects g on (g.project_id=p.project_id),
im_companies c
where p.project_id = :project_id
and p.company_id = c.company_id
"]} {
ad_return_complaint 1 [lang::message::lookup "" intranet-ganttproject.Project_Not_Found "Didn't find project \#%project_id%"]
return
}
# ---------------------------------------------------------------
# Open the "taskreport.csv" file
# ---------------------------------------------------------------
set project_dir [im_filestorage_project_path $main_project_id]
set tj_folder "taskjuggler"
set tj_dir "$project_dir/$tj_folder"
set csv_file "taskreport.csv"
if {[catch {
set fl [open "$tj_dir/$csv_file" ]
set content [read $fl]
close $fl
} err]} {
ad_return_complaint 1 "<b>Unable to read $tj_dir/$csv_file</b>:<br><pre>\n$err</pre>"
ad_script_abort
}
set values [im_csv_get_values $content ";"]
set debug_html ""
foreach line $values {
set id [lindex $line 0]
set start [lindex $line 2]
set end [lindex $line 3]
set gp_task_id [lindex [split $id "."] end]
regexp {^t(.*)} $gp_task_id match task_id
regexp {^(....-..-..)} $start match start_date
regexp {^(....-..-..)} $end match end_date
db_dml update_projects "
update im_projects set
start_date = :start_date,
end_date = :end_date
where
project_id = :task_id
"
im_audit -object_id $task_id
}
set content [im_ganttproject_gantt_component \
-project_id $project_id \
-return_url $return_url \
-export_var_list [list project_id] \
-auto_open 1 \
-zoom "in" \
-max_col 30 \
-max_row 100 \
]
# ---------------------------------------------------------------------
# Projects Submenu
# ---------------------------------------------------------------------
set bind_vars [ns_set create]
ns_set put $bind_vars project_id $project_id
set parent_menu_id [im_menu_id_from_label "project"]
set menu_label ""
set sub_navbar [im_sub_navbar \
-components \
-base_url [export_vars -base "/intranet/projects/view" {project_id}] \
$parent_menu_id \
$bind_vars \
"" \
"pagedesriptionbar" \
$menu_label \
]
<master>
<property name="doc(title)">@page_title;literal@</property>
<property name="context">@context_bar;literal@</property>
<property name="main_navbar_label">projects</property>
<property name="sub_navbar">@sub_navbar;literal@</property>
<!-- ------------------------------------------------------------------------------ -->
<%= [im_box_header [lang::message::lookup "" intranet-ganttproject.TaskJuggler_Output_Files "TaskJuggler Output Files"]] %>
<%
set project_id $main_project_id
set project_path [im_filestorage_project_path $project_id]
set folder_type "project"
set object_name [lang::message::lookup "" intranet-filestorage.Folder_type_Project "Project"]
%>
<%= [im_filestorage_base_component $user_id $project_id $main_project_name $project_path $folder_type "taskjuggler"] %>
<%= [im_box_footer] %>
<p>
The files above have been automatically generated by ]project-open[ and TaskJuggler.<br>
You can click on the icons of the files above in order to inspect their contents.<br>
For interpretation, please see <a href='http://www.taskjuggker.com/'>www.taskjuggler.org</a>.
</p>
<br>&nbsp;<br>
<h1>Import TaskJuggler Schedule</h1>
<p>
The following button will read the "@taskreport_csv@" file<br>
and import the values into the current project.
</p>
<form action=taskjuggler-import method=POST>
<%= [export_vars -form {project_id}] %>
<input type="submit" name="import" value="Import Schedule">
from TaskJuggler into ]project-open[
</form>
# /packages/intranet-ganttproject/www/taskjuggler.xml.tcl
#
# Copyright (C) 2003 - 2010 ]project-open[
#
# All rights reserved. Please check
# http://www.project-open.com/license/ for details.
# ---------------------------------------------------------------
# Page Contract
# ---------------------------------------------------------------
ad_page_contract {
Create a TaskJuggler .tpj file for scheduling
@author frank.bergmann@project-open.com
} {
project_id:integer
{return_url ""}
}
# ---------------------------------------------------------------
# Defaults & Security
# ---------------------------------------------------------------
set today [db_string today "select to_char(now(), 'YYYY-MM-DD')"]
set user_id [auth::require_login]
set hours_per_day 8.0
set default_currency [im_parameter -package_id [im_package_cost_id] "DefaultCurrency" "" "EUR"]
set page_title [lang::message::lookup "" intranet-ganttproject.TaskJuggler_Scheduling "TaskJuggler Scheduling"]
set context_bar [im_context_bar $page_title]
if {"" == $return_url} { set return_url [im_url_with_query] }
# ---------------------------------------------------------------
# Get information about the project
# ---------------------------------------------------------------
if {![db_0or1row project_info "
select g.*,
p.*,
p.project_id as main_project_id,
p.project_name as main_project_name,
p.start_date::date as project_start_date,
p.end_date::date as project_end_date,
c.company_name,
im_name_from_user_id(p.project_lead_id) as project_lead_name
from im_projects p left join im_gantt_projects g on (g.project_id=p.project_id),
im_companies c
where p.project_id = :project_id
and p.company_id = c.company_id
"]} {
ad_return_complaint 1 [lang::message::lookup "" intranet-ganttproject.Project_Not_Found "Didn't find project \#%project_id%"]
return
}
# ---------------------------------------------------------------
# Create the TJ Header
# ---------------------------------------------------------------
set base_tj "
/*
* This file has been automatically created by \]project-open\[
* Please do not edit manually.
*/
project p$main_project_id \"$project_name\" \"1.0\" $project_start_date - $project_end_date {
currency \"$default_currency\"
}
"
# ---------------------------------------------------------------
# Create the TJ Footer
# ---------------------------------------------------------------
set taskreport_csv "taskreport.csv"
set taskreport_html "taskreport.html"
set statusreport_html "statusreport.html"
set resourcereport_html "resourcereport.html"
set resourcereport_html "resourcereport.html"
set weekly_calendar_html "weekly_calendar.html"
set gantt_chart_html "gantt_chart.html"
set footer_tj "
# The main report that will be parsed by \]po\[
csvtaskreport \"$taskreport_csv\" {
columns id, name, start, end, effort, duration, chart
loadunit days
}
htmltaskreport \"$taskreport_html\"
htmlstatusreport \"$statusreport_html\"
htmltaskreport \"$gantt_chart_html\" {
headline \"Project Gantt Chart\"
columns hierarchindex, name, start, end, effort, duration, chart
loadunit days
}
"
# ---------------------------------------------------------------
# Resource TJ Entries
# ---------------------------------------------------------------
set project_resources_sql "
select distinct
p.*,
im_name_from_user_id(p.person_id) as user_name,
pa.email,
uc.*,
e.*
from users_contact uc,
acs_rels r,
im_biz_object_members bom,
persons p,
parties pa
LEFT OUTER JOIN im_employees e ON (pa.party_id = e.employee_id)
where
r.rel_id = bom.rel_id
and r.object_id_two = uc.user_id
and uc.user_id = p.person_id
and uc.user_id = pa.party_id
and r.object_id_one in (
select children.project_id as subproject_id
from im_projects parent,
im_projects children
where children.project_status_id not in (
[im_project_status_deleted],
[im_project_status_canceled]
)
and children.tree_sortkey between
parent.tree_sortkey and
tree_right(parent.tree_sortkey)
and parent.project_id = :main_project_id
UNION
select :main_project_id
)
"
set resource_tj "resource members \"All Members\" {\n"
db_foreach project_resources $project_resources_sql {
set user_tj "\tresource r$person_id \"$user_name\" {\n"
if {"" != $hourly_cost} {
append user_tj "\t\trate [expr {$hourly_cost * $hours_per_day}]\n"
}
# ---------------------------------------------------------------
# Absences
set absences_sql "
select ua.start_date::date as absence_start_date,
ua.end_date::date + 1 as absence_end_date
from im_user_absences ua
where ua.owner_id = :person_id and
ua.end_date >= :project_start_date
order by start_date
"
db_foreach resource_absences $absences_sql {
append user_tj "\t\tvacation $absence_start_date - $absence_end_date\n"
}
# ---------------------------------------------------------------
# Timesheet Entries
set timesheet_sql "
SELECT child.project_id as child_project_id,
h.day::date as hour_date,
h.hours as hour_hours
FROM im_projects parent,
im_projects child,
im_hours h
WHERE parent.project_id = :main_project_id AND
child.tree_sortkey between parent.tree_sortkey and tree_right(parent.tree_sortkey) AND
child.project_id = h.project_id AND
h.user_id = :person_id
"
db_foreach timesheet $timesheet_sql {
set key "r$person_id"
set bookings ""
if {[info exists booking_hash($key)]} { set booking $booking_hash($key) }
append bookings "\t\tbooking t$child_project_id $hour_date +${hour_hours}h\n"
set booking_hash($key) $bookings
}
# ---------------------------------------------------------------
# Close the resource definition
append user_tj "\t}\n"
append resource_tj "$user_tj\n"
}
append resource_tj "}\n"
# ---------------------------------------------------------------
# Task TJ Entries
# ---------------------------------------------------------------
# Start writing out the tasks recursively
set tasks_tj [im_taskjuggler_write_subtasks -depth 0 -default_start $project_start_date $main_project_id]
# ---------------------------------------------------------------
# Bookings Entries
# ---------------------------------------------------------------
set bookings_tj ""
foreach key [array names booking_hash] {
set bookings $booking_hash($key)
append bookings_tj "supplement resource $key {\n$bookings\n}\n"
}
# ---------------------------------------------------------------
# Join the various parts
# ---------------------------------------------------------------
set tj_content "
$base_tj
$resource_tj
$tasks_tj
$bookings_tj
$footer_tj
"
# ---------------------------------------------------------------
# Write to file
# ---------------------------------------------------------------
set project_dir [im_filestorage_project_path $main_project_id]
set tj_folder "taskjuggler"
set tj_file "taskjuggler.tjp"
# Create a "taskjuggler" folder
set tj_dir "$project_dir/$tj_folder"
if {[catch {
if {![file exists $tj_dir]} {
file mkdir $tj_dir
im_exec chmod ug+w $tj_dir
}
} err_msg]} {
ad_return_complaint 1 "<b>Error creating TaskJuggler directory</b>:<br>
<pre>$err_msg</pre>"
ad_script_abort
}
if {[catch {
set fl [open "$tj_dir/$tj_file" "w"]
puts $fl $tj_content
close $fl
} err]} {
ad_return_complaint 1 "<b>Unable to write to $tj_dir/$tj_file</b>:<br><pre>\n$err</pre>"
ad_script_abort
}
# ---------------------------------------------------------------
# Run TaskJuggler
# ---------------------------------------------------------------
set serverroot [acs_root_dir]
# Check if exists
if {[catch {
set cmd "which taskjuggler"
ns_log Notice "im_exec $cmd"
im_exec bash -c $cmd
} err]} {
ad_return_complaint 1 "<b>TaskJuggler not Installed</b>:<br>
\]project-open\[ couldn't find the 'taskjuggler' executable in your installation.<br>
Please install from <a href='http://www.taskjuggler.org/'>www.taskjuggler.org</a>.<br>
Here is the detailed error message:<br>&nbsp;<br>
<pre>$err</pre>
"
ad_script_abort
}
# Run TaskJuggler and process the input file
if {[catch {
set cmd "export HOME=$serverroot; cd $tj_dir; taskjuggler $tj_file"
ns_log Notice "im_exec $cmd"
im_exec bash -c $cmd
} err]} {
# Format the tj content with line numbers
set tj_content_lines [split $tj_content "\n"]
set ctr 1
set tj_content_pretty ""
foreach line $tj_content_lines {
set ctr_str $ctr
while {[string length $ctr_str] < 3} { set ctr_str " $ctr_str" }
append tj_content_pretty "$ctr_str $line\n"
incr ctr
}
ad_return_complaint 1 "<b>Error executing TaskJuggler</b>:<br>
<pre>
$err
</pre>
<b>Source</b><br>
Here is the TaskJuggler file that has caused the issue:
<pre>\n$tj_content_pretty</pre>
"
ad_script_abort
}
# ---------------------------------------------------------------------
# Projects Submenu
# ---------------------------------------------------------------------
set bind_vars [ns_set create]
ns_set put $bind_vars project_id $project_id
set parent_menu_id [im_menu_id_from_label "project"]
set menu_label ""
set sub_navbar [im_sub_navbar \
-components \
-base_url [export_vars -base "/intranet/projects/view" {project_id}] \
$parent_menu_id \
$bind_vars \
"" \
"pagedesriptionbar" \
$menu_label \
]
# ---------------------------------------------------------------
# Successfull execution
# Parse the output report
# ---------------------------------------------------------------
set content "<pre>$tj_content</pre>"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment