Index: openacs-4/contrib/packages/simulation/tcl/template-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/contrib/packages/simulation/tcl/Attic/template-procs.tcl,v diff -u -r1.68 -r1.69 --- openacs-4/contrib/packages/simulation/tcl/template-procs.tcl 15 Mar 2004 17:22:19 -0000 1.68 +++ openacs-4/contrib/packages/simulation/tcl/template-procs.tcl 20 Apr 2004 21:12:23 -0000 1.69 @@ -74,6 +74,7 @@ sim_type suggested_duration enroll_type casting_type enroll_start enroll_end send_start_note_date case_start case_end + show_states_p stylesheet } { if { [info exists row($attr)] } { set varname attr_$attr @@ -483,10 +484,20 @@ {-admin:boolean} } { Enroll a user in a simulation. Sends out an email to the user for casting type - open and group. Creates a SimPlay message notification for the user. + open and group. Creates a SimPlay message notification for the user. Note: this proc + will perform a check of whether the user is already enrolled and will do nothing if + that is the case. @author Peter Marklund } { + if { [simulation::template::user_enrolled_p \ + -workflow_id $workflow_id \ + -user_id $user_id] } { + # Attempting to enroll an already enrolled user would throw a unique constraint error. + # It is better for the caller if we just return. + return + } + if { ![empty_string_p $simulation_array] } { upvar $simulation_array sim_template } else { @@ -511,21 +522,26 @@ if { [string equal $sim_template(casting_type) "open"] || [string equal $sim_template(casting_type) "group"] } { # Notify users that they are enrolled and can do their casting - set subject "You have been enrolled in simulation $sim_template(pretty_name)" + set subject [_ simulation.enrollment_notification_email_subject] set package_id [ad_conn package_id] set casting_page_url \ [export_vars -base "[ad_url][apm_package_url_from_id $package_id]simplay/cast" { workflow_id }] - set body "Dear $user_name, -This is to notify you that you have been enrolled in simulation $sim_template(pretty_name). You may visit the -casting page at ${casting_page_url} to choose case or role. -" + set body [_ simulation.enrollment_notification_email_body] acs_mail_lite::send \ -to_addr $email \ -from_addr [ad_system_owner] \ -subject $subject\ -body $body } + + # Sign up the user for email notification of received messages + notification::request::new \ + -type_id [notification::type::get_type_id -short_name [simulation::notification::message::type_short_name]] \ + -user_id $user_id \ + -object_id [ad_conn package_id] \ + -interval_id [notification::get_interval_id -name "instant"] \ + -delivery_method_id [notification::get_delivery_method_id -name "email"] if { $admin_p } { # Notify admin of all activity in the workflow. In particular this includes timed out tasks. @@ -537,13 +553,6 @@ -delivery_method_id [notification::get_delivery_method_id -name "email"] } else { - # Sign up the user for email notification of received messages - notification::request::new \ - -type_id [notification::type::get_type_id -short_name [simulation::notification::message::type_short_name]] \ - -user_id $user_id \ - -object_id [ad_conn package_id] \ - -interval_id [notification::get_interval_id -name "instant"] \ - -delivery_method_id [notification::get_delivery_method_id -name "email"] # Sign up the user for email notification of assigned tasks notification::request::new \ @@ -565,6 +574,7 @@ } { simulation::template::get -workflow_id $workflow_id -array sim_template + set admin_user_id [ad_conn user_id] set enroll_user_list [list] set invite_email_list [list] db_foreach select_enrolled_and_invited_users { @@ -580,6 +590,7 @@ and pamm.party_id = spsm.party_id and pamm.member_id = cu.user_id and pamm.party_id <> pamm.member_id + and pamm.member_id <> :admin_user_id } { if { [string equal $type "auto_enroll"] } { # enroll the user automatically @@ -590,7 +601,6 @@ } } # Always enroll the admin creating the simulation - set admin_user_id [ad_conn user_id] acs_user::get -user_id $admin_user_id -array admin_user simulation::template::enroll_user \ -admin \ @@ -618,9 +628,8 @@ set package_id [ad_conn package_id] set enrollment_page_url \ [export_vars -base "[ad_url][apm_package_url_from_id $package_id]simplay/enroll" { workflow_id }] - set subject "You have been invited to join simulation $sim_template(pretty_name)" - set body "Dear $user_name, -You have been invited to join simulation $sim_template(pretty_name). Please visit the enrollment page at $enrollment_page_url to accept the invitation. Thank you!" + set subject [_ simulation.invitation_email_subject] + set body [_ simulation.invitation_email_body] acs_mail_lite::send \ -to_addr $email \ -from_addr [ad_system_owner] \ @@ -680,13 +689,9 @@ set simulation_start_date [lindex $row 5] set simulation_description [lindex $row 6] - set subject "Simulation $simulation_name starts on $simulation_start_date" - set body "Dear $user_name, -this email is sent to you as a reminder that you are participating in simulation $simulation_name that will start on $simulation_start_date. Here is the -simulation description: + set subject [_ simulation.reminder_email_subject] + set body [_ simulation.reminder_email_body] -$simulation_description" - acs_mail_lite::send \ -to_addr $email \ -from_addr [ad_system_owner] \ @@ -746,9 +751,8 @@ set package_id [ad_conn package_id] set simplay_url \ [export_vars -base "[ad_url][apm_package_url_from_id $package_id]simplay/enroll" { workflow_id }] - set subject "Simulation $simulation(pretty_name) has started" - set body "Dear $user_name, -Simulation $simulation(pretty_name) has now started. Please visit $simplay_url to participate. Thank you!" + set subject [_ simulation.simulation_started_email_subject] + set body [_ simulation.simulation_started_email_body] acs_mail_lite::send \ -to_addr $email \ @@ -778,9 +782,14 @@ @author Peter Marklund } { - # Get the list of all enrolled and uncast users + # The admin is a special user who is only cast in roles that cannot be filled with non-admin users + # We don't include the admin user in the users to cast lists + set admin_user_id [admin_user_id -workflow_id $workflow_id] + + # Get the list of all enrolled and uncast users in random order set users_to_cast [db_list users_to_cast { - select distinct spsm.party_id + select q.party_id + from (select distinct spsm.party_id from sim_party_sim_map spsm where spsm.simulation_id = :workflow_id and spsm.type = 'enrolled' @@ -791,12 +800,15 @@ and wcrpm.case_id = wc.case_id and wc.workflow_id = :workflow_id ) + and spsm.party_id <> :admin_user_id) q + order by random() }] # Get the subset of enrolled and uncast users that are not in any of - # the role groups + # the role groups (in random order) set users_to_cast_not_in_groups [db_list users_to_cast_not_in_groups { - select distinct spsm.party_id + select q.party_id + from (select distinct spsm.party_id from sim_party_sim_map spsm where spsm.simulation_id = :workflow_id and spsm.type = 'enrolled' @@ -816,6 +828,8 @@ and srpm.party_id = pamm.party_id and pamm.member_id = spsm.party_id ) + and spsm.party_id <> :admin_user_id) q + order by random() }] # Get the users in all of the role groups. Also get the short names of all of the roles @@ -833,27 +847,40 @@ if { ![info exists group_members($group_id)] } { # Only select enrolled users from the group set group_members($group_id) [db_list select_enrolled_group_members { - select pamm.member_id + select q.member_id from + (select distinct pamm.member_id from party_approved_member_map pamm, users u, sim_party_sim_map spsm where pamm.party_id = :group_id and pamm.member_id = u.user_id and spsm.simulation_id = :workflow_id and spsm.party_id = u.user_id - and spsm.type = 'enrolled' + and spsm.type = 'enrolled' ) q + order by random() }] - set group_members($group_id) [util::randomize_list $group_members($group_id)] } } } + # We need to refill (re-initialize) the groups that should be in multiple (all) cases, so + # keep the original group member array around + array set full_group_members [array get group_members] + set multiple_case_groups [db_list select_multiple_case_groups { + select party_id + from sim_party_sim_map + where simulation_id = :workflow_id + and (type = 'invited' or type = 'auto_enroll') + and multiple_cases_p = 't' + }] + #ns_log Notice "simulation::template::cast workflow_id=$workflow_id - initialized variables users_to_cast=$users_to_cast users_to_cast_not_in_groups=$users_to_cast_not_in_groups group_members=[array get group_members] roles=[array get roles] multiple_case_groups=$multiple_case_groups" # First do user-role assignments in any existing simulation cases set current_cases [db_list select_current_cases { select wc.case_id from workflow_cases wc where wc.workflow_id = :workflow_id }] + #ns_log Notice "simulation::template::cast workflow_id=$workflow_id - before current cases loop current_cases=$current_cases" foreach case_id $current_cases { cast_users_in_case \ -workflow_id $workflow_id \ @@ -862,14 +889,17 @@ -role_names_array role_short_name \ -groups_array group_members \ -users_var users_to_cast \ - -users_not_in_groups_var users_to_cast_not_in_groups + -users_not_in_groups_var users_to_cast_not_in_groups \ + -full_groups_array full_group_members \ + -multiple_case_groups $multiple_case_groups } # If there are users left to cast, create new cases for them and repeat the same # assignment procedure as above set case_counter [llength $current_cases] set workflow_short_name [workflow::get_element -workflow_id $workflow_id -element short_name] while { [llength $users_to_cast] > 0 } { + #ns_log Notice "simulation::template::cast workflow_id=$workflow_id - beginning of new cases loop users_to_cast=$users_to_cast" # Create a new case incr case_counter @@ -887,7 +917,9 @@ -role_names_array role_short_name \ -groups_array group_members \ -users_var users_to_cast \ - -users_not_in_groups_var users_to_cast_not_in_groups + -users_not_in_groups_var users_to_cast_not_in_groups \ + -full_groups_array full_group_members \ + -multiple_case_groups $multiple_case_groups } } @@ -899,6 +931,8 @@ {-groups_array:required} {-users_var:required} {-users_not_in_groups_var:required} + {-full_groups_array:required} + {-multiple_case_groups:required} } { Internal helper proc that will do user-role assignments in an existing simulation case. @@ -910,6 +944,7 @@ upvar $groups_array group_members upvar $users_var users_to_cast upvar $users_not_in_groups_var users_to_cast_not_in_groups + upvar $full_groups_array full_group_members set admin_user_id [admin_user_id -workflow_id $workflow_id] @@ -928,76 +963,116 @@ where wcrpm.case_id = :case_id and wcrpm.role_id = :role_id }] - + if { [expr $users_already_in_case >= $one_role(users_per_case)] } { set n_users_to_assign 0 } else { set n_users_to_assign [expr $one_role(users_per_case) - $users_already_in_case] } + + #ns_log Notice "simulation::template::cast_users_in_case case_id=$case_id - beginning of role loop role_id=$role_id n_users_to_assign=$n_users_to_assign group_members=[array get group_members]" set assignees [list] for { set i 0 } { $i < $n_users_to_assign } { incr i } { - # Get user from random non-empty group mapped to role - foreach group_id [util::randomize_list $one_role(parties)] { - # Remove users from the list that have already been cast - set not_cast_list [list] - foreach user_id $group_members($group_id) { - if { [lsearch -exact $users_to_cast $user_id] != -1 } { - lappend not_cast_list $user_id - } - } - set group_members($group_id) $not_cast_list + set user_was_cast_p 0 - if { [llength $group_members($group_id)] > 0 } { - break - } + # Get the list of users in groups mapped to this role + set role_group_users [list] + foreach group_id [util::randomize_list $one_role(parties)] { + set role_group_users [concat $role_group_users $group_members($group_id)] } - if { [llength $group_members($group_id)] > 0 } { - # There is a role group with at least one user that hasn't been cast. - # Cast a random user from that group - set user_id [lindex $group_members($group_id) 0] - if { ![string equal $user_id $admin_user_id] } { - lappend assignees $user_id - } + # 1. Get random user from users_to_cast list who fulfils either of: + # a) User is in non-multiple case group mapped to role (group_members) + # b) User is in multi case group mapped to role (group_members) + # c) User is not in any group (users_to_cast_not_in_groups) + #ns_log Notice "simulation::template::cast_users_in_case case_id=$case_id - before users_to_cast loop, users_to_cast=$users_to_cast role_group_users=$role_group_users" + foreach user_id $users_to_cast { + + set cast_user_p 0 + if { [lsearch $role_group_users $user_id] != -1 } { + # Case a) or b) - user is in a group mapped to the role + #ns_log Notice "simulation::template::cast_users_in_case case_id=$case_id - in users to assign loop, role_id=$role_id i=$i casting user_id=$user_id from group (case a or b)" - # Remove the user from the group member list - set group_members($group_id) [lreplace $group_members($group_id) 0 0] + set cast_user_p 1 - # Remove the user from the users_to_cast list - set cast_list_index [lsearch -exact $users_to_cast $user_id] - set users_to_cast [lreplace $users_to_cast $cast_list_index $cast_list_index] + remove_user_from_casting_groups \ + -user_id $user_id \ + -role_groups $one_role(parties) \ + -groups_array group_members \ + -full_groups_array full_group_members \ + -multiple_case_groups $multiple_case_groups + #ns_log Notice "simulation::template::cast_users_in_case case_id=$case_id - in users_to_cast loop, after remove_users_from_casting_groups, group_members=[array get group_members]" - } else { - # There is no group mapped to the role with a user that hasn't been cast + } elseif { [lsearch $users_to_cast_not_in_groups $user_id] != -1 } { + # Case c) - user not in a group mapped to any role + #ns_log Notice "simulation::template::cast_users_in_case case_id=$case_id - in users to assign loop, role_id=$role_id i=$i casting user_id=$user_id who is not in group (case c)" - # Are there any uncast users who are not in any groups? - if { [llength $users_to_cast_not_in_groups] > 0 } { - # Fill the role with a user not in any of the role groups - set user_id [lindex $users_to_cast_not_in_groups 0] - if { ![string equal $user_id $admin_user_id] } { - lappend assignees $user_id - } + set cast_user_p 1 - # Remove user from the not-in-group list - set users_to_cast_not_in_groups [lreplace $users_to_cast_not_in_groups 0 0] + # Remove user from the users_to_cast_not_in_groups list + set cast_list_index [lsearch -exact $users_to_cast_not_in_groups $user_id] + set users_to_cast_not_in_groups [lreplace $users_to_cast_not_in_groups $cast_list_index $cast_list_index] + } - # Remove the user from the users_to_cast list + if { $cast_user_p } { + # Cast user + lappend assignees $user_id + set user_was_cast_p 1 + + # Remove user from users_to_cast list set cast_list_index [lsearch -exact $users_to_cast $user_id] set users_to_cast [lreplace $users_to_cast $cast_list_index $cast_list_index] - } else { - # No more users to cast, resort to the logged in user (admin) - - lappend assignees $admin_user_id - # Don't add the admin more than once break } } + + # 2. Get user from multiple group mapped to role (a user who is not in the users_to_cast list because he has been cast before) + if { !$user_was_cast_p } { + foreach group_id $one_role(parties) { + if { [lsearch $multiple_case_groups $group_id] != -1 } { + # We have a non-empty (they are refilled) multiple group mapped to the role + # Cast random user from that group + set user_id [lindex $group_members($group_id) 0] + + #ns_log Notice "simulation::template::cast_users_in_case case_id=$case_id - in users to assign loop, role_id=$role_id i=$i casting already cast user_id=$user_id from multi case group $group_id" + + set user_was_cast_p 1 + lappend assignees $user_id + + remove_user_from_casting_groups \ + -user_id $user_id \ + -role_groups $one_role(parties) \ + -groups_array group_members \ + -full_groups_array full_group_members \ + -multiple_case_groups $multiple_case_groups + + # Remove user from users_to_cast list + set cast_list_index [lsearch -exact $users_to_cast $user_id] + set users_to_cast [lreplace $users_to_cast $cast_list_index $cast_list_index] + + break + } + } + } + + # 3. Last resort - cast admin (filler) + if { !$user_was_cast_p } { + #ns_log Notice "simulation::template::cast_users_in_case case_id=$case_id - in users to assign loop, role_id=$role_id i=$i casting resorting to cast admin_user_id=$admin_user_id" + set user_was_cast_p 1 + lappend assignees $admin_user_id + # Only cast admin once + break + } } # Keep track of which users we decided to assign to the role and move on to the next one - set row($role_short_name($role_id)) $assignees + # It can happen with multi groups that a user gets mapped multiple times to one role. However, there is + # a role-party unique constraint in workflow that we mustn't violate. + # TODO: If we are removing a user here it means we are one of more users short + # of the targeted number of users for the role. In that case we should assign the admin as well if we haven't already done so. + set row($role_short_name($role_id)) [template::util::spellcheck::get_sorted_list_with_unique_elements -the_list $assignees] } # Do all the user-role assignments in the case @@ -1006,6 +1081,33 @@ -array row \ } +ad_proc -private simulation::template::remove_user_from_casting_groups { + {-user_id:required} + {-role_groups:required} + {-groups_array:required} + {-full_groups_array:required} + {-multiple_case_groups:required} +} { + Remove a cast user from the groups data structure and refill and empty + multicase groups. This is an internal proc used by the casting algorithm. + + @author Peter Marklund +} { + upvar $groups_array group_members + upvar $full_groups_array full_group_members + + # Remove the user from group_members + foreach group_id $role_groups { + set group_index [lsearch -exact $group_members($group_id) $user_id] + set group_members($group_id) [lreplace $group_members($group_id) $group_index $group_index] + + # Refill the group if it's now empty and multi-case + if { [llength $group_members($group_id)] == 0 && [lsearch $multiple_case_groups $group_id] != -1 } { + set group_members($group_id) $full_group_members($group_id) + } + } +} + ad_proc -private simulation::template::admin_user_id { {-workflow_id:required} } {