SMS texting
authorJason Etheridge <jason@esilibrary.com>
Thu, 22 Sep 2011 13:17:50 +0000 (09:17 -0400)
committerJason Etheridge <jason@esilibrary.com>
Thu, 5 Jan 2012 17:46:01 +0000 (12:46 -0500)
For SMS functionality we're basically converting a carrier and a phone number
into an email address and sending via SMTP like other email notifications.
There is administrative UI for configuring these mappings:

    Admin -> Server Settings -> SMS Carriers

The permission ADMIN_SMS_CARRIER is needed to use this interface. In a stock
Evergreen installation, this is given to the Global Administrator group.

In this interface, any occurance of "$number" in the Email Gateway field will
change into the pertinent SMS phone number when the mapping gets used. We're not
doing anything fancy here for verifying or munging phone numbers, but you could
do munging in the pertinent SMS Action/Trigger templates, or better, contribute
code for the OPAC templates and/or the utility method get_sms_gateway_email in
Trigger/Reactor.pm.

Our set of stock mappings comes from
http://en.wikipedia.org/wiki/List_of_SMS_gateways

There's no automated process for keeping this up-to-date, though some volunteer
with a Wikipedia account could add the page to their watchlist for changes and
let us know if something needs changing.

Set the org unit setting "SMS: Enable features that send SMS text messages."
(database name "sms.enable") to True to expose the following:

  * SMS notification widgets in the TT-OPAC hold placement interface (to notify
    when a hold is ready for pickup)

  * "(SMS)" links next to call numbers on the TT-OPAC record details page. The
    interface spawned by these links require user authentication by default, but
    this can be changed by setting the org unit setting "SMS: Disable auth
    requirement for texting call numbers." to True. The database name for that
    setting is "sms.disable_authentication_requirement.callnumbers".

  * New options in TT-OPAC My Account -> Account Preferences -> Notification
    Preferences (we also add in some missing prefs for holds outside of SMS)

There are two pertinent Action/Trigger templates under Admin -> Local
Administration -> Notifications / Action Triggers:

  * SMS Call Number
  * Hold Ready for Pickup SMS Notification

Don't disable these. We rely on the sms.enable YAOUS as the master on/off
switch.

Also, it's probably best to have just one sms.enable for the whole consortium.
If you mix and match on/off settings here, then user preferences for SMS can get
blown away if the user updates their settings in the TT-OPAC at an org where SMS
is disabled.  Hrmm, the same would probably happen if the user jumps between the
TT-OPAC and the original JS-PAC.

