Index: openacs-4/contrib/packages/project-manager/project-manager.info
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/Attic/project-manager.info,v
diff -u -r1.16.2.17 -r1.16.2.18
--- openacs-4/contrib/packages/project-manager/project-manager.info	8 Oct 2004 21:24:57 -0000	1.16.2.17
+++ openacs-4/contrib/packages/project-manager/project-manager.info	26 Oct 2004 01:22:26 -0000	1.16.2.18
@@ -7,14 +7,14 @@
     <initial-install-p>f</initial-install-p>
     <singleton-p>f</singleton-p>
     
-    <version name="2.21b1" url="http://openacs.org/repository/download/apm/project-manager-2.21b1.apm">
+    <version name="2.50b1" url="http://openacs.org/repository/download/apm/project-manager-2.50b1.apm">
         <owner url="mailto:jader@bread.com">Jade Rubick</owner>
         <summary>Project management tool for OpenACS</summary>
-        <release-date>2004-10-08</release-date>
+        <release-date>2004-10-13</release-date>
         <vendor url="mailto:jader@bread.com">Integrated Bakery Resources</vendor>
         <description format="text/plain">Track tasks, estimates and actual progress for a project.  See the &lt;a href=&quot;http://openacs.org/projects/dotwrk/project_management/&quot;&gt;project page&lt;/a&gt; for more information.</description>
 
-        <provides url="project-manager" version="2.21b1"/>
+        <provides url="project-manager" version="2.50b1"/>
         <requires url="acs-datetime" version="4.0"/>
         <requires url="acs-templating" version="4.6.4"/>
         <requires url="calendar" version="2.0.1"/>
@@ -25,8 +25,8 @@
         <requires url="organizations" version="0.3d"/>
 
         <callbacks>
-            <callback type="after-instantiate"  proc="pm::install::package_instantiate"/>
             <callback type="before-uninstantiate"  proc="pm::install::package_uninstantiate"/>
+            <callback type="after-instantiate"  proc="pm::install::package_instantiate"/>
         </callbacks>
         <parameters>
             <parameter datatype="string"  min_n_values="1"  max_n_values="1"  name="LoggerPrimaryURL"  description="This is the primary logger instance that is linked in to this project-manager instance. Links to this instance are added to 
Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/contrib/packages/project-manager/lib/process-instances-postgresql.xql'.
Fisheye: No comparison available.  Pass `N' to diff?
Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/contrib/packages/project-manager/lib/process-instances.adp'.
Fisheye: No comparison available.  Pass `N' to diff?
Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/contrib/packages/project-manager/lib/process-instances.tcl'.
Fisheye: No comparison available.  Pass `N' to diff?
Index: openacs-4/contrib/packages/project-manager/sql/postgresql/project-manager-functions-create.sql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/sql/postgresql/Attic/project-manager-functions-create.sql,v
diff -u -r1.13.2.3 -r1.13.2.4
--- openacs-4/contrib/packages/project-manager/sql/postgresql/project-manager-functions-create.sql	2 Jul 2004 23:13:48 -0000	1.13.2.3
+++ openacs-4/contrib/packages/project-manager/sql/postgresql/project-manager-functions-create.sql	26 Oct 2004 01:22:27 -0000	1.13.2.4
@@ -434,7 +434,7 @@
 -- If it is not associated with a project, then it is placed in the root
 -- project repository folder.
 
-select define_function_args('pm_task__new_task_item', 'project_id, title, description, html_p, end_date, percent_complete, estimated_hours_work, estimated_hours_work_min, estimated_hours_work_max, status_id, creation_date, creation_user, creation_ip, package_id');
+select define_function_args('pm_task__new_task_item', 'project_id, title, description, html_p, end_date, percent_complete, estimated_hours_work, estimated_hours_work_min, estimated_hours_work_max, status_id, process_instance_id, creation_date, creation_user, creation_ip, package_id');
 
 create or replace function pm_task__new_task_item (
         integer,        -- project_id
@@ -447,6 +447,7 @@
         numeric,        -- estimated_hours_work_min
         numeric,        -- estimated_hours_work_max,
         integer,        -- status_id
+        integer,        -- process_instance_id
         timestamptz,    -- creation_date
         integer,        -- creation_user
         varchar,        -- creation_ip
@@ -464,10 +465,11 @@
         p_estimated_hours_work_min              alias for $8;
         p_estimated_hours_work_max              alias for $9;
         p_status_id                             alias for $10;
-        p_creation_date                         alias for $11;
-        p_creation_user                         alias for $12;
-        p_creation_ip                           alias for $13;
-        p_package_id                            alias for $14;
+        p_process_instance_id                   alias for $11;
+        p_creation_date                         alias for $12;
+        p_creation_user                         alias for $13;
+        p_creation_ip                           alias for $14;
+        p_package_id                            alias for $15;
 
         v_item_id               cr_items.item_id%TYPE;
         v_revision_id           cr_revisions.revision_id%TYPE;
@@ -515,9 +517,9 @@
         PERFORM content_item__set_live_revision (v_revision_id);
 
         insert into pm_tasks (
-                task_id, task_number, status)
+                task_id, task_number, status, process_instance)
         values (
-                v_item_id, v_task_number, p_status_id);
+                v_item_id, v_task_number, p_status_id, p_process_instance_id);
 
         insert into pm_tasks_revisions (
                 task_revision_id, end_date, percent_complete, estimated_hours_work, estimated_hours_work_min, estimated_hours_work_max, actual_hours_worked)
Index: openacs-4/contrib/packages/project-manager/sql/postgresql/project-manager-table-create.sql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/sql/postgresql/Attic/project-manager-table-create.sql,v
diff -u -r1.19.2.5 -r1.19.2.6
--- openacs-4/contrib/packages/project-manager/sql/postgresql/project-manager-table-create.sql	8 Oct 2004 21:25:00 -0000	1.19.2.5
+++ openacs-4/contrib/packages/project-manager/sql/postgresql/project-manager-table-create.sql	26 Oct 2004 01:22:27 -0000	1.19.2.6
@@ -157,6 +157,146 @@
 ';
 
 
+-- PROCESSES
+
+create sequence pm_process_seq;
+
+create table pm_process (
+        process_id                      integer
+                                        constraint pm_process_id_pk
+                                        primary key,
+        one_line                        varchar(200)
+                                        constraint pm_process_one_line_nn
+                                        not null,
+        description                     varchar(1000),
+        party_id                        integer
+                                        constraint pm_process_party_fk
+                                        references parties
+                                        constraint pm_process_party_nn
+                                        not null,
+        creation_date                   timestamptz,
+        deleted_p                       char(1) default 'f'
+                                        constraint pm_process_deleted_p_ck
+                                        check (deleted_p in ('t','f')),
+);
+
+comment on table pm_process is '
+ Processes are a set of templates for tasks, so that people can 
+ create sets of tasks quickly. Their structure needs to match that of
+ tasks. The process holds the meta information, and is also an identifier
+ that is used by the user to select which process they''d like to copy or
+ use 
+';
+
+create or replace view 
+pm_process_active as 
+  SELECT *  FROM pm_process where deleted_p = 'f';
+
+-- each time a process is used, it creates an instance of that process
+-- we use this to allow a user to see overviews of process status, etc..
+
+create sequence pm_process_instance_seq start 1;
+
+create table pm_process_instance (
+        instance_id                     integer
+                                        constraint pm_process_instance_id_pk
+                                        primary key,
+        name                            varchar(200),
+        process_id                      integer
+                                        constraint pm_process_instance_process_fk
+                                        references pm_process on delete cascade,
+        project_item_id                 integer
+                                        constraint pm_process_project_fk
+                                        references cr_items
+);
+
+
+create sequence pm_process_task_seq;
+
+create table pm_process_task (
+        process_task_id                 integer
+                                        constraint pm_process_task_id_pk
+                                        primary key,
+        process_id                      integer
+                                        constraint pm_process_process_id_fk
+                                        references
+                                        pm_process
+                                        constraint pm_process_process_id_nn
+                                        not null,
+        one_line                        varchar(200)
+                                        constraint pm_process_task_one_line_nn
+                                        not null,
+        description                     varchar(4000),
+        mime_type                       varchar(200)
+                                        constraint pm_process_task_mime_type_fk
+                                        references cr_mime_types(mime_type)
+                                        on update no action on delete no action
+                                        default 'text/plain'
+        -- dates are optional, because it may be computed in reference
+        -- to all other items, or simply not have a deadline
+        -- percent complete is always 0
+        estimated_hours_work            numeric,
+        -- PERT charts require minimum and maximum estimates
+        -- these are optionally used
+        estimated_hours_work_min        numeric,
+        estimated_hours_work_max        numeric,
+        ordering                        integer
+);
+
+comment on table pm_process_task is '
+  A template for the tasks that will be created by the process
+';
+
+create sequence pm_process_task_dependency_seq;
+
+create table pm_process_task_dependency (
+        dependency_id                   integer
+                                        constraint pm_proc_task_dependcy_pk
+                                        primary key,
+        process_task_id                 integer
+                                        constraint pm_proc_task_proc_task_fk
+                                        references pm_process_task
+                                        on delete cascade,
+        parent_task_id                  integer
+                                        constraint pm_proc_task_parent_id_fk
+                                        references pm_process_task
+                                        on delete cascade,
+        dependency_type                 varchar
+                                        constraint pm_process_task_dep_type
+                                        references pm_task_dependency_types,
+        constraint pm_proc_task_depend_uq
+        unique (process_task_id, parent_task_id)
+);
+
+comment on table pm_process_task_dependency is '
+  Keeps track of dependencies. Used to create the dependencies in the
+  new tasks.
+';
+
+create table pm_process_task_assignment (
+        process_task_id         integer
+                                constraint pm_proc_task_assign_task_fk
+                                references pm_process_task(process_task_id)
+                                on delete cascade,
+        role_id                 integer
+                                constraint pm_task_assignment_role_fk
+                                references pm_roles,
+        party_id                integer
+                                constraint pm_task_assignment_party_fk 
+                                references parties(party_id)
+                                on delete cascade,
+        constraint pm_proc_task_assgn_uq
+        unique (process_task_id, role_id, party_id)
+);
+
+
+comment on table pm_process_task_assignment is '
+  Maps who is assigned to process tasks. These will be the default people
+  assigned to the new tasks
+';
+
+
+
 -- TASKS
 
 -- we create two tables to store task information
@@ -197,12 +337,16 @@
                                         references pm_task_status,
         deleted_p                       char(1) default 'f'
                                         constraint pm_tasks_deleted_p_ck
-                                        check (deleted_p in ('t','f'))
+                                        check (deleted_p in ('t','f')),
+        process_instance                integer
+                                        constraint pm_tasks_process_instance_fk
+                                        references 
+                                        pm_process_instance;
 );
 
 CREATE OR REPLACE view 
 pm_tasks_active as 
-  SELECT task_id, task_number, status FROM pm_tasks where deleted_p = 'f';
+  SELECT task_id, task_number, status, process_instance FROM pm_tasks where deleted_p = 'f';
 
 
 create table pm_tasks_revisions (
@@ -431,7 +575,7 @@
 );
 
 
--- WORKGROUPS
+-- WORKGROUPS: currently not used
 
 create sequence pm_workgroup_seq;
 
@@ -486,6 +630,8 @@
   Maps who is a part of what task, and in what capacity
 ';
 
+-- TASK CROSS REFERENCES
+
 create table pm_task_xref (
         task_id_1               integer
                                 constraint pm_task_xref_task1_nn
@@ -507,113 +653,6 @@
 ';
 
 
--- PROCESSES
-
-create sequence pm_process_seq;
-
-create table pm_process (
-        process_id                      integer
-                                        constraint pm_process_id_pk
-                                        primary key,
-        one_line                        varchar(200)
-                                        constraint pm_process_one_line_nn
-                                        not null,
-        description                     varchar(1000),
-        party_id                        integer
-                                        constraint pm_process_party_fk
-                                        references parties
-                                        constraint pm_process_party_nn
-                                        not null,
-        creation_date                   timestamptz
-);
-
-comment on table pm_process is '
- Processes are a set of templates for tasks, so that people can 
- create sets of tasks quickly. Their structure needs to match that of
- tasks. The process holds the meta information, and is also an identifier
- that is used by the user to select which process they''d like to copy or
- use 
-';
-
-create sequence pm_process_task_seq;
-
-create table pm_process_task (
-        process_task_id                 integer
-                                        constraint pm_process_task_id_pk
-                                        primary key,
-        process_id                      integer
-                                        constraint pm_process_process_id_fk
-                                        references
-                                        pm_process
-                                        constraint pm_process_process_id_nn
-                                        not null,
-        one_line                        varchar(200)
-                                        constraint pm_process_task_one_line_nn
-                                        not null,
-        description                     varchar(4000),
-        -- dates are optional, because it may be computed in reference
-        -- to all other items, or simply not have a deadline
-        -- percent complete is always 0
-        estimated_hours_work            numeric,
-        -- PERT charts require minimum and maximum estimates
-        -- these are optionally used
-        estimated_hours_work_min        numeric,
-        estimated_hours_work_max        numeric,
-        ordering                        integer
-);
-
-comment on table pm_process_task is '
-  A template for the tasks that will be created by the process
-';
-
-create sequence pm_process_task_dependency_seq;
-
-create table pm_process_task_dependency (
-        dependency_id                   integer
-                                        constraint pm_proc_task_dependcy_pk
-                                        primary key,
-        process_task_id                 integer
-                                        constraint pm_proc_task_proc_task_fk
-                                        references pm_process_task
-                                        on delete cascade,
-        parent_task_id                  integer
-                                        constraint pm_proc_task_parent_id_fk
-                                        references pm_process_task
-                                        on delete cascade,
-        dependency_type                 varchar
-                                        constraint pm_process_task_dep_type
-                                        references pm_task_dependency_types,
-        constraint pm_proc_task_depend_uq
-        unique (process_task_id, parent_task_id)
-);
-
-comment on table pm_process_task_dependency is '
-  Keeps track of dependencies. Used to create the dependencies in the
-  new tasks.
-';
-
-create table pm_process_task_assignment (
-        process_task_id         integer
-                                constraint pm_proc_task_assign_task_fk
-                                references pm_process_task(process_task_id)
-                                on delete cascade,
-        role_id                 integer
-                                constraint pm_task_assignment_role_fk
-                                references pm_roles,
-        party_id                integer
-                                constraint pm_task_assignment_party_fk 
-                                references parties(party_id)
-                                on delete cascade,
-        constraint pm_proc_task_assgn_uq
-        unique (process_task_id, role_id, party_id)
-);
-
-
-comment on table pm_process_task_assignment is '
-  Maps who is assigned to process tasks. These will be the default people
-  assigned to the new tasks
-';
-
 create table pm_users_viewed (
         viewing_user    integer constraint
                         pm_users_viewed_viewing_user_fk
Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/contrib/packages/project-manager/sql/postgresql/upgrade/upgrade-2.21b1-2.50b1.sql'.
Fisheye: No comparison available.  Pass `N' to diff?
Index: openacs-4/contrib/packages/project-manager/tcl/calendar-procs-postgresql.xql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/calendar-procs-postgresql.xql,v
diff -u -r1.1.2.6 -r1.1.2.7
--- openacs-4/contrib/packages/project-manager/tcl/calendar-procs-postgresql.xql	15 Sep 2004 23:05:51 -0000	1.1.2.6
+++ openacs-4/contrib/packages/project-manager/tcl/calendar-procs-postgresql.xql	26 Oct 2004 01:22:27 -0000	1.1.2.7
@@ -36,7 +36,8 @@
       p.first_names || ' ' || p.last_name || ' (' ||
       substring(r.one_line from 1 for 1) || ')' as full_name,
       p.person_id,
