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.1 -r1.2 --- openacs-4/contrib/packages/project-manager/tcl/project-procs.tcl 26 Aug 2003 00:05:58 -0000 1.1 +++ openacs-4/contrib/packages/project-manager/tcl/project-procs.tcl 4 Sep 2003 22:45:22 -0000 1.2 @@ -18,78 +18,449 @@ These are the items we'd like to compute + PROJECTS: estimated_completion_date timestamptz, earliest_completion_date timestamptz, latest_completion_date timestamptz, actual_hours_completed numeric, estimated_hours_total numeric + 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_finish(i) = latest_start(i) + activity_time(i) - loop through all items that have parent_id equal to this item - if a subproject, - then call compute_status(subproject) - use statistics from that project - if a task - then use statistics + The statistics are computed based on: - after loop, then compute statistics for this project - save values to database + project statistics are based on that project + subproject statistics + + that means if a project has a subproject, then the tasks for both of those projects are put together in one list, and computed together. + + so for a project with no subprojects, the values are computed for the tasks in that project + + for a project with subprojects, the statistics are based on the tasks of both of those projects. + + this function returns a list of task_item_ids of all tasks under + a project, plus all subproject tasks. + } { - ns_log Notice "computing status for $project_item_id" - set project_list [list] - set task_list [list] + # TODO: + # + # ------------------------------------------------------------------------- + # to improve this in the future, be more intelligent about what is updated. + # i.e., this procedure updates everything, which is necessary sometimes, + # but not if you only edit one task. + # ------------------------------------------------------------------------- + # Add in resource limits. (it's not realistic that 300 tasks can be done in + # one day) + # ------------------------------------------------------------------------- + # Use dependency types -- currently they're all treated like finish_to_start + # ------------------------------------------------------------------------- + + # note if you want to understand the algorithms in this function, you + # should look at: + # http://mscmga.ms.ic.ac.uk/jeb/or/netaon.html + + ns_log Notice "-----------------------------------------" + + # -------------------------------------------------------------------- + # for now, hardcode in a day is 8 hours. Later, we want to set this by + # person + # -------------------------------------------------------------------- + set hours_day 8 + + + # ------------------------- + # get subprojects and tasks + # ------------------------- + set task_list [list] + set task_list_project [list] + foreach sub_item [db_list_of_lists select_project_children { }] { set my_id [lindex $sub_item 0] set my_type [lindex $sub_item 1] if {[string equal $my_type "pm_project"]} { + # --------------------------------------------- + # gets all tasks that are a part of subprojects + # --------------------------------------------- set project_return [project_manager::project::compute_status $my_id] - lappend project_list $my_id - ns_log Notice "added project to list $my_id" + set task_list_project [concat $task_list_project $project_return] } elseif {[string equal $my_type "pm_task"]} { lappend task_list $my_id } } - # -------------------------------- - # get information from subprojects - # -------------------------------- - set projects_total_hours_worked 0 + set task_list [concat $task_list $task_list_project] - if {[llength $project_list] > 0} { - db_1row projects_query { } - ns_log Notice "Project total hours: $projects_total_hours_worked" - } else { - ns_log Notice "No subprojects" + # ------------------------- + # no tasks for this project + # ------------------------- + if {[llength $task_list] == 0} { + return [list] } + + # -------------------------------------------------------------- + # we now have list of tasks that includes all subprojects' tasks + # -------------------------------------------------------------- - # -------------------------- - # get information from tasks - note currently no support for subtasks - # -------------------------- - set tasks_total_hours_worked 0 + # returns actual_hours_completed, estimated_hours_total, and + # today_j (julian date for today) + db_1row tasks_group_query { } - if {[llength $task_list] > 0} { - db_1row tasks_query { } - ns_log Notice "Tasks total hours: $tasks_total_hours_worked" - } else { - ns_log Notice "No tasks" + + # -------------------------------------------------------------- + # Set up activity_time for all tasks + # Also set up deadlines for tasks that have hard-coded deadlines + # -------------------------------------------------------------- + + db_foreach tasks_query { } { + + set activity_time($my_iid) [expr $to_work - $worked] + + if {[exists_and_not_null task_deadline_j]} { + + set latest_finish($my_iid) $task_deadline_j + set latest_start($my_iid) [expr $task_deadline_j - [expr $activity_time($my_iid) / double($hours_day)]] + + } } - # estimated_completion_date timestamptz, - # earliest_completion_date timestamptz, - # latest_completion_date timestamptz, - # actual_hours_completed numeric, - # estimated_hours_total numeric + # -------------------------------------------------------------------- + # We need to keep track of all the dependencies so we can meaningfully + # compute deadlines, earliest start times, etc.. + # -------------------------------------------------------------------- - # now update projects - set total_hours_worked [expr $projects_total_hours_worked + $tasks_total_hours_worked] + db_foreach dependency_query { } { + # task_item_id depends on parent_task_id + lappend depends($task_item_id) $parent_task_id + + # parent_task_id is dependent on task_item_id + lappend dependent($parent_task_id) $task_item_id + + set dependency_types($task_item_id-$parent_task_id) $dependency_type + + ns_log Notice "id: $dependency_id task: $task_item_id parent: $parent_task_id type: $dependency_type" + } + + + # -------------------------------------------------------------- + # need to get some info on this project, so that we can base the + # task information off of them + # -------------------------------------------------------------- + + # gives up planned_start_date and planned_end_date + db_1row project_info { } + + # -------------------------------------------------------------- + # task_list contains all the tasks + # a subset of those do not depend on any other tasks + # -------------------------------------------------------------- + + # ---------------------------------------------------------------------- + # we want to go through and fill in all the values for earliest start. + # the brain-dead, brute force way of doing this, would be go through the + # task_list length(task_list) times, and each time, compute the values + # for each item that depends on one of those tasks. This is extremely + # inefficient. + # ---------------------------------------------------------------------- + # Instead, we create two lists, one is of tasks we just added + # earliest_start values for, the next is a new list of ones we're going to + # add earliest_start values for. We call these lists + # present_tasks and future_tasks + # ---------------------------------------------------------------------- + + set present_tasks [list] + set future_tasks [list] + + # ----------------------------------------------------- + # make a list of tasks that don't depend on other tasks + # ----------------------------------------------------- + # while we're at it, save earliest_start and earliest_finish + # info for these items + # ----------------------------------------------------- + + foreach task_item $task_list { + + 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)]] + + lappend present_tasks $task_item + + ns_log Notice "Begin 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" + return [list] + } + + ns_log Notice "present_tasks: $present_tasks" + + # ------------------------------------------------------ + # figure out the earliest start and finish times + # ------------------------------------------------------ + + while {[llength $present_tasks] > 0} { + + set future_tasks [list] + + foreach task_item $present_tasks { + + ns_log Notice "this task_item: $task_item" + + # ----------------------------------------------------- + # some tasks may already have earliest_start filled in + # the first run of tasks, for example, had their values + # filled in earlier + # ----------------------------------------------------- + + if {![exists_and_not_null earliest_start($task_item)]} { + + ns_log Notice " info exists for $task_item" + + # --------------------------------------------- + # set the earliest_start for this task = + # max(activity_time(i-1) + earliest_start(i-1)) + # + # (i-1 means an item that this task depends on) + # --------------------------------------------- + + set max_earliest_start $today_j + + foreach dependent_item $depends($task_item) { + + set my_earliest_start [expr [expr $activity_time($dependent_item) / double($hours_day)] + $earliest_start($dependent_item)] + + if {$my_earliest_start > $max_earliest_start} { + set max_earliest_start $my_earliest_start + } + } + + set earliest_start($task_item) $max_earliest_start + set earliest_finish($task_item) [expr $max_earliest_start + [expr $activity_time($task_item) / double($hours_day)]] + + ns_log Notice \ + " earliest_start ($task_item): $earliest_start($task_item)" + ns_log Notice \ + " earliest_finish($task_item): $earliest_finish($task_item)" + + } + + # ------------------------------- + # add to list of tasks to process + # ------------------------------- + + if {[info exists dependent($task_item)]} { + set future_tasks [concat $future_tasks $dependent($task_item)] + } + } + + ns_log Notice "future tasks: $future_tasks" + + set present_tasks $future_tasks + } + + # ---------------------------------------------- + # set up earliest date project will be completed + # ---------------------------------------------- + + set max_earliest_finish $today_j + + foreach task_item $task_list { + + ns_log Notice "*Earliest start ($task_item): $earliest_start($task_item)" + if {$max_earliest_finish < $earliest_finish($task_item)} { + set max_earliest_finish $earliest_finish($task_item) + } + } + + + # ----------------------------------------------------------------- + # Now compute latest_start and latest_finish dates. + # Note the latest_finish dates may be set to an arbitrary deadline. + # ----------------------------------------------------------------- + + # ---------------------------------------------------------------------- + # we want to go through and fill in all the values for latest start + # and latest_finish. + # the brain-dead, brute force way of doing this, would be go through the + # task_list length(task_list) times, and each time, compute the values + # for each item that depends on one of those tasks. This is extremely + # inefficient. + # ---------------------------------------------------------------------- + # Instead, we create two lists, one is of tasks we just added + # latest_finish values for, the next is a new list of ones we're going to + # add latest_finish values for. We call these lists + # present_tasks and future_tasks + # ---------------------------------------------------------------------- + # The biggest problem with this algorithm is that you can have items at + # two different levels in the hierarchy. For example, + # 2155 + # / | \ + # 2161 2173 2179 + # | | + # 2167 2195 + # if you trace through this algorithm, you'll see that we'll get to 2155 + # before 2161's values have been set, which can cause an error. The + # solution we arrive at is to defer to the future_tasks list any item + # that causes an error. That should work. + + set present_tasks [list] + set future_tasks [list] + + # ----------------------------------------------------- + # make a list of tasks that don't have tasks depend on them + # ----------------------------------------------------- + # while we're at it, save latest_start and latest_finish + # info for these items + # ----------------------------------------------------- + + foreach task_item $task_list { + + if {![info exists dependent($task_item)]} { + + set latest_finish($task_item) $end_date_j + set latest_start($task_item) [expr $latest_finish($task_item) - [expr $activity_time($task_item) / double($hours_day)]] + + lappend present_tasks $task_item + + ns_log Notice "Begin latest_start($task_item): $latest_start($task_item) latest_finish: $latest_finish($task_item)" + } + } + + + # ------------------------------- + # stop if we have no dependencies + # ------------------------------- + if {[llength $present_tasks] == 0} { + ns_log Notice "No tasks with dependencies" + return [list] + } + + ns_log Notice "LATEST present_tasks: $present_tasks" + + # ------------------------------------------------------ + # figure out the latest start and finish times + # ------------------------------------------------------ + + while {[llength $present_tasks] > 0} { + + set future_tasks [list] + + foreach task_item $present_tasks { + + ns_log Notice "this task_item: $task_item" + + # ----------------------------------------------------- + # some tasks may already have latest_start filled in + # the first run of tasks, for example, had their values + # filled in earlier + # ----------------------------------------------------- + + if {[info exists dependent($task_item)]} { + + ns_log Notice " info exists for dependent($task_item)" + + # --------------------------------------------- + # set the latest_start for this task = + # min(latest_start(i+1) - activity_time(i) + # + # (i+1 means an item that depends on this task) + # (i means this task) + # --------------------------------------------- + + set min_latest_start $end_date_j + + foreach dependent_item $dependent($task_item) { + + if {![exists_and_not_null latest_start($dependent_item)]} { + # let's not do this task_item yet + lappend future_tasks $task_item + set defer_p t + } else { + + set my_latest_start [expr $latest_start($dependent_item) - [expr $activity_time($dependent_item) / double($hours_day)]] + + if {$my_latest_start < $min_latest_start} { + set min_latest_start $my_latest_start + } + + set defer_p f + } + } + + if {[string equal $defer_p f]} { + + set latest_start($task_item) $min_latest_start + set latest_finish($task_item) [expr $min_latest_start + [expr $activity_time($task_item) / double($hours_day)]] + + ns_log Notice \ + " latest_start ($task_item): $latest_start($task_item)" + ns_log Notice \ + " latest_finish($task_item): $latest_finish($task_item)" + } else { + ns_log Notice "Deferring $task_item" + } + } + + # ------------------------------- + # add to list of tasks to process + # ------------------------------- + + if {[info exists depends($task_item)]} { + set future_tasks [concat $future_tasks $depends($task_item)] + } + } + + ns_log Notice "future tasks: $future_tasks" + + set present_tasks $future_tasks + } + + # ---------------------------------------------- + # set up latest start date for project + # ---------------------------------------------- + + set min_latest_start $end_date_j + + foreach task_item $task_list { + + ns_log Notice "*Latest start ($task_item): $latest_start($task_item)" + if {$min_latest_start > $latest_start($task_item)} { + set max_earliest_finish $earliest_finish($task_item) + } + } + + + # estimated_finish_date + # latest_finish + db_dml update_project { } - return "ok" + # now we go through and save all the values for the tasks! + # this is very inefficient and stupid + + foreach task_item $task_list { + db_dml update_task { } + } + + + ns_log Notice "*******************" + + return $task_list + } @@ -120,7 +491,7 @@ ns_log Notice "root: $root_folder , last_item_id $last_item_id" - set return_code [compute_status $project_item_id] + set return_code [compute_status $last_item_id] return $return_code }