From 8345edb40cb886a19c10c06fa31f664bd4212bce Mon Sep 17 00:00:00 2001
From: Olli-Antti Kivilahti <olli-antti.kivilahti@jns.fi>
Date: Tue, 29 Oct 2013 10:06:52 +0200
Subject: [PATCH] Bug 11157 - Placing a simple item-level hold for serials.

For biblios marked as a serial or if the sysprefs:
-OPACShowPlaceItemLevelHold or
-ShowPlaceItemLevelHold
are enabled.
Adds a "Place hold"-link after each item in the detailed biblio view.

Uses the template parameter 'norequest', to disable the "Place hold"-link in
the sidebar opac-detail-sidebar.inc for serials.

Added a new parameter itemnumber to opac-reserve.pl/.tt and reserve.pl/.tt.
This causes the reserving module to display only the desired item, preselect it
and hide unnecessary buttons.
---
 C4/Items.pm                                        |  6 ++++++
 catalogue/detail.pl                                |  6 ++++++
 installer/data/mysql/sysprefs.sql                  |  2 ++
 .../en/modules/admin/preferences/cataloguing.pref  |  7 +++++++
 .../prog/en/modules/admin/preferences/opac.pref    |  7 +++++++
 .../prog/en/modules/catalogue/detail.tt            |  8 ++++++++
 .../prog/en/modules/reserve/request.tt             | 17 ++++++++++++++---
 koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt | 11 ++++++++++-
 .../opac-tmpl/prog/en/modules/opac-reserve.tt      |  9 +++++++--
 opac/opac-detail.pl                                |  9 ++++++++-
 opac/opac-reserve.pl                               | 14 +++++++++++---
 reserve/request.pl                                 | 22 +++++++++++++++++++++-
 t/db_dependent/Items.t                             |  9 +++++++++
 13 files changed, 116 insertions(+), 11 deletions(-)

diff --git a/C4/Items.pm b/C4/Items.pm
index dc6b44c..7120924 100644
--- a/C4/Items.pm
+++ b/C4/Items.pm
@@ -1219,6 +1219,7 @@ $filter->{fromDate} = '01/01/2013'; #Filters only serial issues by the serialite
 $filter->{toDate} = '31/12/2014';   #Filters only serial issues by the serialitems.publisheddate
                                           #The ending date in C4::Context->preference('dateformat') format
 $filter->{limit} = 15;              #Limits the returning set to this many items
+$filter->{itemnumber} = 165;        #Returns the item with this itemnumber
                                           
 Filters are expected to be validated! If a filter is not defined, that filter is not present in the $filter-HASH
 
@@ -1341,6 +1342,11 @@ sub GetItemsInfo {
         }
         push @$queryParams, $filter->{toDate};
     }
+    if (exists $filter->{itemnumber}) {
+        push @$queryParams, $filter->{itemnumber};
+        $query .= " AND items.itemnumber = ?";
+    }
+    
     
     $query .= "ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
     if (exists $filter->{limit}) {
diff --git a/catalogue/detail.pl b/catalogue/detail.pl
index 72258fd..618455c 100755
--- a/catalogue/detail.pl
+++ b/catalogue/detail.pl
@@ -143,6 +143,10 @@ my $dat = &GetBiblioData($biblionumber);
 my $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber);
 my @subscriptions       = GetSubscriptions( $dat->{title}, $dat->{issn}, undef, $biblionumber );
 my @subs;
