Index: openacs-4/contrib/packages/project-manager/tcl/project-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/project-procs.tcl,v diff -u -r1.6.2.2 -r1.6.2.3 --- openacs-4/contrib/packages/project-manager/tcl/project-procs.tcl 20 May 2004 17:30:04 -0000 1.6.2.2 +++ openacs-4/contrib/packages/project-manager/tcl/project-procs.tcl 2 Jul 2004 23:13:48 -0000 1.6.2.3 @@ -224,14 +224,23 @@ -project_lead $creation_user \ ] + # we want the logger project to show up in logger! + set logger_URLs [parameter::get -parameter "LoggerURLsToKeepUpToDate" -default ""] + foreach url $logger_URLs { + # get the package_id + set node_id [site_node::get_node_id -url $url] + array set node [site_node::get -node_id $node_id] + set this_package_id $node(package_id) + + logger::package::map_project \ + -project_id $logger_project \ + -package_id $this_package_id + } + # create a project manager project (associating the logger project # with the logger project) set project_revision [db_exec_plsql new_project_item { *SQL }] - # add in the default variable (hopefully hours) - logger::project::map_variable \ - -project_id $logger_project \ - -variable_id [logger::variable::get_default_variable_id] return $project_revision } @@ -291,6 +300,26 @@ @error } { + # if we edit the name of the project, we need to edit the logger + # project name too. + + set logger_project [pm::project::get_logger_project \ + -project_item_id $project_item_id] + + set active_p [pm::status::open_p -task_status_id $status_id] + set customer_name [organizations::name -organization_id "$organization_id"] + + if {![empty_string_p $customer_name]} { + append customer_name " - " + } + + logger::project::edit \ + -project_id $logger_project \ + -name "$customer_name$project_name" \ + -description "$description" \ + -project_lead $creation_user \ + -active_p $active_p + set returnval [db_exec_plsql update_project " select pm_project__new_project_revision ( :project_item_id, @@ -476,6 +505,10 @@ } { # set temp [expr $min_latest_start + [expr $activity_time($task_item) / double($hours_day)]] + if {[empty_string_p $latest_start_j]} { + return "" + } + set t_start_date $latest_start_j set t_today $t_start_date @@ -562,10 +595,17 @@ TASKS: earliest start(i) = max(activity_time(i-1) + earliest_start(i-1)) earliest_finish(i) = earliest_start(i) + activity_time(i) - latest_start(i) = min(last_start(i+1) - activity_time(i) + latest_start(i) = min(latest_start(i+1) - activity_time(i) latest_finish(i) = latest_start(i) + activity_time(i) + (i-1 means an item that this task depends on) + +

+ + These algorithms are explained at: + http://mscmga.ms.ic.ac.uk/jeb/or/netaon.html