-      s.status_type as status
+      s.status_type as status,
+      r.is_lead_p
       FROM
       pm_tasks_active ts, 
       pm_task_status s,
Index: openacs-4/contrib/packages/project-manager/tcl/calendar-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/calendar-procs.tcl,v
diff -u -r1.1.2.8 -r1.1.2.9
--- openacs-4/contrib/packages/project-manager/tcl/calendar-procs.tcl	24 Sep 2004 20:53:52 -0000	1.1.2.8
+++ openacs-4/contrib/packages/project-manager/tcl/calendar-procs.tcl	26 Oct 2004 01:22:27 -0000	1.1.2.9
@@ -86,13 +86,21 @@
 
             # highlight what you're assigned to.
             if {[string equal $person_id $user_id]} {
-                set font_begin "<strong>"
-                set font_end "</strong>"
+                set font_begin "<span class=\"selected\">"
+                set font_end "</span>"
             } else {
                 set font_begin ""
                 set font_end ""
             }
 
+            if { \
+                     ![empty_string_p $is_lead_p] && \
+                     [string is true $is_lead_p]} {
+
+                set font_begin "$font_begin<i>"
+                set font_end "</i>$font_end"
+            }
+
             # if this is another row of the same item, just add the name.
             if {[string equal $last_task_id $task_id]} {
                 append day_details "<li>, ${font_begin}${full_name}${font_end}</li>"
Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/contrib/packages/project-manager/tcl/process-procs-postgresql.xql'.
Fisheye: No comparison available.  Pass `N' to diff?
Index: openacs-4/contrib/packages/project-manager/tcl/process-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/process-procs.tcl,v
diff -u -r1.2.2.1 -r1.2.2.2
--- openacs-4/contrib/packages/project-manager/tcl/process-procs.tcl	2 Jul 2004 23:13:48 -0000	1.2.2.1
+++ openacs-4/contrib/packages/project-manager/tcl/process-procs.tcl	26 Oct 2004 01:22:27 -0000	1.2.2.2
@@ -27,21 +27,7 @@
     @error 
 } {
 
-    db_transaction {
-        db_dml delete_tasks {
-            DELETE FROM 
-            pm_process_task
-            WHERE
-            process_id = :process_id
-        }
-
-        db_dml delete_process {
-            DELETE FROM
-            pm_process
-            WHERE
-            process_id = :process_id
-        }
-    }
+    db_dml delete_process { }
     
 }
 
@@ -182,3 +168,434 @@
 
     return
 }
+
+
+ad_proc -public pm::process::get {
+    {-process_id:required}
+    {-process_task_id ""}
+    {-one_line_array:required}
+    {-description_array:required}
+    {-description_mime_type_array:required}
+    {-estimated_hours_work_array:required}
+    {-estimated_hours_work_min_array:required}
+    {-estimated_hours_work_max_array:required}
+    {-dependency_array:required}
+    {-tasks_list:required}
+} {
+    Sets a bunch of information in a set of arrays on all
+    process tasks for a given process
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-09-23
+    
+    @param process_id
+
+    @param one_line_array
+
+    @param description_array
+
+    @param estimated_hours_work_array
+
+    @param estimated_hours_work_min_array
+
+    @param estimated_hours_work_max_array
+
+    @param dependency_array
+
+    @param tasks_list
+
+    @return 
+    
+    @error 
+} {
+
+    # set variables in calling environment, using names passed in
+    upvar 1 $one_line_array                 one_line_arr
+    upvar 1 $description_array              description_arr
+    upvar 1 $description_mime_type_array    description_mime_type_arr
+    upvar 1 $estimated_hours_work_array     estimated_hours_work_arr
+    upvar 1 $estimated_hours_work_min_array estimated_hours_work_min_arr
+    upvar 1 $estimated_hours_work_max_array estimated_hours_work_max_arr
+    upvar 1 $dependency_array               dependency_arr
+    upvar 1 $tasks_list                     process_tasks_l
+
+    if {[exists_and_not_null process_task_id]} {
+        set process_task_where_clause " and t.process_task_id in ([join $process_task_id ", "])"
+    } else {
+        set process_task_where_clause ""
+    }
+
+    db_foreach get_process_tasks { } {
+        set one_line_arr($process_tid)                 $one_line
+        set description_arr($process_tid)              $description
+        set description_mime_type_arr($process_tid)    $description_mime_type
+        set estimated_hours_work_arr($process_tid)     $estimated_hours_work
+        set estimated_hours_work_min_arr($process_tid) $estimated_hours_work_min
+        set estimated_hours_work_max_arr($process_tid) $estimated_hours_work_max
+        set dependency_arr($process_tid)               $process_parent_task
+
+        # make sure that we don't have empty values for estimated
+        # hours work
+        if {[empty_string_p $estimated_hours_work_arr($process_tid)]} {
+            set estimated_hours_work_arr($process_tid) 0
+        }
+        if {[empty_string_p $estimated_hours_work_min_arr($process_tid)]} {
+            set estimated_hours_work_min_arr($process_tid) 0
+        }
+        if {[empty_string_p $estimated_hours_work_max_arr($process_tid)]} {
+            set estimated_hours_work_max_arr($process_tid) 0
+        }
+
+
+        lappend process_tasks_l $process_tid
+    }
+    
+}
+
+
+ad_proc -public pm::process::select_html {
+} {
+    Returns the option part of the 
+    HTML for a select list of process options
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-14
+    
+    @return 
+    
+    @error 
+} {
+
+    set html "<option value=\"\">Select process</option>"
+
+    db_foreach get_processes get_processes {
+        append html "<option value=\"$process_id\">$process_name</option>\n"
+    }
+
+    return $html
+}
+
+
+ad_proc -public pm::process::task_assignee_role_list {
+    {-process_task_id:required}
+} {
+    Returns a list of lists, with all assignees to a particular 
+    process task. {{party_id role_id} {party_id role_id}}
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-15
+    
+    @param process_task_id
+
+    @return 
+    
+    @error 
+} {
+
+    return [db_list_of_lists get_assignees_roles { }]
+
+}
+
+
+ad_proc -public pm::process::instantiate {
+    {-process_id:required}
+    {-project_item_id:required}
+    {-name:required}
+} {
+    Returns a unique process instance id
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-15
+    
+    @return 
+    
+    @error 
+} {
+
+    set instance_id [db_nextval pm_process_instance_seq]
+
+    db_dml add_instance { }
+
+    return $instance_id
+}
+
+
+ad_proc -public pm::process::instances {
+    {-project_item_id:required}
+} {
+    Returns a list of lists of form
+    {{process_instance_id process_instance_name} { } ...}
+
+    of processes in use by tasks in a particular project
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-15
+    
+    @param project_item_id
+
+    @return 
+    
+    @error 
+} {
+
+    return [db_list_of_lists get_process_instance { }]
+    
+}
+
+
+ad_proc -public pm::process::instance_options {
+    {-project_item_id:required}
+    {-process_instance_id ""}
+} {
+    Returns the options portion of HTML for process instances
+    associated with tasks in a project
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-15
+    
+    @param project_item_id
+
+    @return 
+    
+    @error 
+} {
+
+    set instances [pm::process::instances -project_item_id $project_item_id]
+
+    set html ""
+
+    foreach inst $instances {
+        set instance_id [lindex $inst 0]
+        set name        [lindex $inst 1]
+
+        if {[string equal $instance_id $process_instance_id]} {
+            set sel "selected=\"selected\""
+        } else {
+            set sel ""
+        }
+
+        append html "<option $sel value=\"$instance_id\">$name (\#$instance_id)</option>"
+    }
+
+    return $html
+}
+
+
+ad_proc -public pm::process::url {
+    {-process_instance_id:required}
+    {-project_item_id:required}
+    {-fully_qualified_p "t"}
+} {
+    Returns the URL for a process instance
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-15
+    
+    @param process_instance_id
+
+    @return 
+    
+    @error 
+} {
+
+    return [pm::util::url -fully_qualified_p $fully_qualified_p][export_vars -base one {project_item_id {instance_id $process_instance_id}}]
+
+}
+
+
+ad_proc -public pm::process::email_alert {
+    {-process_instance_id:required}
+    {-project_item_id:required}
+} {
+    Sends out an email notification when a process is instantiated.
+    Gives the status of all tasks created.
+
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-05
+    
+    @param process_instance_id
+
+    @return 
+    
+    @error 
+} {
+
+    set task_term       \
+        [parameter::get -parameter "Taskname" -default "Task"]
+    set task_term_lower \
+        [parameter::get -parameter "taskname" -default "task"]
+    set use_uncertain_completion_times_p \
+        [parameter::get -parameter "UseUncertainCompletionTimesP" -default "0"]
+
+    set user_id [ad_conn user_id]
+
+    db_1row get_from_address_and_more { }
+    db_1row get_project_info { }
+
+    set process_name [pm::process::name \
+                          -process_instance_id $process_instance_id]
+
+    set project_url [pm::project::url -project_item_id $project_item_id]
+    set process_url [pm::process::url \
+                         -process_instance_id $process_instance_id \
+                         -project_item_id $project_item_id]
+    
+    set subject_out "New: $process_name"
+    set intro_text "$mod_username assigned you to a process."
+    
+    set task_info [db_list_of_lists get_task_info { }]
+
+    set task_list [list]
+    foreach ti $task_info {
+        lappend task_list [lindex $ti 0]
+    }
+
+    set assignees [db_list_of_lists get_assignees { }]
+
+    # make a list of those who are assigned in some capacity
+    set to_addresses [list]
+
+    foreach ass $assignees {
+        set to_address [lindex $ass 0]
+
+        if {[lsearch $to_addresses $to_address] < 0} {
+            lappend to_addresses $to_address
+        }
+    }
+
+    # make the notification for each assignee
+    foreach to_address $to_addresses {
+
+        set notification_text "${intro_text}
+<h3>Process overview</h3>
+<table border=\"0\" bgcolor=\"#ddddff\">
+  <tr>
+    <td>Project:</td>
+    <td><a href=\"${project_url}\">$project_name</a> (\#$project_item_id)</td>
+  </tr>
+  <tr>
+    <td>Overview of process:</td>
+    <td><a href=\"${process_url}\">$process_name</a></td>
+  </tr>
+</table>
+
+<h3>$task_term and current status</h3>
+<table border=\"0\" bgcolor=\"\#ddddff\" cellpadding=\"1\" cellspacing=\"1\">
+  <th>$task_term name</th>
+  <th>Slack time</th>
+  <th>Lead</th>
+  <th>Latest finish</th>"
+
+        foreach ti $task_info {
+            set task_item_id     [lindex $ti 0]
+            set subject          [lindex $ti 1]
+            set today_j          [lindex $ti 2]
+            set earliest_start_j [lindex $ti 3]
+            set latest_start_j   [lindex $ti 4]
+            set latest_finish    [lindex $ti 5]
+            set status_type      [lindex $ti 6]
+            
+            set slack_time [pm::task::slack_time \
+                                -earliest_start_j $earliest_start_j \
+                                -today_j $today_j \
+                                -latest_start_j $latest_start_j]
+            
+            set pretty_latest_finish [lc_time_fmt $latest_finish "%x"]
+            
+            set lead_html ""
+            
+            # we highlight rows when the user is assigned to this task
+            set assignee_involved_p f
+
+            foreach ass $assignees {
+                set to_addr    [lindex $ass 0]
+                set role       [lindex $ass 1]
+                set is_lead_p  [lindex $ass 2]
+                set user_name  [lindex $ass 3]
+                set my_task_id [lindex $ass 4]
+                
+                # ignore anything that isn't for this task
+                if {[string equal $my_task_id $task_item_id]} {
+
+                    if {[string is true $is_lead_p]} {
+                        append lead_html "$user_name<br />"
+                    }
+                    
+                    if {[string equal $to_addr $to_address]} {
+                        set assignee_involved_p t
+                    }
+                }
+            }
+
+            if {[string equal $status_type "c"]} {
+                append notification_text "<tr bgcolor=\"dddddd\">"
+            } elseif {[string is true $assignee_involved_p]} {
+                append notification_text "<tr bgcolor=\"ffdddd\">"
+            } else {
+                append notification_text "<tr>"
+            }
+            
+            append notification_text "
+  <td>$subject</td>
+  <td>$slack_time</td>
+  <td>$lead_html</td>
+  <td>$pretty_latest_finish</td>
+</tr>"
+        }
+        
+        # build table of current status 
+        append notification_text "</table> <p>If the row is in red, you are involved in this task. If it is in grey, then it has already been completed.</p>"
+        
+        pm::util::email \
+            -to_addr $to_address \
+            -from_addr $from_address \
+            -subject $subject_out \
+            -body $notification_text \
+            -mime_type "text/html"
+    }
+    
+}
+
+
+ad_proc -public pm::process::name {
+    {-process_instance_id:required}
+} {
+    Returns the name when given a process_instance_id
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-20
+    
+    @param process_instance_id
+
+    @return 
+    
+    @error 
+} {
+
+    db_1row get_name { }
+
+    return $process_name
+}
+
+
+ad_proc -public pm::process::process_name {
+    {-process_id:required}
+} {
+    Returns the name when given a process_id
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-20
+    
+    @param process_id
+
+    @return 
+    
+    @error 
+} {
+
+    db_1row get_name { }
+
+    return $one_line
+}
+
+
Index: openacs-4/contrib/packages/project-manager/tcl/project-manager-procs-postgresql.xql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/project-manager-procs-postgresql.xql,v
diff -u -r1.2.2.1 -r1.2.2.2
--- openacs-4/contrib/packages/project-manager/tcl/project-manager-procs-postgresql.xql	2 Jul 2004 23:13:48 -0000	1.2.2.1
+++ openacs-4/contrib/packages/project-manager/tcl/project-manager-procs-postgresql.xql	26 Oct 2004 01:22:27 -0000	1.2.2.2
@@ -35,5 +35,24 @@
     t.name
     </querytext>
   </fullquery>
+
+  <fullquery name="pm::util::subsite_assignees_list_of_lists_not_cached.get_assignees">
+    <querytext>
+      SELECT
+      p.first_names || ' ' || p.last_name as name,
+      p.person_id
+      FROM
+      persons p,
+      acs_rels r,
+      membership_rels mr
+      WHERE
+      r.object_id_one = :user_group_id and
+      mr.rel_id = r.rel_id and
+      p.person_id = r.object_id_two and
+      member_state = 'approved'
+      ORDER BY
+      p.first_names, p.last_name
+    </querytext>
+  </fullquery>
   
 </queryset>
Index: openacs-4/contrib/packages/project-manager/tcl/project-manager-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/project-manager-procs.tcl,v
diff -u -r1.2.2.9 -r1.2.2.10
--- openacs-4/contrib/packages/project-manager/tcl/project-manager-procs.tcl	23 Sep 2004 22:00:06 -0000	1.2.2.9
+++ openacs-4/contrib/packages/project-manager/tcl/project-manager-procs.tcl	26 Oct 2004 01:22:27 -0000	1.2.2.10
@@ -601,6 +601,7 @@
 
 
 ad_proc -public pm::util::url {
+    {-fully_qualified_p "t"}
 } {
     Returns the URL of where the project manager is located,
     fully qualified
@@ -614,7 +615,49 @@
 } {
     set package_id [pm::util::package_id]
 
-    set package_url "[ad_url][site_node::get_url_from_object_id -object_id $package_id]"
+    if {[string is true $fully_qualified_p]} {
+        set return_val [ad_url]
+    } else {
+        set return_val ""
+    }
+    append return_val [site_node::get_url_from_object_id -object_id $package_id]
 
-    return $package_url
+    return $return_val
 }
+
+
+ad_proc -public pm::util::subsite_assignees_list_of_lists {
+} {
+    Returns a list of lists of possible assignees
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-13
+    
+    @return 
+    
+    @error 
+} {
+    return [util_memoize [list pm::util::subsite_assignees_list_of_lists_not_cached]]
+}
+
+
+ad_proc -public pm::util::subsite_assignees_list_of_lists_not_cached {
+} {
+    Returns a list of lists of possible assignees
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-13
+    
+    @return 
+    
+    @error 
+} {
+
+    set subsite_id [ad_conn subsite_id]
+    set user_group_id [application_group::group_id_from_package_id \
+                           -package_id $subsite_id]
+
+    set assignees [db_list_of_lists get_assignees { }]
+    
+    return $assignees
+}
Index: openacs-4/contrib/packages/project-manager/tcl/project-procs-postgresql.xql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/project-procs-postgresql.xql,v
diff -u -r1.4.2.3 -r1.4.2.4
--- openacs-4/contrib/packages/project-manager/tcl/project-procs-postgresql.xql	16 Jul 2004 20:59:40 -0000	1.4.2.3
+++ openacs-4/contrib/packages/project-manager/tcl/project-procs-postgresql.xql	26 Oct 2004 01:22:27 -0000	1.4.2.4
@@ -45,6 +45,18 @@
     </querytext>
   </fullquery>
 
+  <fullquery name="pm::project::log_hours.add_task_logger_map">
+    <querytext>
+      INSERT INTO
+      pm_task_logger_proj_map
+      (task_item_id,
+      logger_entry)
+      VALUES
+      (:task_item_id,
+      :entry_id)
+    </querytext>
+  </fullquery>
+
   <fullquery name="pm::project::new.new_project_item">
     <querytext>
         select pm_project__new_project_item (
@@ -200,4 +212,24 @@
     </querytext>
   </fullquery>
 
+  <fullquery name="pm::project::get_list_of_open.get_vals">
+    <querytext>
+      SELECT
+      case when o.name is null then p.title else p.title || ' (' || o.name || ')' end,
+      p.item_id
+      FROM pm_projectsx p
+        LEFT JOIN 
+        organizations o
+        ON p.customer_id = o.organization_id,
+      cr_items i, 
+      pm_project_status s
+      WHERE 
+      p.project_id  = i.live_revision and
+      s.status_id   = p.status_id and
+      s.status_type = 'o'
+      ORDER BY
+      lower(p.title), lower(o.name) 
+    </querytext>
+  </fullquery>
+
 </queryset>
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.8 -r1.6.2.9
--- openacs-4/contrib/packages/project-manager/tcl/project-procs.tcl	8 Oct 2004 21:25:04 -0000	1.6.2.8
+++ openacs-4/contrib/packages/project-manager/tcl/project-procs.tcl	26 Oct 2004 01:22:27 -0000	1.6.2.9
@@ -81,7 +81,7 @@
     -logger_project_id:required
     -variable_id:required
     -value:required
-    -timestamp_ansi:required
+    {-timestamp_ansi ""}
     {-description ""}
     {-task_item_id ""}
     {-project_item_id ""}
@@ -145,6 +145,10 @@
         set creation_user [ad_conn user_id]
     }
 
+    if {[empty_string_p $timestamp_ansi]} {
+        set timestamp_ansi [clock format [clock seconds] -format "%Y-%m-%d"]
+    }
+
     # add in the new entry
     logger::entry::new -entry_id $entry_id \
         -project_id $logger_project_id \
@@ -157,21 +161,13 @@
     # if we have a pm_task_id, then we need to note that this
     # entry is logged to a particular task.
     if {[exists_and_not_null task_item_id]} {
-        db_dml add_logger_map "
-                INSERT INTO
-                pm_task_logger_proj_map
-                (task_item_id,
-                 logger_entry)
-                VALUES
-                (:task_item_id,
-                 :entry_id)
-             "
+        db_dml add_task_logger_map { }
         
         set returnval [pm::task::update_hours \
                            -task_item_id $task_item_id \
                            -update_tasks_p $update_status_p]
 
-        if {[string equal $update_status_p "t"]} {
+        if {[string is true $update_status_p]} {
             pm::project::compute_status $project_item_id
         }
     }
@@ -1595,7 +1591,6 @@
 }
 
 
-
 ad_proc -public pm::project::get_list_of_open {
 } {
     Returns a list of lists, of all open project ids and their names
@@ -1608,28 +1603,53 @@
     @error 
 } {
 
-    set return_val [db_list_of_lists get_vals "
-        SELECT
-        case when o.name is null then p.title else o.name || ' - ' || p.title end,
-        p.item_id
-        FROM pm_projectsx p
-          LEFT JOIN 
-          organizations o
-          ON p.customer_id = o.organization_id,
-        cr_items i, 
-        pm_project_status s
-        WHERE 
-        p.project_id  = i.live_revision and
-        s.status_id   = p.status_id and
-        s.status_type = 'o'
-        ORDER BY
-        lower(o.name), lower(p.title)
-        "]
+    set return_val [db_list_of_lists get_vals { }]
 
     return $return_val
 }
 
 
+ad_proc -public pm::project::select_list_of_open {
+    {-selected ""}
+} {
+    Returns a select list of all open project ids and their names
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-13
+    
+    @return html for select list of open projects
+    
+    @error 
+} {
+    # is the selected project closed?
+    set open_p [pm::project::open_p -project_item_id $selected]
+
+    if {[string is false $open_p]} {
+        set name [pm::project::name -project_item_id $selected]
+        set html "<option value=\"$selected\">$name</option>"
+    } else {
+        set html ""
+    }
+
+    set list_of_lists [pm::project::get_list_of_open]
+
+    foreach lol $list_of_lists {
+        set name [lindex $lol 0]
+        set id   [lindex $lol 1]
+
+        if {[string equal $id $selected]} {
+            set sel "selected=\"selected\""
+        } else {
+            set sel ""
+        }
+
+        append html "<option $sel value=\"$id\">$name</option>\n"
+    }
+
+    return $html
+}
+
+
 ad_proc -public pm::project::close {
     {-project_item_id:required}
 } {
@@ -1944,3 +1964,5 @@
     return "[ad_url][ad_conn package_url]one?project_item_id=$project_item_id"
     
 }
+
+
Index: openacs-4/contrib/packages/project-manager/tcl/role-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/role-procs.tcl,v
diff -u -r1.2.2.3 -r1.2.2.4
--- openacs-4/contrib/packages/project-manager/tcl/role-procs.tcl	20 Sep 2004 23:13:49 -0000	1.2.2.3
+++ openacs-4/contrib/packages/project-manager/tcl/role-procs.tcl	26 Oct 2004 01:22:27 -0000	1.2.2.4
@@ -40,11 +40,11 @@
     
     @error 
 } {
-    return [util_memoize [list pm::role::select_list_filter_helper] 300]
+    return [util_memoize [list pm::role::select_list_filter_not_cached] 300]
 }
 
 
-ad_proc -private pm::role::select_list_filter_helper {} {
+ad_proc -private pm::role::select_list_filter_not_cached {} {
     Returns a select list. Used so pm::role::select_list can be cached.
     
     @author Jade Rubick (jader@bread.com)
Index: openacs-4/contrib/packages/project-manager/tcl/task-procs-postgresql.xql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/project-manager/tcl/Attic/task-procs-postgresql.xql,v
diff -u -r1.4.2.4 -r1.4.2.5
--- openacs-4/contrib/packages/project-manager/tcl/task-procs-postgresql.xql	23 Sep 2004 21:46:41 -0000	1.4.2.4
+++ openacs-4/contrib/packages/project-manager/tcl/task-procs-postgresql.xql	26 Oct 2004 01:22:27 -0000	1.4.2.5
@@ -3,6 +3,20 @@
 <queryset>
   <rdbms><type>postgresql</type><version>7.3</version></rdbms>
 
+  <fullquery name="pm::task::name.get_name">
+    <querytext>
+      SELECT
+      r.title
+      FROM
+      cr_items i,
+      cr_revisions r
+      WHERE
+      i.item_id = r.item_id and
+      i.item_id = :task_item_id and
+      i.live_revision = r.revision_id
+    </querytext>
+  </fullquery>
+
   <fullquery name="pm::task::get_item_id.get_item_id">
     <querytext>
       SELECT
@@ -66,9 +80,59 @@
     </querytext>
   </fullquery>
 
-  <fullquery name="pm::task::options_list.get_dependency_tasks">
+  <fullquery name="pm::task::dependency_delete_all.delete_deps">
     <querytext>
+      DELETE FROM 
+      pm_task_dependency 
+      WHERE 
+      task_id = :task_item_id
+    </querytext>
+  </fullquery>
+
+  <fullquery name="pm::task::dependency_add.get_tasks">
+    <querytext>
       SELECT
+      task.item_id as t_item_id
+      FROM
+      cr_items task,
+      cr_items project
+      WHERE 
+      task.parent_id = project.item_id and
+      project.item_id = :project_item_id
+    </querytext>
+  </fullquery>
+
+  <fullquery name="pm::task::dependency_add.get_dependencies">
+    <querytext>
+      SElECT
+      d.task_id as dep_task,
+      d.parent_task_id as dep_task_parent
+      FROM
+      pm_task_dependency d
+      WHERE
+      d.task_id in ([join $project_tasks ", "])
+    </querytext>
+  </fullquery>
+
+  <fullquery name="pm::task::dependency_add.insert_dep">
+    <querytext>
+      INSERT INTO 
+      pm_task_dependency 
+      (dependency_id, 
+      task_id, 
+      parent_task_id, 
+      dependency_type) 
+      values 
+      (:dependency_id, 
+      :task_item_id, 
+      :parent_id, 
+      'finish_before_start')
+    </querytext>
+  </fullquery>
+
+  <fullquery name="pm::task::options_list_html.get_dependency_tasks">
+    <querytext>
+      SELECT
         r.item_id, 
         r.title as task_title        
 	FROM
@@ -110,6 +174,23 @@
     </querytext>
   </fullquery>
 
+  <fullquery name="pm::task::edit.update_logger_entries">
+    <querytext>
+      UPDATE 
+      logger_entries 
+      SET 
+      project_id = :logger_project 
+      WHERE 
+      entry_id in 
+      (select 
+      logger_entry 
+      from 
+      pm_task_logger_proj_map 
+      where 
+      task_item_id = :task_item_id)
+    </querytext>
+  </fullquery>
+
   <fullquery name="pm::task::new.new_task_item">
     <querytext>
         select pm_task__new_task_item (
@@ -123,31 +204,157 @@
         :estimated_hours_work_min,
         :estimated_hours_work_max,
         :status_id,
+        :process_instance_id,
         coalesce (:creation_date,current_timestamp),
         :creation_user,
         :creation_ip,
         :package_id)
     </querytext>
   </fullquery>
 
-  <fullquery name="pm::task::process_task_info.get_process_tasks">
+
+  <fullquery name="pm::task::email_alert.get_from_address_and_more">
     <querytext>
-        SELECT
-        t.process_task_id as process_tid,
-        t.one_line,
-        t.description,
-        t.estimated_hours_work,
-        t.estimated_hours_work_min,
-        t.estimated_hours_work_max,
-        d.dependency_id,
-        d.parent_task_id as process_parent_task
-        FROM
-        pm_process_task t LEFT JOIN pm_process_task_dependency d ON t.process_task_id = d.process_task_id
+        SELECT 
+        p.email as from_address,
+        p2.first_names || ' ' || p2.last_name as mod_username
+        FROM 
+        parties p,
+        persons p2
         WHERE
-        t.process_id = :process_id
-        ORDER BY
-        t.ordering,
-        t.process_task_id
+        p.party_id = :user_id and
+        p.party_id = p2.person_id
     </querytext>
   </fullquery>
+
+  <fullquery name="pm::task::email_alert.get_task_info">
+    <querytext>
+      SELECT
+      t.title as subject,
+      t.description,
+      t.mime_type as description_mime_type,
+      to_char(t.earliest_start,'MM-DD-YYYY') as earliest_start,
+      to_char(t.earliest_finish,'MM-DD-YYYY') as earliest_finish,
+      to_char(t.latest_start,'MM-DD-YYYY') as latest_start,
+      to_char(t.latest_finish,'MM-DD-YYYY') as latest_finish,
+      t.estimated_hours_work as work,
+      t.estimated_hours_work_min as work_min,
+      t.estimated_hours_work_max as work_max,
+      t.percent_complete,
+      p.title as project_name,
+      t.parent_id as project_item_id,
+      a.process_instance
+      FROM
+      pm_tasks_revisionsx t, 
+      pm_tasks_active a,
+      cr_items i,
+      cr_items project,
+      pm_projectsx p
+      WHERE
+      t.item_id = :task_item_id and
+      t.item_id = a.task_id and
+      t.revision_id = i.live_revision and
+      t.item_id = i.item_id and
+      t.parent_id = project.item_id and
+      project.item_id = p.item_id and
+      project.live_revision = p.revision_id
+    </querytext>
+  </fullquery>
+
+  <fullquery name="pm::task::email_alert.get_assignees">
+    <querytext>
+      SELECT
+      p.email as to_address,
+      r.one_line as role,
+      r.is_lead_p
+      FROM
+      pm_task_assignment a,
+      parties p,
+      pm_roles r
+      WHERE
+      task_id    = :task_item_id and
+      a.party_id = p.party_id and
+      a.role_id  = r.role_id
+    </querytext>
+  </fullquery>
+
+
+  <fullquery name="pm::task::get.get_tasks">
+    <querytext>
+      SELECT
+      t.title as one_line,
+      t.description,
+      t.mime_type as description_mime_type,
+      t.estimated_hours_work as estimated_hours_work,
+      t.estimated_hours_work_min as estimated_hours_work_min,
+      t.estimated_hours_work_max as estimated_hours_work_max,
+      t.percent_complete,
+      to_char(t.end_date, 'DD') as end_date_day,
+      to_char(t.end_date, 'MM') as end_date_month,
+      to_char(t.end_date, 'YYYY') as end_date_year,
+      d.parent_task_id,
+      i.item_id as tid,
+      t.parent_id as project
+      FROM
+      pm_tasks_revisionsx t, 
+      cr_items i
+        LEFT JOIN
+        pm_task_dependency d
+        ON i.item_id = d.task_id
+      WHERE
+      t.revision_id = i.live_revision and
+      t.item_id = i.item_id
+      $task_where_clause
+    </querytext>
+  </fullquery>
+
+
+  <fullquery name="pm::task::assignee_role_list.get_assignees_roles">
+    <querytext>
+      SELECT
+      party_id,
+      role_id
+      FROM
+      pm_task_assignment
+      WHERE
+      task_id = :task_item_id
+    </querytext>
+  </fullquery>
+
+
+  <fullquery name="pm::task::open.update_status">
+    <querytext>
+      UPDATE
+      pm_tasks
+      SET
+      status = :status_code
+      WHERE 
+      task_id = :task_item_id
+    </querytext>
+  </fullquery>
+  
+  <fullquery name="pm::task::close.update_status">
+    <querytext>
+      UPDATE
+      pm_tasks
+      SET
+      status = :status_code
+      WHERE 
+      task_id = :task_item_id
+    </querytext>
+  </fullquery>
+
+  <fullquery name="pm::task::get_assignee_names.get_assignees">
+    <querytext>
+      SELECT
+      p.first_names || ' ' || p.last_name
+      FROM
+      pm_task_assignment a,
+      persons p
+      WHERE
+      task_id = :task_item_id and
+      a.party_id = p.person_id
+    </querytext>
+  </fullquery>
+
 </queryset>
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.2.24 -r1.4.2.25
--- openacs-4/contrib/packages/project-manager/tcl/task-procs.tcl	8 Oct 2004 21:25:04 -0000	1.4.2.24
+++ openacs-4/contrib/packages/project-manager/tcl/task-procs.tcl	26 Oct 2004 01:22:27 -0000	1.4.2.25
@@ -13,7 +13,24 @@
 namespace eval pm::task {}
 
 
+ad_proc -public pm::task::name {
+    {-task_item_id:required}
+} {
+    Returns the name of the task
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-25
+    
+    @param task_item_id
 
+    @return 
+    
+    @error -1
+} {
+    return [db_string get_name { } -default "-1"]
+}
+
+
 ad_proc -public pm::task::options_list {
     {-edit_p "f"}
     -project_item_id
@@ -81,7 +98,7 @@
       SELECT
         r.item_id, 
         r.title as task_title        
-	FROM
+        FROM
         pm_tasks_revisionsx r, 
         cr_items i,
         pm_tasks_active t
@@ -147,6 +164,173 @@
 }
 
 
+ad_proc -public pm::task::options_list_html {
+    {-edit_p "f"}
+    -project_item_id
+    {-task_item_id ""}
+    {-dependency_task_id ""}
+    {-dependency_task_ids ""}
+    {-number "0"}
+    {-depends_on_new ""}
+    {-current_number "0"}
+} {
+    Returns a list of options suiteable for HTML.
+    Contains a list of possible tasks that this task can
+    depend upon, or selected. These tasks are limited to just the
+    one project.
+
+    <p />
+
+    There is one special case that we handle: if you are creating new
+    tasks (not editing), you can have them depend on each other. 
+    So if you create two tasks at the same time, you may want task 
+    2 to depend on task 1. Instead of a task_item_id, we then 
+    specify a value of this form:
+
+    <blockquote>
+    numX
+    </blockquote>
+    
+    where X represents the number of the new task, ranging from 1
+    to n.
+
+    <p />
+
+    To be more efficient when creating multiple tasks at the same 
+    time, we should cache the database calls.
+
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-05-13
+    
+    @param edit_p Is this for a task being edited? Or a new task?
+
+    @param project_item_id The project we're finding tasks from
+
+    @param task_item_id The task ID. This is used because we do not 
+    want a task to depend on itself, so it is excluded from the list.
+
+    @param dependency_task_ids For edited tasks, the current task_ids
+    that it depends on. Used because sometimes it can be closed, and
+    it wouldn't otherwise appear on the list. This is a list.
+
+    @param dependency_task_id For edited tasks, the current task that
+    it depends on, used for setting the default option in HTML.
+
+    @param number When the list is returned, it includes entries for 
+    number new tasks, in the numX format described in these docs.
+
+    @param depends_on_new When you're using a process, you want the 
+    dependency to be on other new tasks. The format for this should
+    be num1 num2, etc.. So we make the default if this parameter is set.
+
+    @param current_number The current number. Used for new tasks. It
+    prevents allowing dependencies on the task being created.
+
+    @return 
+    
+    @error 
+} {
+
+    # get tasks this task can depend on 
+
+    if {[exists_and_not_null dependency_task_ids]} {
+       
+        set union_clause "
+     UNION
+      SELECT
+        r.item_id, 
+        r.title as task_title        
+	FROM
+        pm_tasks_revisionsx r, 
+        cr_items i,
+        pm_tasks_active t
+        WHERE
+        r.parent_id = :project_item_id and
+        r.revision_id = i.live_revision and
+        i.item_id = t.task_id
+        and t.task_id in ([join $dependency_task_ids ","])"
+    } else {
+        set union_clause ""
+    }
+
+    set keys [list]
+
+    db_foreach get_dependency_tasks { } {
+        set options($task_title) $item_id
+        lappend keys $task_title
+    } 
+
+    set keys [lsort $keys]
+
+    # ---------------------------------------------------------------
+    # Start setting up the output.
+    # These are for new tasks, the already created tasks are added to
+    # the list later.
+    # ---------------------------------------------------------------
+
+    set dependency_options_full "<option value=\"\">--None--</option> "
+
+    if {[string is false $edit_p]} {
+
+        # now set up dependency options
+
+        for {set j 1} {$j <= $number} {incr j} {
+
+            if {[string equal $depends_on_new $j]} {
+                set selected "selected=\"selected\" "
+            } else {
+                set selected ""
+            }
+
+            if {![string equal $current_number $j]} {
+                append dependency_options_full "<option ${selected}value=\"num$j\">New Task \#$j</option> "
+            }
+        }
+    }
+
+    # -------------------------------------------------
+    # Now add the tasks that are already in the project
+    # -------------------------------------------------
+
+
+    if {[string is true $edit_p]} {
+        foreach key $keys {
+
+            # for editing tasks, we skip ourselves (because depending on
+            # ourselves just sometimes isn't an option)
+            if {![string equal $task_item_id $options($key)]} {
+
+                if {[string equal $options($key) $dependency_task_id]} {
+                    set selected "selected=\"selected\" "
+                } else {
+                    set selected ""
+                }
+
+                # check for case when there is a quote in the name of
+                # a task. We have to filter this out, or we get an
+                # error. -- not sure what this comment is for -- JR
+                
+
+                append dependency_options_full "<option ${selected}value=\"$options($key)\">$key</option> "
+            }
+        }
+    } else {
+
+        foreach key $keys {
+            
+            # check for case when there is a quote in the name of a
+            # task. We have to filter this out, or we get an error. --
+            # not sure what this comment is for -- JR
+
+            append dependency_options_full "<option value=\"$options($key)\">$key</option> "
+        }
+    }
+
+
+    return $dependency_options_full
+}
+
+
 ad_proc -public pm::task::dependency_delete_all {
     -task_item_id:required
 
@@ -156,13 +340,14 @@
     @author Jade Rubick (jader@bread.com)
     @creation-date 2004-02-23
     
-    @param task_item_id The task we wish to remove the dependencies from
+    @param task_item_id The task we wish to remove the dependencies from.
 
     @return 
     
     @error 
 } {
-    db_dml delete_deps "delete from pm_task_dependency where task_id = :task_item_id"
+    db_dml delete_deps { }
+
     return 1
 }
 
@@ -214,16 +399,7 @@
     @error 
 } {
 
-    set project_tasks [db_list get_tasks "
-        SELECT
-        task.item_id as t_item_id
-        FROM
-        cr_items task,
-        cr_items project
-        WHERE 
-        task.parent_id = project.item_id and
-        project.item_id = :project_item_id
-    "]
+    set project_tasks [db_list get_tasks { }]
 
     # we do not allow tasks to depend on items outside of their
     # project. So if it's not in the list of tasks for that project,
@@ -239,15 +415,7 @@
     if {$loop_limit > 0} {
 
         set dep_list [list]
-        db_foreach get_dependencies "
-            SElECT
-            d.task_id as dep_task,
-            d.parent_task_id as dep_task_parent
-            FROM
-            pm_task_dependency d
-            WHERE
-            d.task_id in ([join $project_tasks ", "])
-        " {
+        db_foreach get_dependencies { } {
             lappend dep_list d-$dep_task-$dep_task_parent
         }
 
@@ -266,12 +434,11 @@
 
     }
 
-    
-    if {[string equal $valid_p "TRUE"]} {
+    if {[string is true $valid_p]} {
         # after it passes
         set dependency_id [db_nextval pm_task_dependency_seq]
         
-        db_dml insert_dep "insert into pm_task_dependency (dependency_id, task_id, parent_task_id, dependency_type) values (:dependency_id, :task_item_id, :parent_id, 'finish_before_start')"
+        db_dml insert_dep { }
         
     } else {
         ns_log Notice "Task dependency for $task_item_id on $parent_id was not added due to looping or being outside of the current project"
@@ -457,8 +624,6 @@
     -estimated_hours_work:required
     -estimated_hours_work_min:required
     -estimated_hours_work_max:required
-    -actual_hours_worked:required
-    {-status_id}
     -update_user:required
     -update_ip:required
     -package_id:required
@@ -491,10 +656,6 @@
 
     @param estimated_hours_work_max
 
-    @param actual_hours_worked The number of hours worked to date
-
-    @param status_id The code representing the status
-
     @param update_user The user updating the task
 
     @param update_ip The IP address of the request
@@ -505,56 +666,42 @@
     
     @error 
 } {
-    if {![exists_and_not_null status_id]} {
-        set status_id [pm::task::current_status \
-                           -task_item_id $task_item_id]
-    }
-
     # simple sanity check for min and max estimated hours
     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 {$percent_complete >= 100} {
 
-    # we have to update all logged hours to make sure the hours are
-    # updated whenever the project is changed.
+        set status_id [pm::task::default_status_closed]
 
-    set logger_project [pm::project::get_logger_project -project_item_id $project_item_id]
+    } elseif {$percent_complete < 100} {
 
-    db_dml update_logger_entries {
-        UPDATE 
-        logger_entries 
-        SET 
-        project_id = :logger_project 
-        WHERE 
-        entry_id in 
-        (select logger_entry from pm_task_logger_proj_map where task_item_id = :task_item_id)
+        set status_id [pm::task::default_status_open]
     }
 
-    # 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 \
-            -comment "$comment" \
-            -comment_type $comment_type
-    } else {
-        # does not need comment because that's done later
-        pm::task::open \
-            -task_item_id $task_item_id 
+    set actual_hours_worked [pm::task::update_hours \
+                                 -task_item_id $task_item_id]
 
-    }
+    set return_val [db_exec_plsql new_task_revision { *SQL }]
 
+    # we have to update all logged hours to make sure the hours are
+    # set to the correct project whenever the project is changed.
+
+    set logger_project [pm::project::get_logger_project -project_item_id $project_item_id]
+
+    db_dml update_logger_entries { }
+
     return $return_val
 }
 
 
 
 ad_proc -public pm::task::new {
     -project_id:required
-    -title:required
+    {-title "Subject missing"}
     {-description ""}
     {-mime_type "text/plain"}
     {-end_date ""}
@@ -564,10 +711,25 @@
     {-estimated_hours_work_max "0"}
     {-creation_date ""}
     {-status_id ""}
+    {-process_instance_id ""}
     -creation_user:required
     -creation_ip:required
     -package_id:required
 } {
+    Creates a new task. 
+
+    @param process_instance_id If a process was used to create the
+    task, then it is linked in to that process instance, so we can
+    things like display only tasks that are a part of a process.
+
+    @author Jade Rubick (jader@bread.com)
+    @creation-date who knows?
+
+    @return new task_item_id
+    
+    @error
+
+} {
     if {![exists_and_not_null status_id]} {
         set status_id [pm::task::default_status_open]
     }
@@ -578,9 +740,15 @@
         set estimated_hours_work_min $temp
     }
 
-    set return_val [db_exec_plsql new_task_item { *SQL }]
+    set task_revision [db_exec_plsql new_task_item { *SQL }]
+    set task_item_id  [pm::task::get_item_id \
+                           -task_id $task_revision]
 
-    return $return_val
+    if {$percent_complete >= 100} {
+        pm::task::close -task_item_id $task_item_id
+    }
+
+    return $task_item_id
 }
 
 
@@ -739,7 +907,7 @@
         select sum(le.value) from logger_entries le where entry_id in (select logger_entry from pm_task_logger_proj_map where task_item_id = :task_item_id) and le.variable_id = '[logger::variable::get_default_variable_id]'
     " -default "0"]
 
-    if {[string equal $update_tasks_p "t"]} {
+    if {[string is true $update_tasks_p]} {
 
         db_dml update_current_task "
         UPDATE
@@ -865,10 +1033,9 @@
 
 
 ad_proc -public pm::task::open {
-    -task_item_id:required
+    {-task_item_id:required}
 } {
-    Opens a task, and sends notifications, unless it was already 
-    open. If it was already open, does nothing.
+    Opens a task.
     
     @author Jade Rubick (jader@bread.com)
     @creation-date 2004-04-22
@@ -879,137 +1046,15 @@
     
     @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,
-    r.mime_type,
-    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 [pm::task::assignee_email_list -task_item_id $task_item_id]
-
-    if {[llength $assignees] > 0} {
-
-        set to_address $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 [pm::task::get_url $task_item_id]
-
-        set subject "Task Reopened (was $status_description): $task_title"
-
-        set notification_text "Task reopened, was $status_description\n\n"
-
-        set task_description [ad_html_text_convert -from $mime_type -to "text/html" -- $task_description]
-
-        append notification_text "
-<h3>Task overview</h3>
-<table border=\"0\" bgcolor=\"#ddddff\">
-  <tr>
-    <td>Subject:</td>
-    <td>$task_title (<a href=\"$task_url\">\#$task_item_id</a>)</td>
-  </tr>
-  <tr>
-    <td>Project:</td>
-    <td>$project_name</td>
-  </tr>
-</table>
-
-<h3>Description</h3>
-<table border=\"0\" bgcolor=\"\#ddddff\">
-  <tr>
-    <td>$task_description</td>
-  </tr>
-</table>
-
-<h3>Dates:</h3>
-<table border=\"0\" bgcolor=\"#ddddff\">
-  <tr>
-    <td>Latest start:</td>
-    <td>$latest_start</td>
-  </tr>
-  <tr>
-    <td>Latest finish</td>
-    <td><i>$latest_finish</i></td>
-  </tr>
-</table>
-"
-
-        pm::util::email \
-            -to_addr  $to_address \
-            -from_addr $from_address \
-            -subject $subject \
-            -body $notification_text \
-            -mime_type "text/html"
-    }
-
-    return
+    db_dml update_status { }
 }
 
 ad_proc -public pm::task::close {
-    -task_item_id:required
-    {-comment ""}
-    {-comment_type "text/plain"}
+    {-task_item_id:required}
 } {
-    Closes a task, and sends notifications, unless it was already 
-    closed. If it was already closed, does nothing.
+    Closes a task
     
     @author Jade Rubick (jader@bread.com)
     @creation-date 2004-04-22
@@ -1020,180 +1065,10 @@
     
     @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,
-    r.mime_type,
-    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"
+    db_dml update_status { }
 
-    # 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 [pm::task::assignee_email_list -task_item_id $task_item_id]
-
-    if {[llength $assignees] > 0} {
-
-        set to_address $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 "<h3>Work done on this task</h3><table border=\"0\" bgcolor=\"#ddddff\">"
-
-        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" {
-            append work_log "<tr>"
-
-            if {![string equal $time_stamp_pretty $last_time_stamp]} {
-                append work_log "<td>$time_stamp_pretty</td><td>"
-            } else {
-                append work_log "<td>&nbsp;</td><td>"
-            }
-            append work_log "$user_name</td><td>$description</td><td>$value hrs</td>\n"
-
-            set last_time_stamp $time_stamp_pretty
-
-            append work_log "</tr>"
-        }
-
-        append work_log "</table>"
-
-        set task_url [pm::task::get_url $task_item_id]
-
-        set subject "Task Closed (was $status_description) $task_title"
-
-        set notification_text "Task closed, was $status_description\n\n"
-
-        set task_description [ad_html_text_convert -from $mime_type -to "text/html" -- $task_description]
-
-        set comment [ad_html_text_convert -from $comment_type -to "text/html" -- $comment]
-
-        append notification_text "
-<h3>Comment:</h3>
-$comment
-
-<hr />
-<table border=\"0\" bgcolor=\"#ddddff\">
-  <tr>
-    <td>Subject:</td>
-    <td><a href=\"$task_url\">$task_title</a> (\#$task_item_id)</td>
-  </tr>
-  <tr>
-    <td>Project:</td>
-    <td>$project_name</td>
-  </tr>
-</table>
-"
-        append notification_text "$work_log"
-
-        append notification_text "
-<h3>Description</h3>
-<table border=\"0\" bgcolor=\"\#ddddff\">
-  <tr>
-    <td>$task_description</td>
-  </tr>
-</table>
-
-<h3>Dates:</h3>
-<table border=\"0\" bgcolor=\"#ddddff\">
-  <tr>
-    <td>Latest start:</td>
-    <td>$latest_start</td>
-  </tr>
-  <tr>
-    <td>Latest finish</td>
-    <td><i>$latest_finish</i></td>
-  </tr>
-</table>
-"
-
-        
-        pm::util::email \
-            -to_addr  $to_address \
-            -from_addr $from_address \
-            -subject $subject \
-            -body $notification_text \
-            -mime_type "text/html"
-    }
-
-    return
 }
 
 
@@ -1452,26 +1327,9 @@
 
 ad_proc -public pm::task::email_alert {
     -task_item_id:required
-    {-user_id ""}
-    {-assignee_id ""}
-    {-assignee_role_name ""}
     {-edit_p "t"}
     {-comment ""}
     {-comment_mime_type "text/plain"}
-    {-description ""}
-    {-description_mime_type "text/plain"}
-    {-old_description ""}
-    {-old_description_mime_type "text/plain"}
-    {-subject ""}
-    {-work ""}
-    {-work_min ""}
-    {-work_max ""}
-    {-project_name ""}
-    {-earliest_start ""}
-    {-earliest_finish ""}
-    {-latest_start ""}
-    {-latest_finish ""}
-    {-url ""}
 } {
     Sends out an email notification when changes have been made to a task
 
@@ -1486,168 +1344,108 @@
     
     @param task_item_id
 
-    @param user_id The user making the change
-
-    @param assignee_id The party_id of the user assigned to the task.
-
-    @param assignee_role_name The role name for what the party is
-    assigned to do
-
     @param edit_p Is this an edited task, or a new one? t for edited,
     f otherwise.
 
-    @param description
-
-    @param old_description
-
-    @param subject The one line description of the task
-
-    @param work Estimated hours work
-
-    @param work_min Estimated minimimum hours work
-
-    @param work_max Estimated maximum hours work
-
-    @param project_name
-
-    @param earliest_start
-
-    @param earliest_finish
-
-    @param latest_start
-
-    @param latest_finish
-
-    @param url Optionally, a URL that the user is directed to
-
     @return 
     
     @error 
 } {
 
-    set task_term       [parameter::get -parameter "Taskname"    -default "Task"]
-    set task_term_lower [parameter::get -parameter "taskname"    -default "task"]
-    set use_uncertain_completion_times_p [parameter::get -parameter "UseUncertainCompletionTimesP" -default "0"]
+    set task_term       \
+        [parameter::get -parameter "Taskname" -default "Task"]
+    set task_term_lower \
+        [parameter::get -parameter "taskname" -default "task"]
+    set use_uncertain_completion_times_p \
+        [parameter::get -parameter "UseUncertainCompletionTimesP" -default "0"]
 
-    # from address
+    set user_id [ad_conn user_id]
 
-    if {![exists_and_not_null $user_id]} {
-        set user_id [ad_conn user_id]
-    }
+    db_1row get_from_address_and_more { }
 
-    db_1row get_from_address_and_more {
-        SELECT 
-        p.email as from_address,
-        p2.first_names || ' ' || p2.last_name as mod_username
-        FROM 
-        parties p,
-        persons p2
-        WHERE
-        p.party_id = :user_id and
-        p.party_id = p2.person_id
-    }
+    db_1row get_task_info { }
 
-    # to address
+    if {[string is true $edit_p]} {
 
-    if {![exists_and_not_null assignee_id]} {
+        # ----
+        # EDIT
+        # ----
 
-        # bug: we should get the list of assignees here.
-        ns_log Error "the proc pm::task::email_alert is not complete: assignee"
-        
-    }
+        set subject_out "Edited $task_term \#$task_item_id: $subject"
+        set intro_text "$mod_username edited this $task_term_lower"
 
-    set to_address [db_string get_email "select email from parties where party_id = :assignee_id"]
 
+    } else {
 
-    # if they left out any of the task info, then we get it from the database
-    if { \
-             ![exists_and_not_null subject] || \
-             ![exists_and_not_null work] || \
-             ![exists_and_not_null work_min] || \
-             ![exists_and_not_null work_max] || \
-             ![exists_and_not_null project_name] || \
-             ![exists_and_not_null earliest_start] || \
-             ![exists_and_not_null earliest_finish] || \
-             ![exists_and_not_null latest_start] || \
-             ![exists_and_not_null latest_finish] \
-         } {
-        
-        db_1row get_task_info {
-            SELECT
-            t.title as subject,
-            to_char(t.earliest_start,'MM-DD-YYYY') as earliest_start,
-            to_char(t.earliest_finish,'MM-DD-YYYY') as earliest_finish,
-            to_char(t.latest_start,'MM-DD-YYYY') as latest_start,
-            to_char(t.latest_finish,'MM-DD-YYYY') as latest_finish,
-            t.estimated_hours_work as work,
-            t.estimated_hours_work_min as work_min,
-            t.estimated_hours_work_max as work_max,
-            t.percent_complete,
-            p.title as project_name
-            FROM
-            pm_tasks_revisionsx t, 
-            cr_items i,
-            cr_items project,
-            pm_projectsx p
-            WHERE
-            t.item_id = :task_item_id and
-            t.revision_id = i.live_revision and
-            t.item_id = i.item_id and
-            t.parent_id = project.item_id and
-            project.item_id = p.item_id and
-            project.live_revision = p.revision_id
-        }
+        # ---
+        # NEW
+        # ---
 
-    }
-
-
-    if {[string is true $edit_p]} {
-        set subject_out "Edited $task_term \#$task_item_id: $subject"
-        set intro_text "$mod_username edited this $task_term_lower"
-    } else {
         set subject_out "New $task_term \#$task_item_id: $subject"
         set intro_text "$mod_username assigned you to a new $task_term_lower"
+
     }
+
     
     if {[empty_string_p $comment]} {
         set comment_text ""
     } else {
         set comment_text "<h3>Comment:</h3>$comment<p />"
     }
-    
-    if {[exists_and_not_null url]} {
-        set task_url $url
-    } else {
-        set task_url "unavailable"
-    }
 
+    set url [pm::task::get_url $task_item_id]
+    
     set description [ad_html_text_convert -from $description_mime_type -to "text/html" -- $description]
-    set old_description [ad_html_text_convert -from $old_description_mime_type -to "text/html" -- $old_description]
 
-    if {![string equal $description $old_description] && [string is true $edit_p]} {
-        set description_out "$description <h3>Old description:</h3>$old_description"
+    set description_out $description
 
+    set assignees [db_list_of_lists get_assignees { }]
+
+    if {[exists_and_not_null $process_instance]} {
+
+        set process_url [pm::process::url \
+                             -process_instance_id $process_instance \
+                             -project_item_id $project_item_id]
+
+        set process_description [pm::process::name \
+                                     -process_instance_id $process_instance]
+
+        set process_html "
+<h3>Process</h3>
+<table border=\"0\" bgcolor=\"\#ddddff\">
+  <tr>
+    <td><a href=\"$process_url\">$process_description</a></td>
+  </tr>
+</table>
+"
     } else {
-        set description_out $description
+        set process_html ""
     }
 
-    set notification_text "${intro_text}${comment_text}
+    foreach ass $assignees {
+
+        set to_address [lindex $ass 0]
+        set role       [lindex $ass 1]
+        set is_lead_p  [lindex $ass 2]
+
+        set notification_text "${intro_text}${comment_text}
 <h3>Task overview</h3>
 <table border=\"0\" bgcolor=\"#ddddff\">
   <tr>
     <td>Subject:</td>
-    <td><a href=\"${task_url}\">$subject</a> (\#$task_item_id)</td>
+    <td><a href=\"${url}\">$subject</a> (\#$task_item_id)</td>
   </tr>
   <tr>
     <td>Project:</td>
     <td>$project_name</td>
   </tr>
   <tr>
     <td>Your role:</td>
-    <td>$assignee_role_name</td>
+    <td>$role</td>
   </tr>
 </table>
 
+$process_html
 <h3>Description</h3>
 <table border=\"0\" bgcolor=\"\#ddddff\">
   <tr>
@@ -1667,13 +1465,13 @@
   </tr>
 </table>"
 
-    pm::util::email \
-        -to_addr  $to_address \
-        -from_addr $from_address \
-        -subject $subject_out \
-        -body $notification_text \
-        -mime_type "text/html"
-    
+        pm::util::email \
+            -to_addr  $to_address \
+            -from_addr $from_address \
+            -subject $subject_out \
+            -body $notification_text \
+            -mime_type "text/html"
+    }
 }
 
 
@@ -1894,38 +1692,121 @@
 }
 
 
-ad_proc -public pm::task::process_task_info {
-    {-process_id:required}
-    {-one_line_array:required}
-    {-description_array:required}
-    {-estimated_hours_work_array:required}
-    {-estimated_hours_work_min_array:required}
-    {-estimated_hours_work_max_array:required}
-    {-dependency_array:required}
-    {-tasks_list:required}
+
+ad_proc -public pm::task::assignee_html {
+    {-number:required}
+    {-process_task_id ""}
+    {-task_item_id ""}
 } {
-    Sets a bunch of information in a set of arrays on all
-    process tasks for a given process
+    Assignee HTML for new tasks
     
     @author Jade Rubick (jader@bread.com)
-    @creation-date 2004-09-23
+    @creation-date 2004-10-13
     
-    @param process_id
+    @return 
+    
+    @error 
+} {
 
-    @param one_line_array
+    # ------------------------------
+    # cache these to speed it all up
 
-    @param description_array
+    set roles_list_of_lists    [pm::role::select_list_filter]
+    set assignee_list_of_lists [pm::util::subsite_assignees_list_of_lists]
 
-    @param estimated_hours_work_array
 
-    @param estimated_hours_work_min_array
+    # Get assignments for when using processes
+    if {[exists_and_not_null process_task_id]} {
 
-    @param estimated_hours_work_max_array
+        # PROCESS
 
-    @param dependency_array
+        set task_assignee_list_of_lists \
+            [pm::process::task_assignee_role_list \
+                 -process_task_id $process_task_id]
 
-    @param tasks_list
+    } elseif {[exists_and_not_null task_item_id]} {
 
+        # EDITING
+
+        set task_assignee_list_of_lists \
+            [pm::task::assignee_role_list \
+                 -task_item_id $task_item_id]
+
+    } else {
+
+        # NEW
+
+        set task_assignee_list_of_lists [list]
+    }
+
+    # Get assignments for when editing
+
+            
+    set html "<table border=\"0\">"
+
+    foreach role_list $roles_list_of_lists {
+        
+        set role_name [lindex $role_list 0]
+        set role      [lindex $role_list 1]
+        
+        append html "
+        <td align=\"left\" valign=\"top\"><p /><B><I>$role_name</I></B><p />"
+        
+        foreach assignee_list $assignee_list_of_lists {
+            set name      [lindex $assignee_list 0]
+            set person_id [lindex $assignee_list 1]
+
+            if {[lsearch $task_assignee_list_of_lists [list $person_id $role]] >= 0} {
+
+                append html "
+                <input name=\"assignee\" value=\"$number-$person_id-$role\" type=\"checkbox\" checked=\"checked\" /><span class=\"selected\">$name</span>
+                <br />"
+
+            } else {
+
+                append html "
+                <input name=\"assignee\" value=\"$number-$person_id-$role\" type=\"checkbox\" />$name
+                <br />"
+            }
+                        
+        }
+        
+        append html "</td>"
+
+    }
+
+    append html "</table>"
+
+    return $html
+}
+
+
+ad_proc -public pm::task::get {
+    {-tasks_item_id:required}
+    {-one_line_array:required}
+    {-description_array:required}
+    {-description_mime_type_array:required}
+    {-estimated_hours_work_array:required}
+    {-estimated_hours_work_min_array:required}
+    {-estimated_hours_work_max_array:required}
+    {-dependency_array:required}
+    {-percent_complete_array:required}
+    {-end_date_day_array:required}
+    {-end_date_month_array:required}
+    {-end_date_year_array:required}
+    {-project_item_id_array:required}
+} {
+    Stuff information about tasks into several arrays
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-14
+    
+    @param tasks_item_id a list of tasks to retrieve and stuff in
+    arrays
+
+    @param one_line_array stuff one_line info in 
+    one_line_array(task_item_id)
+
     @return 
     
     @error 
@@ -1934,34 +1815,411 @@
     # set variables in calling environment, using names passed in
     upvar 1 $one_line_array                 one_line_arr
     upvar 1 $description_array              description_arr
+    upvar 1 $description_mime_type_array    description_mime_type_arr
     upvar 1 $estimated_hours_work_array     estimated_hours_work_arr
     upvar 1 $estimated_hours_work_min_array estimated_hours_work_min_arr
     upvar 1 $estimated_hours_work_max_array estimated_hours_work_max_arr
     upvar 1 $dependency_array               dependency_arr
-    upvar 1 $tasks_list                     process_tasks_l
+    upvar 1 $percent_complete_array         percent_complete_arr
+    upvar 1 $end_date_day_array             end_date_day_arr
+    upvar 1 $end_date_month_array           end_date_month_arr
+    upvar 1 $end_date_year_array            end_date_year_arr
+    upvar 1 $project_item_id_array          project_item_id_arr
 
-    db_foreach get_process_tasks { } {
-        set one_line_arr($process_tid)                 $one_line
-        set description_arr($process_tid)              $description
-        set estimated_hours_work_arr($process_tid)     $estimated_hours_work
-        set estimated_hours_work_min_arr($process_tid) $estimated_hours_work_min
-        set estimated_hours_work_max_arr($process_tid) $estimated_hours_work_max
-        set dependency_arr($process_tid)               $process_parent_task
+    set task_where_clause " and i.item_id in ([join $tasks_item_id ", "])"
 
+    db_foreach get_tasks { } {
+        set one_line_arr($tid)                 $one_line
+        set description_arr($tid)              $description
+        set description_mime_type_arr($tid)    $description_mime_type
+        set estimated_hours_work_arr($tid)     $estimated_hours_work
+        set estimated_hours_work_min_arr($tid) $estimated_hours_work_min
+        set estimated_hours_work_max_arr($tid) $estimated_hours_work_max
+        set dependency_arr($tid)               $parent_task_id
+        set percent_complete_arr($tid)         $percent_complete
+        set end_date_day_arr($tid)             $end_date_day
+        set end_date_month_arr($tid)           $end_date_month
+        set end_date_year_arr($tid)            $end_date_year
+        set project_item_id_arr($tid)          $project
+
         # make sure that we don't have empty values for estimated
         # hours work
-        if {[empty_string_p $estimated_hours_work_arr($process_tid)]} {
-            set estimated_hours_work_arr($process_tid) 0
+        if {[empty_string_p $estimated_hours_work_arr($tid)]} {
+            set estimated_hours_work_arr($tid) 0
         }
-        if {[empty_string_p $estimated_hours_work_min_arr($process_tid)]} {
-            set estimated_hours_work_min_arr($process_tid) 0
+        if {[empty_string_p $estimated_hours_work_min_arr($tid)]} {
+            set estimated_hours_work_min_arr($tid) 0
         }
-        if {[empty_string_p $estimated_hours_work_max_arr($process_tid)]} {
-            set estimated_hours_work_max_arr($process_tid) 0
+        if {[empty_string_p $estimated_hours_work_max_arr($tid)]} {
+            set estimated_hours_work_max_arr($tid) 0
         }
 
+    }
+    
+}
 
-        lappend process_tasks_l $process_tid
+
+ad_proc -public pm::task::date_html {
+    {-selected_month ""}
+    {-selected_day ""}
+    {-selected_year ""}
+} {
+    Returns HTML for the date widget in the task-add-edit page
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-15
+    
+    @return 
+    
+    @error 
+} {
+    for {set i 1} {$i <= 12} {incr i} {
+
+        # numbers are in the form of 01 - 12
+        if {$i < 10} {
+            set j "0$i"
+        } else {
+            set j $i
+        }
+        set selected_[set j] ""
     }
+    set selected_[set selected_month] "selected=\"selected\""
+
+    return "
+        <table border=\"0\" cellpadding=\"0\" cellspacing=\"2\">
+          <tr>
+            <td nowrap=\"nowrap\">
+              <select name=\"end_date_month\" >
+                <option value=\"\">--</option>
+                <option $selected_01 value=\"1\">January</option>
+                <option $selected_02 value=\"2\">February</option>
+                <option $selected_03 value=\"3\">March</option>
+                <option $selected_04 value=\"4\">April</option>
+                <option $selected_05 value=\"5\">May</option>
+                <option $selected_06 value=\"6\">June</option>
+                <option $selected_07 value=\"7\">July</option>
+                <option $selected_08 value=\"8\">August</option>
+                <option $selected_09 value=\"9\">September</option>
+                <option $selected_10 value=\"10\">October</option>
+                <option $selected_11 value=\"11\">November</option>
+                <option $selected_12 value=\"12\">December</option>
+              </select>&nbsp;</td>
+            <td nowrap=\"nowrap\">
+              <input type=\"text\" name=\"end_date_day\" size=\"2\" value=\"$selected_day\" />
+            </td>
+            <td nowrap=\"nowrap\"><input type=\"text\" name=\"end_date_year\" size=\"4\" maxlength=\"4\" value=\"$selected_year\" />
+            </td>
+          </tr>
+          <tr>
+            <td nowrap=\"nowrap\" align=\"center\">
+              <font size=\"-2\">Month</font>
+            </td>
+            <td nowrap=\"nowrap\" align=\"center\">
+              <font size=\"-2\">Day</font>
+            </td>
+            <td nowrap=\"nowrap\" align=\"center\">
+              <font size=\"-2\">Year</font>
+            </td>
+          </tr>
+        </table>"
+
+}
+
+
+ad_proc -public pm::task::get_assignee_names {
+    {-task_item_id:required}
+} {
+    Returns a list of assignees to a task (first name + last name)
     
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-20
+    
+    @param task_item_id
+
+    @return 
+    
+    @error 
+} {
+
+    return [db_list get_assignees { }]
+
 }
+
+
+ad_proc -public pm::task::assignee_role_list {
+    {-task_item_id:required}
+} {
+    Returns a list of lists, with all assignees to a particular 
+    task. {{party_id role_id} {party_id role_id}}
+
+    Todo: dependency changes, deadline changes
+    
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-18
+    
+    @param task_item_id
+
+    @return 
+    
+    @error 
+} {
+
+    return [db_list_of_lists get_assignees_roles { }]
+
+}
+
+
+ad_proc -public pm::task::what_changed {
+    {-comments_array:required}
+    {-comments_mime_type_array:required}
+    {-task_item_id_array:required}
+    {-number:required}
+    {-old_one_line_array:required}
+    {-old_description_array:required}
+    {-old_description_mime_type_array:required}
+    {-old_estimated_hours_work_array:required}
+    {-old_estimated_hours_work_min_array:required}
+    {-old_estimated_hours_work_max_array:required}
+    {-old_dependency_array:required}
+    {-old_percent_complete_array:required}
+    {-old_end_date_day_array:required}
+    {-old_end_date_month_array:required}
+    {-old_end_date_year_array:required}
+    {-old_project_item_id_array:required}
+    {-old_assignees_array:required}
+} {
+    Compares how a task was and how it currently is, and
+    adds to the comments array a list of changes.
+
+    @author Jade Rubick (jader@bread.com)
+    @creation-date 2004-10-20
+    
+    @param comments_array
+
+    @param comments_mime_type_array
+
+    @param task_item_id_array an array of task_item_ids, with keys
+    based on number
+
+    @param number the keys to the task_item_id array, a list of integers
+
+    @param old_one_line_array
+
+    @param old_description_array
+
+    @param old_description_mime_type_array
+
+    @param old_estimated_hours_work_array
+
+    @param old_estimated_hours_work_min_array
+
+    @param old_estimated_hours_work_max_array
+
+    @param old_dependency_array
+
+    @param old_percent_complete_array
+
+    @param old_assignees_array
+
+    @return 
+    
+    @error 
+} {
+
+    # we will append the changes to these arrays and convert them to
+    # text/html format
+    upvar 1 $comments_array             comments_arr
+    upvar 1 $comments_mime_type_array   comments_mime_type_arr
+
+    upvar 1 $task_item_id_array                 task_item_id
+    upvar 1 $old_one_line_array                 old_one_line_arr
+    upvar 1 $old_description_array              old_description_arr
+    upvar 1 $old_description_mime_type_array    old_description_mime_type_arr
+    upvar 1 $old_estimated_hours_work_array     old_estimated_hours_work_arr
+    upvar 1 $old_estimated_hours_work_min_array old_estimated_hours_work_min_arr
+    upvar 1 $old_estimated_hours_work_max_array old_estimated_hours_work_max_arr
+    upvar 1 $old_percent_complete_array           old_percent_complete_arr
+    upvar 1 $old_end_date_day_array               old_end_date_day_arr
+    upvar 1 $old_end_date_month_array             old_end_date_month_arr
+    upvar 1 $old_end_date_year_array              old_end_date_year_arr
+    upvar 1 $old_project_item_id_array            old_project_item_id_arr
+    upvar 1 $old_assignees_array                  old_assignees_arr
+    upvar 1 $old_dependency_array                 old_dependency_arr
+
+    set use_uncertain_completion_times_p [parameter::get -parameter "UseUncertainCompletionTimesP" -default "1"]
+
+    # get the new task values
+    set tasks_item_id [list]
+    foreach num $number {
+        lappend tasks_item_id $task_item_id($num)
+    }
+
+    pm::task::get \
+        -tasks_item_id                  $tasks_item_id \
+        -one_line_array                  one_line_array \
+        -description_array               description_array \
+        -description_mime_type_array     description_mime_type_array \
+        -estimated_hours_work_array      estimated_hours_work_array \
+        -estimated_hours_work_min_array  estimated_hours_work_min_array \
+        -estimated_hours_work_max_array  estimated_hours_work_max_array \
+        -dependency_array                dependency_array \
+        -percent_complete_array          percent_complete_array \
+        -end_date_day_array              end_date_day_array \
+        -end_date_month_array            end_date_month_array \
+        -end_date_year_array             end_date_year_array \
+        -project_item_id_array           project_item_id_array 
+
+    
+    foreach num $number {
+
+        set changes [list]
+
+        set tid  $task_item_id($num)
+
+        set old $old_percent_complete_arr($tid)
+        set new $percent_complete_array($tid)
+
+        if {![string equal $old $new]} {
+
+            if {$new >= 100 && $old < 100} {
+
+                lappend changes "<b>Closing task</b>"
+
+            } elseif {$new < 100 && $old >= 100} {
+
+                lappend changes "<b>Reopening task</b>"
+
+            } else {
+                lappend changes "Percent complete changed <i>from</i> $old%<i>to</i> $new%"
+            }
+
+        }
+
+        # end date
+        if { \
+                 ![string equal $old_end_date_day_arr($tid) $end_date_day_array($tid)] || \
+                 ![string equal $old_end_date_month_arr($tid) $end_date_month_array($tid)] || \
+                 ![string equal $old_end_date_year_arr($tid) $end_date_year_array($tid)]} {
+
+            # internationalize the dates
+            set iso_date_old "$old_end_date_year_arr($tid)-$old_end_date_month_arr($tid)-$old_end_date_day_arr($tid) 00:00:00"
+            set iso_date_new "$end_date_year_array($tid)-$end_date_month_array($tid)-$end_date_day_array($tid) 00:00:00"
+
+            if {[string equal $iso_date_old "-- 00:00:00"]} {
+                set date_old "no hard deadline"
+            } else {
+                set date_old [lc_time_fmt $iso_date_old "%x"]
+            }
+
+            if {[string equal $iso_date_new "-- 00:00:00"]} {
+                set date_new "no hard deadline"
+            } else {
+                set date_new [lc_time_fmt $iso_date_new "%x"]
+            }
+
+            lappend changes "Hard deadline changed <i>from</i> $date_old <i>to</i> <b>$date_new</b>"
+        }
+
+        # one_line
+        if {![string equal $old_one_line_arr($tid) $one_line_array($tid)]} {
+            lappend changes "Subject changed <i>from</i> $old_one_line_arr($tid)  <i>to</i> $one_line_array($tid)"
+        }
+
+        # description
+        if { \
+                 ![string equal $old_description_arr($tid) $description_array($tid)] || \
+                 ![string equal $old_description_mime_type_arr($tid) $description_mime_type_array($tid)]} {
+
+            set richtext_list [list $old_description_arr($tid) $old_description_mime_type_arr($tid)]
+            set old_description_html [template::util::richtext::get_property html_value $richtext_list]
+            set richtext_list [list $description_array($tid) $description_mime_type_array($tid)]
+            set new_description_html [template::util::richtext::get_property html_value $richtext_list]
+
+            lappend changes "Description changed from<br />$old_description_html <i>(see below for new description)</i>"
+        }
+
+        # estimated_hours_work
+        if {[string is true $use_uncertain_completion_times_p]} {
+
+            if {![string equal $old_estimated_hours_work_min_arr($tid) $estimated_hours_work_min_array($tid)]} {
+                lappend changes "Work estimate (min) changed <i>from</i> $old_estimated_hours_work_min_arr($tid) <i>to</i> $estimated_hours_work_min_array($tid) hrs"
+            }
+
+            if {![string equal $old_estimated_hours_work_max_arr($tid) $estimated_hours_work_max_array($tid)]} {
+                lappend changes "Work estimate (max) changed <i>from</i> $old_estimated_hours_work_max_arr($tid) <i>to</i> $estimated_hours_work_max_array($tid) hrs"
+            }
+        } else {
+
+            if {![string equal $old_estimated_hours_work_arr($tid) $estimated_hours_work_array($tid)]} {
+                lappend changes "Work estimate (min) changed <i>from</i> $old_estimated_hours_work_arr($tid) <i>to</i> $estimated_hours_work_array($tid) hrs"
+            }
+
+        }
+
+        set new_assignees [pm::task::get_assignee_names \
+                               -task_item_id $task_item_id($num)]
+
+        # check for assignees that have been added
+
+        foreach new $new_assignees {
+            if { [lsearch $old_assignees_arr($tid) $new] == -1} {
+                lappend changes "Added: $new"
+            }
+        }
+
+        # check for assignees that have been removed
+        foreach old $old_assignees_arr($tid) {
+            if { [lsearch $new_assignees $old] == -1} {
+                lappend changes "Removed: $old"
+            }
+        }
+
+        # project
+
+        if {![string equal $old_project_item_id_arr($tid) $project_item_id_array($tid)]} {
+
+            set old [pm::project::name -project_item_id $old_project_item_id_arr($tid)]
+
+            lappend changes "Project changed <i>from</i> $old"
+
+        }
+
+        # dependency
+        if {![string equal $old_dependency_arr($tid) $dependency_array($tid)]} {
+
+            if {[empty_string_p $old_dependency_arr($tid)]} {
+                set old "Nothing"
+            } else {
+                set old [pm::task::name \
+                             -task_item_id $old_dependency_arr($tid)]
+            }
+
+            if {[empty_string_p $dependency_array($tid)]} {
+                set new "Nothing" 
+            } else {
+                set new [pm::task::name \
+                             -task_item_id $dependency_array($tid)]
+            }
+
+            lappend changes "Dependency changed <i>from</i> $old ($old_dependency_arr($tid))  <i>to</i> $new ($dependency_array($tid))"
+        }
+
+
+        # convert comments to richtext
+        set richtext_list [list $comments_arr($num) $comments_mime_type_arr($num)]
+        set comment_html [template::util::richtext::get_property html_value $richtext_list]
+
+
+        # add in changes
+
+        if {[llength $changes] > 0} {
+            append comment_html "<ul><li>[join $changes "<li>"]</ul>"
+
+            set comments_arr($num)           $comment_html
+            set comments_mime_type_arr($num) "text/html"
+        }
+        
+    }
+
+    
+}