LP#1410369: patron message center page in TPac
authorGalen Charlton <gmc@esilibrary.com>
Wed, 18 Feb 2015 21:29:41 +0000 (21:29 +0000)
committerBill Erickson <berickxx@gmail.com>
Fri, 20 Feb 2015 21:58:17 +0000 (16:58 -0500)
This patch adds My Account tab in TPac for
displaying and managing patron messages.  In particular,
a logged in patron will be able to:

- see a list of their (undeleted) messages
- open a particular message and see it's body;
  doing this automatically marks the message as
  read
- delete messages from either the list or
  single-message views
- mark messages as unread from the list or
  single-message views
- mark messages as read from the list view

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/templates/opac/css/style.css.tt2
Open-ILS/src/templates/opac/myopac/messages.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/myopac/messages/list.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/myopac/messages/single_message.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/myopac/base.tt2

index 856b7de..afcd8a9 100644 (file)
@@ -184,6 +184,7 @@ sub load {
     return $self->load_place_hold if $path =~ m|opac/place_hold|;
     return $self->load_myopac_holds if $path =~ m|opac/myopac/holds|;
     return $self->load_myopac_circs if $path =~ m|opac/myopac/circs|;
+    return $self->load_myopac_messages if $path =~ m|opac/myopac/messages|;
     return $self->load_myopac_payment_form if $path =~ m|opac/myopac/main_payment_form|;
     return $self->load_myopac_payments if $path =~ m|opac/myopac/main_payments|;
     return $self->load_myopac_pay_init if $path =~ m|opac/myopac/main_pay_init|;
index 4a2b5dc..514bc61 100644 (file)
@@ -264,6 +264,176 @@ sub fetch_optin_prefs {
     return [map { {cust => $_, value => $user_set->{$_->name} } } @$opt_ins];
 }
 
+sub load_myopac_messages {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+    my $cgi = $self->cgi;
+
+    my $limit  = $cgi->param('limit') || 20;
+    my $offset = $cgi->param('offset') || 0;
+
+    my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud');
+    $pcrud->connect();
+
+    my $action = $cgi->param('action') || '';
+    if ($action) {
+        my ($changed, $failed) = $self->_handle_message_action($pcrud, $action);
+        if ($changed > 0 || $failed > 0) {
+            $ctx->{message_update_action} = $action;
+            $ctx->{message_update_changed} = $changed;
+            $ctx->{message_update_failed} = $failed;
+        }
+    }
+
+    my $single = $cgi->param('single') || 0;
+    my $id = $cgi->param('message_id');
+
+    my $messages;
+    my $fetch_all = 1;
+    if (!$action && $single && $id) {
+        $messages = $self->_fetch_and_mark_read_single_message($pcrud, $id);
+        if (scalar(@$messages) == 1) {
+            $ctx->{display_single_message} = 1;
+            $ctx->{patron_message_id} = $id;
+            $fetch_all = 0;
+        }
+    }
+
+    if ($fetch_all) {
+        # fetch all the messages
+        ($ctx->{patron_messages_count}, $messages) =
+            $self->_fetch_user_messages($pcrud, $offset, $limit);    
+    }
+
+    $pcrud->kill_me;
+
+    foreach my $aum (@$messages) {
+
+        push @{ $ctx->{patron_messages} }, {
+            id          => $aum->id,
+            title       => $aum->title,
+            message     => $aum->message,
+            create_date => $aum->create_date,
+            is_read     => defined($aum->read_date) ? 1 : 0,
+            library     => $aum->sending_lib->name, 
+        };
+    }
+
+    $ctx->{patron_messages_limit} = $limit;
+    $ctx->{patron_messages_offset} = $offset;
+
+    return Apache2::Const::OK;
+}
+
+sub _fetch_and_mark_read_single_message {
+    my $self = shift;
+    my $pcrud = shift;
+    my $id = shift;
+
+    $pcrud->request('open-ils.pcrud.transaction.begin', $self->editor->authtoken)->gather(1);
+    my $messages = $pcrud->request(
+        'open-ils.pcrud.search.aum.atomic',
+        $self->editor->authtoken,
+        {
+            usr     => $self->editor->requestor->id,
+            deleted => 'f',
+            id      => $id,
+        },
+        {
+            flesh => 1,
+            flesh_fields => { aum => ['sending_lib'] },
+        }
+    )->gather(1);
+    if (@$messages) {
+        $messages->[0]->read_date('now');
+        $pcrud->request(
+            'open-ils.pcrud.update.aum',
+            $self->editor->authtoken,
+            $messages->[0]
+        )->gather(1);
+    }
+    $pcrud->request('open-ils.pcrud.transaction.commit', $self->editor->authtoken)->gather(1);
+
+    return $messages;
+}
+
+sub _fetch_user_messages {
+    my $self = shift;
+    my $pcrud = shift;
+    my $offset = shift;
+    my $limit = shift;
+
+    my %paging = ($limit or $offset) ? (limit => $limit, offset => $offset) : ();
+
+    my $all_messages = $pcrud->request(
+        'open-ils.pcrud.id_list.aum.atomic',
+        $self->editor->authtoken,
+        {
+            usr     => $self->editor->requestor->id,
+            deleted => 'f'
+        },
+        {}
+    )->gather(1);
+
+    my $messages = $pcrud->request(
+        'open-ils.pcrud.search.aum.atomic',
+        $self->editor->authtoken,
+        {
+            usr     => $self->editor->requestor->id,
+            deleted => 'f'
+        },
+        {
+            flesh => 1,
+            flesh_fields => { aum => ['sending_lib'] },
+            order_by => { aum => 'create_date DESC' },
+            %paging
+        }
+    )->gather(1);
+
+    return scalar(@$all_messages), $messages;
+}
+
+sub _handle_message_action {
+    my $self = shift;
+    my $pcrud = shift;
+    my $action = shift;
+    my $cgi = $self->cgi;
+
+    my @ids = $cgi->param('message_id');
+    return (0, 0) unless @ids;
+
+    my $changed = 0;
+    my $failed = 0;
+    $pcrud->request('open-ils.pcrud.transaction.begin', $self->editor->authtoken)->gather(1);
+    for my $id (@ids) {
+        my $aum = $pcrud->request(
+            'open-ils.pcrud.retrieve.aum',
+            $self->editor->authtoken,
+            $id
+        )->gather(1);
+        next unless $aum;
+        if      ($action eq 'mark_read') {
+            $aum->read_date('now');
+        } elsif ($action eq 'mark_unread') {
+            $aum->clear_read_date();
+        } elsif ($action eq 'mark_deleted') {
+            $aum->deleted('t');
+        }
+        $pcrud->request('open-ils.pcrud.update.aum', $self->editor->authtoken, $aum)->gather(1) ?
+            $changed++ :
+            $failed++;
+    }
+    if ($failed) {
+        $pcrud->request('open-ils.pcrud.transaction.rollback', $self->editor->authtoken)->gather(1);
+        $changed = 0;
+        $failed = scalar(@ids);
+    } else {
+        $pcrud->request('open-ils.pcrud.transaction.commit', $self->editor->authtoken)->gather(1);
+    }
+    return ($changed, $failed);
+}
+
 sub _load_lists_and_settings {
     my $self = shift;
     my $e = $self->editor;
index 76b5709..277e82b 100644 (file)
@@ -886,7 +886,7 @@ div.result_table_utils_cont {
     /*padding-left:10px;*/
 }
 
-#acct_checked_main_header, #acct_holds_main_header, #acct_checked_hist_header, #acct_list_header, #acct_list_header_anon, #temp_list_holds {
+#acct_checked_main_header, #acct_holds_main_header, #acct_checked_hist_header, #acct_list_header, #acct_list_header_anon, #temp_list_holds, #acct_messages_main_header {
     border-collapse: collapse;
 }
 
@@ -897,12 +897,12 @@ div.result_table_utils_cont {
 
 .hold_note_title { font-weight: bold; }
 
-#acct_checked_main_header td, #acct_holds_main_header td, #acct_checked_hist_header td, #acct_list_header td, #acct_list_header_anon td, #temp_list_holds td {
+#acct_checked_main_header td, #acct_holds_main_header td, #acct_checked_hist_header td, #acct_list_header td, #acct_list_header_anon td, #temp_list_holds td, #acct_messages_main_header td {
     background: [% css_colors.accent_lighter2 %];
     padding: 10px;
 }
 
-#acct_checked_main_header th, #acct_holds_main_header th, #acct_checked_hist_header th, #acct_list_header th, #acct_list_header_anon th, #temp_list_holds th {
+#acct_checked_main_header th, #acct_holds_main_header th, #acct_checked_hist_header th, #acct_list_header th, #acct_list_header_anon th, #temp_list_holds th, #acct_messages_main_header th {
     text-align: left;
     padding: 0px 10px 0px 10px;
 }