Signed-off-by: Jason Etheridge <jason@esilibrary.com>
18 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendSMS.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql [new file with mode: 0644]
Open-ILS/src/templates/conify/global/config/sms_carrier.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/myopac/prefs_notify.tt2
Open-ILS/src/templates/opac/parts/place_hold.tt2
Open-ILS/src/templates/opac/parts/record/copy_table.tt2
Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/sms_cn.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/conify/global/config/sms_carrier.js [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul

index 7e0b301..041d408 100644 (file)
@@ -725,6 +725,25 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
         </permacrud>
        </class>
 
+       <class id="csc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::sms_carrier" oils_persist:tablename="config.sms_carrier" reporter:label="SMS Carrier" oils_persist:field_safe="true">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.sms_carrier_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Region" name="region" reporter:datatype="text" oils_persist:i18n="true"/>
+                       <field reporter:label="Name" name="name" reporter:datatype="text" oils_persist:i18n="true"/>
+            <field reporter:label="Active" name="active" reporter:datatype="bool" oils_persist:i18n="true"/>
+                       <field reporter:label="Email Gateway" name="email_gateway" reporter:datatype="text" oils_persist:i18n="true"/>
+               </fields>
+               <links/>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="ADMIN_SMS_CARRIER" global_required="true"/>
+                <retrieve/>
+                <update permission="ADMIN_SMS_CARRIER" global_required="true"/>
+                <delete permission="ADMIN_SMS_CARRIER" global_required="true"/>
+            </actions>
+        </permacrud>
+       </class>
+
        <class id="mra" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="metabib::record_attr" oils_persist:tablename="metabib.record_attr" reporter:label="SVF Record Attribute" oils_persist:field_safe="true">
                <fields oils_persist:primary="id">
                        <field reporter:label="Record ID" name="id" reporter:datatype="id" oils_obj:required="true"/>
@@ -4496,6 +4515,8 @@ SELECT  usr,
                        <field reporter:label="Holdable Formats (for M-type hold)" name="holdable_formats" reporter:datatype="text"/>
                        <field reporter:label="Hold ID" name="id" reporter:datatype="id" />
                        <field reporter:label="Notifications Phone Number" name="phone_notify" reporter:datatype="text"/>
+                       <field reporter:label="Notifications SMS Number" name="sms_notify" reporter:datatype="text"/>
+                       <field reporter:label="Notifications SMS Carrier" name="sms_carrier" reporter:datatype="text"/>
                        <field reporter:label="Pickup Library" name="pickup_lib" reporter:datatype="org_unit"/>
                        <field reporter:label="Last Targeting Date/Time" name="prev_check_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Requesting Library" name="request_lib" reporter:datatype="org_unit"/>
@@ -4536,6 +4557,7 @@ SELECT  usr,
                        <link field="bib_rec" reltype="might_have" key="id" map="" class="rhrr"/>
                        <link field="cancel_cause" reltype="might_have" key="id" map="" class="ahrcc"/>
                        <link field="notes" reltype="has_many" key="hold" map="" class="ahrn"/>
+                       <link field="sms_carrier" reltype="might_have" key="code" map="" class="csc"/>
                </links>
        </class>
        <class id="alhr" controller="open-ils.cstore" oils_obj:fieldmapper="action::last_hold_request" reporter:label="Last Captured Hold Request" oils_persist:readonly="true">
index a018e3f..e1d2c82 100644 (file)
@@ -1241,6 +1241,75 @@ sub create_update_asset_copy_template {
     $e->commit and return $retval;
 }
 
+__PACKAGE__->register_method(
+    method      => "acn_sms_msg",
+    api_name    => "open-ils.cat.acn.send_sms_text",
+    signature   => q^
+        Send an SMS text from an A/T template for specified call numbers.
+
+        First parameter is null or an auth token (whether a null is allowed
+        depends on the sms.disable_authentication_requirement.callnumbers OU
+        setting).
+
+        Second parameter is the id of the context org.
+
+        Third parameter is the code of the SMS carrier from the
+        config.sms_carrier table.
+
+        Fourth parameter is the SMS number.
+
+        Fifth parameter is the ACN id's to target, though currently only the
+        first ACN is used by the template (and the UI is only sending one).
+    ^
+);
+
+sub acn_sms_msg {
+    my($self, $conn, $auth, $org_id, $carrier, $number, $target_ids) = @_;
+
+    my $sms_enable = $U->ou_ancestor_setting_value(
+        $org_id || $U->fetch_org_tree->id,
+        'sms.enable'
+    );
+    # We could maybe make a Validator for this on the templates
+    if (! $U->is_true($sms_enable)) {
+        return -1;
+    }
+
+    my $disable_auth = $U->ou_ancestor_setting_value(
+        $org_id || $U->fetch_org_tree->id,
+        'sms.disable_authentication_requirement.callnumbers'
+    );
+
+    my $e = new_editor(
+        (defined $auth)
+        ? (authtoken => $auth, xact => 1)
+        : (xact => 1)
+    );
+    return $e->event unless $disable_auth || $e->checkauth;
+
+    my $targets = $e->batch_retrieve_asset_call_number($target_ids);
+
+    $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
+                  # simply making this method authoritative because of weirdness
+                  # with transaction handling in A/T code that causes rollback
+                  # failure down the line if handling many targets
+
+    return undef unless @$targets;
+    return $U->fire_object_event(
+        undef,                    # event_def
+        'acn.format.sms_text',    # hook
+        $targets,
+        $org_id,
+        undef,                    # granularity
+        {                         # user_data
+            sms_carrier => $carrier,
+            sms_notify => $number
+        }
+    );
+}
+
+
+
 1;
 
 # vi:et:ts=4:sw=4
index 1bd0e43..5cf5488 100644 (file)
@@ -273,6 +273,32 @@ $_TT_helpers = {
         return $str ? (new XML::LibXML)->parse_string($str) : undef;
     },
 
+    # returns an email addresses derived from sms_carrier and sms_notify
+    get_sms_gateway_email => sub {
+        my $sms_carrier = shift;
+        my $sms_notify = shift;
+
+        if (! defined $sms_notify || $sms_notify eq '') {
+            return '';
+        }
+
+        my $query = {
+            select => {'csc' => ['id','name','email_gateway']},
+            from => 'csc',
+            where => {id => $sms_carrier}
+        };
+        my $carriers = new_editor()->json_query($query);
+
+        my @addresses = ();
+        foreach my $carrier ( @{ $carriers } ) {
+            my $address = $carrier->{email_gateway};
+            $address =~ s/\$number/$sms_notify/g;
+            push @addresses, $address;
+        }
+
+        return join(',',@addresses);
+    },
+
     unapi_bre => sub {
         my ($bre_id, $unapi_args) = @_;
         $unapi_args ||= {};
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendSMS.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendSMS.pm
new file mode 100644 (file)
index 0000000..de775ac
--- /dev/null
@@ -0,0 +1,21 @@
+package OpenILS::Application::Trigger::Reactor::SendSMS;
+use strict; use warnings;
+use Error qw/:try/;
+use Data::Dumper;
+use Email::Send;
+use Email::Simple;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Application::Trigger::Reactor;
+use OpenSRF::Utils::Logger qw/:logger/;
+use Encode;
+$Data::Dumper::Indent = 0;
+
+use base 'OpenILS::Application::Trigger::Reactor::SendEmail';
+
+# This module is just another name for SendEmail, as a way to get around the
+# "ev_def_owner_hook_val_react_clean_delay_once" index/constraint on the table
+# action.event_definition.  The template fed to SendSMS is responsible for
+# using helpers.get_sms_email_gateway to, for example,  convert .sms_carrier
+# and .sms_notify off of a hold into an email address.
+
+1;
index 5091c50..b19b8bc 100644 (file)
@@ -21,6 +21,7 @@ use OpenILS::WWW::EGCatLoader::Account;
 use OpenILS::WWW::EGCatLoader::Search;
 use OpenILS::WWW::EGCatLoader::Record;
 use OpenILS::WWW::EGCatLoader::Container;
+use OpenILS::WWW::EGCatLoader::SMS;
 
 my $U = 'OpenILS::Application::AppUtils';
 
@@ -127,6 +128,12 @@ sub load {
         );
     }
 
+    my $org_unit = $self->cgi->param('loc') || $self->ctx->{aou_tree}->()->id;
+    my $skip_sms_auth = $self->ctx->{get_org_setting}->($org_unit, 'sms.disable_authentication_requirement.callnumbers');
+    if ($skip_sms_auth) {
+        return $self->load_sms_cn if $path =~ m|opac/sms_cn|;
+    }
+
     # ----------------------------------------------------------------
     #  Everything below here requires authentication
     # ----------------------------------------------------------------
@@ -152,6 +159,7 @@ sub load {
     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 if $path =~ m|opac/myopac/prefs|;
+    return $self->load_sms_cn if $path =~ m|opac/sms_cn|;
 
     return Apache2::Const::OK;
 }
index 116f8c5..28fe7c8 100644 (file)
@@ -174,9 +174,46 @@ sub load_myopac_prefs_notify {
     $user_prefs = $self->update_optin_prefs($user_prefs)
         if $self->cgi->request_method eq 'POST';
 
-    $self->ctx->{opt_in_settings} = $user_prefs; 
+    $self->ctx->{opt_in_settings} = $user_prefs;
 
-    return Apache2::Const::OK;
+    my %settings;
+    my $set_map = $self->ctx->{user_setting_map};
+    foreach my $key (qw/
+        opac.default_phone
+        opac.default_sms_notify
+    /) {
+        my $val = $self->cgi->param($key);
+        $settings{$key}= $val unless $$set_map{$key} eq $val;
+    }
+
+    my $key = 'opac.default_sms_carrier';
+    my $val = $self->cgi->param('sms_carrier');
+    $settings{$key}= $val unless $$set_map{$key} eq $val;
+
+    $key = 'opac.hold_notify';
+    my @notify_methods = ();
+    if ($self->cgi->param($key . ".email") eq 'on') {
+        push @notify_methods, "email";
+    }
+    if ($self->cgi->param($key . ".phone") eq 'on') {
+        push @notify_methods, "phone";
+    }
+    if ($self->cgi->param($key . ".sms") eq 'on') {
+        push @notify_methods, "sms";
+    }
+    $val = join("|",@notify_methods);
+    $settings{$key}= $val unless $$set_map{$key} eq $val;
+
+    # Send the modified settings off to be saved
+    $U->simplereq(
+        'open-ils.actor', 
+        'open-ils.actor.patron.settings.update',
+        $self->editor->authtoken, undef, \%settings);
+
+    # re-fetch user prefs 
+    $self->ctx->{updated_user_settings} = \%settings;
+    return $self->_load_user_with_prefs || Apache2::Const::OK;
 }
 
 sub fetch_optin_prefs {
@@ -331,7 +368,7 @@ sub load_myopac_prefs_settings {
             $settings{$key} = undef if $$set_map{$key};
         }
     }
-    
+
     # Send the modified settings off to be saved
     $U->simplereq(
         'open-ils.actor', 
@@ -537,6 +574,14 @@ sub load_place_hold {
 
     $ctx->{hold_type} = $cgi->param('hold_type');
     $ctx->{default_pickup_lib} = $e->requestor->home_ou; # unless changed below
+    $ctx->{email_notify} = $cgi->param('email_notify');
+    if ($cgi->param('phone_notify_checkbox')) {
+        $ctx->{phone_notify} = $cgi->param('phone_notify');
+    }
+    if ($cgi->param('sms_notify_checkbox')) {
+        $ctx->{sms_notify} = $cgi->param('sms_notify');
+        $ctx->{sms_carrier} = $cgi->param('sms_carrier');
+    }
 
     return $self->generic_redirect unless @targets;
 
@@ -550,12 +595,57 @@ sub load_place_hold {
         ) or return Apache2::Const::HTTP_BAD_REQUEST;
 
         $ctx->{default_pickup_lib} = $ctx->{patron_recipient}->home_ou;
+    } else {
+        $ctx->{staff_recipient} = $self->editor->retrieve_actor_user([
+            $e->requestor->id,
+            {
+                flesh => 1,
+                flesh_fields => {
+                    au => ['settings']
+                }
+            }
+        ]) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+    my $user_setting_map = {
+        map { $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) }
+            @{
+                $ctx->{patron_recipient}
+                ? $ctx->{patron_recipient}->settings
+                : $ctx->{staff_recipient}->settings
+            }
+    };
+    $ctx->{user_setting_map} = $user_setting_map;
+
+    my $default_notify = $$user_setting_map{'opac.hold_notify'} || '';
+    if ($default_notify =~ /email/) {
+        $ctx->{default_email_notify} = 'checked';
+    } else {
+        $ctx->{default_email_notify} = '';
+    }
+    if ($default_notify =~ /phone/) {
+        $ctx->{default_phone_notify} = 'checked';
+    } else {
+        $ctx->{default_phone_notify} = '';
+    }
+    if ($default_notify =~ /sms/) {
+        $ctx->{default_sms_notify} = 'checked';
+    } else {
+        $ctx->{default_sms_notify} = '';
     }
 
     my $request_lib = $e->requestor->ws_ou;
     my @hold_data;
     $ctx->{hold_data} = \@hold_data;
 
+    sub data_filler {
+        my $hdata = shift;
+        if ($ctx->{email_notify}) { $hdata->{email_notify} = $ctx->{email_notify}; }
+        if ($ctx->{phone_notify}) { $hdata->{phone_notify} = $ctx->{phone_notify}; }
+        if ($ctx->{sms_notify}) { $hdata->{sms_notify} = $ctx->{sms_notify}; }
+        if ($ctx->{sms_carrier}) { $hdata->{sms_carrier} = $ctx->{sms_carrier}; }
+        return $hdata;
+    }
+
     my $type_dispatch = {
         T => sub {
             my $recs = $e->batch_retrieve_biblio_record_entry(\@targets, {substream => 1});
@@ -589,12 +679,12 @@ sub load_place_hold {
                     $part_required = 1 if $np_copies->[0]->{count} == 0;
                 }
 
-                push(@hold_data, {
-                    target => $rec, 
+                push(@hold_data, data_filler({
+                    target => $rec,
                     record => $rec,
                     parts => $parts,
                     part_required => $part_required
-                });
+                }));
             }
         },
         V => sub {
@@ -607,7 +697,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($vol) = grep {$_->id eq $id} @$vols;
-                push(@hold_data, {target => $vol, record => $vol->record});
+                push(@hold_data, data_filler({target => $vol, record => $vol->record}));
             }
         },
         C => sub {
@@ -623,7 +713,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($copy) = grep {$_->id eq $id} @$copies;
-                push(@hold_data, {target => $copy, record => $copy->call_number->record});
+                push(@hold_data, data_filler({target => $copy, record => $copy->call_number->record}));
             }
         },
         I => sub {
@@ -638,7 +728,7 @@ sub load_place_hold {
 
             for my $id (@targets) { 
                 my ($iss) = grep {$_->id eq $id} @$isses;
-                push(@hold_data, {target => $iss, record => $iss->subscription->record_entry});
+                push(@hold_data, data_filler({target => $iss, record => $iss->subscription->record_entry}));
             }
         }
         # ...
@@ -758,10 +848,10 @@ sub attempt_hold_placement {
         my $breq = $bses->request( 
             $method, 
             $e->authtoken, 
-            {   patronid => $usr, 
+            data_filler({   patronid => $usr,
                 pickup_lib => $pickup_lib, 
                 hold_type => $hold_type
-            }
+            }),
             \@create_targets
         );
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/SMS.pm
new file mode 100644 (file)
index 0000000..a1288a6
--- /dev/null
@@ -0,0 +1,57 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use OpenILS::Event;
+use OpenSRF::Utils::JSON;
+use Data::Dumper;
+$Data::Dumper::Indent = 0;
+use DateTime;
+my $U = 'OpenILS::Application::AppUtils';
+
+sub load_sms_cn {
+    my $self = shift;
+    my $ctx = $self->ctx;
+    my $gos = $ctx->{get_org_setting};
+    my $e = $self->editor;
+    my $cgi = $self->cgi;
+
+    my $org_unit = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
+
+    $self->_load_user_with_prefs();
+
+    $ctx->{page} = 'sms_cn';
+    $ctx->{sms_carrier} = $cgi->param('sms_carrier');
+    $ctx->{sms_notify} = $cgi->param('sms_notify');
+    $ctx->{copy_id} = $cgi->param('copy_id');
+    $ctx->{query} = $cgi->param('query');
+
+    my $acn_results = $e->json_query({
+        select => {
+            acp => ['call_number']
+        },
+        from => 'acp',
+        where => {id => $ctx->{copy_id}}
+    });
+
+    my $acn_ids = [map { $_->{call_number} } @$acn_results];
+    $ctx->{acn_ids} = $acn_ids;
+
+    my $resp = $U->simplereq('open-ils.cat', 'open-ils.cat.acn.send_sms_text',
+        $e->authtoken, $org_unit,
+        $ctx->{sms_carrier}, $ctx->{sms_notify},
+        $acn_ids
+    );
+
+    $ctx->{event} = $resp;
+
+    $ctx->{orig_params} = $cgi->Vars;
+
+    return Apache2::Const::OK;
+    return $self->generic_redirect;
+}
+
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sms_carriers.sql
new file mode 100644 (file)
index 0000000..ee1e7a6
--- /dev/null
@@ -0,0 +1,1395 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.settings_group (name, label) VALUES
+    (
+        'sms',
+        oils_i18n_gettext(
+            'sms',
+            'SMS Text Messages',
+            'csg',
+            'label'
+        )
+    )
+;
+
+INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
+    (
+        'sms.enable',
+        'sms',
+        oils_i18n_gettext(
+            'sms.enable',
+            'Enable features that send SMS text messages.',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'sms.enable',
+            'Current features that use SMS include hold-ready-for-pickup notifications and a "Send Text" action for call numbers in the OPAC. If this setting is not enabled, the SMS options will not be offered to the user.',
+            'coust',
+            'description'
+        ),
+        'bool'
+    )
+    ,(
+        'sms.disable_authentication_requirement.callnumbers',
+        'sms',
+        oils_i18n_gettext(
+            'sms.disable_authentication_requirement.callnumbers',
+            'Disable auth requirement for texting call numbers.',
+            'coust',
+            'label'
+        ),
+        oils_i18n_gettext(
+            'sms.disable_authentication_requirement.callnumbers',
+            'Disable authentication requirement for sending call number information via SMS from the OPAC.',
+            'coust',
+            'description'
+        ),
+        'bool'
+    )
+;
+
+CREATE TABLE config.sms_carrier (
+    id              SERIAL PRIMARY KEY,
+    region          TEXT,
+    name            TEXT,
+    email_gateway   TEXT,
+    active          BOOLEAN DEFAULT TRUE
+);
+
+ALTER TABLE action.hold_request ADD COLUMN sms_notify TEXT;
+ALTER TABLE action.hold_request ADD COLUMN sms_carrier INT REFERENCES config.sms_carrier (id);
+ALTER TABLE action.hold_request ADD CONSTRAINT sms_check CHECK (
+    sms_notify IS NULL
+    OR sms_carrier IS NOT NULL -- and implied sms_notify IS NOT NULL
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype,fm_class) VALUES (
+    'opac.default_sms_carrier',
+    'sms',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_sms_carrier',
+        'Default SMS/Text Carrier',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_sms_carrier',
+        'Default SMS/Text Carrier',
+        'cust',
+        'description'
+    ),
+    'link',
+    'csc'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'opac.default_sms_notify',
+    'sms',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_sms_notify',
+        'Default SMS/Text Number',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_sms_notify',
+        'Default SMS/Text Number',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'opac.default_phone',
+    'opac',
+    TRUE,
+    oils_i18n_gettext(
+        'opac.default_phone',
+        'Default Phone Number',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'opac.default_phone',
+        'Default Phone Number',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+SELECT setval( 'config.sms_carrier_id_seq', 1000 );
+INSERT INTO config.sms_carrier VALUES
+
+    -- Testing
+    (
+        1,
+        oils_i18n_gettext(
+            1,
+            'Local',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            1,
+            'Test Carrier',
+            'csc',
+            'name'
+        ),
+        'opensrf+$number@localhost',
+        FALSE
+    ),
+
+    -- Canada & USA
+    (
+        2,
+        oils_i18n_gettext(
+            2,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            2,
+            'Rogers Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@pcs.rogers.com',
+        TRUE
+    ),
+    (
+        3,
+        oils_i18n_gettext(
+            3,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            3,
+            'Rogers Wireless (Alternate)',
+            'csc',
+            'name'
+        ),
+        '1$number@mms.rogers.com',
+        TRUE
+    ),
+    (
+        4,
+        oils_i18n_gettext(
+            4,
+            'Canada & USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            4,
+            'Telus Mobility',
+            'csc',
+            'name'
+        ),
+        '$number@msg.telus.com',
+        TRUE
+    ),
+
+    -- Canada
+    (
+        5,
+        oils_i18n_gettext(
+            5,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            5,
+            'Koodo Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@msg.telus.com',
+        TRUE
+    ),
+    (
+        6,
+        oils_i18n_gettext(
+            6,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            6,
+            'Fido',
+            'csc',
+            'name'
+        ),
+        '$number@fido.ca',
+        TRUE
+    ),
+    (
+        7,
+        oils_i18n_gettext(
+            7,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            7,
+            'Bell Mobility & Solo Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@txt.bell.ca',
+        TRUE
+    ),
+    (
+        8,
+        oils_i18n_gettext(
+            8,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            8,
+            'Bell Mobility & Solo Mobile (Alternate)',
+            'csc',
+            'name'
+        ),
+        '$number@txt.bellmobility.ca',
+        TRUE
+    ),
+    (
+        9,
+        oils_i18n_gettext(
+            9,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            9,
+            'Aliant',
+            'csc',
+            'name'
+        ),
+        '$number@sms.wirefree.informe.ca',
+        TRUE
+    ),
+    (
+        10,
+        oils_i18n_gettext(
+            10,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            10,
+            'PC Telecom',
+            'csc',
+            'name'
+        ),
+        '$number@mobiletxt.ca',
+        TRUE
+    ),
+    (
+        11,
+        oils_i18n_gettext(
+            11,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            11,
+            'SaskTel',
+            'csc',
+            'name'
+        ),
+        '$number@sms.sasktel.com',
+        TRUE
+    ),
+    (
+        12,
+        oils_i18n_gettext(
+            12,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            12,
+            'MTS Mobility',
+            'csc',
+            'name'
+        ),
+        '$number@text.mtsmobility.com',
+        TRUE
+    ),
+    (
+        13,
+        oils_i18n_gettext(
+            13,
+            'Canada',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            13,
+            'Virgin Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@vmobile.ca',
+        TRUE
+    ),
+
+    -- International
+    (
+        14,
+        oils_i18n_gettext(
+            14,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            14,
+            'Iridium',
+            'csc',
+            'name'
+        ),
+        '$number@msg.iridium.com',
+        TRUE
+    ),
+    (
+        15,
+        oils_i18n_gettext(
+            15,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            15,
+            'Globalstar',
+            'csc',
+            'name'
+        ),
+        '$number@msg.globalstarusa.com',
+        TRUE
+    ),
+    (
+        16,
+        oils_i18n_gettext(
+            16,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            16,
+            'Bulletin.net',
+            'csc',
+            'name'
+        ),
+        '$number@bulletinmessenger.net', -- International Formatted number
+        TRUE
+    ),
+    (
+        17,
+        oils_i18n_gettext(
+            17,
+            'International',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            17,
+            'Panacea Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@api.panaceamobile.com',
+        TRUE
+    ),
+
+    -- USA
+    (
+        18,
+        oils_i18n_gettext(
+            18,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            18,
+            'C Beyond',
+            'csc',
+            'name'
+        ),
+        '$number@cbeyond.sprintpcs.com',
+        TRUE
+    ),
+    (
+        19,
+        oils_i18n_gettext(
+            19,
+            'Alaska, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            19,
+            'General Communications, Inc.',
+            'csc',
+            'name'
+        ),
+        '$number@mobile.gci.net',
+        TRUE
+    ),
+    (
+        20,
+        oils_i18n_gettext(
+            20,
+            'California, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            20,
+            'Golden State Cellular',
+            'csc',
+            'name'
+        ),
+        '$number@gscsms.com',
+        TRUE
+    ),
+    (
+        21,
+        oils_i18n_gettext(
+            21,
+            'Cincinnati, Ohio, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            21,
+            'Cincinnati Bell',
+            'csc',
+            'name'
+        ),
+        '$number@gocbw.com',
+        TRUE
+    ),
+    (
+        22,
+        oils_i18n_gettext(
+            22,
+            'Hawaii, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            22,
+            'Hawaiian Telcom Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@hawaii.sprintpcs.com',
+        TRUE
+    ),
+    (
+        23,
+        oils_i18n_gettext(
+            23,
+            'Midwest, USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            23,
+            'i wireless (T-Mobile)',
+            'csc',
+            'name'
+        ),
+        '$number.iws@iwspcs.net',
+        TRUE
+    ),
+    (
+        24,
+        oils_i18n_gettext(
+            24,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            24,
+            'i-wireless (Sprint PCS)',
+            'csc',
+            'name'
+        ),
+        '$number@iwirelesshometext.com',
+        TRUE
+    ),
+    (
+        25,
+        oils_i18n_gettext(
+            25,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            25,
+            'MetroPCS',
+            'csc',
+            'name'
+        ),
+        '$number@mymetropcs.com',
+        TRUE
+    ),
+    (
+        26,
+        oils_i18n_gettext(
+            26,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            26,
+            'Kajeet',
+            'csc',
+            'name'
+        ),
+        '$number@mobile.kajeet.net',
+        TRUE
+    ),
+    (
+        27,
+        oils_i18n_gettext(
+            27,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            27,
+            'Element Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@SMS.elementmobile.net',
+        TRUE
+    ),
+    (
+        28,
+        oils_i18n_gettext(
+            28,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            28,
+            'Esendex',
+            'csc',
+            'name'
+        ),
+        '$number@echoemail.net',
+        TRUE
+    ),
+    (
+        29,
+        oils_i18n_gettext(
+            29,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            29,
+            'Boost Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@myboostmobile.com',
+        TRUE
+    ),
+    (
+        30,
+        oils_i18n_gettext(
+            30,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            30,
+            'BellSouth',
+            'csc',
+            'name'
+        ),
+        '$number@bellsouth.com',
+        TRUE
+    ),
+    (
+        31,
+        oils_i18n_gettext(
+            31,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            31,
+            'Bluegrass Cellular',
+            'csc',
+            'name'
+        ),
+        '$number@sms.bluecell.com',
+        TRUE
+    ),
+    (
+        32,
+        oils_i18n_gettext(
+            32,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            32,
+            'AT&T Enterprise Paging',
+            'csc',
+            'name'
+        ),
+        '$number@page.att.net',
+        TRUE
+    ),
+    (
+        33,
+        oils_i18n_gettext(
+            33,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            33,
+            'AT&T Mobility/Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@txt.att.net',
+        TRUE
+    ),
+    (
+        34,
+        oils_i18n_gettext(
+            34,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            34,
+            'AT&T Global Smart Messaging Suite',
+            'csc',
+            'name'
+        ),
+        '$number@sms.smartmessagingsuite.com',
+        TRUE
+    ),
+    (
+        35,
+        oils_i18n_gettext(
+            35,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            35,
+            'Alltel (Allied Wireless)',
+            'csc',
+            'name'
+        ),
+        '$number@sms.alltelwireless.com',
+        TRUE
+    ),
+    (
+        36,
+        oils_i18n_gettext(
+            36,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            36,
+            'Alaska Communications',
+            'csc',
+            'name'
+        ),
+        '$number@msg.acsalaska.com',
+        TRUE
+    ),
+    (
+        37,
+        oils_i18n_gettext(
+            37,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            37,
+            'Ameritech',
+            'csc',
+            'name'
+        ),
+        '$number@paging.acswireless.com',
+        TRUE
+    ),
+    (
+        38,
+        oils_i18n_gettext(
+            38,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            38,
+            'Cingular (GoPhone prepaid)',
+            'csc',
+            'name'
+        ),
+        '$number@cingulartext.com',
+        TRUE
+    ),
+    (
+        39,
+        oils_i18n_gettext(
+            39,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            39,
+            'Cingular (Postpaid)',
+            'csc',
+            'name'
+        ),
+        '$number@cingular.com',
+        TRUE
+    ),
+    (
+        40,
+        oils_i18n_gettext(
+            40,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            40,
+            'Cellular One (Dobson) / O2 / Orange',
+            'csc',
+            'name'
+        ),
+        '$number@mobile.celloneusa.com',
+        TRUE
+    ),
+    (
+        41,
+        oils_i18n_gettext(
+            41,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            41,
+            'Cellular South',
+            'csc',
+            'name'
+        ),
+        '$number@csouth1.com',
+        TRUE
+    ),
+    (
+        42,
+        oils_i18n_gettext(
+            42,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            42,
+            'Cellcom',
+            'csc',
+            'name'
+        ),
+        '$number@cellcom.quiktxt.com',
+        TRUE
+    ),
+    (
+        43,
+        oils_i18n_gettext(
+            43,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            43,
+            'Chariton Valley Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@sms.cvalley.net',
+        TRUE
+    ),
+    (
+        44,
+        oils_i18n_gettext(
+            44,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            44,
+            'Cricket',
+            'csc',
+            'name'
+        ),
+        '$number@sms.mycricket.com',
+        TRUE
+    ),
+    (
+        45,
+        oils_i18n_gettext(
+            45,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            45,
+            'Cleartalk Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@sms.cleartalk.us',
+        TRUE
+    ),
+    (
+        46,
+        oils_i18n_gettext(
+            46,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            46,
+            'Edge Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@sms.edgewireless.com',
+        TRUE
+    ),
+    (
+        47,
+        oils_i18n_gettext(
+            47,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            47,
+            'Syringa Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@rinasms.com',
+        TRUE
+    ),
+    (
+        48,
+        oils_i18n_gettext(
+            48,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            48,
+            'T-Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@tmomail.net',
+        TRUE
+    ),
+    (
+        49,
+        oils_i18n_gettext(
+            49,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            49,
+            'Straight Talk / PagePlus Cellular',
+            'csc',
+            'name'
+        ),
+        '$number@vtext.com',
+        TRUE
+    ),
+    (
+        50,
+        oils_i18n_gettext(
+            50,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            50,
+            'South Central Communications',
+            'csc',
+            'name'
+        ),
+        '$number@rinasms.com',
+        TRUE
+    ),
+    (
+        51,
+        oils_i18n_gettext(
+            51,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            51,
+            'Simple Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@smtext.com',
+        TRUE
+    ),
+    (
+        52,
+        oils_i18n_gettext(
+            52,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            52,
+            'Sprint (PCS)',
+            'csc',
+            'name'
+        ),
+        '$number@messaging.sprintpcs.com',
+        TRUE
+    ),
+    (
+        53,
+        oils_i18n_gettext(
+            53,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            53,
+            'Nextel',
+            'csc',
+            'name'
+        ),
+        '$number@messaging.nextel.com',
+        TRUE
+    ),
+    (
+        54,
+        oils_i18n_gettext(
+            54,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            54,
+            'Pioneer Cellular',
+            'csc',
+            'name'
+        ),
+        '$number@zsend.com', -- nine digit number
+        TRUE
+    ),
+    (
+        55,
+        oils_i18n_gettext(
+            55,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            55,
+            'Qwest Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@qwestmp.com',
+        TRUE
+    ),
+    (
+        56,
+        oils_i18n_gettext(
+            56,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            56,
+            'US Cellular',
+            'csc',
+            'name'
+        ),
+        '$number@email.uscc.net',
+        TRUE
+    ),
+    (
+        57,
+        oils_i18n_gettext(
+            57,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            57,
+            'Unicel',
+            'csc',
+            'name'
+        ),
+        '$number@utext.com',
+        TRUE
+    ),
+    (
+        58,
+        oils_i18n_gettext(
+            58,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            58,
+            'Teleflip',
+            'csc',
+            'name'
+        ),
+        '$number@teleflip.com',
+        TRUE
+    ),
+    (
+        59,
+        oils_i18n_gettext(
+            59,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            59,
+            'Virgin Mobile',
+            'csc',
+            'name'
+        ),
+        '$number@vmobl.com',
+        TRUE
+    ),
+    (
+        60,
+        oils_i18n_gettext(
+            60,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            60,
+            'Verizon Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@vtext.com',
+        TRUE
+    ),
+    (
+        61,
+        oils_i18n_gettext(
+            61,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            61,
+            'USA Mobility',
+            'csc',
+            'name'
+        ),
+        '$number@usamobility.net',
+        TRUE
+    ),
+    (
+        62,
+        oils_i18n_gettext(
+            62,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            62,
+            'Viaero',
+            'csc',
+            'name'
+        ),
+        '$number@viaerosms.com',
+        TRUE
+    ),
+    (
+        63,
+        oils_i18n_gettext(
+            63,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            63,
+            'TracFone',
+            'csc',
+            'name'
+        ),
+        '$number@mmst5.tracfone.com',
+        TRUE
+    ),
+    (
+        64,
+        oils_i18n_gettext(
+            64,
+            'USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            64,
+            'Centennial Wireless',
+            'csc',
+            'name'
+        ),
+        '$number@cwemail.com',
+        TRUE
+    ),
+
+    -- South Korea and USA
+    (
+        65,
+        oils_i18n_gettext(
+            65,
+            'South Korea and USA',
+            'csc',
+            'region'
+        ),
+        oils_i18n_gettext(
+            65,
+            'Helio',
+            'csc',
+            'name'
+        ),
+        '$number@myhelio.com',
+        TRUE
+    )
+;
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES
+    (
+        514,
+        'ADMIN_SMS_CARRIER',
+        oils_i18n_gettext(
+            514,
+            'Allows a user to add/create/delete SMS Carrier entries.',
+            'ppl',
+            'description'
+        )
+    )
+;
+
+INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
+    SELECT
+        pgt.id, perm.id, aout.depth, TRUE
+    FROM
+        permission.grp_tree pgt,
+        permission.perm_list perm,
+        actor.org_unit_type aout
+    WHERE
+        pgt.name = 'Global Administrator' AND
+        aout.name = 'Consortium' AND
+        perm.code = 'ADMIN_SMS_CARRIER';
+
+INSERT INTO action_trigger.reactor (
+    module,
+    description
+) VALUES (
+    'SendSMS',
+    'Send an SMS text message based on a user-defined template'
+);
+
+INSERT INTO action_trigger.event_definition (
+    active,
+    owner,
+    name,
+    hook,
+    validator,
+    reactor,
+    delay,
+    delay_field,
+    group_field,
+    template
+) VALUES (
+    true,
+    1, -- admin
+    'Hold Ready for Pickup SMS Notification',
+    'hold.available',
+    'HoldIsAvailable',
+    'SendSMS',
+    '00:30:00',
+    'shelf_time',
+    'sms_notify',
+    '
+[%- USE date -%]
+[%- user = target.0.usr -%]
+From: [%- params.sender_email || default_sender %]
+To: [%- params.recipient_email || helpers.get_sms_gateway_email(target.0.sms_carrier,target.0.sms_notify) %]
+Subject: Hold Available Notification
+
+Dear [% user.family_name %], [% user.first_given_name %]
+The item(s) you requested are available for pickup from the Library.
+
+[% FOR hold IN target %]
+    Title: [% hold.current_copy.call_number.record.simple_record.title %]
+    Author: [% hold.current_copy.call_number.record.simple_record.author %]
+    Call Number: [% hold.current_copy.call_number.label %]
+    Barcode: [% hold.current_copy.barcode %]
+    Library: [% hold.pickup_lib.name %]
+[% END %]
+    '
+);
+
+INSERT INTO action_trigger.environment (
+    event_def,
+    path
+) VALUES (
+    currval('action_trigger.event_definition_id_seq'),
+    'current_copy.call_number.record.simple_record'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'usr'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'pickup_lib.billing_address'
+);
+
+INSERT INTO action_trigger.hook(
+    key,
+    core_type,
+    description,
+    passive
+) VALUES (
+    'acn.format.sms_text',
+    'acn',
+    oils_i18n_gettext(
+        'acn.format.sms_text',
+        'A text message has been requested for a call number.',
+        'ath',
+        'description'
+    ),
+    FALSE
+);
+
+INSERT INTO action_trigger.event_definition (
+    active,
+    owner,
+    name,
+    hook,
+    validator,
+    reactor,
+    template
+) VALUES (
+    true,
+    1, -- admin
+    'SMS Call Number',
+    'acn.format.sms_text',
+    'NOOP_True',
+    'SendSMS',
+    '
+[%- USE date -%]
+From: [%- params.sender_email || default_sender %]
+To: [%- params.recipient_email || helpers.get_sms_gateway_email(user_data.sms_carrier,user_data.sms_notify) %]
+Subject: Call Number
+
+Title: [% target.record.simple_record.title %]
+Author: [% target.record.simple_record.author %]
+Call Number: [% target.label %]
+Library: [% target.owning_lib.name %]
+    '
+);
+
+INSERT INTO action_trigger.environment (
+    event_def,
+    path
+) VALUES (
+    currval('action_trigger.event_definition_id_seq'),
+    'record.simple_record'
+), (
+    currval('action_trigger.event_definition_id_seq'),
+    'owning_lib.billing_address'
+);
+
+
+-- DELETE FROM actor.usr_setting WHERE name = 'opac.default_phone' OR name in ( SELECT name FROM config.usr_setting_type WHERE grp = 'sms' ); DELETE FROM config.usr_setting_type WHERE name = 'opac.default_phone' OR grp = 'sms'; DELETE FROM actor.org_unit_setting WHERE name in ( SELECT name FROM config.org_unit_setting_type WHERE grp = 'sms' ); DELETE FROM config.org_unit_setting_type_log WHERE field_name in ( SELECT name FROM config.org_unit_setting_type WHERE grp = 'sms' ); DELETE FROM config.org_unit_setting_type WHERE grp = 'sms'; DELETE FROM config.settings_group WHERE name = 'sms'; DELETE FROM permission.grp_perm_map WHERE perm = 514; DELETE FROM permission.perm_list WHERE id = 514; ALTER TABLE action.hold_request DROP CONSTRAINT sms_check; ALTER TABLE action.hold_request DROP COLUMN sms_notify; ALTER TABLE action.hold_request DROP COLUMN sms_carrier; DROP TABLE config.sms_carrier; DELETE FROM action_trigger.event WHERE event_def = ( SELECT id FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification' ); DELETE FROM action_trigger.environment WHERE event_def = ( SELECT id FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification' ); DELETE FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification'; DELETE FROM action_trigger.event WHERE event_def IN ( SELECT id FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text' ); DELETE FROM action_trigger.environment WHERE event_def IN ( SELECT id FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text' ); DELETE FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text'; DELETE FROM action_trigger.hook WHERE key = 'acn.format.sms_text'; DELETE FROM action_trigger.reactor WHERE module = 'SendSMS'; DELETE FROM config.upgrade_log WHERE version = 'XXXX';
+
+COMMIT;
diff --git a/Open-ILS/src/templates/conify/global/config/sms_carrier.tt2 b/Open-ILS/src/templates/conify/global/config/sms_carrier.tt2
new file mode 100644 (file)
index 0000000..ce5d3d9
--- /dev/null
@@ -0,0 +1,27 @@
+[% WRAPPER base.tt2 %]
+[% ctx.page_title = 'SMS Carriers' %]
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/config/sms_carrier.js'> </script>
+
+<!-- grid -->
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+        <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+            <div>SMS Carriers</div>
+            <div>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.showCreateDialog()'>New Carrier</button>
+                <button dojoType='dijit.form.Button' onClick='thingGrid.deleteSelected()'>Delete Selected</button>
+            </div>
+        </div>
+        <table  jsId="thingGrid"
+                dojoType="openils.widget.AutoGrid"
+                fieldOrder="['id', 'region', 'name', 'email_gateway', 'active']"
+                query="{id: '*'}"
+                defaultCellWidth='20'
+                fmClass='csc'
+                editOnEnter='true'>
+        </table>
+    </div>
+</div>
+[% END %]
+
+
index 17d0b67..a947a91 100644 (file)
             class="opac-button" />
     </div>
 
+    [% setting = 'opac.hold_notify' %]
+    <input name='[% setting %]' type="hidden"
+        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+
+    <table class="full-width data_grid" id="acct_search_main">
+        <tbody>
+
+            [% IF ctx.updated_user_settings %]
+            <tr><td colspan='2'>
+                <div class='renew-summary'>
+                    [% l('Account Successfully Updated') %]
+                </div>
+            </td></tr>
+            [% END %]
+
+            <tr>
+                <td>[% l('Notify by Email by default when a hold is ready for pickup?') %]</td>
+                <td>
+                    [% setting = 'opac.hold_notify' %]
+                    <input name='[% setting %].email' type="checkbox"
+                        [% IF (matches = ctx.user_setting_map.$setting.match('email')); %] checked='checked' [% END %]/>
+                </td>
+            </tr>
+            <tr>
+                <td>[% l('Notify by Phone by default when a hold is ready for pickup?') %]</td>
+                <td>
+                    [% setting = 'opac.hold_notify' %]
+                    <input name='[% setting %].phone' type="checkbox"
+                        [% IF (matches = ctx.user_setting_map.$setting.match('phone')); %] checked='checked' [% END %]/>
+                </td>
+            </tr>
+            <tr>
+                <td>[% l('Default Phone Number') %]</td>
+                <td>
+                    [% setting = 'opac.default_phone' %]
+                    <input name='[% setting %]' type="text"
+                        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                </td>
+            </tr>
+            [% IF ctx.get_org_setting(CGI.param('loc') OR ctx.aou_tree.id, 'sms.enable') == 1 %]
+            <tr>
+                <td>[% l('Notify by SMS/Text by default when a hold is ready for pickup?') %]</td>
+                <td>
+                    [% setting = 'opac.hold_notify' %]
+                    <input name='[% setting %].sms' type="checkbox"
+                        [% IF (matches = ctx.user_setting_map.$setting.match('sms')); %] checked='checked' [% END %]/>
+                </td>
+            </tr>
+            <tr>
+                <td>[% l('Default SMS/Text Carrier') %]</td>
+                <td>[% INCLUDE "opac/parts/sms_carrier_selector.tt2" %]</td>
+            </tr>
+            <tr>
+                <td>[% l('Default SMS/Text Number') %]</td>
+                <td>
+                    [% setting = 'opac.default_sms_notify' %]
+                    <input name='[% setting %]' type="text"
+                        [% IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                </td>
+            </tr>
+            [% END %]
+        </tbody>
+    </table>
     <table>
         <thead><tr>
             <th>[% l('Notifation Type') %]</th>
index adb1483..a75ca01 100644 (file)
             [% PROCESS "opac/parts/org_selector.tt2";
                 PROCESS build_org_selector name='pickup_lib' value=ctx.default_pickup_lib id='pickup_lib' can_have_vols_only=1 %]
         </p>
+        <p>
+            [% l('Notify when hold is ready for pickup?') %]
+            <blockquote>
+                <input type="checkbox" name="email_notify" value="t"
+                    [% IF ctx.default_email_notify %]checked="checked"[% END %]/>
+                    [% l('Yes, by Email') %]<br/>
+                <input type="checkbox" name="phone_notify_checkbox"
+                    [% IF ctx.default_phone_notify %]checked="checked"[% END %]/>
+                    [% l('Yes, by Phone') %]<br/>
+                <blockquote>
+                    <input type="text" name="phone_notify" [% setting = 'opac.default_phone';
+                    IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                </blockquote>
+                [% IF ctx.get_org_setting(CGI.param('loc') OR ctx.aou_tree.id, 'sms.enable') == 1 %]
+                <input type="checkbox" name="sms_notify_checkbox"
+                    [% IF ctx.default_sms_notify %]checked="checked"[% END %]/>
+                    [% l('Yes, by Text Messaging') %]<br/>
+                <blockquote>
+                    [% INCLUDE "opac/parts/sms_carrier_selector.tt2" %]
+                    <input type="text" name="sms_notify" [% setting = 'opac.default_sms_notify';
+                    IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                </blockquote>
+                [% END %]
+            </blockquote>
+        </p>
+        <p>
+            [% |l %]If you use the Traveling Library Center (TLC) and ABC Express
+            services, please select "Outreach" to have the item delivered
+            during your scheduled visit.[% END %]
+        </p>
         <input type="submit" name="submit" value="[% l('Submit') %]" title="[% l('Submit') %]"
             alt="[% l('Submit') %]" class="opac-button" />
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
index f6af5b1..8afecfd 100644 (file)
@@ -48,7 +48,7 @@ END;
                 org_name | html
             -%]
             </td>
-            <td header='copy_header_callnumber'>[% callnum | html %]</td>
+            <td header='copy_header_callnumber'>[% callnum | html %] [% IF ctx.get_org_setting(CGI.param('loc') OR ctx.aou_tree.id, 'sms.enable') == 1 %](<a href="[% mkurl(ctx.opac_root _ '/sms_cn', {copy_id => copy_info.id}) %]">SMS</a>)[% END %]</td>
             [%- IF has_parts == 'true' %]
             <td header='copy_header_part'>[% copy_info.part_label | html %]</td>
             [%- END %]
diff --git a/Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2 b/Open-ILS/src/templates/opac/parts/sms_carrier_selector.tt2
new file mode 100644 (file)
index 0000000..c118b80
--- /dev/null
@@ -0,0 +1,26 @@
+[%
+    setting = 'opac.default_sms_carrier';
+    IF ctx.user_setting_map.$setting;
+        default_carrier = ctx.user_setting_map.$setting;
+    END;
+
+    temp = ctx.search_csc('active','t');
+
+    # turn the list of objects into a list of hashes to
+    # leverage TT's array.sort('<hashkey>') behavior
+    carriers = [];
+    FOR o IN temp;
+        carriers.push({
+            id => o.id,
+            region => o.region,
+            name => o.name
+        });
+    END;
+%]
+<select name="sms_carrier">
+    [% FOR carrier IN carriers.sort('name','region') -%]
+    <option value='[% carrier.id | html %]'[%
+        default_carrier == carrier.id ? ' selected="selected"' : ''
+    %]>[% carrier.name | html %] ([% carrier.region | html %])</option>
+    [% END -%]
+</select>
diff --git a/Open-ILS/src/templates/opac/sms_cn.tt2 b/Open-ILS/src/templates/opac/sms_cn.tt2
new file mode 100644 (file)
index 0000000..8eb9b70
--- /dev/null
@@ -0,0 +1,44 @@
+[%  PROCESS "opac/parts/header.tt2";
+    WRAPPER "opac/parts/base.tt2";
+    INCLUDE "opac/parts/topnav.tt2";
+    ctx.page_title = l("Send Call Number via Text/SMS") %]
+    <div id="search-wrapper">
+        [% INCLUDE "opac/parts/searchbar.tt2" %]
+    </div>
+    <div id="content-wrapper">
+        <div id="main-content">
+            <div class="common-full-pad"></div>
+            <div>
+                <p>
+                    [% IF ctx.event != -1 %]
+                    [% IF ctx.sms_notify %]
+                    <h1>Call Number sent via SMS/Text</h1>
+                    [% ELSE %]
+                    <h1>Call Number to send via SMS/Text</h1>
+                    [% END %]
+                    <pre>[% ctx.event.template_output.data %]</pre>
+                    <blockquote>
+                        <form method="POST">
+                            <blockquote>
+                                <input type="hidden" name="copy_id" value="[% ctx.copy_id %]"/>
+                                [% INCLUDE "opac/parts/sms_carrier_selector.tt2" %]
+                                <input type="text" name="sms_notify" [% setting = 'opac.default_sms_notify';
+                                IF ctx.user_setting_map.$setting; %] value='[% ctx.user_setting_map.$setting | html %]' [% END %]/>
+                                <input type="submit"
+                                    name="submit"
+                                    value="[% l('Submit') %]"
+                                    title="[% l('Submit') %]"
+                                    alt="[% l('Submit') %]"
+                                    class="opac-button" />
+                            </blockquote>
+                        </form>
+                    </blockquote>
+                    [% ELSE %]
+                    <span>SMS not enabled for this site.</span>
+                    [% END %]
+                </p>
+            </div>
+            <div class="common-full-pad"></div>
+        </div>
+    </div>
+[% END %]
diff --git a/Open-ILS/web/js/ui/default/conify/global/config/sms_carrier.js b/Open-ILS/web/js/ui/default/conify/global/config/sms_carrier.js
new file mode 100644 (file)
index 0000000..80407e5
--- /dev/null
@@ -0,0 +1,46 @@
+dojo.require('dojox.grid.DataGrid');
+dojo.require('openils.widget.AutoGrid');
+dojo.require('dojox.grid.cells.dijit');
+dojo.require('dojo.data.ItemFileWriteStore');
+dojo.require('dijit.Dialog');
+dojo.require('openils.PermaCrud');
+
+var thingList;
+
+function thingInit() {
+
+    thingGrid.disableSelectorForRow = function(rowIdx) {
+        var item = thingGrid.getItem(rowIdx);
+        return (thingGrid.store.getValue(item, 'id') < 0);
+    }
+
+    buildGrid();
+}
+
+function buildGrid() {
+
+    fieldmapper.standardRequest(
+        ['open-ils.pcrud', 'open-ils.pcrud.search.csc.atomic'],
+        {   async: true,
+            params: [
+                openils.User.authtoken,
+                {"id":{"!=":null}},
+                {"order_by":{"csc":"name"}}
+            ],
+            oncomplete: function(r) {
+                if(thingList = openils.Util.readResponse(r)) {
+                    thingList = openils.Util.objectSort(thingList,'name');
+                    dojo.forEach(thingList,
+                                 function(e) {
+                                     thingGrid.store.newItem(csc.toStoreItem(e));
+                                 }
+                                );
+                }
+            }
+        }
+    );
+}
+
+openils.Util.addOnLoad(thingInit);
+
+
index 9e91870..8ecc211 100644 (file)
 <!ENTITY staff.main.menu.admin.server_admin.conify.acn_prefix.label "Call Number Prefixes">
 <!ENTITY staff.main.menu.admin.server_admin.conify.acn_suffix.label "Call Number Suffixes">
 <!ENTITY staff.main.menu.admin.server_admin.conify.billing_type.label "Billing Types">
+<!ENTITY staff.main.menu.admin.server_admin.conify.sms_carrier.label "SMS Carriers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.z3950_source.label "Z39.50 Servers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.circulation_modifier.label "Circulation Modifiers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.org_unit_setting_type "Organization Unit Setting Types">
index ac82ea4..402acb0 100644 (file)
@@ -1000,6 +1000,10 @@ main.menu.prototype = {
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/acq/distribution_formula', null, event); }
             ],
+            'cmd_server_admin_sms_carrier' : [
+                ['oncommand'],
+                function(event) { open_eg_web_page('conify/global/config/sms_carrier', null, event); }
+            ],
             'cmd_server_admin_z39_source' : [
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/config/z3950_source', null, event); }
index 923000b..dd1f479 100644 (file)
     <command id="cmd_server_admin_acq_currency_type" />
     <command id="cmd_server_admin_acq_exchange_rate" />
     <command id="cmd_server_admin_acq_distrib_formula" />
+    <command id="cmd_server_admin_sms_carrier" />
     <command id="cmd_server_admin_z39_source" />
     <command id="cmd_server_admin_circ_mod" 
              perm="CREATE_CIRC_MOD DELETE_CIRC_MOD UPDATE_CIRC_MOD ADMIN_CIRC_MOD"
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.coded_value_maps.label;" command="cmd_server_admin_coded_value_map"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.metabib_field.label;" command="cmd_server_admin_metabib_field"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.billing_type.label;" command="cmd_server_admin_billing_type"/>
+                <menuitem label="&staff.main.menu.admin.server_admin.conify.sms_carrier.label;" command="cmd_server_admin_sms_carrier"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.z3950_source.label;" command="cmd_server_admin_z39_source"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_modifier.label;" command="cmd_server_admin_circ_mod"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.global_flag.label;" command="cmd_server_admin_global_flag"/>