LP1182519 Per-Hold Behind Desk Value lp1182519-hold-behind-desk-per-hold-3
authorBill Erickson <berick@esilibrary.com>
Tue, 21 May 2013 15:59:36 +0000 (11:59 -0400)
committerBill Erickson <berick@esilibrary.com>
Mon, 3 Jun 2013 20:31:44 +0000 (16:31 -0400)
The true/false value for behind_desk is now stored directly on the hold
object for more accurate tracking of the location of captured holds.
If configured, patrons can now set their preferred value for behind the
desk holds.

 * DB/IDL changes to support the new column
 * API support for loading the setting from the patron for cases when no
   value is provided by the caller.
 * Staff client grid column definition for the Behind Desk field
 * Hold counts in the staff client now show total, available, and
   behind-desk (at the workstation).
 * Printed hold receipt now uses the behind_desk value on the hold to
   decide if a hold is behind the desk.
 * Release notes

Signed-off-by: Bill Erickson <berick@esilibrary.com>
15 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/sql/Pg/090.schema.action.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.per-hold-behind-desk.sql [new file with mode: 0644]
Open-ILS/src/templates/opac/myopac/prefs_settings.tt2
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/server/circ/util.js
Open-ILS/xul/staff_client/server/locale/en-US/circ.properties
Open-ILS/xul/staff_client/server/locale/en-US/patron.properties
Open-ILS/xul/staff_client/server/patron/summary.js
Open-ILS/xul/staff_client/server/patron/summary_overlay.xul
docs/RELEASE_NOTES_NEXT/Circulation/per-hold-behind-desk.txt [new file with mode: 0644]

index 122e044..828e92a 100644 (file)
@@ -5148,6 +5148,7 @@ SELECT  usr,
                        <field reporter:label="Shelf Expire Time" name="shelf_expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Notes" name="notes" reporter:datatype="link" oils_persist:virtual="true"/>
                        <field reporter:label="Current Shelf Lib" name="current_shelf_lib" reporter:datatype="org_unit"/>
+                       <field reporter:label="Behind Desk" name="behind_desk" reporter:datatype="bool"/>
                </fields>
                <links>
                        <link field="fulfillment_lib" reltype="has_a" key="id" map="" class="aou"/>
@@ -5281,6 +5282,7 @@ SELECT  usr,
                        <field reporter:label="Issuance Label" name="issuance_label" reporter:datatype="text" />
                        <field reporter:label="Is Staff Hold?" name="is_staff_hold" reporter:datatype="bool" />
                        <field reporter:label="Potential Copies" name="potential_copies" reporter:datatype="int" />
+                       <field reporter:label="Behind Desk" name="behind_desk" reporter:datatype="bool"/>
                </fields>
                <links>
                        <link field="fulfillment_lib" reltype="has_a" key="id" map="" class="aou"/>
@@ -5349,6 +5351,7 @@ SELECT  usr,
                        <field reporter:label="Shelf Expire Time" name="shelf_expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Notes" name="notes" reporter:datatype="link" oils_persist:virtual="true"/>
                        <field reporter:label="Current Shelf Lib" name="current_shelf_lib" reporter:datatype="org_unit"/>
+                       <field reporter:label="Behind Desk" name="behind_desk" reporter:datatype="bool"/>
                </fields>
                <links>
                        <link field="fulfillment_lib" reltype="has_a" key="id" map="" class="aou"/>
index d8f7e8d..229b250 100644 (file)
@@ -274,6 +274,22 @@ sub promote_lineitem_holds {
             $hold->target( $li->eg_bib_id );
         }
 
+        # if behind-the-desk holds are supported at the 
+        # pickup library, apply the patron default
+        my $bdous = $U->ou_ancestor_setting_value(
+            $hold->pickup_lib, 
+            'circ.holds.behind_desk_pickup_supported', 
+            $mgr->editor
+        );
+
+        if ($bdous) {
+            my $set = $mgr->editor->search_actor_user_setting(
+                {usr => $hold->usr, name => 'circ.holds_behind_desk'})->[0];
+    
+            $hold->behind_desk('t') if $set and 
+                OpenSRF::Utils::JSON->JSON2perl($set->value);
+        }
+
         $mgr->editor->create_action_hold_request( $hold ) or return 0;
     }
 