@@ -1134,7 +1134,7 @@ div#facet_sidebar {
     padding-left: 1em;
 }
 #opac.result.sort { width: 160px; }
-.renew-summary { font-size: [% css_fonts.size_bigger %]; font-style: italic; margin: 0.5ex 0; }
+.renew-summary, .message-update-summary { font-size: [% css_fonts.size_bigger %]; font-style: italic; margin: 0.5ex 0; }
 .failure-text { margin-left: 4em; font-style: italic; color: [% css_colors.text_alert %]; }
 .refine-controls { font-size: [% css_fonts.size_bigger %]; padding: 0.5ex 0; }
 #adv_search_refine input[type=text] { border: 1px inset [% css_colors.accent_light %] !important; }
@@ -1199,6 +1199,7 @@ a.dash-link:hover { text-decoration: underline !important; }
 .results-paginator-list { padding-left: 1em; }
 .results-paginator-selected { color: [% css_colors.text_alert %]; }
 .inactive-hold { background: [% css_colors.accent_lightest %]; }
+.unread-patron-message { font-weight: bold; }
 
 #hold-items-list td { padding: 5px; margin-bottom: 20px; }
 .hold-items-list-title { font-size: [% css_fonts.size_bigger %]; }
diff --git a/Open-ILS/src/templates/opac/myopac/messages.tt2 b/Open-ILS/src/templates/opac/myopac/messages.tt2
new file mode 100644 (file)
index 0000000..1cecd70
--- /dev/null
@@ -0,0 +1,16 @@
+[%  PROCESS "opac/parts/header.tt2";
+    PROCESS "opac/parts/misc_util.tt2";
+    WRAPPER "opac/parts/myopac/base.tt2";
+    myopac_page = "messages";
+    limit = (ctx.patron_messages_limit.defined) ? ctx.patron_messages_limit : 20;
+    offset = (ctx.patron_messages_offset.defined) ? ctx.patron_messages_offset : 0;
+    count = (ctx.patron_messages_count.defined) ? ctx.patron_messages_count : 0;
+    display_single = (ctx.display_single_message.defined) ? ctx.display_single_message : 0;
+%]
+<h3 class="sr-only">[% l('My Messages') %]</h3>
+[% IF display_single;
+    PROCESS "opac/myopac/messages/single_message.tt2";
+ELSE;
+    PROCESS "opac/myopac/messages/list.tt2";
+END %]
+[% END %]
diff --git a/Open-ILS/src/templates/opac/myopac/messages/list.tt2 b/Open-ILS/src/templates/opac/myopac/messages/list.tt2
new file mode 100644 (file)
index 0000000..94dd00e
--- /dev/null
@@ -0,0 +1,113 @@
+<div id="myopac_messages_div">
+    <div class="header_middle">
+        <span id="acct_messages_header" style="float:left;">[% l("Messages") %]</div>
+        <span class='float-left' style='padding-left: 10px;'>
+            <a href='[% mkurl('messages', { limit => limit, offset => (offset - limit)} ) %]'
+                [% IF offset <= 0 %] class='invisible' [% END %]><span class="nav_arrow_fix">&#9668;</span>[% l('Previous') %]</a>
+            [% IF offset > 0 || count > limit;
+                curpage = 0;
+                WHILE curpage * limit < count;
+                    IF curpage * limit == offset;
+            %]
+            [% curpage + 1 %]
+                    [%- ELSE %]
+            <a href='[% mkurl('messages', {limit => limit, offset => (curpage * limit)}) %]'>[% curpage + 1 %]</a>
+                    [%- END;
+                    curpage = curpage + 1;
+                END;
+            END %]
+            <a href='[% mkurl('messages', {limit => limit, offset => (offset + limit)}) %]'
+               [% IF count <= limit + offset %] class='invisible' [% END %] >[% l('Next') %]<span class="nav_arrow_fix">&#9658;</span></a>
+        </span>
+    </div>
+    <div class="clear-both"></div>
+    [% IF ctx.message_update_action.defined %]
+        [% IF ctx.message_update_changed > 0 %]
+        <div class="message-update-summary">
+            [% IF ctx.message_update_action == 'mark_read';
+                l('Marked [_1] message(s) as read.', ctx.message_update_changed);
+               ELSIF ctx.message_update_action == 'mark_unread';
+                l('Marked [_1]  message(s) as unread.', ctx.message_update_changed);
+               ELSIF ctx.message_update_action == 'mark_unread';
+                l('Deleted [_1] message(s).', ctx.message_update_changed);
+               END
+            %]
+        </div>
+        [% END %]
+        [% IF ctx.message_update_failed > 0 %]
+        <div class="message-update-summary alert">
+            [% IF ctx.message_update_action == 'mark_read';
+                l('Failed to mark [_1] message(s) as read.', ctx.message_update_failed);
+               ELSIF ctx.message_update_action == 'mark_unread';
+                l('Failed to mark [_1]  message(s) as unread.', ctx.message_update_failed);
+               ELSIF ctx.message_update_action == 'mark_unread';
+                l('Failed to delete [_1] message(s).', ctx.message_update_failed);
+               END
+            %]
+        </div>
+        [% END %]
+    [% END %]
+    <div class="clear-both"></div>
+    <div id="messages_main">
+        <form method="post" id="messages-form"
+         onsubmit="if (document.getElementById('acct_messages_actions').value == 'mark_deleted') { return confirm('[% l("Are you sure you wish to permanently delete the selected messsage(s)?") %]') } else { return true; }">
+            <div>
+                <span>
+                    <select name="action" id="acct_messages_actions"
+                        title="[% l('Select your action for the selected messages') %]">
+                        <option id="acct_messages_actions_none" value="">
+                            -- [% l('Actions for selected messages') %] --
+                        </option>
+                        <option value="mark_read">[% l('Mark As Read') %]</option>
+                        <option value="mark_unread">[% l('Mark As Unread') %]</option>
+                        <option value="mark_deleted">[% l('Delete') %]</option>
+                    </select>
+                </span>
+                <span style="padding-left:9px;">
+                    <input type="submit"
+                        value="[% l('Go') %]"
+                        title="[% l('Go') %]"
+                        class="opac-button" />
+                </span>
+                <span style="padding-left:5px;">
+                    <a href="#"><img
+                        alt="[% l('Messages Help') %]"
+                        title="[% l('Actions for messages') %]"
+                        src="[% ctx.media_prefix %]/images/question-mark.png" /></a>
+                </span>
+            </div>
+            [% IF count < 1 %]
+            <div class="warning_box">[% l('No messages found.') %]</div>
+            [% ELSE %]
+            <table id="acct_messages_main_header" title="[% l('Messages') %]"
+                class="table_no_border_space table_no_cell_pad">
+                <thead>
+                <tr>
+                    <th align="center">
+                        <input type="checkbox" title="[% l('Select All Messages') %]"
+                        onclick="var inputs=document.getElementsByTagName('input'); for (i = 0; i < inputs.length; i++) { if (inputs[i].name == 'message_id' &amp;&amp; !inputs[i].disabled) inputs[i].checked = this.checked;}"/>
+                    </th>
+                    <th>[% l('Date') %]</th>
+                    <th>[% l('Library') %]</th>
+                    <th>[% l('Subject') %]</th>
+                </tr>
+                </thead>
+                <tbody>
+                [% FOR message IN ctx.patron_messages; %]
+                    <tr name="acct_message_row"
+                        [% IF !message.is_read %]class="unread-patron-message"[% END %]>
+                        <td align="center" style="text-align:center;">
+                        <input type="checkbox" name="message_id" value="[% message.id %]"
+                            [% html_text_attr('title', l('Select message [_1]', message.title)) %]/>
+                        </td>
+                        <td>[% date.format(ctx.parse_datetime(message.create_date), DATE_FORMAT); %]</td>
+                        <td>[% message.library | html %]</td>
+                        <td><a href="[% mkurl('messages', { single => 1, message_id => message.id } ) %]">[% message.title | html %]</a></td>
+                    </tr>
+                [% END %]
+                </tbody>
+            </table>
+            [% END %]
+        </form>
+    </div>
+</div>
diff --git a/Open-ILS/src/templates/opac/myopac/messages/single_message.tt2 b/Open-ILS/src/templates/opac/myopac/messages/single_message.tt2
new file mode 100644 (file)
index 0000000..3d6a82c
--- /dev/null
@@ -0,0 +1,51 @@
+<div id="myopac_messages_div">
+    <div class="header_middle">
+        <span id="acct_messages_header" style="float:left;">[% l("Message") %]</div>
+    </div>
+    <div class="clear-both"></div>
+    <div id="single_message_main">
+        <form method="post" id="messages-form" action="[% ctx.opac_root %]/myopac/messages">
+            <input type="hidden" name="message_id" value="[% ctx.patron_message_id %]" />
+            <input type="hidden" name="offset" value="[% offset %]" />
+            <input type="hidden" name="limit" value="[% limit %]" />
+            <span style="padding-left:9px;">
+                <a href="[% mkurl('messages', {}, ['single', 'message_id']) %]" class="opac-button">
+                [% l('Return to Message List') %]
+                </a>
+                <button type="submit" name="action" value="mark_deleted" class="opac-button"
+                    onclick="return confirm('[% l("Are you sure you wish to permanently delete this messsage?") %]')">
+                    [% l('Delete') %]
+                </button>
+                <button type="submit" name="action" value="mark_unread" class="opac-button">
+                    [% l('Mark Unread') %]
+                </button>
+            </span>
+        </form>
+
+        <table title="[% l('Message') %]"
+            class='light_border data_grid'>
+            <tbody id='myopac_message_tbody'>
+            </tbody>
+            <tr>
+                <td width='30%'
+                    class='color_4 light_border'>[% l("Date") %]</td>
+                <td>[% date.format(ctx.parse_datetime(ctx.patron_messages.0.create_date), DATE_FORMAT); %]</td>
+            </tr>
+            <tr>
+                <td width='30%'
+                    class='color_4 light_border'>[% l("Library") %]</td>
+                <td>[% ctx.patron_messages.0.library | html %]</td>
+            </tr>
+            <tr>
+                <td width='30%'
+                    class='color_4 light_border'>[% l("Subject") %]</td>
+                <td>[% ctx.patron_messages.0.title | html %]</td>
+            </tr>
+            <tr>
+                <td width='30%'
+                    class='color_4 light_border'>[% l("Message") %]</td>
+                <td>[% ctx.patron_messages.0.message | html %]</td>
+            </tr>
+        </table>
+    </div>
+</div>
index 8f8a592..74426df 100644 (file)
@@ -2,6 +2,7 @@
 
 [% myopac_pages = [
         {url => "main", name => l("Account Summary")},
+        {url => "messages", name => l("Messages")},
         {url => "circs", name => l("Items Checked Out")},
         {url => "holds", name => l("Holds")},
         {url => "prefs", name => l("Account Preferences")},