LP1182519 Per-Hold Behind Desk Value
authorBill Erickson <berick@esilibrary.com>
Tue, 21 May 2013 15:59:36 +0000 (11:59 -0400)
committerBill Erickson <berick@esilibrary.com>
Fri, 23 Aug 2013 14:26:13 +0000 (10:26 -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>
Conflicts:

Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm

Resolved 2 conflicts in Actor.pm sub hold_request_count
Retained the parameters as:
  my( $self, $client, $authtoken, $user_id, $ctx_org) = @;

And retained:

my @ready = grep { .... } @$holds code.

Signed-off-by: Garry Collum <gcollum@gmail.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 0064b76..d270341 100644 (file)
@@ -5179,6 +5179,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"/>
@@ -5339,6 +5340,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"/>
@@ -5407,6 +5409,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 1a774d8..c8ca798 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 37ad23b..9c9efc4 100644 (file)
@@ -1929,11 +1929,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;
 
@@ -1945,7 +1951,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,
@@ -1954,15 +1960,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 cb99b3d..9de0fca 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 381028d..a7f60a4 100644 (file)
@@ -406,6 +406,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 351d9ca..e8aa81e 100644 (file)
@@ -395,7 +395,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 b72b300..2249624 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 5b81b63..19e728a 100644 (file)
@@ -2762,7 +2762,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 ]) {
@@ -3075,26 +3092,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 74402fb..9a9be26 100644 (file)
@@ -317,6 +317,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 4ab2f9f..f7d7d5f 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 c6e8543..cb23327 100644 (file)
@@ -438,7 +438,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) {
@@ -448,16 +450,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.
+