LP#1570072: update hold notification methods upon preference changes
authorCesar Velez <cesar.velez@equinoxinitiative.org>
Tue, 22 Jan 2019 17:16:54 +0000 (12:16 -0500)
committerMike Rylander <mrylander@gmail.com>
Mon, 24 Feb 2020 16:32:52 +0000 (11:32 -0500)
This patch adds a feature where if a patron's hold notification
preferences are changed, they are given an opportunity to have
notfication methods for their pending hold requests updated to
reflect their new prefernces.

This feature affects both the public catalog My Account interface
and the staff patron registration form. In both cases, the user
is presented with a modal (staff-side) or interstitial page (public
catalog) asking them whether to update current hold requests.

Sponsored-by: MassLNC

Additional-work-by: Mike Rylander <mrylander@gmail.com>
Additional-work-by: Galen Charlton <gmc@equinoxinitiative.org>

Signed-off-by: Cesar Velez <cesar.velez@equinoxinitiative.org>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Terran McCanna <tmccanna@georgialibraries.org>
Signed-off-by: Mike Rylander <mrylander@gmail.com>

14 files changed:
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/templates/opac/myopac/holds.tt2
Open-ILS/src/templates/opac/myopac/holds/edit.tt2
Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
Open-ILS/src/templates/opac/myopac/prefs_notify_changed_holds.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/hold_notify.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2
Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
Open-ILS/src/templates/staff/circ/patron/t_hold_notify_update.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
Open-ILS/web/js/ui/default/opac/simple.js
Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js

index e19915e..11c29f8 100644 (file)
@@ -4549,6 +4549,282 @@ sub copy_has_holds_count {
 }
 
 __PACKAGE__->register_method(
+    method    => "retrieve_holds_by_usr_notify_value_staff",
+    api_name  => "open-ils.circ.holds.retrieve_by_notify_staff",
+    signature => {
+        desc   => "Retrieve the hold, for the specified user using the notify value.  $ses_is_req_note",
+        params => [
+            { desc => 'Authentication token', type => 'string' },
+            { desc => 'User ID',              type => 'number' },
+            { desc => 'notify value',         type => 'string' },
+            { desc => 'notify_type',          type => 'string' }
+        ],
+        return => {
+            desc => 'Hold objects with transits attached, event on error',
+        }
+    }
+);
+
+sub retrieve_holds_by_usr_notify_value_staff {
+    
+    my($self, $conn, $auth, $usr_id, $contact, $cType) = @_;
+
+    my $e = new_editor(authtoken=>$auth);
+    $e->checkauth or return $e->event;
+
+    if ($e->requestor->id != $usr_id){
+        $e->allowed('VIEW_HOLD') or return $e->event;
+    }
+
+    my $q = {
+        "select" => { "ahr" => ["id", "sms_notify", "phone_notify", "email_notify", "sms_carrier"]},
+        "from" => "ahr",
+        "where" => {
+            "usr"          =>      $usr_id,
+            "capture_time" =>      undef,
+            "cancel_time"  =>      undef,
+            "fulfillment_time" =>  undef,
+        }
+    };
+
+    if ($cType eq "day_phone" or $cType eq "evening_phone" or
+        $cType eq "other_phone" or $cType eq "default_phone"){
+            $q->{where}->{"-not"} = [
+                { "phone_notify" => { "=" => $contact} },
+                { "phone_notify" => { "<>" => undef } }
+            ];
+    }
+
+
+    if ($cType eq "default_sms") {
+        $q->{where}->{"-not"} = [
+            { "sms_notify" => { "=" => $contact} },
+            { "sms_notify" => { "<>" => undef } }
+        ];
+    }
+
+    if ($cType eq "default_sms_carrier_id") {
+        $q->{where}->{"-not"} = [
+            { "sms_carrier" => { "=" => int($contact)} },
+            { "sms_carrier" => { "<>" => undef } }
+        ];
+    }
+
+    if ($cType =~ /notify/){
+        # this is was notification pref change
+        # we find all unfulfilled holds that match have that pref
+        my $optr = $contact == 1 ? "<>" : "="; # unless it's email, true val means we want to query for not null
+        my $conj = $optr eq '=' ? '-or' : '-and';
+        if ($cType =~ /sms/) {
+            $q->{where}->{$conj} = [ { sms_notify => { $optr => undef } }, { sms_notify => { $optr => '' } } ];
+        }
+        if ($cType =~ /phone/) {
+            $q->{where}->{$conj} = [ { phone_notify => { $optr => undef } }, { phone_notify => { $optr => '' } } ];
+        }
+        if ($cType =~ /email/) {
+            if ($contact) {
+                $q->{where}->{'+ahr'} = 'email_notify';
+            } else {
+                $q->{where}->{'-not'} = {'+ahr' => 'email_notify'};
+            }
+        }
+    }
+
+    my $holds = $e->json_query($q);
+    #$hold_ids = [ map { $_->{id} } @$hold_ids ];
+
+    return $holds;
+}
+
+__PACKAGE__->register_method(
+    method    => "batch_update_holds_by_value_staff",
+    api_name  => "open-ils.circ.holds.batch_update_holds_by_notify_staff",
+    signature => {
+        desc   => "Update a user's specified holds, affected by the contact/notify value change. $ses_is_req_note",
+        params => [
+            { desc => 'Authentication token', type => 'string' },
+            { desc => 'User ID',              type => 'number' },
+            { desc => 'Hold IDs',             type => 'array'  },
+            { desc => 'old notify value',     type => 'string' },
+            { desc => 'new notify value',     type => 'string' },
+            { desc => 'field name',           type => 'string' },
+            { desc => 'SMS carrier ID',       type => 'number' }
+
+        ],
+        return => {
+            desc => 'Hold objects with transits attached, event on error',
+        }
+    }
+);
+
+sub batch_update_holds_by_value_staff {
+    my($self, $conn, $auth, $usr_id, $hold_ids, $oldval, $newval, $cType, $carrierId) = @_;
+
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    $e->checkauth or return $e->event;
+    if ($e->requestor->id != $usr_id){
+        $e->allowed('UPDATE_HOLD') or return $e->event;
+    }
+
+    my @success;
+    for my $id (@$hold_ids) {
+        
+        my $hold = $e->retrieve_action_hold_request($id);
+
+        if ($cType eq "day_phone" or $cType eq "evening_phone" or
+            $cType eq "other_phone" or $cType eq "default_phone") {
+
+            if ($newval eq '') {
+                $hold->clear_phone_notify();
+            }
+            else {
+                $hold->phone_notify($newval);
+            }
+        }
+        
+        if ($cType eq "default_sms"){
+            if ($newval eq '') {
+                $hold->clear_sms_notify();
+                $hold->clear_sms_carrier(); # TODO: prevent orphan sms_carrier, via db trigger
+            }
+            else {
+                $hold->sms_notify($newval);
+                $hold->sms_carrier($carrierId);
+            }
+
+        }
+
+        if ($cType eq "default_sms_carrier_id") {
+            $hold->sms_carrier($newval);
+        }
+
+        if ($cType =~ /notify/){
+            # this is a notification pref change
+            if ($cType =~ /email/) { $hold->email_notify($newval); }
+            if ($cType =~ /sms/ and !$newval) { $hold->clear_sms_notify(); }
+            if ($cType =~ /phone/ and !$newval) { $hold->clear_phone_notify(); }
+            # the other case, where x_notify is changed to true,
+            # is covered by an actual value being assigned
+        }
+
+        $e->update_action_hold_request($hold) or return $e->die_event;
+        push @success, $id;
+    }
+
+    #$e->disconnect;
+    $e->commit; #unless $U->event_code($res);
+    return \@success;
+
+}
+
+
+__PACKAGE__->register_method(
+    method    => "retrieve_holds_by_usr_with_notify",
+    api_name  => "open-ils.circ.holds.retrieve.by_usr.with_notify",
+    signature => {
+        desc   => "Retrieve the hold, for the specified user using the notify value.  $ses_is_req_note",
+        params => [
+            { desc => 'Authentication token', type => 'string' },
+            { desc => 'User ID',              type => 'number' },
+        ],
+        return => {
+            desc => 'Lists of holds with notification values, event on error',
+        }
+    }
+);
+
+sub retrieve_holds_by_usr_with_notify {
+    
+    my($self, $conn, $auth, $usr_id) = @_;
+
+    my $e = new_editor(authtoken=>$auth);
+    $e->checkauth or return $e->event;
+
+    if ($e->requestor->id != $usr_id){
+        $e->allowed('VIEW_HOLD') or return $e->event;
+    }
+
+    my $q = {
+        "select" => { "ahr" => ["id", "phone_notify", "email_notify", "sms_carrier", "sms_notify"]},
+        "from" => "ahr",
+        "where" => {
+            "usr"          =>      $usr_id,
+            "capture_time" =>      undef,
+            "cancel_time"  =>      undef,
+            "fulfillment_time" =>  undef,
+        }
+    };
+
+    my $holds = $e->json_query($q);
+    return $holds;
+}
+
+__PACKAGE__->register_method(
+    method    => "batch_update_holds_by_value",
+    api_name  => "open-ils.circ.holds.batch_update_holds_by_notify",
+    signature => {
+        desc   => "Update a user's specified holds, affected by the contact/notify value change. $ses_is_req_note",
+        params => [
+            { desc => 'Authentication token', type => 'string' },
+            { desc => 'User ID',              type => 'number' },
+            { desc => 'Hold IDs',             type => 'array'  },
+            { desc => 'old notify value',     type => 'string' },
+            { desc => 'new notify value',     type => 'string' },
+            { desc => 'notify_type',          type => 'string' }
+        ],
+        return => {
+            desc => 'Hold objects with transits attached, event on error',
+        }
+    }
+);
+
+sub batch_update_holds_by_value {
+    my($self, $conn, $auth, $usr_id, $hold_ids, $oldval, $newval, $cType) = @_;
+
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    $e->checkauth or return $e->event;
+    if ($e->requestor->id != $usr_id){
+        $e->allowed('UPDATE_HOLD') or return $e->event;
+    }
+
+    my @success;
+    for my $id (@$hold_ids) {
+        
+        my $hold = $e->retrieve_action_hold_request(int($id));
+
+        if ($cType eq "day_phone" or $cType eq "evening_phone" or
+            $cType eq "other_phone" or $cType eq "default_phone") {
+            # change phone number value on hold
+            $hold->phone_notify($newval);
+        }
+        if ($cType eq "default_sms") {
+            # change SMS number value on hold
+            $hold->sms_notify($newval);
+        }
+
+        if ($cType eq "default_sms_carrier_id") {
+            $hold->sms_carrier(int($newval));
+        }
+
+        if ($cType =~ /notify/){
+            # this is a notification pref change
+            if ($cType =~ /email/) { $hold->email_notify($newval); }
+            if ($cType =~ /sms/ and !$newval) { $hold->clear_sms_notify(); }
+            if ($cType =~ /phone/ and !$newval) { $hold->clear_phone_notify(); }
+            # the other case, where x_notify is changed to true,
+            # is covered by an actual value being assigned
+        }
+
+        $e->update_action_hold_request($hold) or return $e->die_event;
+        push @success, $id;
+    }
+
+    #$e->disconnect;
+    $e->commit; #unless $U->event_code($res);
+    return \@success;
+}
+
+__PACKAGE__->register_method(
     method        => "hold_metadata",
     api_name      => "open-ils.circ.hold.get_metadata",
     authoritative => 1,
@@ -4573,6 +4849,7 @@ __PACKAGE__->register_method(
     }
 );
 