+ Tasks in ongoing projects are given null completion dates, unless they already have deadlines. @@ -607,6 +647,11 @@ # Before hacking on this, you might want to look at: # http://www.joelonsoftware.com/articles/fog0000000069.html + # the first thing that should be done on this code is that it + # should be broken out to a number of utility procs. + + set debug 0 + # TODO: # # ------------------------------------------------------------------------- @@ -630,7 +675,9 @@ # should look at: # http://mscmga.ms.ic.ac.uk/jeb/or/netaon.html - # ns_log Notice "-----------------------------------------" + if {[string is true $debug]} { + ns_log Notice "-----------------------------------------" + } # -------------------------------------------------------------------- # for now, hardcode in a day is 8 hours. Later, we want to set this by @@ -664,7 +711,9 @@ set task_list [concat $task_list $task_list_project] - # ns_log Notice "task_list: $task_list" + if {[string is true $debug]} { + ns_log Notice "Tasks in this project (task_list): $task_list" + } # ------------------------- # no tasks for this project @@ -681,13 +730,19 @@ # today_j (julian date for today) db_1row tasks_group_query { } - # ns_log notice "Julian today: $today_j" + if {[string is true $debug]} { + ns_log notice "Today's date (julian format): $today_j" + } # -------------------------------------------------------------- # Set up activity_time for all tasks # Also set up deadlines for tasks that have hard-coded deadlines # -------------------------------------------------------------- + if {[string is true $debug]} { + ns_log notice "Going through tasks and saving their values" + } + db_foreach tasks_query { } { # We now save information about all the tasks, so that we can @@ -703,13 +758,17 @@ set old_LF_j($my_iid) $old_latest_finish_j set old_task_status($my_iid) $status_type - # ns_log Notice "old_task_status: $my_iid $status_type" + if {[string is true $debug]} { + ns_log Notice "old_task_status: $my_iid $status_type (o=open, c=closed)" + } set activity_time($my_iid) [expr $to_work - $worked] if {[exists_and_not_null task_deadline_j]} { - # ns_log notice "$my_iid has a deadline $task_deadline_j" + if {[string is true $debug]} { + ns_log notice "$my_iid has a deadline (julian: $task_deadline_j)" + } set latest_finish($my_iid) $task_deadline_j @@ -736,7 +795,9 @@ set dependency_types($task_item_id-$parent_task_id) $dependency_type - # ns_log Notice "dependency (id: $dependency_id) task: $task_item_id parent: $parent_task_id type: $dependency_type" + if {[string is true $debug]} { + ns_log Notice "dependency (id: $dependency_id) task: $task_item_id parent: $parent_task_id type: $dependency_type" + } } @@ -749,7 +810,7 @@ # if ongoing_p is t, then end_date_j should be null db_1row project_info { } - if {[string equal $ongoing_p t] && ![empty_string_p $end_date_j]} { + if {[string is true $ongoing_p] && ![empty_string_p $end_date_j]} { ns_log Error "Project cannot be ongoing and have a non-null end-date. Setting end date to blank" set end_date_j "" } @@ -788,24 +849,31 @@ if {![info exists depends($task_item)]} { set earliest_start($task_item) $start_date_j - #set earliest_finish($task_item) [expr $earliest_start($task_item) + [expr $activity_time($task_item) / double($hours_day)]] set earliest_finish($task_item) [earliest_finish $earliest_start($task_item) $activity_time($task_item) $hours_day] lappend present_tasks $task_item - # ns_log Notice "Begin earliest_start($task_item): $earliest_start($task_item)" + if {[string is true $debug]} { + ns_log Notice "preliminary earliest_start($task_item): $earliest_start($task_item)" + } } } # ------------------------------- # stop if we have no dependencies # ------------------------------- if {[llength $present_tasks] == 0} { - # ns_log Notice "No tasks with dependencies" + + if {[string is true $debug]} { + ns_log Notice "No tasks with dependencies" + } + return [list] } - # ns_log Notice "present_tasks: $present_tasks" + if {[string is true $debug]} { + ns_log Notice "present_tasks: $present_tasks" + } # ------------------------------------------------------ # figure out the earliest start and finish times @@ -817,7 +885,9 @@ foreach task_item $present_tasks { - # ns_log Notice "this task_item: $task_item" + if {[string is true $debug]} { + ns_log Notice "-this task_item: $task_item" + } # ----------------------------------------------------- # some tasks may already have earliest_start filled in @@ -827,7 +897,9 @@ if {![exists_and_not_null earliest_start($task_item)]} { - # ns_log Notice " !info exists for $task_item" + if {[string is true $debug]} { + ns_log Notice " !info exists for $task_item" + } # --------------------------------------------- # set the earliest_start for this task = @@ -856,10 +928,12 @@ set earliest_finish($task_item) [earliest_finish $max_earliest_start $activity_time($task_item) $hours_day] - # ns_log Notice \ - # " earliest_start ($task_item): $earliest_start($task_item)" - # ns_log Notice \ - # " earliest_finish($task_item): $earliest_finish($task_item)" + if {[string is true $debug]} { + ns_log Notice \ + " earliest_start ($task_item): $earliest_start($task_item)" + ns_log Notice \ + " earliest_finish($task_item): $earliest_finish($task_item)" + } } @@ -872,7 +946,9 @@ } } - # ns_log Notice "future tasks: $future_tasks" + if {[string is true $debug]} { + ns_log Notice "future tasks: $future_tasks" + } set present_tasks $future_tasks } @@ -885,10 +961,14 @@ foreach task_item $task_list { - # ns_log Notice "*Earliest start ($task_item): $earliest_start($task_item)" - if {$max_earliest_finish < $earliest_finish($task_item)} { + if {[string is true $debug] && [exists_and_not_null earliest_finish($task_item)]} { + ns_log Notice "* EF: ($task_item): $earliest_finish($task_item)" + } + + if {[exists_and_not_null earliest_finish($task_item)] && $max_earliest_finish < $earliest_finish($task_item)} { set max_earliest_finish $earliest_finish($task_item) } + } @@ -969,13 +1049,17 @@ # info for these items # ----------------------------------------------------- - # ns_log Notice "Starting foreach task-item $task_list" + if {[string is true $debug]} { + ns_log Notice "Starting foreach task-item $task_list" + } foreach task_item $task_list { if {![info exists dependent($task_item)]} { - # ns_log Notice " !info exists dependent($task_item)" + if {[string is true $debug]} { + ns_log Notice " !info exists dependent($task_item)" + } # we check this because some tasks already have # hard deadlines set. @@ -986,14 +1070,24 @@ # has precedence. However, sometimes the project is # ongoing, so we have to make sure that there actually # is an end_date_j - if {![empty_string_p $end_date_j]} { - if {$end_date_j < $latest_finish($task_item)} { - set latest_finish($task_item) $end_date_j - } - } + # commented out: we need to trust the user. If they + # set the deadline outside the project deadline, + # that's their business + + #if {![empty_string_p $end_date_j]} { + # if {$end_date_j < $latest_finish($task_item)} { + # set latest_finish($task_item) $end_date_j + # } + #} + # we also set the latest_start date + if {[string is false [exists_and_not_null activity_time($task_item)]]} { + set activity_time($task_item) 0 + ns_log Notice "setting activity_time($task_item) 0" + } + set late_start_temp \ [latest_start \ -end_date_j $latest_finish($task_item) \ @@ -1013,10 +1107,18 @@ # we specify that the task is an ongoing task if {[empty_string_p $end_date_j]} { set ongoing_task($task_item) true - # ns_log Notice "NSDBAHNITD: end_date_j was empty ti:$task_item" + + if {[string is true $debug]} { + ns_log Notice "NSDBAHNITD: end_date_j was empty ti:$task_item" + } } else { set latest_finish($task_item) $end_date_j + if {[string is false [exists_and_not_null activity_time($task_item)]]} { + set activity_time($task_item) 0 + ns_log Notice "setting activity_time($task_item) 0 (location 2)" + } + set latest_start($task_item) \ [latest_start \ -end_date_j $latest_finish($task_item) \ @@ -1027,9 +1129,20 @@ } lappend present_tasks $task_item - # ns_log Notice "Begin latest_start($task_item): $latest_start($task_item) latest_finish: $latest_finish($task_item)" + if {[string is true $debug] && [exists_and_not_null latest_start($task_item)]} { + ns_log Notice "preliminary latest_start($task_item): $latest_start($task_item)" + } + + if {[string is true $debug] && [exists_and_not_null latest_finish($task_item)]} { + ns_log Notice "preliminary latest_finish($task_item): $latest_finish($task_item)" + } + + + } else { - #ns_log Notice " info exists dependent($task_item)" + if {[string is true $debug]} { + ns_log Notice " info exists dependent($task_item)" + } } } @@ -1038,11 +1151,15 @@ # stop if we have no dependencies # ------------------------------- if {[llength $present_tasks] == 0} { - # ns_log Notice "No tasks with dependencies" + if {[string is true $debug]} { + ns_log Notice "No tasks with dependencies" + } return [list] } - # ns_log Notice "LATEST present_tasks: $present_tasks" + if {[string is true $debug]} { + ns_log Notice "LATEST present_tasks: $present_tasks" + } # ------------------------------------------------------ # figure out the latest start and finish times @@ -1054,7 +1171,9 @@ foreach task_item $present_tasks { - # ns_log Notice "this task_item: $task_item" + if {[string is true $debug]} { + ns_log Notice "this task_item: $task_item" + } # ----------------------------------------------------- # some tasks may already have latest_start filled in. @@ -1064,7 +1183,9 @@ if {[info exists dependent($task_item)]} { - # ns_log Notice " info exists for dependent($task_item)" + if {[string is true $debug]} { + ns_log Notice " info exists for dependent($task_item)" + } # --------------------------------------------- # set the latest_start for this task = @@ -1074,20 +1195,31 @@ # (i means this task) # --------------------------------------------- - # we set this to the end date, and then move it back + # we set this to the end date, and then move it forward # as we find dependent items that have earlier # latest_start dates. The problem is that the # end_date_j is empty when there is no deadline. # So we need to remember that min_latest_start can # be an empty value set min_latest_start $end_date_j + if {[string is true $debug]} { + ns_log Notice " min_latest_start: $end_date_j" + } + foreach dependent_item $dependent($task_item) { - + + if {[string is true $debug]} { + ns_log Notice " dependent_item: $dependent_item" + } + if {[exists_and_not_null ongoing_task($dependent_item)]} { set defer_p f set my_latest_start "" - # ns_log Notice "ongoing_task, no defer" + + if {[string is true $debug]} { + ns_log Notice " ongoing_task, no defer" + } } elseif {![exists_and_not_null latest_start($dependent_item)]} { # we defer the task if the dependent item has no @@ -1109,10 +1241,17 @@ # a deadline. :( if {$defer_count($task_item) > 5} { set defer_p f - # ns_log Notice " no defer because defer count exceeded" + + if {[string is true $debug]} { + ns_log Notice " no defer because defer count exceeded" + } } else { lappend future_tasks $task_item - # ns_log Notice " defer" + + if {[string is true $debug]} { + ns_log Notice " defer" + } + set defer_p t } @@ -1122,15 +1261,26 @@ # the dependent item has a deadline + if {[string is false [exists_and_not_null activity_time($task_item)]]} { + set activity_time($task_item) 0 + ns_log Notice "setting activity_time($task_item) 0 (location 3)" + } + set my_latest_start \ [latest_start \ -end_date_j $latest_start($dependent_item) \ -hours_to_complete $activity_time($task_item) \ -hours_day $hours_day] - # ns_log Notice " my_latest_start: $my_latest_start" - - if {$my_latest_start < $min_latest_start} { + if {[string is true $debug]} { + ns_log Notice " my_latest_start: $my_latest_start" + } + + if {[exists_and_not_null min_latest_start]} { + if {$my_latest_start < $min_latest_start} { + set min_latest_start $my_latest_start + } + } else { set min_latest_start $my_latest_start } @@ -1143,9 +1293,38 @@ # we check that latest_start doesn't already exist # which it might for hard-deadlines + + # we have to be fairly careful here. We want to + # set the latest_start date to the minimum + # latest_start, but only when min_latest_start + # actually has a value + if {[exists_and_not_null latest_start($task_item)]} { - if {$min_latest_start < $latest_start($task_item)} { - set latest_start($task_item) $min_latest_start + + if {[exists_and_not_null min_latest_start]} { + + if {$min_latest_start < $latest_start($task_item)} { + set latest_start($task_item) $min_latest_start + } + + } else { + + if {[string is true $debug]} { + ns_log notice " setting latest start date (ignoring min_latest_start" + } + + if {[string is false [exists_and_not_null activity_time($task_item)]]} { + set activity_time($task_item) 0 + ns_log Notice "setting activity_time($task_item) 0 (location 4)" + } + + + set latest_start($task_item) \ + [latest_start \ + -end_date_j $latest_finish($task_item) \ + -hours_to_complete $activity_time($task_item) \ + -hours_day $hours_day] + } } else { @@ -1159,10 +1338,12 @@ set latest_start($task_item) $min_latest_start } - # ns_log Notice " min_latest_start: $min_latest_start" + if {[string is true $debug]} { + ns_log Notice " min_latest_start: $min_latest_start" + } # we now set the latest finish. Ongoing tasks set - # the latest finish to empty + # the latest finish to empty (sometimes) if {[empty_string_p $latest_start($task_item)]} { set temp_lf "" } else { @@ -1173,8 +1354,20 @@ # task, then we check whether temp_lf is earlier, # and set it to temp_lf if so + if {[string is true $debug]} { + ns_log Notice " temp_lf: $temp_lf" + } + if {[empty_string_p $temp_lf]} { - set latest_finish($task_item) "" + + # if the task is ongoing, we clear the + # latest_finish. Otherwise, we leave the + # latest_finish as it is. + + if {[exists_and_not_null ongoing_task($task_item)] && [string is true $ongoing_task($task_item)]} { + set latest_finish($task_item) "" + } + } else { if {[exists_and_not_null latest_finish($task_item)]} { if {$temp_lf < $latest_finish($task_item)} { @@ -1185,12 +1378,21 @@ } } - #ns_log Notice \ - # " latest_start ($task_item): $latest_start($task_item)" - #ns_log Notice \ - # " latest_finish($task_item): $latest_finish($task_item)" + if {[string is true $debug]} { + if {[exists_and_not_null latest_start($task_item)]} { + ns_log Notice \ + " latest_start ($task_item): $latest_start($task_item)" + } + if {[exists_and_not_null latest_finish($task_item)]} { + ns_log Notice \ + " latest_finish($task_item): $latest_finish($task_item)" + } + } + } else { - # ns_log Notice "Deferring $task_item" + if {[string is true $debug]} { + ns_log Notice "Deferring $task_item" + } } } @@ -1203,7 +1405,9 @@ } } - # ns_log Notice "future tasks: $future_tasks" + if {[string is true $debug]} { + ns_log Notice "future tasks: $future_tasks" + } set present_tasks $future_tasks } @@ -1220,8 +1424,11 @@ foreach task_item $task_list { - # ns_log Notice "*Latest start ($task_item): $latest_start($task_item)" - if {$min_latest_start > $latest_start($task_item)} { + if {[string is true $debug]} { + ns_log Notice "* LS ($task_item): $latest_start($task_item)" + } + + if {[exists_and_not_null earliest_finish($task_item)] && $min_latest_start > $latest_start($task_item)} { set max_earliest_finish $earliest_finish($task_item) } } @@ -1240,9 +1447,19 @@ # this is very inefficient and stupid foreach task_item $task_list { - set es "J[expr ceil( [set earliest_start($task_item)])]" - set ef "J[expr ceil( [set earliest_finish($task_item)])]" + if {[exists_and_not_null earliest_start($task_item)]} { + set es "J[expr ceil( [set earliest_start($task_item)])]" + } else { + set es "" + } + + if {[exists_and_not_null earliest_finish($task_item)]} { + set ef "J[expr ceil( [set earliest_finish($task_item)])]" + } else { + set ef "" + } + if {[exists_and_not_null latest_start($task_item)]} { set ls "J[expr floor([set latest_start($task_item)])]" } else { @@ -1275,7 +1492,9 @@ } - # ns_log Notice "*******************" + if {[string is true $debug]} { + ns_log Notice "*******************" + } return $task_list @@ -1342,6 +1561,33 @@ } +ad_proc -public pm::project::get_project { + -logger_project:required +} { + Returns the project_item_id when given the logger project + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-05-28 + + @param logger_project + + @return project_item_id + + @error +} { + return [db_string get_logger_project " + SELECT + i.item_id + FROM + pm_projectsx p, cr_items i + WHERE + i.live_revision = p.revision_id and logger_project = :logger_project + " -default "no_project"] + +} + + + ad_proc -public pm::project::get_list_of_open { } { Returns a list of lists, of all open project ids and their names @@ -1374,3 +1620,319 @@ return $return_val } + + +ad_proc -public pm::project::close { + {-project_item_id:required} +} { + Closes a project + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-07-02 + + @param project_item_id + + @return + + @error +} { + + set closed_id [pm::status::default_closed] + + db_dml update_status { + UPDATE + pm_projects + SET + status_id = :closed_id + WHERE + project_id in (select live_revision from cr_items where item_id = :project_item_id) + } + +} + + +ad_proc -public pm::project::open_p { + {-project_item_id:required} +} { + Returns true if the project is open + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-06-03 + + @param project_item_id + + @return 1 if open, 0 if closed + + @error +} { + set return_val [db_string get_open_or_closed { + SELECT + case when status_type = 'c' then 0 else 1 end + FROM + pm_projectsx p, + cr_items i, + pm_project_status s + WHERE + i.item_id = p.item_id and + i.live_revision = p.revision_id and + p.status_id = s.status_id and + p.item_id = :project_item_id + } -default "0"] + + return $return_val +} + + +ad_proc -public pm::project::assign { + {-project_item_id:required} + {-role_id:required} + {-party_id:required} + {-send_email_p "t"} +} { + Assigns a user to a project + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-06-11 + + @param project_item_id + + @param role_id + + @param party_id + + @return + + @error +} { + + db_dml insert_assignment { + insert into pm_project_assignment + (project_id, role_id, party_id) + VALUES + (:project_item_id, :role_id, :party_id) + } + + if {[string is true $send_email_p]} { + + set project_name [pm::project::name \ + -project_item_id $project_item_id] + + set project_url [pm::project::url \ + -project_item_id $project_item_id] + + set to_addr [cc_email_from_party $party_id] + set from_addr [cc_email_from_party [ad_conn user_id]] + + set subject "Assigned to project: $project_name" + + set content "You have been assigned to a project: $project_name + +Link: $project_url" + + pm::util::email \ + -to_addr $to_addr \ + -from_addr $from_addr \ + -subject $subject \ + -body $content \ + -mime_type "text/plain" + } + + return +} + + +ad_proc -public pm::project::unassign { + {-project_item_id:required} + {-party_id:required} +} { + Removes a user from a project + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-06-11 + + @param project_item_id + + @param party_id + + @return + + @error +} { + + db_dml remove_assignment { + DELETE FROM + pm_project_assignment + WHERE + project_id = :project_item_id and + party_id = :party_id + } + + return +} + + +ad_proc -public pm::project::assign_remove_everyone { + {-project_item_id:required} +} { + Removes all users from a project + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-06-11 + + @param project_item_id + + @return party_ids of all users removed from the project + + @error +} { + + set current_assignees [db_list get_assignees { + SELECT + party_id + FROM + pm_project_assignment + WHERE + project_id = :project_item_id + }] + + db_dml remove_assignment { + DELETE FROM + pm_project_assignment + WHERE + project_id = :project_item_id + } + + return $current_assignees +} + + +ad_proc -public pm::project::assignee_filter_select { + {-status_id:required} +} { + Returns a list of lists, people who are assigned to projects with a + status of status_id. Used in the list-builder filters for + the projects list page. Cached 5 minutes. + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-06-11 + + @param status_id + + @return + + @error +} { + return [util_memoize [list pm::project::assignee_filter_select_helper -status_id $status_id] 600] +} + + +ad_proc -private pm::project::assignee_filter_select_helper { + {-status_id:required} +} { + Returns a list of lists, people who are assigned projects with a + status of status_id. Used in the list-builder filters for + the projects list page. Cached 5 minutes. + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-06-11 + + @param status_id + + @return + + @error +} { + return [db_list_of_lists get_people { +SELECT + distinct(first_names || ' ' || last_name) as fullname, + u.person_id + FROM + persons u, + pm_project_assignment a, + pm_projects p, + cr_items i + WHERE + u.person_id = a.party_id and + i.item_id = a.project_id and + p.status_id = :status_id and + i.live_revision = p.project_id + ORDER BY + fullname + }] +} + + +ad_proc -public pm::project::assignee_email_list { + -project_item_id:required +} { + Returns a list of assignee email addresses + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-06-30 + + @param project_item_id + + @return + + @error +} { + + return [db_list get_addresses { + SELECT + p.email + FROM + parties p, + pm_project_assignment a + WHERE + a.project_id = :project_item_id and + a.party_id = p.party_id + }] + +} + + +ad_proc -public pm::project::name { + -project_item_id:required +} { + Returns the name for a project + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-07-01 + + @param project_item_id + + @return + + @error +} { + return [db_string get_name { + SELECT + title + FROM + cr_revisions p, + cr_items i + WHERE + i.live_revision = p.revision_id + and i.item_id = :project_item_id + } -default ""] +} + + +ad_proc -public pm::project::url { + -project_item_id:required +} { + Returns the URL for a project, when given the project_item_id + + @author Jade Rubick (jader@bread.com) + @creation-date 2004-07-01 + + @param project_item_id + + @return + + @error +} { + + return "[ad_url][ad_conn package_url]one?project_item_id=$project_item_id" + +}