+if (scalar(@subscriptions)) {
+	$template->param('isSerial',1);
+}
+
 
 foreach my $subscription (@subscriptions) {
     my %cell;
@@ -412,4 +416,6 @@ if (C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->pref
 my ( $holdcount, $holds ) = C4::Reserves::GetReservesFromBiblionumber($biblionumber,1);
 $template->param( holdcount => $holdcount, holds => $holds );
 
+$template->param('ShowPlaceItemLevelHold',  C4::Context->preference('ShowPlaceItemLevelHold'));
+
 output_html_with_http_headers $query, $cookie, $template->output;
diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql
index 4db2935..5efaf5f 100644
--- a/installer/data/mysql/sysprefs.sql
+++ b/installer/data/mysql/sysprefs.sql
@@ -270,6 +270,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
 ('OpacShowLibrariesPulldownMobile','1',NULL,'Show the libraries pulldown on the mobile version of the OPAC.','YesNo'),
 ('OpacShowRecentComments','0',NULL,'If ON a link to recent comments will appear in the OPAC masthead','YesNo'),
 ('OPACShowUnusedAuthorities','1','','Show authorities that are not being used in the OPAC.','YesNo'),
+('OPACShowPlaceItemLevelHold','0',NULL,'Allow placing a item-level hold from the detailed biblio view.','YesNo'),
 ('opacsmallimage','','','Enter a complete URL to an image to replace the default Koha logo','free'),
 ('OpacStarRatings','all','disable|all|details',NULL,'Choice'),
 ('OpacSuppression','0','','Turn ON the OPAC Suppression feature, requires further setup, ask your system administrator for details','YesNo'),
@@ -341,6 +342,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
 ('ShowPatronImageInWebBasedSelfCheck','0','','If ON, displays patron image when a patron uses web-based self-checkout','YesNo'),
 ('ShowReviewer','full','none|full|first|surname|firstandinitial|username','Choose how a commenter\'s identity is presented alongside comments in the OPAC','Choice'),
 ('ShowReviewerPhoto','1','','If ON, photo of reviewer will be shown beside comments in OPAC','YesNo'),
+('ShowPlaceItemLevelHold','0',NULL,'Allow placing a item-level hold from the detailed biblio view.','YesNo'),
 ('singleBranchMode','0',NULL,'Operate in Single-branch mode, hide branch selection in the OPAC','YesNo'),
 ('SlipCSS','',NULL,'Slips CSS url.','free'),
 ('SMSSendDriver','','','Sets which SMS::Send driver is used to send SMS messages.','free'),
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref
index 96f3603..4620462 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref
@@ -18,6 +18,13 @@ Cataloging:
                   yes: Display
                   no: "Don't Display"
             - easy ways to create analytical record relationships
+        -
+            - pref: ShowPlaceItemLevelHold
+              default: 0
+              choices:
+                  yes: "Allow"
+                  no : "Don't allow"
+            - placing a item-level hold from the detailed biblio view. This is always enabled for serials because serials need only item-level holds.
     Spine Labels:
         -
             - When using the quick spine label printer,
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
index 28d25c6..91125fa 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
@@ -131,6 +131,13 @@ OPAC:
                   no: "Don't show"
             - the item's barcode on the holdings tab.
         -
+            - pref: OPACShowPlaceItemLevelHold
+              default: 0
+              choices:
+                  yes: "Allow"
+                  no : "Don't allow"
+            - placing a item-level hold from the detailed biblio view. This is always enabled for serials because serials need only item-level holds.
+        -
             - pref: OpacHighlightedWords
               choices:
                   yes: Highlight
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt
index d5a006b..39d125a 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt
@@ -422,6 +422,7 @@ function verify_images() {
                 [% IF ( hostrecords ) %]<th>Host records</th>[% END %]
                 [% IF ( analyze ) %]<th>Used in</th><th></th>[% END %]
                 [% IF ( ShowCourseReserves ) %]<th>Course Reserves</th>[% END %]
+                [% IF ( ShowPlaceItemLevelHold || isSerial ) %]<th id="placeItemLevelHoldTh">Reserve</th>[% END %]
             </tr>
         </thead>
         <tbody>
@@ -590,6 +591,13 @@ function verify_images() {
                        [% END %]
                     </td>
                 [% END %]
+                [% IF ( ShowPlaceItemLevelHold || isSerial ) %]
+                    <td>
+                        <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber %]&itemnumber=[% item.itemnumber %]">
+                            Place hold
+                        </a>
+                    </td>
+                [% END %]
                 </tr>
             [% END %]
         </tbody>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
index 7f7d05f..b0eb339 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
@@ -191,7 +191,9 @@ function checkMultiHold() {
   [% END %]
 
   [% UNLESS ( multi_hold ) %]
-    <h1>Place a hold on [% INCLUDE 'biblio-default-view.inc' %][% title |html %]</a></h1>
+    <h1>Place a hold on [% INCLUDE 'biblio-default-view.inc' %][% title |html %]</a>
+        [% IF targetItemTitle %] - <a href="/cgi-bin/koha/catalogue/moredetail.pl?type=&itemnumber=[% targetItemnumber %]&biblionumber=[% biblionumber %]&bi=[% biblioitemnumber %]#item[% targetItemnumber %]">[% targetItemTitle %]</a>[% END %]
+    </h1>
   [% ELSE %]
     <h1>Confirm Holds</h1>
   [% END %]
@@ -208,6 +210,7 @@ function checkMultiHold() {
                 <input type="text" size="40" id="patron" class="focus" name="findborrower" />
                 <input type="submit" value="Search" />
                 <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
+                [% IF targetItemnumber %]<input type="hidden" name="itemnumber" value="[% targetItemnumber %]" />[% END %]
             </fieldset>
         [% ELSE %]
           <fieldset>
@@ -221,6 +224,7 @@ function checkMultiHold() {
             [% END %]
             </select>
             <input type="hidden" name="biblionumber" value="[% biblionumber %]" /><fieldset class="action"><input type="submit" value="Select" /></fieldset></fieldset>
+            [% IF targetItemnumber %]<input type="hidden" name="itemnumber" value="[% targetItemnumber %]" />[% END %]
 
         [% END %]
         [% IF ( multi_hold ) %]
@@ -344,8 +348,11 @@ function checkMultiHold() {
 	</li>
 
         [% UNLESS ( multi_hold ) %]
-          <li> <label for="requestany">Place a hold on the next available copy </label>
+          <li>
+            [% UNLESS targetItemnumber %]
+               <label for="requestany">Place a hold on the next available copy </label>
                <input type="checkbox" id="requestany" name="request" checked="checked" value="Any" />
+            [% END %]
                <input type="hidden" name="biblioitem" value="[% biblioitemnumber %]" />
                <input type="hidden" name="alreadyreserved" value="[% alreadyreserved %]" />
           </li>
@@ -395,7 +402,11 @@ function checkMultiHold() {
         <tr class="[% itemloo.backgroundcolor %]">
             <td>
             [% IF ( itemloo.available ) %]
-                <input type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
+                [% IF targetItemnumber %]
+                    <input type="radio" name="checkitem" checked="checked" value="[% itemloo.itemnumber %]" />
+                [% ELSE %]
+                    <input type="radio" name="checkitem" value="[% itemloo.itemnumber %]" />
+                [% END %]
             [% ELSIF ( itemloo.override ) %]
                 <input type="radio" name="checkitem" class="needsoverride" value="[% itemloo.itemnumber %]" />
                 <img src="/intranet-tmpl/[% theme %]/img/famfamfam/silk/error.png" alt="Requires override of hold policy" />
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt
index f0741ed..3f19656 100644
--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt
+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt
@@ -70,7 +70,7 @@
     [% END %]
 // ------------------------------ //    
 //>>> Document READY starts here! //
-// ------------------------------ //    
+// ------------------------------ //
      $(document).ready(function() { 
         $('#bibliodescriptions').tabs();
         $(".branch-info-tooltip-trigger").tooltip({
@@ -132,6 +132,7 @@
                     null,
                 [% END %]
                 [% IF ( ShowCourseReservesHeader ) %]null,[% END %]
+                [% IF ( isSerial ) %]null,[% END %]
             ]
         }));
 
@@ -1550,6 +1551,7 @@ YAHOO.util.Event.onContentReady("furtherm", function () {
             <th>Item hold queue priority</th>
         [% END %]
         [% IF ( ShowCourseReservesHeader ) %]<th id="item_coursereserves">Course reserves</th>[% END %]
+        [% IF ( isSerial ) %]<th id="placeItemLevelHoldTh">Reserve</th>[% END %]
         </tr></thead>
 	    <tbody>[% FOREACH ITEM_RESULT IN items %]
       [% IF ITEM_RESULT.this_branch %]
@@ -1630,6 +1632,13 @@ YAHOO.util.Event.onContentReady("furtherm", function () {
                 [% END %]
             </td>
         [% END %]
+        [% IF OPACShowPlaceItemLevelHold || isSerial %]
+            <td>
+                <a href="opac-reserve.pl?biblionumber=[% biblionumber %]&itemnumber=[% ITEM_RESULT.itemnumber %]">
+                    Place hold
+                </a>
+            </td>
+        [% END %]
 	    </tr>
 	    [% END %]</tbody>
 	</table>
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-reserve.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-reserve.tt
index bf334fa..3e317af 100644
--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-reserve.tt
+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-reserve.tt
@@ -425,6 +425,7 @@
                             <!-- ITEM HOLDS -->
 
                                 <li class="radio">
+                                 [% UNLESS singleItemLevelHold %]
                                   [% UNLESS ( bibitemloo.holdable ) %]
                                     <input type="radio" name="reqtype_[% bibitemloo.biblionumber %]"
                                            id="reqany_[% bibitemloo.biblionumber %]"
@@ -437,11 +438,12 @@
                                            id="reqany_[% bibitemloo.biblionumber %]"
                                            class="selectany"
                                            value="Any"
-                                           checked="checked"
+                                           [% UNLESS singleItemLevelHold %] checked="checked" [% END %]
                                     />
                                   [% END %]
 
                                   <label for="reqany_[% bibitemloo.biblionumber %]">Next available copy</label>
+                                 [% END %][%# IF singleItemLevelHold %]
                                   [% UNLESS ( bibitemloo.holdable ) %]
                                     <input type="radio" name="reqtype_[% bibitemloo.biblionumber %]"
                                            id="reqspecific_[% bibitemloo.biblionumber %]"
@@ -454,6 +456,7 @@
                                            id="reqspecific_[% bibitemloo.biblionumber %]"
                                            class="selectspecific"
                                            value="Specific"
+                                           [% IF singleItemLevelHold %] checked="checked" [% END %]
                                     />
                                   [% END %]
                                   <label for="reqspecific_[% bibitemloo.biblionumber %]">A specific copy</label>
@@ -489,7 +492,9 @@
                               <td class="copy">
                                 [% IF ( itemLoo.available ) %]
                                   <input type="radio" class="checkitem checkitem_[% bibitemloo.biblionumber %]" name="checkitem_[% bibitemloo.biblionumber %]"
-                                         value="[% itemLoo.itemnumber %]" />
+                                         value="[% itemLoo.itemnumber %]"
+                                         [% IF singleItemLevelHold %] checked="checked" [% END %]
+                                         />
                                 [% ELSE %]
                                   <input disabled="disabled" type="radio" class="checkitem" name="checkitem" value="[% itemLoo.itemnumber %]" />
                                   <img src="/opac-tmpl/lib/famfamfam/silk/cross.png" alt="Cannot be put on hold" title="Cannot be put on hold" />
diff --git a/opac/opac-detail.pl b/opac/opac-detail.pl
index 0544539..8d17b56 100755
--- a/opac/opac-detail.pl
+++ b/opac/opac-detail.pl
@@ -691,7 +691,7 @@ if ( $show_holds_count || $show_priority) {
 }
 $template->param( show_priority => $has_hold ) ;
 
-my $norequests = 1;
+my $norequests = 1; #"No requests"-flag hides the "Place hold"-link in opac-detail-sidebar.inc
 my %itemfields;
 my (@itemloop, @otheritemloop);
 my $currentbranch = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
@@ -756,6 +756,11 @@ for my $itm (@items) {
     }
 }
 
+#There is no point in enabling "Place hold" for Serials.
+# Placing a title-level hold for a Serial is crazy since we don't want "any" item but instead the specific volume and number.
+# Also choosing an item-level hold from opac-reserve.pl is problematic since there could be thousands of items.
+$norequests = 1 if $template->{VARS}->{'isSerial'};
+
 # Display only one tab if one items list is empty
 if (scalar(@itemloop) == 0 || scalar(@otheritemloop) == 0) {
     $template->param(SeparateHoldings => 0);
@@ -1203,4 +1208,6 @@ if ( length C4::Context->preference('NumberingFormulaParsingRegexp') < 3 ) {
 }
 $template->{VARS}->{useFilterIssueInput} = 1 if (C4::Context->preference('FilterSerialsByIssue'));
 
+$template->param('OPACShowPlaceItemLevelHold',  C4::Context->preference('OPACShowPlaceItemLevelHold'));
+
 output_html_with_http_headers $query, $cookie, $template->output;
diff --git a/opac/opac-reserve.pl b/opac/opac-reserve.pl
index 4bba9a6..9007b1a 100755
--- a/opac/opac-reserve.pl
+++ b/opac/opac-reserve.pl
@@ -73,8 +73,10 @@ if ($borr->{reservefee} > 0){
 my $branches = GetBranches();
 my $itemTypes = GetItemTypes();
 
-# There are two ways of calling this script, with a single biblio num
-# or multiple biblio nums.
+# There are three ways of calling this script, with a single biblio num
+# or multiple biblio nums or as a itemhold, where the target item has already been chosen.
+my $targetItemnumber = $query->param('itemnumber') ? $query->param('itemnumber') : undef;
+$template->param('singleItemLevelHold' => 1);
 my $biblionumbers = $query->param('biblionumbers');
 my $reserveMode = $query->param('reserve_mode');
 if ($reserveMode && ($reserveMode eq 'single')) {
@@ -127,8 +129,14 @@ foreach my $biblioNumber (@biblionumbers) {
 
     my $biblioData = GetBiblioData($biblioNumber);
     $biblioDataHash{$biblioNumber} = $biblioData;
+    my @itemInfos;
 
-    my @itemInfos = GetItemsInfo($biblioNumber);
+    if ($targetItemnumber) { #We just want the one item so use the filters to find it!
+        @itemInfos = GetItemsInfo($biblioNumber, {itemnumber => $targetItemnumber});
+    }
+    else {
+        @itemInfos = GetItemsInfo($biblioNumber);
+    }
 
     my $marcrecord= GetMarcBiblio($biblioNumber);
 
diff --git a/reserve/request.pl b/reserve/request.pl
index c411ce9..4151f54 100755
--- a/reserve/request.pl
+++ b/reserve/request.pl
@@ -57,10 +57,27 @@ my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user(
     }
 );
 
+my $targetItemnumber = $input->param('itemnumber') ? $input->param('itemnumber') : undef;
+if ($targetItemnumber) {
+    $template->param('targetItemnumber', $targetItemnumber);
+    my $targetItem = C4::Items::GetItem($targetItemnumber);
+    if ($targetItem->{enumchron}) {
+        $template->param('targetItemTitle', $targetItem->{enumchron});
+    }
+    elsif ($targetItem->{barcode}) {
+        $template->param('targetItemTitle', $targetItem->{barcode});
+    }
+    else {
+        $template->param('targetItemTitle', $targetItem->{itemnumber});
+    }
+}
+
+
 my $multihold = $input->param('multi_hold');
 $template->param(multi_hold => $multihold);
 my $showallitems = $input->param('showallitems');
 
+
 # get Branches and Itemtypes
 my $branches = GetBranches();
 my $itemtypes = GetItemTypes();
@@ -243,7 +260,10 @@ foreach my $biblionumber (@biblionumbers) {
     my @itemnumbers;
 
     ## $items is array of 'item' table numbers
-    if (my $items = get_itemnumbers_of($biblionumber)->{$biblionumber}){
+    if ($targetItemnumber) { #If we just want to place a hold on one item
+        push @itemnumbers, $targetItemnumber;
+    }
+    elsif (my $items = get_itemnumbers_of($biblionumber)->{$biblionumber}){
         @itemnumbers  = @$items;
     }
     my @hostitems = get_hostitemnumbers_of($biblionumber);
diff --git a/t/db_dependent/Items.t b/t/db_dependent/Items.t
index 1d28525..750e0ba 100755
--- a/t/db_dependent/Items.t
+++ b/t/db_dependent/Items.t
@@ -165,6 +165,11 @@ subtest 'Filter items tests' => sub {
               holdingbranch => 'MPL', },
             $biblionumber
     );
+    my ($item3_bibnum, $item3_bibitemnum, $item3_itemnumber) = AddItem(
+            { homebranch => 'IPT',
+              holdingbranch => 'IPT', },
+            $biblionumber
+    );
     
     # Testing the branch filter
     my @shouldBeItem2 = C4::Items::GetItemsInfo($biblionumber, {branch => 'MPL'});
@@ -173,6 +178,10 @@ subtest 'Filter items tests' => sub {
     # Testing the dates filter
     my @shouldBeEmpty = C4::Items::GetItemsInfo($biblionumber, {toDate => '01/01/1933'});
     is( scalar(@shouldBeEmpty), 0, "Filtering by date");
+    
+    # Testing the itemnumber filter
+    my @shouldBeItem3 = C4::Items::GetItemsInfo($biblionumber, {itemnumber => $item3_itemnumber});
+    is( $shouldBeItem3[0]->{itemnumber}, $item3_itemnumber, "Filtering by itemnumber");
 };
 
 # Helper method to set up a Biblio.
-- 
1.8.1.2