+
 sub hold_metadata {
     my ($self, $client, $hold_type, $hold_targets, $org_id) = @_;
 
index 105c3bd..7388d79 100644 (file)
@@ -225,6 +225,7 @@ sub load {
     return $self->load_myopac_circ_history_export if $path =~ m|opac/myopac/circ_history/export|;
     return $self->load_myopac_circ_history if $path =~ m|opac/myopac/circ_history|;
     return $self->load_myopac_hold_history if $path =~ m|opac/myopac/hold_history|;
+    return $self->load_myopac_prefs_notify_changed_holds if $path =~ m|opac/myopac/prefs_notify_changed_holds|;
     return $self->load_myopac_prefs_notify if $path =~ m|opac/myopac/prefs_notify|;
     return $self->load_myopac_prefs_settings if $path =~ m|opac/myopac/prefs_settings|;
     return $self->load_myopac_prefs_my_lists if $path =~ m|opac/myopac/prefs_my_lists|;
index 5bc2f8b..e246bd5 100644 (file)
@@ -235,9 +235,272 @@ sub load_myopac_prefs_notify {
 
     # re-fetch user prefs
     $self->ctx->{updated_user_settings} = \%settings;
+
+    # update holds: check if any changes affect any holds
+    my @llchgs = $self->_parse_prefs_notify_hold_related();
+    my @ffectedChgs;
+
+    if ( $self->cgi->param('hasHoldsChanges') ) {
+        # propagate pref_notify changes to holds
+        for my $chset (@llchgs){
+            # FIXME is this still needed?
+        }
+    
+    }
+    else {
+        my $holds = $U->simplereq('open-ils.circ', 'open-ils.circ.holds.retrieve.by_usr.with_notify',
+            $e->authtoken, $e->requestor->id);
+
+        if (@$holds > 0) {
+
+            my $default_phone_changes = {};
+            my $sms_changes           = {};
+            my $new_phone;
+            my $new_carrier;
+            my $new_sms;
+            for my $chset (@llchgs) {
+                next if scalar(@$chset) < 3;
+                my ($old, $new, $field) = @$chset;
+
+                my $bool = $field =~ /_notify/ ? 1 : 0;
+
+                # find holds that would change
+                my $affected = [];
+                foreach my $hold (@$holds) {
+                    if ($field eq 'email_notify') {
+                        my $curr = $hold->{$field} eq 't' ? 'true' : 'false';
+                        push @$affected, $hold if $curr ne $new;
+                    } elsif ($field eq 'default_phone') {
+                        my $old_phone = $hold->{phone_notify} // '';
+                        $new_phone = $new // '';
+                        push @{ $default_phone_changes->{ $old_phone } }, $hold->{id}
+                            if $old_phone ne $new_phone;
+                    } elsif ($field eq 'phone_notify') {
+                        my $curr = ($hold->{$field} // '' ne '') ? 'true' : 'false';
+                        push @$affected, $hold if $curr ne $new;
+                    } elsif ($field eq 'sms_notify') {
+                        my $curr = ($hold->{$field} // '' ne '') ? 'true' : 'false';
+                        push @$affected, $hold if $curr ne $new;
+                    } elsif ($field eq 'sms_info') {
+                        my $old_carrier = $hold->{'sms_carrier'} // '';
+                        my $old_sms = $hold->{'sms_notify'} // '';
+                        $new_carrier = $new->{carrier} // '';
+                        $new_sms = $new->{sms} // '';
+                        if (!($old_carrier eq $new_carrier && $old_sms eq $new_sms)) {
+                            push @{ $sms_changes->{ join("\t", $old_carrier, $old_sms) } }, $hold->{id};
+                        }
+                    }
+                }
+
+                # append affected array to chset
+                if (scalar(@$affected) > 0){
+                    push(@$chset, [ map { $_->{id} } @$affected ]);
+                    push(@ffectedChgs, $chset);
+                }
+            }
+
+
+            foreach my $old_phone (keys %$default_phone_changes) {
+                push(@ffectedChgs, [ $old_phone, $new_phone, 'default_phone', $default_phone_changes->{$old_phone} ]);
+            }
+            foreach my $old_sms_info (keys %$sms_changes) {
+                my ($old_carrier, $old_sms) = split /\t/, $old_sms_info;
+                push(@ffectedChgs, [
+                                        { carrier => $old_carrier, sms => $old_sms },
+                                        { carrier => $new_carrier, sms => $new_sms },
+                                        'sms_info',
+                                        $sms_changes->{$old_sms_info}
+                                   ]);
+            }
+
+            if ( scalar(@ffectedChgs) ){
+                $self->ctx->{affectedChgs} = \@ffectedChgs;
+            }
+        }
+    }
+
     return $self->_load_user_with_prefs || Apache2::Const::OK;
 }
 
+sub _parse_prefs_notify_hold_related {
+
+    my $self = shift;
+    my $for_update = shift;
+
+    # create an array of change arrays
+    my @chgs;
+
+    my @phone_notify = $self->cgi->multi_param('phone_notify[]');
+    push(@chgs, \@phone_notify) if scalar(@phone_notify);
+
+    my $turning_on_phone_notify  = !$for_update &&
+                                   scalar(@phone_notify) &&
+                                   $phone_notify[1] eq 'true';
+    my $turning_off_phone_notify = !$for_update &&
+                                   scalar(@phone_notify) &&
+                                   $phone_notify[1] eq 'false';
+
+    my $changing_default_phone = 0;
+    if (!$turning_off_phone_notify) {
+        my @default_phone = $self->cgi->multi_param('default_phone[]');
+        if ($for_update) {
+            while (scalar(@default_phone) > 0) {
+                my $chg = [ splice(@default_phone, 0, 4) ];
+                if (scalar(@default_phone) > 0 && $default_phone[0] eq 'on') {
+                    push @$chg, shift(@default_phone);
+                    push(@chgs, $chg);
+                    $changing_default_phone = 1;
+                }
+            }
+        } else {
+            if (scalar(@default_phone)) {
+                push @chgs, \@default_phone;
+                $changing_default_phone = 1;
+            }
+        }
+    }
+
+    if ($turning_on_phone_notify && $changing_default_phone) {
+        # we don't need to have both the phone_notify and default_phone
+        # changes; the latter will suffice
+        @chgs = grep { $_->[2] ne 'phone_notify' } @chgs;
+    } elsif ($turning_on_phone_notify && !$changing_default_phone) {
+        # replace the phone_notify change with a default_phone change
+        @chgs = grep { $_->[2] ne 'phone_notify' } @chgs;
+        my $default_phone = $self->cgi->param('opac.default_phone'); # we assume this is set
+        push @chgs, [ '', $default_phone, 'default_phone' ];
+    }
+
+    # on to SMS
+    # ... since both carrier and number are needed to send an SMS notifcation,
+    # we need to treat the pair as a unit
+    my @sms_notify = $self->cgi->multi_param('sms_notify[]');
+    push(@chgs, \@sms_notify) if scalar(@sms_notify);
+
+    my $turning_on_sms_notify  = !$for_update &&
+                                   scalar(@sms_notify) &&
+                                   $sms_notify[1] eq 'true';
+    my $turning_off_sms_notify = !$for_update &&
+                                   scalar(@sms_notify) &&
+                                   $sms_notify[1] eq 'false';
+
+    my $changing_sms_info = 0;
+    if (!$turning_off_sms_notify) {
+        my @sms_carrier = $self->cgi->multi_param('default_sms_carrier_id[]');
+        my @sms = $self->cgi->multi_param('default_sms[]');
+
+        if (scalar(@sms) || scalar(@sms_carrier)) {
+            my $new_carrier = scalar(@sms_carrier) ? $sms_carrier[1] : $self->cgi->param('sms_carrier');
+            my $new_sms = scalar(@sms) ? $sms[1] : $self->cgi->param('opac.default_sms_notify');
+            push @chgs, [
+                            { carrier => '', sms => '' },
+                            { carrier => $new_carrier, sms => $new_sms },
+                            'sms_info'
+                        ];
+           $changing_sms_info = 1;
+        }
+    }
+
+    my @sms_info = $self->cgi->multi_param('sms_info[]'); # only sent by confirmation page
+    if (scalar(@sms_info)) {
+        while (scalar(@sms_info) > 0) {
+            my $chg = [ splice(@sms_info, 0, 4) ];
+            if (scalar(@sms_info) > 0 && $sms_info[0] eq 'on') {
+                push @$chg, shift(@sms_info);
+                my ($carrier, $sms) = split /,/, $chg->[0], -1;
+                $chg->[0] = { carrier => $carrier, sms => $sms };
+                ($carrier, $sms) = split /,/, $chg->[1], -1;
+                $chg->[1] = { carrier => $carrier, sms => $sms };
+                push(@chgs, $chg);
+                $changing_sms_info = 1;
+            }
+        }
+    }
+
+    if ($turning_on_sms_notify && $changing_sms_info) {
+        # we don't need to have both the sms_notify and sms_info
+        # changes; the latter will suffice
+        @chgs = grep { $_->[2] ne 'sms_notify' } @chgs;
+    } elsif ($turning_on_sms_notify && !$changing_sms_info) {
+        # replace the sms_notify change with a sms_info change
+        @chgs = grep { $_->[2] ne 'sms_notify' } @chgs;
+        my $sms_info = {
+            carrier => $self->cgi->param('sms_carrier'),
+            sms     => $self->cgi->param('opac.default_sms_notify'),
+        };
+        push @chgs, [ { carrier => '', sms => ''}, $sms_info, 'sms_info' ];
+    }
+
+    my @email_notify = $self->cgi->multi_param('email_notify[]');
+    push(@chgs, \@email_notify) if scalar(@email_notify);
+
+    if ($for_update) {
+        # if we're updating, keep only the ones that have been
+        # explicitly checked by the user
+        @chgs = grep { scalar(@$_) == 5 && $_->[4] eq 'on' } @chgs;
+    }
+    return @chgs;
+}
+
+sub load_myopac_prefs_notify_changed_holds {
+    my $self = shift;
+    my $e = $self->editor;
+
+    my $hasChanges = $self->cgi->param('hasHoldsChanges');
+    
+    return $self->_load_user_with_prefs || Apache2::Const::OK unless $hasChanges;
+
+    my @ll = $self->_parse_prefs_notify_hold_related(1);
+
+    my @updates;
+    for my $chset (@ll){
+        my ($old, $new, $type, $holdids, $doit) = @$chset;
+        next if $doit ne 'on';
+        
+        # parse string list into array list
+        my @holdids = split(',', $holdids);
+        
+        if ($type =~ /_notify/){
+            # translate true/false string into 1/0
+            $old = $old eq 'true' ? 1 : 0;
+            $new = $new eq 'true' ? 1 : 0;
+        }
+
+        my $update;
+        if ($type eq 'sms_info') {
+            if ($new->{carrier} eq '' && $new->{sms} eq '') {
+                # clear SMS number first to avoid check contrainst issue
+                $update = $U->simplereq('open-ils.circ', "open-ils.circ.holds.batch_update_holds_by_notify",
+                    $e->authtoken, $e->requestor->id, [@holdids], $old->{sms}, $new->{sms}, 'default_sms');
+                push (@updates, $update) if (scalar(@$update) > 0);
+                $update = $U->simplereq('open-ils.circ', "open-ils.circ.holds.batch_update_holds_by_notify",
+                    $e->authtoken, $e->requestor->id, [@holdids], $old->{carrier}, $new->{carrier}, 'default_sms_carrier_id');
+                push (@updates, $update) if (scalar(@$update) > 0);
+            } else {
+                $update = $U->simplereq('open-ils.circ', "open-ils.circ.holds.batch_update_holds_by_notify",
+                    $e->authtoken, $e->requestor->id, [@holdids], $old->{carrier}, $new->{carrier}, 'default_sms_carrier_id');
+                push (@updates, $update) if (scalar(@$update) > 0);
+                $update = $U->simplereq('open-ils.circ', "open-ils.circ.holds.batch_update_holds_by_notify",
+                    $e->authtoken, $e->requestor->id, [@holdids], $old->{sms}, $new->{sms}, 'default_sms');
+                push (@updates, $update) if (scalar(@$update) > 0);
+            }
+        } else {
+            $update = $U->simplereq('open-ils.circ', "open-ils.circ.holds.batch_update_holds_by_notify",
+                $e->authtoken, $e->requestor->id, [@holdids], $old, $new, $type);
+
+            # append affected array to chset
+            if (scalar(@$update) > 0){
+                push(@updates, $update);
+            }
+        }
+    }
+
+    $self->ctx->{'updated'} = \@updates;
+
+    return $self->_load_user_with_prefs || Apache2::Const::OK;
+
+}
+
 sub fetch_optin_prefs {
     my $self = shift;
     my $e = $self->editor;
@@ -959,6 +1222,10 @@ sub handle_hold_update {
             my $val = {"id" => $_};
             $val->{"frozen"} = $self->cgi->param("frozen");
             $val->{"pickup_lib"} = $self->cgi->param("pickup_lib");
+            $val->{"email_notify"} = $self->cgi->param("email_notify") ? 1 : 0;
+            $val->{"phone_notify"} = $self->cgi->param("phone_notify");
+            $val->{"sms_notify"} = $self->cgi->param("sms_notify");
+            $val->{"sms_carrier"} = int($self->cgi->param("sms_carrier")) if $val->{"sms_notify"};
 
             for my $field (qw/expire_time thaw_date/) {
                 # XXX TODO make this support other date formats, not just
index d881d9f..9aa0b2a 100644 (file)
@@ -1,6 +1,7 @@
 [%  PROCESS "opac/parts/header.tt2";
     PROCESS "opac/parts/misc_util.tt2";
     PROCESS "opac/parts/hold_status.tt2";
+    PROCESS "opac/parts/hold_notify.tt2";
     PROCESS "opac/parts/myopac/column_sort_support.tt2";
     WRAPPER "opac/parts/myopac/base.tt2";
     myopac_page = "holds";
                 <th>[% l('Pickup Location') %]</th>
                 <th>[% l('Cancel if not filled by') %]</th>
                 <th>[% l('Status') %]</th>
+                <th>[% l('Notify Method') %]</th>
                 <th>[% l('Notes') %]</th>
             </tr>
             </thead>
                             [% PROCESS get_hold_status hold=hold; %]
                         </div>
                     </td>
+                    <td>
+                        <div name="acct_holds_notify">
+                            [% PROCESS get_hold_notify h=ahr; %]
+                        </div>
+                    </td>
                     <td class="hold_notes">
                     [%- FOREACH pubnote IN ahr.notes;
                         IF pubnote.pub == 't';
index 6b567a6..ab87373 100644 (file)
                             <em>[% l('Enter date in MM/DD/YYYY format') %]</em>
                         </td>
                     </tr>
+                    <tr>
+                        <td>[% l('Email Notification') %]</td>
+                        <td><input type="checkbox" name="email_notify"
+                                [% IF ahr.email_notify == 't' %] checked [% END %] />
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>[% l('Phone Notification') %]</td>
+                        <td><input type="text" name="phone_notify"
+                                value="[% ahr.phone_notify | html %]" /></td>
+                    </tr>
+                    <tr>
+                        <td>[% l('SMS Notification') %]</td>
+                        <td><input onblur="check_sms_carrier(event)" type="text" name="sms_notify"
+                                value="[% ahr.sms_notify | html %]" /></td>
+                    </tr>
+                    <tr>
+                        <td>[% l('Default Mobile Carrier') %]</td>
+                        <td>[% INCLUDE "opac/parts/sms_carrier_selector.tt2" ahr, sms_carrier_hide_warning="true", sms_carrier_hide_label="true" %]</td>
+                    </tr>
                     [% END %]
                      
                     <tr><td colspan='4'>
index 47b7f72..7b17ada 100644 (file)
     prefs_page = 'prefs_notify' %]
 
 <h3 class="sr-only">[% l('Notification Preferences') %]</h3>
-<form method='post'>
-    [% setting = 'opac.hold_notify' %]
-    <input name='[% setting %]' type="hidden"
-        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+    [% IF ctx.affectedChgs %]
 
-    <table class="full-width data_grid" id="acct_search_main" 
-        title="[% l('Notification Preferences') %]">
-        <tbody>
-
-            [% IF ctx.updated_user_settings %]
-            <tr><td colspan='2'>
-                <div class='renew-summary'>
-                    [% l('Account Successfully Updated') %]
-                </div>
-            </td></tr>
-            [% END %]
+        [% # get hash of sms carriers keyed by id:
+            temp = ctx.search_csc('active','t');
+            tcos = { '0' => 'None' };
+            FOR o IN temp;
+                id = o.id;
+                tcos.$id = o;
+            END;
+        %]
+  <h4 class="">[% l('You have updated notification preferences. Those changes only affect future holds. Would you like to update existing holds to use the new information?') %]</h4>
+    <form id="hold_updates_form" name="hold_updates_form" method='post' action="./prefs_notify_changed_holds" onsubmit='return updateHoldsCheck()'>
+        <table class="full-width data_grid" id="acct_search_main" 
+            title="[% l('Notification Preferences') %]">
+            <tbody>
+        [% SET blnk = l('Blank') %]
+        [% FOR c IN ctx.affectedChgs %]
+            <tr>
+              <td>
+                [% IF c.2 == 'sms_info' %]
+                <input type='hidden' name="[% c.2 %][]" value="[% c.0.carrier _ ',' _ c.0.sms | html %]" />
+                <input type='hidden' name="[% c.2 %][]" value="[% c.1.carrier _ ',' _ c.1.sms | html %]" />
+                [% ELSE %]
+                <input type='hidden' name="[% c.2 %][]" value="[% c.0 %]" />
+                <input type='hidden' name="[% c.2 %][]" value="[% c.1 %]" />
+                [% END %]
+                <input type='hidden' name="[% c.2 %][]" value="[% c.2 %]" />
+                <input type='hidden' name="[% c.2 %][]" value="[% FOREACH i IN c.3 %][% i %],[% END %]" />
+                <input id="[% c %]" type="checkbox" onchange="canSubmit(event)" name="[% c.2 %][]"/>
+                [% IF c.2 == 'sms_info' %]
+                    [% SET disp_name = l('SMS carrier/number') %]
+                    <label for="[% c %]">[% c.3.size %] hold(s) currently with [% disp_name %] set to '[% old = c.0.carrier; tcos.$old.name() ? tcos.$old.name() : blnk | html %]/[% c.0.sms ? c.0.sms : blnk | html %]'. Update to '[% new = c.1.carrier; tcos.$new.name()  ? tcos.$new.name() : blnk | html %]/[% c.1.sms ? c.1.sms : blnk | html %]'?</label>
+                [% ELSIF c.2.match('_notify') %]
+                    [% SET f_name = c.2.replace("_", " "); Y = l('YES'); N = l('NO') %]
+                    <label for="[% c %]">[% c.3.size %] hold(s) currently with [% f_name %] set to [% c.0 == 'false' ? N : Y %]. Update to [% c.1 == 'false' ? N : Y %]?</label>
+                [% ELSE %]
+                    [% SET f_name = c.2.replace("_", " ") %]
+                    <label for="[% c %]">[% c.3.size %] hold(s) currently with [% f_name %] set to '[% c.0 ? c.0 : blnk %]'. Update to '[% c.1 ? c.1 : blnk %]'?</label>
+                [% END %]
+              </td>
+            </tr>
+        [% END %]
+            </tbody>
+        </table>
+        <input type='submit' disabled value="[% l('Update') %]" class="opac-button" />
+        <a href='/eg/opac/myopac/prefs_notify'>[% l('Continue without updating') %]</a>
+    </form>
 
+    [% ELSE %]
+        <form id="hold_notify_form" name="hold_notify_form" method='post'>
             [% setting = 'opac.hold_notify' %]
-            <tr>
-                [%# WCAG insists that labels for checkboxes contain the input
-                    or directly follow the input, which would not look right
-                    with the rest of the table.  As an alternative, we can
-                    repeat the label as a title attr.
-                    http://www.w3.org/TR/WCAG20-TECHS/H44.html %]
-                [% email_label = l('Notify by Email by default when a hold is ready for pickup?') %]
+            <input name='[% setting %]' type="hidden"
+                [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
 
-                <td><label for='[% setting %].email'>[% email_label %]</label></td>
-                <td>
-                    <input id='[% setting %].email' name='[% setting %].email' 
-                        type="checkbox" title="[% email_label %]"
-                        [% IF (matches = ctx.user_setting_map.$setting.match('email')); %] checked='checked' [% END %]/>
-                </td>
-            </tr>
-            [%- IF allow_phone_notifications == 'true';
-                setting = 'opac.hold_notify'; 
-            -%]
-            <tr>
-                [% phone_label = l('Notify by Phone by default when a hold is ready for pickup?') %]
-                <td><label for='[% setting %].phone'>[% phone_label %]</label></td>
-                <td>
-                    <input id='[% setting %].phone' name='[% setting %].phone' 
-                        type="checkbox" title="[% phone_label %]"
-                        [% IF (matches = ctx.user_setting_map.$setting.match('phone')); %] checked='checked' [% END %]/>
-                </td>
-            </tr>
-            [% setting = 'opac.default_phone' %]
-            <tr>
-                <td><label for='[% setting %]'>[% l('Default Phone Number') %]</label></td>
-                <td>
-                    <input id='[% setting %]' name='[% setting %]' type="text"
-                        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
-                </td>
-            </tr>
-            [%- END %]
-            [%- IF ctx.get_org_setting(ctx.search_ou, 'sms.enable') == 1;
-               setting = 'opac.hold_notify';
-            -%]
-            <tr>
-                [% sms_label = l('Notify by Text by default when a hold is ready for pickup?') %]
-                <td><label for='[% setting %].sms'>[% sms_label %]</label></td>
-                <td>
-                    <input id='[% setting %].sms' name='[% setting %].sms' 
-                        type="checkbox" title="[% sms_label %]"
-                        [% IF (matches = ctx.user_setting_map.$setting.match('sms')); %] checked='checked' [% END %]/>
-                </td>
-            </tr>
-            <tr>
-                <td>[% l('Default Mobile Carrier') %]</td>
-                <td>[% INCLUDE "opac/parts/sms_carrier_selector.tt2" sms_carrier_hide_label="true" %]</td>
-            </tr>
-            [% setting = 'opac.default_sms_notify' %]
-            <tr>
-                <td><label for='[% setting %]'>[% l('Default Mobile Number') %]</label></td>
-                <td>
-                    <input id='[% setting %]' name='[% setting %]' type="text"
-                        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
-                    [% l('Hint: use the full 10 digits of your phone #, no spaces, no dashes'); %]
-                </td>
-            </tr>
-            [% END %]
-            [% FOR optin IN ctx.opt_in_settings %]
-            <tr>
-                <td><label for='[% optin.cust.name | uri %]'>[% optin.cust.label | html %]</label></td>
-                <td>
-                    <input type='checkbox' name='setting' 
-                        value='[% optin.cust.name | uri %]' 
-                        id='[% optin.cust.name | uri %]' 
-                        title="[% optin.cust.label | html %]"
-                        [% IF optin.value %] checked='checked' [% END %]/>
-                </td>
-            </tr>
-            [% END %]
-        </tbody>
-    </table>
+            <table class="full-width data_grid" id="acct_search_main" 
+                title="[% l('Notification Preferences') %]">
+                <tbody>
+
+                    [% IF ctx.updated_user_settings %]
+                    <tr><td colspan='2'>
+                        <div class='renew-summary'>
+                            [% l('Account Successfully Updated') %]
+                        </div>
+                    </td></tr>
+                    [% END %]
+
+                    [% setting = 'opac.hold_notify' %]
+                    <tr>
+                        [%# WCAG insists that labels for checkboxes contain the input
+                            or directly follow the input, which would not look right
+                            with the rest of the table.  As an alternative, we can
+                            repeat the label as a title attr.
+                            http://www.w3.org/TR/WCAG20-TECHS/H44.html %]
+                        [% email_label = l('Notify by Email by default when a hold is ready for pickup?') %]
+
+                        <td><label for='[% setting %].email'>[% email_label %]</label></td>
+                        <td>
+                            <input onchange="record_change(event)" id='[% setting %].email' name='[% setting %].email'
+                                type="checkbox" title="[% email_label %]"
+                                [% IF (matches = ctx.user_setting_map.$setting.match('email')); %] checked='checked' [% END %]/>
+                        </td>
+                    </tr>
+                    [%- IF allow_phone_notifications == 'true';
+                        setting = 'opac.hold_notify'; 
+                    -%]
+                    <tr>
+                        [% phone_label = l('Notify by Phone by default when a hold is ready for pickup?') %]
+                        <td><label for='[% setting %].phone'>[% phone_label %]</label></td>
+                        <td>
+                            <input onchange="record_change(event)" id='[% setting %].phone' name='[% setting %].phone'
+                                type="checkbox" title="[% phone_label %]"
+                                [% IF (matches = ctx.user_setting_map.$setting.match('phone')); %] checked='checked' [% END %]/>
+                        </td>
+                    </tr>
+                    [% setting = 'opac.default_phone' %]
+                    <tr>
+                        <td><label for='[% setting %]'>[% l('Default Phone Number') %]</label></td>
+                        <td>
+                            <input onchange="record_change(event)" id='[% setting %]' name='[% setting %]' type="text"
+                                [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                        </td>
+                    </tr>
+                    [%- END %]
+                    [%- IF ctx.get_org_setting(ctx.search_ou, 'sms.enable') == 1;
+                       setting = 'opac.hold_notify';
+                    -%]
+                    <tr>
+                        [% sms_label = l('Notify by Text by default when a hold is ready for pickup?') %]
+                        <td><label for='[% setting %].sms'>[% sms_label %]</label></td>
+                        <td>
+                            <input onchange="record_change(event)" id='[% setting %].sms' name='[% setting %].sms'
+                                type="checkbox" title="[% sms_label %]"
+                                [% IF (matches = ctx.user_setting_map.$setting.match('sms')); %] checked='checked' [% END %]/>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>[% l('Default Mobile Carrier') %]</td>
+                        <td>[% INCLUDE "opac/parts/sms_carrier_selector.tt2" sms_carrier_hide_label="true" %]</td>
+                    </tr>
+                    [% setting = 'opac.default_sms_notify' %]
+                    <tr>
+                        <td><label for='[% setting %]'>[% l('Default Mobile Number') %]</label></td>
+                        <td>
+                            <input onchange="record_change(event)" id='[% setting %]' name='[% setting %]' type="text"
+                                [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                            [% l('Hint: use the full 10 digits of your phone #, no spaces, no dashes'); %]
+                        </td>
+                    </tr>
+                    [% END %]
+                    [% FOR optin IN ctx.opt_in_settings %]
+                    <tr>
+                        <td><label for='[% optin.cust.name | uri %]'>[% optin.cust.label | html %]</label></td>
+                        <td>
+                            <input type='checkbox' name='setting' 
+                                value='[% optin.cust.name | uri %]' 
+                                id='[% optin.cust.name | uri %]' 
+                                title="[% optin.cust.label | html %]"
+                                [% IF optin.value %] checked='checked' [% END %]/>
+                        </td>
+                    </tr>
+                    [% END %]
+                </tbody>
+            </table>
 
-    <input type='submit' value="[% l('Save') %]" class="opac-button" />
-</form>
+            <input type='submit' value="[% l('Save') %]" class="opac-button" />
+        </form>
+    [% END %]
 [% END %]
 
 
diff --git a/Open-ILS/src/templates/opac/myopac/prefs_notify_changed_holds.tt2 b/Open-ILS/src/templates/opac/myopac/prefs_notify_changed_holds.tt2
new file mode 100644 (file)
index 0000000..0a33c3f
--- /dev/null
@@ -0,0 +1,29 @@
+[%  PROCESS "opac/parts/header.tt2";
+    WRAPPER "opac/parts/myopac/prefs_base.tt2";
+    myopac_page = "prefs";
+    prefs_page = 'prefs_notify' %]
+
+<h3 class="sr-only">[% l('Affected Holds') %]</h3>
+<div id="update_hold_notify_confirm" >
+    [% IF ctx.updated %]
+        <p>[% l('Hold Notification Information Updated.') %]</p>
+<!--
+        <ul>
+        [% FOREACH c IN ctx.updated %]
+            <li>
+            [% l('Holds updated: ') %]
+                [% FOREACH i IN c %]
+                    [% i %]
+                [% END %]
+            </li>
+        [% END %]
+        </ul>
+-->
+    [% ELSE %]
+    <p>[% l('No changes') %].</p>
+    [% END %]
+    <a href='/eg/opac/myopac/prefs_notify'>[% l('Continue.') %]</a>
+</form>
+[% END %]
+
+
diff --git a/Open-ILS/src/templates/opac/parts/hold_notify.tt2 b/Open-ILS/src/templates/opac/parts/hold_notify.tt2
new file mode 100644 (file)
index 0000000..4d755f1
--- /dev/null
@@ -0,0 +1,28 @@
+[% BLOCK get_hold_notify %]
+    [% # get hash of sms carriers keyed by id:
+        temp = ctx.search_csc('active','t');
+        tcos = { '0' => 'None' };
+        FOR o IN temp;
+            id = o.id;
+            tcos.$id = o;
+        END;
+    %]
+    [% SET any_notify = 0 %]
+    <div>
+    [% IF h.email_notify == 't' %]
+        [% any_notify = 1 %]
+        <strong>[% l("Email") %]</strong>: [% l("Yes") %]<br/>
+    [% END %]
+    [% IF h.phone_notify %]
+        [% any_notify = 1 %]
+        <strong>[% l("Phone") %]</strong>: [% h.phone_notify | html %]<br/>
+    [% END %]
+    [% IF h.sms_notify %]
+        [% any_notify = 1, cid = h.sms_carrier; %]
+        <strong>[% l("SMS") %]</strong>: [% h.sms_notify | html %] ([% tcos.$cid.name() | html %])<br/>
+    [% END %]
+    [% UNLESS any_notify %]
+        <span style="color:red">[% l("None") %]</span>
+    [% END %]
+    </div>
+[% END %]
index 95ba8ff..3515bc7 100644 (file)
     END;
 %]
 [% IF NOT sms_carrier_hide_label; '<label for="sms_carrier">' _ l('Mobile carrier:') _ '</label>'; END; %]
-<select name="sms_carrier" id="sms_carrier" [% IF sms_carrier_hide_label; 'aria-label="' _ l('Mobile carrier') _ '"'; END; %]>
+<select onchange="record_change(event)" id="sms_carrier" name="sms_carrier" id="sms_carrier" [% IF sms_carrier_hide_label; 'aria-label="' _ l('Mobile carrier') _ '"'; END; %]>
     <option value="">[% l('Please select your mobile carrier') %]</option>
     [% FOR carrier IN carriers.sort('name','region') -%]
     <option value='[% carrier.id | html %]'[%
-        default_carrier == carrier.id ? ' selected="selected"' : ''
+        default_carrier == carrier.id || ahr.sms_carrier == carrier.id ? ' selected="selected"' : ''
     %]>[% carrier.name | html %] ([% carrier.region | html %])</option>
     [% END -%]
 </select>
index f61f5ee..1c66575 100644 (file)
@@ -113,7 +113,7 @@ MACRO draw_form_input(cls, field, path, type, disable) BLOCK;
 %]
   <div class="col-md-3 reg-field-input">
     <input 
-      [% IF type == "email" %]type="text"
+      [% IF type == "email" %]type="text" ng-required="hold_notify_type.email"
       [% ELSE %]type="[% type %]"
       [% END %]
       class="form-control" 
@@ -696,8 +696,9 @@ within the "form" by name for validation.
     <label>{{user_setting_types['opac.default_phone'].label()}}</label>
   </div>
   <div class="col-md-3 reg-field-input">
-    <input 
+    <input ng-required="hold_notify_type.phone" 
       ng-change="field_modified()" 
+      ng-blur="handle_field_changed(user_settings, 'opac.default_phone')"
       type='text' ng-model="user_settings['opac.default_phone']"/>
   </div>
 </div>
@@ -773,8 +774,9 @@ within the "form" by name for validation.
     <label>[% l('Default SMS/Text Number') %]</label>
   </div>
   <div class="col-md-3 reg-field-input">
-    <input 
+    <input ng-required="hold_notify_type.sms" 
       ng-change="field_modified()" ng-model="user_settings['opac.default_sms_notify']"
+      ng-blur="handle_field_changed(user_settings, 'opac.default_sms_notify')"
       type='text'/>
   </div>
 </div>
@@ -785,7 +787,8 @@ within the "form" by name for validation.
   </div>
   <div class="col-md-3 reg-field-input">
     <span class="nullable">
-      <select str-to-int class="form-control" ng-model="user_settings['opac.default_sms_carrier']" ng-options="c.id() as c.name() for c in sms_carriers">
+      <select str-to-int ng-required="user_settings['opac.default_sms_notify']" class="form-control" ng-model="user_settings['opac.default_sms_carrier']" ng-options="c.id() as c.name() for c in sms_carriers"
+      ng-blur="handle_field_changed(user_settings, 'opac.default_sms_carrier')">
         <option value="">Select a Carrier</option>
       </select>
     </span>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_hold_notify_update.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_hold_notify_update.tt2
new file mode 100644 (file)
index 0000000..b80891f
--- /dev/null
@@ -0,0 +1,42 @@
+<div class="modal-header">
+  <button type="button" class="close" ng-click="ok('no-update')"
+    aria-hidden="true">&times;</button>
+  <h4 class="modal-title">[% l('Update Hold Notification Info?') %]</h4>
+</div>
+<div class="modal-body">
+<form name="updateHoldsForm">
+  <div class="row" ng-repeat="f in ch_fields">
+    <div class="col-md-11">
+      <span ng-switch="f.name">
+        <span ng-switch-when="phone_notify">[% l("You have set Notify by Phone to '[_1]'", '{{prettyBool(f.newval)}}') %]</span>
+        <span ng-switch-when="sms_notify">[% l("You have set Notify by SMS to '[_1]'", '{{prettyBool(f.newval)}}') %]</span>
+        <span ng-switch-when="email_notify">[% l("You have set Notify by Email to '[_1]'", '{{prettyBool(f.newval)}}') %]</span>
+        <span ng-switch-when="default_phone">[% l("You have set Default Phone Number to '[_1]'", '{{f.newval}}') %]</span>
+        <span ng-switch-when="default_sms">[% l("You have set Default SMS/Text Number to '[_1]'", '{{f.newval}}') %]</span>
+        <span ng-switch-when="default_sms_carrier_id">[% l("You have set Default SMS Carrier to '[_1]'", '{{prettyCarrier(f.newval)}}') %]</span>
+      </span>
+      <ul style="padding-left:0" ng-if="isNumberCh(f)" class="list-unstyled">
+        <li ng-repeat="(k, h) in f.groups" style="margin-left: 20px">
+          <input id="{{f.name + h[0].id}}" type="checkbox" ng-model="h.isChecked" ng-change="groupChanged(f, k)" style="position: absolute" />
+          <label ng-if="f.newval" for="{{f.name + h[0].id}}" style="padding-left: 1.5em">[% l("'[_1]' is currently used for [_2] hold(s). Update to '[_3]'?", "{{k}}", "{{f.groups[k].length}}", "{{f.newval ? f.newval : '(null)'}}") %]</label>
+          <label ng-if="!f.newval" for="{{f.name + h[0].id}}" style="padding-left: 1.5em">[% l("'[_1]' is currently used for [_2] hold(s). Remove that from the hold(s)?", "{{k}}", "{{f.groups[k].length}}") %]</label>
+        </li>
+      </ul>
+      <ul style="padding-left:0" ng-if="!isNumberCh(f)" class="list-unstyled">
+        <li style="margin-left: 20px">
+          <input id="{{f.name}}" type="checkbox" ng-model="f.isChecked" ng-change="nonGrpChanged(f)" style="position: absolute" />
+          <label ng-if="f.name.includes('sms_carrier')" for="{{f.name}}" style="padding-left: 1.5em">[% l("A carrier other than '[_1]' is currently used in [_2] hold(s). Update to '[_3]'?", "{{prettyCarrier(f.newval)}}", "{{f.affects.length}}", "{{prettyCarrier(f.newval)}}") %]</label>
+          <label ng-if="!f.name.includes('sms_carrier')" for="{{f.name}}" style="padding-left: 1.5em">[% l("[_1] hold(s) have it set to [_2]. Update to [_3]?", "{{f.affects.length}}", "{{prettyBool(f.old)}}", "{{prettyBool(f.newval)}}") %]</label>
+        </li>
+      </ul>
+    </div>
+  </div>
+<div class="modal-footer">
+  <div class="row">
+    <div class="col-md-10 pull-right">
+      <input type="submit" class="btn btn-primary" ng-disabled="chgCt < 1" ng-click="ok()" value="[% l('Update Holds') %]"/>
+      <input type="submit" class="btn btn-warning" ng-click="ok('no-update')" value="[% l('Do Not Update Holds') %]"/>
+    </div>
+  </div>
+
+</div>
index 48700fc..961ad03 100644 (file)
@@ -87,6 +87,7 @@
   <eg-grid-field path='hold.requestor.id' parent-idl-class="ahr" label="[% l('Requestor ID') %]" hidden></eg-grid-field>
   <eg-grid-field path='hold.requestor.usrname' parent-idl-class="ahr" label="[% l('Requestor Username') %]" hidden></eg-grid-field>
   <eg-grid-field path='hold.sms_carrier.name' parent-idl-class="ahr" label="[% l('Notifications SMS Carrier') %]" hidden></eg-grid-field>
+  <eg-grid-field path='hold.sms_carrier' label="[% l('SMS Carrier') %]" hidden></eg-grid-field>
 
   <eg-grid-field path='part.label' parent-idl-class="bmp" label="[% l('Part') %]" hidden></eg-grid-field>
   <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
index c113814..e121fd1 100644 (file)
@@ -119,3 +119,198 @@ function exclude_onchange(checkbox) {
 
     checkbox.form.submit();
 }
+
+// prefs notify update holds-related code
+var hold_notify_prefs = [];
+document.addEventListener("DOMContentLoaded", function() {
+    var form = document.getElementById('hold_notify_form');
+    if (!form) return;
+    var els = form.elements;
+    for (i = 0; i < els.length; i++){
+        var e = els[i];
+        if (e.id.startsWith("opac") || e.id == 'sms_carrier'){
+            hold_notify_prefs.push({
+                name : e.id,
+                oldval : e.type == 'checkbox' ? e.checked : e.value,
+                newval : null
+            });
+            // set required attribute input fields that need it
+            if (e.id.includes('hold_notify') && !e.id.includes('email')){
+                var fieldToReq = e.id.includes('sms') ? 'opac.default_sms_notify' : 'opac.default_phone';
+                toggle_related_required(fieldToReq, e.checked);
+            }
+
+        }
+    }
+    form.addEventListener('submit', addHoldUpdates);
+});
+
+function appendChgInputs(chg){
+    // server-side we'll parse the param as an array where:
+    // [ #oldval, #newval, #name, [#arr of affected holds], #propagateBool ]
+    // this first POST will set the first three, and the confirmation interstitial
+    // the rest.
+    var form = document.getElementById('hold_notify_form');
+
+    var inputold = document.createElement('input');
+    inputold.setAttribute('type', 'hidden');
+    inputold.setAttribute('name', chg.name + '[]');
+    inputold.setAttribute('value', chg.oldval);
+    form.appendChild(inputold);
+
+    var inputnew = document.createElement('input');
+    inputnew.setAttribute('type', 'hidden');
+    inputnew.setAttribute('name', chg.name + '[]');
+    inputnew.setAttribute('value', chg.newval);
+    form.appendChild(inputnew);
+
+    var inputname = document.createElement('input');
+    inputname.setAttribute('type', 'hidden');
+    inputname.setAttribute('name', chg.name + '[]');
+    inputname.setAttribute('value', chg.name);
+    form.appendChild(inputname);
+}
+
+function addHoldUpdates(){
+    paramTranslate(hold_notify_prefs).forEach(function(chg){
+        // only append a change if it actually changed from
+        // what we had server-side originally
+        if (chg.newval != null && chg.oldval != chg.newval) appendChgInputs(chg);
+    });
+    return true;
+}
+
+function chkPh(number){
+    // normalize phone # for comparison, only digits
+    if (number == null || number == undefined) return '';
+    var regex = /[^\d]/g;
+    return number.replace(regex, '');
+}
+
+function idxOfName(n){
+    return hold_notify_prefs.findIndex(function(e){ return e.name === n});
+}
+
+function record_change(evt){
+    var field = evt.target;
+    switch(field.id){
+        case "opac.hold_notify.email":
+            var chg = hold_notify_prefs[idxOfName(field.id)]
+            chg.newval = field.checked;
+            break;
+        case "opac.hold_notify.phone":
+            var chg = hold_notify_prefs[idxOfName(field.id)]
+            chg.newval = field.checked;
+            toggle_related_required('opac.default_phone', chg.newval);
+            break;
+        case "opac.hold_notify.sms":
+            var chg = hold_notify_prefs[idxOfName(field.id)]
+            chg.newval = field.checked;
+            toggle_related_required('opac.default_sms_notify', chg.newval);
+            break;
+        case "sms_carrier": // carrier id string
+            var chg = hold_notify_prefs[idxOfName(field.id)]
+            chg.newval = field.value;
+            break;
+        case "opac.default_phone":
+            var chg = hold_notify_prefs[idxOfName(field.id)]
+            if (chkPh(field.value) != chkPh(chg.oldval)){
+                chg.newval = field.value;
+            }
+            break;
+        case "opac.default_sms_notify":
+            var chg = hold_notify_prefs[idxOfName(field.id)]
+            if (chkPh(field.value) != chkPh(chg.oldval)){
+                chg.newval = field.value;
+                toggle_related_required('sms_carrier', chg.newval ? true : false);
+            }
+            break;
+    }
+}
+
+// there are the param values for the changed fields we expect server-side
+function paramTranslate(chArr){
+    return chArr.map(function(ch){
+        var n = "";
+        switch(ch.name){
+            case "opac.hold_notify.email":
+                n = "email_notify";
+                break;
+            case "opac.hold_notify.phone":
+                n = "phone_notify";
+                break;
+            case "opac.hold_notify.sms":
+                n = "sms_notify";
+                break;
+            case "sms_carrier": // carrier id string
+                n = "default_sms_carrier_id";
+                break;
+            case "opac.default_phone":
+                n = "default_phone";
+                break;
+            case "opac.default_sms_notify":
+                n = "default_sms";
+                break;
+        }
+        return { name : n, oldval : ch.oldval, newval : ch.newval };
+    });
+}
+
+function updateHoldsCheck() {
+    // just dynamically add an input that flags that we have
+    // holds-related updates
+    var form = document.getElementById('hold_updates_form');
+    if (!form) return;
+    var els = form.elements;
+    var isValid = false;
+    for (i = 0; i < els.length; i++){
+        var e = els[i];
+        if (e.type == "checkbox" && e.checked){
+            var flag = document.createElement('input');
+            flag.setAttribute('name', 'hasHoldsChanges');
+            flag.setAttribute('type', 'hidden');
+            flag.setAttribute('value', 1);
+            form.appendChild(flag);
+            isValid = true;
+            return isValid;
+        }
+    }
+
+    alert("No option selected.");
+    return isValid;
+}
+
+function check_sms_carrier(e){
+    var sms_num = e.target;
+    // if sms number has anything in it that's not just whitespace, then require a carrier
+    if (!sms_num.value.match(/\S+/)) return;
+
+    var carrierSelect = document.getElementById('sms_carrier');
+    if (carrierSelect.selectedIndex == 0){
+        carrierSelect.setAttribute("required", "");
+    }
+
+}
+
+function canSubmit(evt){
+   // check hold updates form to see if we have any selected
+   // enable the submit button if we do
+    var form = document.getElementById('hold_updates_form');
+    var submit = form.querySelector('input[type="submit"]');
+    if (!form || !submit) return;
+    var els = form.elements;
+    for (i = 0; i < els.length; i++){
+        var e = els[i];
+        if (e.type == "checkbox" && !e.hidden && e.checked){
+            submit.removeAttribute("disabled");
+            return;
+        }
+    }
+
+    submit.setAttribute("disabled","");
+}
+
+function toggle_related_required(id, isRequired){
+    var input = document.getElementById(id);
+    input.required = isRequired;
+}
index 5949658..72a9ebd 100644 (file)
@@ -1258,6 +1258,8 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
 
     $scope.page_data_loaded = false;
     $scope.hold_notify_type = { phone : null, email : null, sms : null };
+    $scope.hold_notify_observer = {};
+    $scope.hold_rel_contacts = {};
     $scope.clone_id = patronRegSvc.clone_id = $routeParams.clone_id;
     $scope.stage_username = 
         patronRegSvc.stage_username = $routeParams.stage_username;
@@ -1375,6 +1377,7 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         }
 
         extract_hold_notify();
+
         if ($scope.patron.isnew)
             set_new_patron_defaults(prs);
 
@@ -1424,6 +1427,28 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
             }
         });
     }
+    
+    // add watchers for hold notify method prefs
+    $scope.$watch('hold_notify_type.phone', function(newVal, oldVal) {
+        var notifyOpt = $scope.hold_notify_observer['phone'];
+        if (newVal !== null) {
+            notifyOpt.newval = newVal;
+        }
+    });
+
+    $scope.$watch('hold_notify_type.sms', function(newVal, oldVal) {
+        var notifyOpt = $scope.hold_notify_observer['sms'];
+        if (newVal !== null) {
+            notifyOpt.newval = newVal;
+        }
+    });
+    
+    $scope.$watch('hold_notify_type.email', function(newVal, oldVal) {
+        var notifyOpt = $scope.hold_notify_observer['email'];
+        if (newVal !== null) {
+            notifyOpt.newval = newVal;
+        }
+    });
 
     // update the currently displayed field documentation
     $scope.set_selected_field_doc = function(cls, field) {
@@ -1870,15 +1895,45 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
     }
 
     function extract_hold_notify() {
-        var notify = $scope.user_settings['opac.hold_notify'];
-        if (!notify) return;
+        var p = $scope.patron;
+        var notify = $scope.user_settings['opac.hold_notify'] || '';
+
         $scope.hold_notify_type.phone = Boolean(notify.match(/phone/));
         $scope.hold_notify_type.email = Boolean(notify.match(/email/));
         $scope.hold_notify_type.sms = Boolean(notify.match(/sms/));
+
+        // stores original loaded values for comparison later
+        for (var k in $scope.hold_notify_type){
+            var val = $scope.hold_notify_type[k];
+
+            if ($scope.hold_notify_type.hasOwnProperty(k)){
+                $scope.hold_notify_observer[k] = {old : val, newval: null};
+            }
+        }
+
+        // actual value from user
+        $scope.hold_rel_contacts.day_phone = { old: p.day_phone, newval : null };
+        $scope.hold_rel_contacts.other_phone = { old: p.other_phone, newval : null };
+        $scope.hold_rel_contacts.evening_phone = { old: p.evening_phone, newval : null };
+        // from user_settings
+        $scope.hold_rel_contacts.default_phone = { old: $scope.user_settings['opac.default_phone'], newval : null };
+        $scope.hold_rel_contacts.default_sms = { old: $scope.user_settings['opac.default_sms_notify'], newval : null };
+        $scope.hold_rel_contacts.default_sms_carrier_id = { old: $scope.user_settings['opac.default_sms_carrier'], newval : null };
+
+    }
+
+    function normalizePhone(number){
+        // normalize phone # for comparison, only digits
+        if (number == null || number == undefined) return '';
+        
+        var regex = /[^\d]/g;
+        return number.replace(regex, '');
     }
 
     $scope.invalidate_field = function(field) {
-        patronRegSvc.invalidate_field($scope.patron, field);
+        patronRegSvc.invalidate_field($scope.patron, field).then(function() {
+            $scope.handle_field_changed($scope.patron, field);
+        });
     }
 
     address_alert = function(addr) {
@@ -1983,17 +2038,29 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         console.debug('changing field ' + field_name + ' to ' + value);
 
         switch (field_name) {
-            case 'day_phone' : 
+            case 'day_phone' :
+                if (normalizePhone(value) !== normalizePhone($scope.hold_rel_contacts.day_phone.old)){
+                    $scope.hold_rel_contacts.day_phone.newval = value;
+                }
                 if ($scope.patron.day_phone && 
                     $scope.patron.isnew && 
                     $scope.org_settings['patron.password.use_phone']) {
                     $scope.patron.passwd = $scope.patron.day_phone.substr(-4);
                 }
-            case 'evening_phone' : 
+                $scope.dupe_value_changed(field_name, value);
+                break;
+            case 'evening_phone' :
+                if (normalizePhone(value) !== normalizePhone($scope.hold_rel_contacts.evening_phone.old)){
+                    $scope.hold_rel_contacts.evening_phone.newval = value;
+                }
+                $scope.dupe_value_changed(field_name, value);
+                break;
             case 'other_phone' : 
+                if (normalizePhone(value) !== normalizePhone($scope.hold_rel_contacts.other_phone.old)){
+                    $scope.hold_rel_contacts.other_phone.newval = value;
+                }
                 $scope.dupe_value_changed(field_name, value);
                 break;
-
             case 'ident_value':
             case 'ident_value2':
                 $scope.dupe_value_changed('ident', value);
@@ -2030,6 +2097,21 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
                 $scope.barcode_changed(value);
                 apply_username_regex();
                 break;
+            case 'opac.default_phone':
+                if (normalizePhone(value) !== normalizePhone($scope.hold_rel_contacts.default_phone.old)){
+                    $scope.hold_rel_contacts.default_phone.newval = value;
+                }
+                break;
+            case 'opac.default_sms_notify':
+                if (normalizePhone(value) !== normalizePhone($scope.hold_rel_contacts.default_sms.old)){
+                    $scope.hold_rel_contacts.default_sms.newval = value;
+                }
+                break;
+            case 'opac.default_sms_carrier':
+                if (value !== $scope.hold_rel_contacts.default_sms_carrier_id.old){
+                    $scope.hold_rel_contacts.default_sms_carrier_id.newval = value;
+                }
+                break;
         }
     }
 
@@ -2171,11 +2253,15 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
                 var ids = $scope.patron.groups.map(function(g) {return g.id()});
                 return patronRegSvc.apply_secondary_groups(updated_user.id(), ids)
             }
-
             return $q.when();
 
+        }).then(findChangedFieldsAffectedHolds)
+        .then(function(changed_fields_plus_holds) {
+            var needModal = changed_fields_plus_holds[0] && changed_fields_plus_holds[0].length > 0;
+            return needModal
+                ? $scope.update_holds_notify_modal(changed_fields_plus_holds[0])
+                : $q.when(); // nothing changed, continue
         }).then(function() {
-
             if (updated_user) {
                 egWorkLog.record(
                     $scope.patron.isnew
@@ -2219,7 +2305,254 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
             }
         });
     }
+    
+    var phone_inputs = ["day_phone", "evening_phone","other_phone", "default_phone"];
+    var sms_inputs = ["default_sms", "default_sms_carrier_id"];
+    var method_prefs = ["sms_notify", "phone_notify", "email_notify"];
+    var groupBy = function(xs, key){
+        return xs.reduce(function(rv, x){
+            (rv[x[key]] = rv[x[key]] || []).push(x);
+            return rv;
+        }, {});
+    };
+
+    function findChangedFieldsAffectedHolds(){
+    
+        var changed_hold_fields = [];
+
+        var default_phone_changed = false;
+        var default_sms_carrier_changed = false;
+        var default_sms_changed = false;
+        for (var c in $scope.hold_rel_contacts){
+            var newinput = $scope.hold_rel_contacts[c].newval;
+            if ($scope.hold_rel_contacts.hasOwnProperty(c)
+                && newinput !== null // null means user has not provided a value in this session
+                && newinput != $scope.hold_rel_contacts[c].old){
+                var changed = $scope.hold_rel_contacts[c];
+                changed.name = c;
+                changed.isChecked = false;
+                changed_hold_fields.push(changed);
+                if (c === 'default_phone') default_phone_changed = true;
+                if (c === 'default_sms_carrier_id') default_sms_carrier_changed = true;
+                if (c === 'default_sms') default_sms_changed = true;
+            }
+        }
 
+        for (var c in $scope.hold_notify_observer){
+            var newinput = $scope.hold_notify_observer[c].newval;
+            if ($scope.hold_notify_observer.hasOwnProperty(c)
+                && newinput !== null // null means user has not provided a value in this session
+                && newinput != $scope.hold_notify_observer[c].old){
+                var changed = $scope.hold_notify_observer[c];
+                changed.name = c + "_notify";
+                changed.isChecked = false;
+                changed_hold_fields.push(changed);
+
+                // if we're turning on phone notifications, offer to update to the
+                // current default number
+                if (c === 'phone' && $scope.user_settings['opac.default_phone'] && newinput && !default_phone_changed) {
+                    changed_hold_fields.push({
+                        name: 'default_phone',
+                        old: 'nosuch',
+                        newval: $scope.user_settings['opac.default_phone'],
+                        isChecked: false
+                    });
+                }
+                // and similarly for SMS
+                if (c === 'sms' && $scope.user_settings['opac.default_sms_carrier'] && newinput && !default_sms_carrier_changed) {
+                    changed_hold_fields.push({
+                        name: 'default_sms_carrier_id',
+                        old: -1,
+                        newval: $scope.user_settings['opac.default_sms_carrier'],
+                        isChecked: false
+                    });
+                }
+                if (c === 'sms' && $scope.user_settings['opac.default_sms_notify'] && newinput && !default_sms_changed) {
+                    changed_hold_fields.push({
+                        name: 'default_sms',
+                        old: 'nosuch',
+                        newval: $scope.user_settings['opac.default_sms_notify'],
+                        isChecked: false
+                    });
+                }
+            }
+        }
+
+        var promises = [];
+        angular.forEach(changed_hold_fields, function(c){
+            promises.push(egCore.net.request('open-ils.circ',
+            'open-ils.circ.holds.retrieve_by_notify_staff',
+            egCore.auth.token(),
+            $scope.patron.id,
+            c.name.includes('notify') || c.name.includes('carrier') ? c.old : c.newval,
+            c.name)
+                .then(function(affected_holds){
+                    if(!affected_holds || affected_holds.length < 1){
+                        // no holds affected - remove change from list
+                        var p = changed_hold_fields.indexOf(c);
+                        changed_hold_fields.splice(p, 1);
+                    } else {
+                        c.affects = affected_holds;
+                        //c.groups = {};
+                        //angular.forEach(c.affects, function(h){
+                        //    c.groups[]
+                        //});
+                        if (!c.name.includes("notify")){
+                            if (c.name === "default_sms_carrier_id") {
+                                c.groups = groupBy(c.affects,'sms_carrier');
+                            } else {
+                                c.groups = groupBy(c.affects, c.name.includes('_phone') ? 'phone_notify':'sms_notify');
+                            }
+                        }
+                    }
+                    return $q.when(changed_hold_fields);
+                })
+            );
+        });
+
+        return $q.all(promises);
+    }
+
+    $scope.update_holds_notify_modal = function(changed_hold_fields){
+        // open modal after-save, pre-reload modal to deal with updated hold notification stuff
+        if ($scope.patron.isnew || changed_hold_fields.length < 1){
+            return $q.when();
+        }
+
+        return $uibModal.open({
+            templateUrl: './circ/patron/t_hold_notify_update',
+            backdrop: 'static',
+            controller:
+                       ['$scope','$uibModalInstance','changed_fields','patron','carriers','def_carrier_id','default_phone','default_sms',
+                function($scope , $uibModalInstance , changed_fields , patron,  carriers,  def_carrier_id , default_phone , default_sms) {
+                // local modal scope
+                $scope.ch_fields = changed_fields;
+                $scope.focusMe = true;
+                $scope.ok = function(msg) {
+
+                    // Need to do this so the page will reload automatically
+                    if (msg == 'no-update') return $uibModalInstance.close();
+
+                    //var selectedChanges = $scope.changed_fields.filter(function(c) {
+                    //    return c.isChecked;
+                    //});
+                    var selectedChanges = [];
+                    angular.forEach($scope.ch_fields, function(f){
+                        if (f.name == 'phone_notify' && f.newval && f.isChecked) {
+                            // convert to default_phone change
+                            f.sel_hids = f.affects.map(function(h){ return h.id});
+                            f.newval = default_phone;
+                            f.name = 'default_phone';
+                            selectedChanges.push(f);
+                        } else if (f.name == 'sms_notify' && f.newval && f.isChecked) {
+                            // convert to default_sms change
+                            f.sel_hids = f.affects.map(function(h){ return h.id});
+                            f.newval = default_sms;
+                            f.name = 'default_sms';
+                            selectedChanges.push(f);
+                        } else if (f.name.includes('notify') || f.name.includes('carrier')){
+                            if (f.isChecked){
+                                f.sel_hids = f.affects.map(function(h){ return h.id});
+                                selectedChanges.push(f);
+                            }
+                        } else {
+                            // this is the sms or phone, so look in the groups obj
+                            f.sel_hids = [];
+                            for (var k in f.groups){
+                                if (f.groups.hasOwnProperty(k)){
+                                    var sel_holds = f.groups[k].filter(function(h){
+                                        return h.isChecked;
+                                    });
+                                    
+                                    var hids = sel_holds.map(function(h){ return h.id});
+                                    f.sel_hids.push.apply(f.sel_hids, hids);
+                                }
+                            }
+
+                            if (f.sel_hids.length > 0) selectedChanges.push(f);
+                        }
+                    });
+
+
+                    // call method to update holds for each change
+                    var chain = $q.when();
+                    angular.forEach(selectedChanges, function(c){
+                        var carrierId = c.name.includes('default_sms') ? Number(def_carrier_id) : null;
+                        chain = chain.then(function() {
+                            return egCore.net.request('open-ils.circ',
+                                    'open-ils.circ.holds.batch_update_holds_by_notify_staff', egCore.auth.token(),
+                                    patron.id,
+                                    c.sel_hids,
+                                    c.old, // TODO: for number changes, old val is effectively moot
+                                    c.newval,
+                                    c.name,
+                                    carrierId).then(function(okList){ console.log(okList) });
+                        });
+                    });
+
+                    // carry out the updates and close modal
+                    chain.finally(function(){ $uibModalInstance.close() });
+                }
+
+                $scope.cancel = function () { $uibModalInstance.dismiss() }
+
+                $scope.isNumberCh = function(c){
+                    return !(c.name.includes('notify') || c.name.includes('carrier'));
+                }
+
+                $scope.chgCt = 0;
+                $scope.groupChanged = function(ch_f, grpK){
+                    var holdArr = ch_f.groups[grpK];
+                    if (holdArr && holdArr.length > 0){
+                        angular.forEach(holdArr, function(h){
+                            if (h.isChecked) { h.isChecked = !h.isChecked; $scope.chgCt-- }
+                            else { h.isChecked = true; $scope.chgCt++ }
+                        });
+                    }
+                }
+                
+                $scope.nonGrpChanged = function(field_ch){
+                    if (field_ch.isChecked) $scope.chgCt++;
+                    else $scope.chgCt--;
+                };
+
+                // use this function as a keydown handler on form
+                // elements that should not submit the form on enter.
+                $scope.preventSubmit = function($event) {
+                    if ($event.keyCode == 13)
+                        $event.preventDefault();
+                }
+
+                $scope.prettyCarrier = function(carrierId){
+                    var sms_carrierObj = carriers.find(function(c){ return c.id == carrierId});
+                    return sms_carrierObj.name;
+                };
+                $scope.prettyBool = function(v){
+                    return v ? 'YES' : 'NO';
+                };
+            }],
+            resolve : {
+                    changed_fields : function(){ return changed_hold_fields },
+                    patron : function(){ return $scope.patron },
+                    def_carrier_id : function(){
+                                       var d = $scope.hold_rel_contacts.default_sms_carrier_id;
+                                       return d.newval ? d.newval : d.old;
+                                    },
+                    default_phone : function() {
+                                        return ($scope.hold_rel_contacts.default_phone.newval) ?
+                                                    $scope.hold_rel_contacts.default_phone.newval :
+                                                    $scope.hold_rel_contacts.default_phone.old;
+                                    },
+                    default_sms : function() {
+                                        return ($scope.hold_rel_contacts.default_sms.newval) ?
+                                                    $scope.hold_rel_contacts.default_sms.newval :
+                                                    $scope.hold_rel_contacts.default_sms.old;
+                                    },
+                    carriers : function(){ return $scope.sms_carriers.map(function(c){ return egCore.idl.toHash(c) }) }
+                }
+        }).result;
+    }
+    
     $scope.edit_passthru.print = function() {
         var print_data = {patron : $scope.patron}