index d818454..797e1bc 100644 (file)
@@ -1917,11 +1917,17 @@ __PACKAGE__->register_method(
     api_name      => "open-ils.actor.user.hold_requests.count",
     authoritative => 1,
     argc          => 1,
-    notes         => 'Returns hold ready/total counts'
+    notes         => q/
+        Returns hold ready vs. total counts.
+        If a context org unit is provided, a third value 
+        is returned with key 'behind_desk', which reports
+        how many holds are ready at the pickup library 
+        with the behind_desk flag set to true.
+    /
 );
        
 sub hold_request_count {
-       my( $self, $client, $authtoken, $user_id ) = @_;
+       my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
     my $e = new_editor(authtoken => $authtoken);
     return $e->event unless $e->checkauth;
 
@@ -1933,7 +1939,7 @@ sub hold_request_count {
     }
 
     my $holds = $e->json_query({
-        select => {ahr => ['pickup_lib', 'current_shelf_lib']},
+        select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
         from => 'ahr',
         where => {
             usr => $user_id,
@@ -1942,15 +1948,27 @@ sub hold_request_count {
         }
     });
 
-       return { 
+    my @ready = grep { 
+        $_->{current_shelf_lib} and # avoid undef warnings
+        $_->{pickup_lib} eq $_->{current_shelf_lib} 
+    } @$holds;
+
+       my $resp = { 
         total => scalar(@$holds), 
-        ready => scalar(
-            grep { 
-                $_->{current_shelf_lib} and # avoid undef warnings
-                $_->{pickup_lib} eq $_->{current_shelf_lib} 
-            } @$holds
-        ) 
+        ready => scalar(@ready)
     };
+
+    if ($ctx_org) {
+        # count of holds ready at pickup lib with behind_desk true.
+        $resp->{behind_desk} = scalar(
+            grep {
+                $_->{pickup_lib} == $ctx_org and
+                $U->is_true($_->{behind_desk})
+            } @ready
+        );
+    }
+
+    return $resp;
 }
 
 __PACKAGE__->register_method(
index cde9619..046dcc9 100644 (file)
@@ -36,6 +36,7 @@ use DateTime::Format::ISO8601;
 use OpenSRF::Utils qw/:datetime/;
 use Digest::MD5 qw(md5_hex);
 use OpenSRF::Utils::Cache;
+use OpenSRF::Utils::JSON;
 my $apputils = "OpenILS::Application::AppUtils";
 my $U = $apputils;
 
@@ -338,6 +339,31 @@ sub create_hold {
         $hold->expire_time(calculate_expire_time($recipient->home_ou));
     }
 
+
+    # if behind-the-desk pickup is supported at the hold pickup lib,
+    # set the value to the patron default, unless a value has already
+    # been applied.  If it's not supported, force the value to false.
+
+    my $bdous = $U->ou_ancestor_setting_value(
+        $hold->pickup_lib, 
+        'circ.holds.behind_desk_pickup_supported', $e);
+
+    if ($bdous) {
+        if (!defined $hold->behind_desk) {
+
+            my $set = $e->search_actor_user_setting({
+                usr => $hold->usr, 
+                name => 'circ.holds_behind_desk'
+            })->[0];
+        
+            $hold->behind_desk('t') if $set and 
+                OpenSRF::Utils::JSON->JSON2perl($set->value);
+        }
+    } else {
+        # behind the desk not supported, force it to false
+        $hold->behind_desk('f');
+    }
+
     $hold->requestor($e->requestor->id);
     $hold->request_lib($e->requestor->ws_ou);
     $hold->selection_ou($hold->pickup_lib) unless $hold->selection_ou;
index ec7bb96..2cc2dba 100644 (file)
@@ -399,6 +399,28 @@ sub load_myopac_prefs_settings {
     my $stat = $self->_load_user_with_prefs;
     return $stat if $stat;
 
+    # if behind-desk holds are supported and the user
+    # setting which controls the value is opac-visible,
+    # add the setting to the list of settings to manage.
+    # note: this logic may need to be changed later to
+    # check whether behind-the-desk holds are supported
+    # anywhere the patron may select as a pickup lib.
+    my $e = $self->editor;
+    my $bdous = $self->ctx->{get_org_setting}->(
+        $e->requestor->home_ou,
+        'circ.holds.behind_desk_pickup_supported');
+
+    if ($bdous) {
+        my $setting = 
+            $e->retrieve_config_usr_setting_type(
+                'circ.holds_behind_desk');
+
+        if ($U->is_true($setting->opac_visible)) {
+            push(@user_prefs, 'circ.holds_behind_desk');
+            $self->ctx->{behind_desk_supported} = 1;
+        }
+    }
+
     return Apache2::Const::OK
         unless $self->cgi->request_method eq 'POST';
 
index 9dec15f..c2066f3 100644 (file)
@@ -402,7 +402,8 @@ CREATE TABLE action.hold_request (
     cut_in_line     BOOL,
        mint_condition  BOOL NOT NULL DEFAULT TRUE,
        shelf_expire_time TIMESTAMPTZ,
-       current_shelf_lib INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED
+       current_shelf_lib INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED,
+    behind_desk BOOLEAN NOT NULL DEFAULT FALSE
 );
 ALTER TABLE action.hold_request ADD CONSTRAINT sms_check CHECK (
     sms_notify IS NULL
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.per-hold-behind-desk.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.per-hold-behind-desk.sql
new file mode 100644 (file)
index 0000000..4f0458c
--- /dev/null
@@ -0,0 +1,31 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+ALTER TABLE action.hold_request 
+    ADD COLUMN behind_desk BOOLEAN NOT NULL DEFAULT FALSE;
+
+-- The value on the hold is the new arbiter of whether a 
+-- hold should be held behind the desk and reported as such
+-- Update existing holds that would in the current regime
+-- be considered behind-the-desk holds to use the new column
+
+UPDATE action.hold_request ahr
+    SET behind_desk = TRUE
+    FROM actor.usr_setting aus
+    WHERE 
+        ahr.cancel_time IS NULL AND
+        ahr.fulfillment_time IS NULL AND
+        aus.usr = ahr.usr AND
+        aus.name = 'circ.holds_behind_desk' AND
+        aus.value = 'true' AND
+        EXISTS (
+            SELECT 1 
+            FROM actor.org_unit_ancestor_setting(
+                'circ.holds.behind_desk_pickup_supported', 
+                ahr.pickup_lib
+            ) 
+            WHERE value = 'true'
+        );
+
+COMMIT;
index 7b27be7..4896a7b 100644 (file)
                             [% IF ctx.user_setting_map.$setting %] checked='checked' [% END %]/>
                     </td>
                 </tr>
+                [%- setting = 'circ.holds_behind_desk'; IF ctx.behind_desk_supported -%]
+                <tr>
+                    <td><label for='[% setting %]'>[% l('Pickup holds from behind the desk when possible?') %]</label></td>
+                    <td>
+                        <input id='[% setting %]' name='[% setting %]' type="checkbox"
+                            [% IF ctx.user_setting_map.$setting %] checked='checked' [% END %]/>
+                    </td>
+                </tr>
+                [% END %]
+
                 <!--
                 <tr>
                     <td><label for='prefs_def_font'>[% l("Default Font Size") %]</label></td>
index d52ae78..bdb332d 100644 (file)
 <!ENTITY staff.patron_display.first_given_name.label 'First Name:'>
 <!ENTITY staff.patron_display.holds.label 'Holds:'>
 <!ENTITY staff.patron_display.holds_available.label 'Available:'>
+<!ENTITY staff.patron_display.holds_available_behind_desk.label 'Behind Desk:'>
 <!ENTITY staff.patron_display.home_ou.label 'Home Library:'>
 <!ENTITY staff.patron_display.ident1.label 'ID 1:'>
 <!ENTITY staff.patron_display.ident2.label 'ID 2:'>
index af7442f..46e7ab7 100644 (file)
@@ -2714,7 +2714,24 @@ circ.util.hold_columns = function(modify,params) {
                     return document.getElementById('circStrings').getString('staff.circ.utils.no');
                 }
             }
+        },
+        {
+            'persist' : 'hidden width ordinal',
+            'id' : 'behind_desk',
+            'label' : document.getElementById('circStrings').getString('staff.circ.utils.hold.behind_desk'),
+            'flex' : 1,
+            'primary' : false,
+            'hidden' : true,
+            'editable' : false, 
+            'render' : function(my) {
+                if (isTrue(my.ahr.behind_desk())) {
+                    return document.getElementById('circStrings').getString('staff.circ.utils.yes');
+                } else {
+                    return document.getElementById('circStrings').getString('staff.circ.utils.no');
+                }
+            }
         }
+
     ];
     for (var i = 0; i < c.length; i++) {
         if (modify[ c[i].id ]) {
@@ -3024,26 +3041,20 @@ circ.util.checkin_via_barcode2 = function(session,params,backdate,auto_print,che
                         } else {
                             print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
                             print_data.route_to = check.route_to;
-                            var behind_the_desk_support = String( data.hash.aous['circ.holds.behind_desk_pickup_supported'] ) == 'true';
-                            if (behind_the_desk_support) {
-                               var usr_settings = network.simple_request('FM_AUS_RETRIEVE',[ses(),check.payload.hold.usr()]); 
-                                if (typeof usr_settings['circ.holds_behind_desk'] != 'undefined') {
-                                    if (usr_settings['circ.holds_behind_desk']) {
-                                        print_data.prefer_behind_holds_desk = true;
-                                        check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.private_hold_shelf');
-                                        print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
-                                        print_data.route_to = check.route_to;
-                                    } else {
-                                        check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.public_hold_shelf');
-                                        print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
-                                        print_data.route_to = check.route_to;
-                                    }
-                                } else {
-                                    check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.public_hold_shelf');
-                                    print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
-                                    print_data.route_to = check.route_to;
-                                }
+
+                            // If the hold is marked as behind-shelf, report it as such 
+                            // in the receipt, regardless of any org or user settings.  
+                            if (isTrue(check.payload.hold.behind_desk())) {
+                                print_data.prefer_behind_holds_desk = true;
+                                check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.private_hold_shelf');
+                                print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
+                                print_data.route_to = check.route_to;
+                            } else {
+                                check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.public_hold_shelf');
+                                print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
+                                print_data.route_to = check.route_to;
                             }
+
                             print_data.destination_shelf_msg = print_data.route_to_msg;
                             print_data.destination_shelf = print_data.route_to;
                             msg += print_data.route_to_msg;
index 2f058ae..c4644b8 100644 (file)
@@ -316,6 +316,7 @@ staff.circ.utils.available_time=Available On
 staff.circ.utils.capture_time=Capture Date
 # Date the hold was cancelled
 staff.circ.utils.hold_cancel_time=Cancel Time
+staff.circ.utils.hold.behind_desk=Behind Desk
 # Controlled entry for why the hold was cancelled
 staff.circ.utils.hold_cancel_cause=Cancel Cause
 # Freetext note pertaining to the cancelled hold 
index cee408d..3672e95 100644 (file)
@@ -436,3 +436,7 @@ staff.item.batch.hold.retry_btn_label=Retry
 staff.item.batch.hold.override_btn_label=Override
 staff.item.batch.hold.user_not_found=User Not Found
 
+# hold count tooltip labels
+staff.patron.summary.hold_counts_behind_desk=Available / Total (Behind Desk)
+staff.patron.summary.hold_counts=Available / Total
+
index e560b64..8cfd2cc 100644 (file)
@@ -435,7 +435,9 @@ patron.summary.prototype = {
                             return function() { 
                                 util.widgets.set_text(e,'...');
                                 var e2 = document.getElementById('patron_holds_available');
+                                var e3 = document.getElementById('patron_holds_available_behind_desk');
                                 if (e2) util.widgets.set_text(e2,'...');
+                                if (e3) util.widgets.set_text(e3,'...');
                                 var under_btn; 
                                 if (xulG) {
                                     if (xulG.display_window) {
@@ -445,16 +447,39 @@ patron.summary.prototype = {
                                 }
                                 obj.network.simple_request(
                                     'FM_AHR_COUNT_RETRIEVE.authoritative',
-                                    [ ses(), obj.patron.id() ],
+                                    [ ses(), obj.patron.id(), ses('ws_ou') ],
                                     function(req) {
                                         var robj = req.getResultObject();
                                         util.widgets.set_text(e,
                                             robj.total
                                         );
-                                        if (e2) util.widgets.set_text(e2,
-                                            robj.ready
-                                        );
-                                        if (under_btn) util.widgets.set_text(under_btn, req.getResultObject().ready + '/' + req.getResultObject().total );
+                                        if (e2) {
+                                            util.widgets.set_text(e2, robj.ready);
+                                        }
+                                        if (e3) {
+                                            if (robj.behind_desk) {
+                                                removeCSSClass(e3.parentNode, 'hideme');
+                                                util.widgets.set_text(e3, robj.behind_desk);
+                                            } else {
+                                                addCSSClass(e3.parentNode, 'hideme');
+                                            }
+                                        }
+                                        if (under_btn) {
+                                            var str = robj.ready + '/' + robj.total;
+                                            if (robj.behind_desk) {
+                                                str += ' (' + robj.behind_desk + ')';
+                                                under_btn.setAttribute(
+                                                    'tooltiptext', patronStrings.getString(
+                                                        'staff.patron.summary.hold_counts_behind_desk')
+                                                );
+                                            } else {
+                                                under_btn.setAttribute(
+                                                    'tooltiptext', patronStrings.getString(
+                                                        'staff.patron.summary.hold_counts')
+                                                );
+                                            }
+                                            util.widgets.set_text(under_btn, str);
+                                        }
                                         obj.holds_summary = robj;
                                         if (obj.holds_summary && obj.bills_summary) 
                                             if (typeof window.xulG == 'object' && typeof window.xulG.stop_sign_page == 'function')
index 4a39089..4496594 100644 (file)
                 value="&staff.patron_display.holds_available.label;"  />
             <description id="patron_holds_available" class="copyable holds_ready value subgroup" />
         </row>
+        <row class='hideme'>
+            <label id="PatronSummaryStatus_holds_available_behind_desk_label" class="copyable text_right holds_ready label subgroup"
+                value="&staff.patron_display.holds_available_behind_desk.label;"  />
+            <description id="patron_holds_available_behind_desk" class="copyable holds_ready value subgroup" />
+        </row>
         <row id="pdsgr2" class="hide_patron_credit" hidden="true">
             <label id="PatronSummaryStatus_credit_label" class="copyable text_left credit label"
                 value="&staff.patron_display.credit.label;" />
diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/per-hold-behind-desk.txt b/docs/RELEASE_NOTES_NEXT/Circulation/per-hold-behind-desk.txt
new file mode 100644 (file)
index 0000000..fead74b
--- /dev/null
@@ -0,0 +1,32 @@
+Per-Hold Behind Desk Setting
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The value for behind-the-desk pickup is now stored directly on each
+hold request.  This allows the system to better track the true location 
+of captured hold items in cases where the patron setting has changed since
+hold capture.
+
+For these features to be accessible, the "Behind Desk Pickup Supported" 
+(circ.holds.behind_desk_pickup_supported) org unit setting must be set 
+to true.
+
+Staff Client
+++++++++++++
+
+In addition to the counts of ready for pickup and available holds, the
+staff client now also displays the number of behind the desk holds ready 
+for pickup at the staff's working location.  If no items are held behind
+the desk, this information does not display, in particular, because this 
+information is useless if behind the desk holds are not supported at the 
+staff's working location.
+
+TPAC Changes
+++++++++++++
+
+The system also allows patrons to set their own behind-the-desk 
+pickup preferences in the TPAC settings interface.  To activate this
+feature, admins need to set the Opac Visible flag to "true" for the 
+"Hold is behind Circ Desk" (circ.holds_behind_desk) user setting and
+"Behind Desk Pickup Supported" must be set to true for the patron's
+home library.
+