Index: openacs-4/contrib/packages/project-manager/tcl/task-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/task-procs.tcl,v diff -u -r1.4 -r1.5 --- openacs-4/contrib/packages/project-manager/tcl/task-procs.tcl 12 Mar 2004 13:44:43 -0000 1.4 +++ openacs-4/contrib/packages/project-manager/tcl/task-procs.tcl 27 Apr 2004 00:49:28 -0000 1.5 @@ -225,9 +225,9 @@ @return task_item_id - @error + @error Returns -1 if there is no such task } { - set return_val [db_string get_item_id { }] + set return_val [db_string get_item_id { } -default "-1"] return $return_val } @@ -246,15 +246,37 @@ @return task_item_id - @error + @error If there is no such task item, then returns -1 } { - set return_val [db_string get_revision_id { }] + set return_val [db_string get_revision_id { } -default "-1"] return $return_val } +ad_proc -public pm::task::current_status { + -task_item_id:required +} { + Returns the current status value for open tasks +} { + set return_val [db_string get_current_status { }] + + return $return_val +} + + +ad_proc -public pm::task::open_p { + -task_item_id:required +} { + Returns 1 if the task is open, 0 otherwise +} { + set return_val [db_string open_p { }] + + return $return_val +} + + ad_proc -public pm::task::default_status_open {} { Returns the default status value for open tasks } { @@ -279,6 +301,7 @@ -project_item_id:required -title:required -description:required + {-mime_type "text/plain"} -end_date:required -percent_complete:required -estimated_hours_work:required @@ -303,6 +326,8 @@ @param description + @param mime_type + @param end_date @param percent_complete @@ -327,16 +352,26 @@ @error } { - if {$percent_complete >= 100} { - set status_id [pm::task::default_status_closed] + if {![exists_and_not_null status_id]} { + set status_id [pm::task::current_status \ + -task_item_id $task_item_id] } - if {![exists_and_not_null status_id]} { - set status_id [pm::task::default_status_open] + if {$estimated_hours_work_min > $estimated_hours_work_max} { + set temp $estimated_hours_work_max + set estimated_hours_work_max $estimated_hours_work_min + set estimated_hours_work_min $temp } set return_val [db_exec_plsql new_task_revision { *SQL }] + # if the we've done 100% of the work, then we close the task + if {$percent_complete >= 100} { + pm::task::close -task_item_id $task_item_id + } else { + pm::task::open -task_item_id $task_item_id + } + return $return_val } @@ -346,6 +381,7 @@ -project_id:required -title:required {-description ""} + {-mime_type "text/plain"} {-end_date ""} {-percent_complete "0"} {-estimated_hours_work "0"} @@ -361,13 +397,39 @@ set status_id [pm::task::default_status_open] } + if {$estimated_hours_work_min > $estimated_hours_work_max} { + set temp $estimated_hours_work_max + set estimated_hours_work_max $estimated_hours_work_min + set estimated_hours_work_min $temp + } + set return_val [db_exec_plsql new_task_item { *SQL }] return $return_val } +ad_proc -public pm::task::delete { + -task_item_id:required +} { + Deletes a given task + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-03-10 + + @param task_item_id + + @return 1, no matter once + + @error No error thrown if there is no such task. +} { + db_exec_plsql delete_task "select pm_task__delete_task_item(:task_item_id)" + + return 1 +} + + ad_proc -public pm::task::get_url { object_id } { @@ -496,3 +558,655 @@ return $total_logged_hours } + + +ad_proc -public pm::task::link { + -task_item_id_1:required + -task_item_id_2:required +} { + Links two tasks together + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-03-10 + + @param task_item_id_1 + + @param task_item_id_2 + + @return + + @error +} { + + if {[string equal $task_item_id_1 $task_item_id_2]} { + # do nothing + ns_log Notice "Project-manager: Cannot link a task to itself!" + } elseif {$task_item_id_1 < $task_item_id_2} { + db_dml link_tasks " + INSERT INTO + pm_task_xref + (task_id_1, task_id_2) + VALUES + (:task_item_id_1, :task_item_id_2)" + } else { + db_dml link_tasks " + INSERT INTO + pm_task_xref + (task_id_1, task_id_2) + VALUES + (:task_item_id_2, :task_item_id_1)" + } + +} + + +ad_proc -public pm::task::assign_remove_everyone { + -task_item_id:required +} { + Removes all assignments for a task + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-04-09 + + @param task_item_id + + @return + + @error +} { + db_dml remove_assignment " + delete from pm_task_assignment where task_id = :task_item_id" +} + + +ad_proc -public pm::task::assign { + -task_item_id:required + -party_id:required + {-role_id ""} +} { + Assigns party_id to task_item_id + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-04-05 + + @param task_item_id + + @param party_id + + @param role_id the role under which the person is assigned + + @return + + @error +} { + if {![exists_and_not_null role_id]} { + set role_id [pm::role::default] + } + + db_dml add_assignment " + insert into pm_task_assignment + (task_id, + role_id, + party_id) + values + (:task_item_id, + :role_id, + :party_id) + " +} + + +ad_proc -public pm::task::open { + -task_item_id:required +} { + Opens a task, and sends notifications, unless it was already + open. If it was already open, does nothing. + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-04-22 + + @param task_item_id + + @return + + @error +} { + # find out what the status of the task was, and while we're at it, + # get other interesting information about the task, in case we + # want to close it. Then we can put this info in the email. + + db_1row get_status " + SELECT + t.status, + s.status_type, + s.description as status_description, + r.title as task_title, + r.estimated_hours_work, + r.estimated_hours_work_min, + r.estimated_hours_work_max, + to_char(r.earliest_start, 'YYYY-MM-DD HH24:MI:SS') as earliest_start_ansi, + to_char(r.earliest_finish, 'YYYY-MM-DD HH24:MI:SS') as earliest_finish_ansi, + to_char(r.latest_start, 'YYYY-MM-DD HH24:MI:SS') as latest_start_ansi, + to_char(r.latest_finish, 'YYYY-MM-DD HH24:MI:SS') as latest_finish_ansi, + r.description as task_description, + project_revision.title as project_name + FROM + pm_tasks t, + cr_items task_item, + pm_task_status s, + pm_tasks_revisionsx r, + cr_items project_item, + cr_revisions project_revision + WHERE + r.parent_id = project_item.item_id and + t.task_id = task_item.item_id and + task_item.live_revision = r.revision_id and + project_item.live_revision = project_revision.revision_id and + r.item_id = t.task_id and + t.status = s.status_id and + t.task_id = :task_item_id" + + if {[string equal $status_type "o"]} { + + # this is already open + return + + } + + # set the new status + + set status_code [pm::task::default_status_open] + + db_dml update_status " + UPDATE + pm_tasks + SET + status = :status_code + WHERE + task_id = :task_item_id" + + # send out an email notification + + set earliest_start [lc_time_fmt $earliest_start_ansi "%x"] + set earliest_finish [lc_time_fmt $earliest_finish_ansi "%x"] + set latest_start [lc_time_fmt $latest_start_ansi "%x"] + set latest_finish [lc_time_fmt $latest_finish_ansi "%x"] + + set assignees [db_list get_assignees " + select + email + FROM + pm_task_assignment a, + parties p + WHERE + task_id = :task_item_id and + a.party_id = p.party_id + "] + + if {[llength $assignees] > 0} { + + set to_address [join $assignees ", "] + + set user_id [ad_conn user_id] + + set from_address [db_string get_from_email "select email from parties where party_id = :user_id" -default "nobody@nowhere.com"] + + set task_url "[parameter::get_from_package_key -package_key acs-kernel -parameter SystemURL][ad_conn package_url]task-one?task_id=$task_item_id" + + set subject "Task reopened (was $status_description): $task_title" + + if {[parameter::get_from_package_key -package_key project-manager -parameter UseUncertainCompletionTimesP]} { + set estimated_work "\nHrs work (min): $estimated_hours_work_min\nHrs work (max): $estimated_hours_work_max" + } else { + set estimated_work "\nHrs work: $estimated_hours_work" + + } + + set notification_text "Task reopened, was $status_description\n\n" + + append notification_text " +------------- +Task ID: \#$task_item_id +Description: $task_title +Project: $project_name + +Link: $task_url + +--------------- +Estimated work: +---------------$estimated_work + +------ +Dates: +------ +Earliest start: $earliest_start +Earliest finish: $earliest_finish +Latest start: $latest_start +Latest finish $latest_finish + +----------- +Description +----------- +$task_description" + + + append notification_text "\n" + + acs_mail_lite::send \ + -to_addr $to_address \ + -from_addr $from_address \ + -subject $subject \ + -body $notification_text + } + + return +} + +ad_proc -public pm::task::close { + -task_item_id:required +} { + Closes a task, and sends notifications, unless it was already + closed. If it was already closed, does nothing. + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-04-22 + + @param task_item_id + + @return + + @error +} { + # find out what the status of the task was + + db_1row get_status " + SELECT + t.status, + s.status_type, + s.description as status_description, + r.title as task_title, + r.estimated_hours_work, + r.estimated_hours_work_min, + r.estimated_hours_work_max, + to_char(r.earliest_start, 'YYYY-MM-DD HH24:MI:SS') as earliest_start_ansi, + to_char(r.earliest_finish, 'YYYY-MM-DD HH24:MI:SS') as earliest_finish_ansi, + to_char(r.latest_start, 'YYYY-MM-DD HH24:MI:SS') as latest_start_ansi, + to_char(r.latest_finish, 'YYYY-MM-DD HH24:MI:SS') as latest_finish_ansi, + r.description as task_description, + project_revision.title as project_name + FROM + pm_tasks t, + cr_items task_item, + pm_task_status s, + pm_tasks_revisionsx r, + cr_items project_item, + cr_revisions project_revision + WHERE + r.parent_id = project_item.item_id and + t.task_id = task_item.item_id and + task_item.live_revision = r.revision_id and + project_item.live_revision = project_revision.revision_id and + r.item_id = t.task_id and + t.status = s.status_id and + t.task_id = :task_item_id" + + if {[string equal $status_type "c"]} { + + # this is already closed + return + + } + + # set the new status + + set status_code [pm::task::default_status_closed] + + db_dml update_status " + UPDATE + pm_tasks + SET + status = :status_code + WHERE + task_id = :task_item_id" + + # send out an email notification + + set earliest_start [lc_time_fmt $earliest_start_ansi "%x"] + set earliest_finish [lc_time_fmt $earliest_finish_ansi "%x"] + set latest_start [lc_time_fmt $latest_start_ansi "%x"] + set latest_finish [lc_time_fmt $latest_finish_ansi "%x"] + + + set assignees [db_list get_assignees " + select + email + FROM + pm_task_assignment a, + parties p + WHERE + task_id = :task_item_id and + a.party_id = p.party_id + "] + + if {[llength $assignees] > 0} { + + set to_address [join $assignees ", "] + + set user_id [ad_conn user_id] + + set from_address [db_string get_from_email "select email from parties where party_id = :user_id" -default "nobody@nowhere.com"] + + set last_time_stamp "" + set work_log "----------------------\nWork done on this task\n----------------------\n\n" + + db_foreach get_logged_time " + SELECT + to_char(le.time_stamp, 'fmDyfm fmMMfm-fmDDfm-YYYY') as time_stamp_pretty, + le.value, + le.description, + r.title as task_name, + submitter.first_names || ' ' || submitter.last_name as user_name + FROM + logger_entries le, + cr_items i, + cr_revisions r, + pm_task_logger_proj_map m, + logger_projects lp, + acs_objects ao, + acs_users_all submitter + WHERE + r.item_id = m.task_item_id and + i.live_revision = r.revision_id and + r.item_id = :task_item_id and + le.project_id = lp.project_id and + ao.object_id = le.entry_id and + le.entry_id = m.logger_entry and + ao.creation_user = submitter.user_id + ORDER BY + le.time_stamp desc" { + if {![string equal $time_stamp_pretty $last_time_stamp]} { + append work_log "* $time_stamp_pretty\n\n" + } + append work_log "[pm::util::string_truncate_and_pad -length 25 -string "$user_name:"] $description ($value hrs)\n" + + set last_time_stamp $time_stamp_pretty + } + + set task_url "[parameter::get_from_package_key -package_key acs-kernel -parameter SystemURL][ad_conn package_url]task-one?task_id=$task_item_id" + + set subject "Task closed (was $status_description) $task_title" + + if {[parameter::get_from_package_key -package_key project-manager -parameter UseUncertainCompletionTimesP]} { + set estimated_work "\nHrs work (min): $estimated_hours_work_min\nHrs work (max): $estimated_hours_work_max" + } else { + set estimated_work "\nHrs work: $estimated_hours_work" + + } + + set notification_text "Task closed, was $status_description\n\n" + + append notification_text " +------------- +Task ID: \#$task_item_id +Description: $task_title +Project: $project_name + +Link: $task_url +" + append notification_text "\n\n$work_log" + + append notification_text " +--------------- +Estimated work: +---------------$estimated_work + +------ +Dates: +------ +Earliest start: $earliest_start +Earliest finish: $earliest_finish +Latest start: $latest_start +Latest finish $latest_finish + +----------- +Description +----------- +$task_description" + + + acs_mail_lite::send \ + -to_addr $to_address \ + -from_addr $from_address \ + -subject $subject \ + -body $notification_text + } + + return +} + + + +ad_proc -public pm::task::email_status {} { + + set send_email_p [parameter::get_from_package_key -package_key "project-manager" -parameter SendDailyEmail -default "0"] + + if {[string equal $send_email_p "0"]} { + ns_log Notice "Parameter SendDailyEmail for project manager says skip email today" + return + } + + acs_mail_lite::send \ + -to_addr jader@bread.com \ + -from_addr jader@bread.com \ + -subject "Reminder: pm::task::email_status is starting..." \ + -body "It is starting" + + set parties [list] + + # what if the person assigned is no longer a part of the subsite? + # right now, we still email them. + + db_foreach get_all_open_tasks " + SELECT + ts.task_id, + ts.task_id as item_id, + ts.task_number, + t.task_revision_id, + t.title, + to_char(t.earliest_start,'J') as earliest_start_j, + to_char(current_timestamp,'J') as today_j, + to_char(t.latest_start,'J') as latest_start_j, + to_char(t.latest_start,'YYYY-MM-DD HH24:MI') as latest_start, + to_char(t.latest_finish,'YYYY-MM-DD HH24:MI') as latest_finish, + t.percent_complete, + t.estimated_hours_work, + t.estimated_hours_work_min, + t.estimated_hours_work_max, + case when t.actual_hours_worked is null then 0 + else t.actual_hours_worked end as actual_hours_worked, + to_char(t.earliest_start,'YYYY-MM-DD HH24:MI') as earliest_start, + to_char(t.earliest_finish,'YYYY-MM-DD HH24:MI') as earliest_finish, + to_char(t.latest_start,'YYYY-MM-DD HH24:MI') as latest_start, + to_char(t.latest_finish,'YYYY-MM-DD HH24:MI') as latest_finish, + p.first_names || ' ' || p.last_name as full_name, + p.party_id, + (select one_line from pm_roles r where ta.role_id = r.role_id) as role + FROM + pm_tasks ts, + pm_tasks_revisionsx t, + pm_task_assignment ta, + acs_users_all p, + cr_items i, + pm_task_status s + WHERE + ts.task_id = t.item_id and + i.item_id = t.item_id and + t.task_revision_id = i.live_revision and + ts.status = s.status_id and + s.status_type = 'o' and + t.item_id = ta.task_id and + ta.party_id = p.party_id + ORDER BY + t.latest_start asc" { + set earliest_start_pretty [lc_time_fmt $earliest_start "%x"] + set earliest_finish_pretty [lc_time_fmt $earliest_finish "%x"] + set latest_start_pretty [lc_time_fmt $latest_start "%x"] + set latest_finish_pretty [lc_time_fmt $latest_finish "%x"] + + if {[exists_and_not_null earliest_start_j]} { + set slack_time [pm::task::slack_time \ + -earliest_start_j $earliest_start_j \ + -today_j $today_j \ + -latest_start_j $latest_start_j] + + } + + if {[lsearch $parties $party_id] == -1} { + lappend parties $party_id + } + + lappend task_list($party_id) $task_id + set titles_arr($task_id) $title + set ls_arr($task_id) $latest_start_pretty + set lf_arr($task_id) $latest_finish_pretty + set slack_arr($task_id) $slack_time + + # how many tasks does this person have? + if {[info exists task_count($party_id)]} { + incr task_count($party_id) + } else { + set task_count($party_id) 1 + } + } + + # transitions are < this value + set OVERDUE_THRESHOLD 0 + set PRESSING_THRESHOLD 7 + set LONGTERM_THRESHOLD 90 + + set TASK_LENGTH 70 + set TASK_ID_LENGTH 9 + + foreach party $parties { + + set subject "Daily Task status report" + set address [db_string get_email "select email from parties where party_id = :party" -default "jade-errors@bread.com"] + + set overdue [list] + set pressing [list] + set longterm [list] + + foreach task $task_list($party) { + + if {$slack_arr($task) < $OVERDUE_THRESHOLD} { + set which_pile overdue + } elseif {$slack_arr($task) < $PRESSING_THRESHOLD} { + set which_pile pressing + } elseif {$slack_arr($task) < $PRESSING_THRESHOLD} { + set which_pile longterm + } else { + set which_pile "" + } + + if {![empty_string_p $which_pile]} { + + set trimmed_task [pm::util::string_truncate_and_pad -length $TASK_ID_LENGTH -string $task] + set trimmed_title [pm::util::string_truncate_and_pad -length $TASK_LENGTH -string $titles_arr($task)] + + lappend $which_pile "$trimmed_task $trimmed_title" + lappend $which_pile "[string repeat " " $TASK_ID_LENGTH] LS: $ls_arr($task) LF: $lf_arr($task) Slack: $slack_arr($task)" + lappend $which_pile "" + } + + } + + set description [list] + + set overdue_title "OVERDUE TASKS" + set overdue_title [pm::util::string_truncate_and_pad -length $TASK_LENGTH -string $overdue_title] + + set overdue_description "consult with people affected, and let them know deadlines are affected" + + set pressing_title "PRESSING TASKS" + set pressing_title [pm::util::string_truncate_and_pad -length $TASK_LENGTH -string $pressing_title] + + set pressing_description "you need to start working on these soon to avoid affecting deadlines" + + set longterm_title "LONG TERM TASKS" + set longterm_title [pm::util::string_truncate_and_pad -length $TASK_LENGTH -string $longterm_title] + set longterm_description "look over these to plan ahead" + + # okay, let's now set up the email body + + lappend description "This is a daily reminder of tasks that are assigned to you" + lappend description "You current have $task_count($party) tasks assigned to you" + + lappend description "" + lappend description "\# $overdue_title" + + set length [string length $overdue_description] + lappend description [string repeat "_" $length] + + lappend description $overdue_description + + lappend description "" + foreach overdue_item $overdue { + lappend description $overdue_item + } + + lappend description "" + lappend description "\# $pressing_title" + + set length [string length $pressing_description] + lappend description [string repeat "_" $length] + + lappend description $pressing_description + + + lappend description "" + foreach pressing_item $pressing { + lappend description $pressing_item + } + + lappend description "" + lappend description "\# $longterm_title" + + set length [string length $longterm_description] + lappend description [string repeat "_" $length] + + lappend description $longterm_description + + lappend description "" + foreach longterm_item $longterm { + lappend description $longterm_item + } + + acs_mail_lite::send \ + -to_addr $address \ + -from_addr $address \ + -subject $subject \ + -body [join $description "\n"] + + } + + # consider also sending out emails to people who have assigned + # tickets to nobody + +} + + + +ad_proc -private pm::task::email_status_init { +} { + Schedules the daily emailings + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-04-14 + + @return + + @error +} { + ns_log Notice "Scheduling daily email notifications for project manager to 5:00 am" + ad_schedule_proc -thread t -debug t -schedule_proc ns_schedule_daily "5 0" pm::task::email_status +}