From 3e9f75fa947177e3ba8e9bd9fff322e2bb62f778 Mon Sep 17 00:00:00 2001 From: Kyle M Hall Date: Wed, 22 May 2013 14:56:31 -0400 Subject: [PATCH] Bug 7710 - multiple holds per title Adds the ability to allow multiple holds on the same record for the same borrower. Adds new system preference MaxHoldsPerRecord, which controls how many holds one person can have on items for the same record. Test Plan: 1) Apply patch for Bug 9394 2) Apply this patch 3) Run updatedatabase.pl 4) Attempt to place 2 holds for the same patron on the same record, you should not be able to ( default is 1 per record ) 5) Set MaxHoldsPerRecord to 3 6) Attempt to place multiple holds for one patron on one record, you should be able to place 3 holds for one patron on a given record. A 4th hold should result in a message stating the patron has placed the max number of holds allowed on this record. Signed-off-by: Maxime Pelletier --- C4/Reserves.pm | 59 ++++++++++++-------- installer/data/mysql/sysprefs.sql | 2 +- installer/data/mysql/updatedatabase.pl | 7 ++ .../en/modules/admin/preferences/circulation.pref | 7 ++- .../prog/en/modules/reserve/request.tt | 4 +- .../opac-tmpl/prog/en/modules/opac-reserve.tt | 2 + opac/opac-reserve.pl | 30 ++++++---- reserve/request.pl | 19 +++++-- 8 files changed, 86 insertions(+), 44 deletions(-) diff --git a/C4/Reserves.pm b/C4/Reserves.pm index fce34c1..5178eb6 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -397,36 +397,47 @@ sub GetReservesFromItemnumber { =head2 GetReservesFromBorrowernumber - $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus); - -TODO :: Descritpion + $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber[,$status][,$biblionumber][,$itemnumber]); =cut sub GetReservesFromBorrowernumber { - my ( $borrowernumber, $status ) = @_; - my $dbh = C4::Context->dbh; - my $sth; - if ($status) { - $sth = $dbh->prepare(" - SELECT * - FROM reserves - WHERE borrowernumber=? - AND found =? - ORDER BY reservedate - "); - $sth->execute($borrowernumber,$status); - } else { - $sth = $dbh->prepare(" - SELECT * - FROM reserves - WHERE borrowernumber=? - ORDER BY reservedate - "); - $sth->execute($borrowernumber); + my ( $borrowernumber, $found, $biblionumber, $itemnumber ) = @_; + + my @params; + + my $sql = "SELECT * FROM reserves WHERE borrowernumber = ?"; + push( @params, $borrowernumber ); + + if ( $biblionumber ) { + $sql .= " AND biblionumber = ? "; + push( @params, $biblionumber ); + } + + if ( $itemnumber ) { + $sql .= " AND itemnumber = ? "; + push( @params, $itemnumber ); } + + if ( $found ) { + $sql .= " AND found = ? "; + push( @params, $found ); + } + + $sql .= " ORDER BY reservedate "; + + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare( $sql ); + + $sth->execute( @params ); + my $data = $sth->fetchall_arrayref({}); - return @$data; + + if ( scalar @$data ) { + return @$data; + } else { + return (); + } } #------------------------------------------------------------------------------------- =head2 CanBookBeReserved diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 3ee5bcb..97f32c5 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -188,6 +188,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('MarcFieldsToOrder','',NULL,'Set the mapping values for a new order line created from a MARC record in a staged file. In a YAML format.','textarea'), ('MARCOrgCode','OSt','','Define MARC Organization Code for MARC21 records - http://www.loc.gov/marc/organizations/orgshome.html','free'), ('MaxFine',NULL,'','Maximum fine a patron can have for all late returns at one moment. Single item caps are specified in the circulation rules matrix.','Integer'), +('MaxHoldsPerRecord', '1', 'The maximum number of holds allowed per bibliographic record per borrower', 'Integer'), ('MaxItemsForBatch','1000',NULL,'Max number of items record to process in a batch (modification or deletion)','Integer'), ('maxItemsInSearchResults','20',NULL,'Specify the maximum number of items to display for each result on a page of results','free'), ('maxoutstanding','5','','maximum amount withstanding to be able make holds','Integer'), @@ -442,7 +443,6 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('XISBN','0','','Use with FRBRizeEditions. If ON, Koha will use the OCLC xISBN web service in the Editions tab on the detail pages. See: http://www.worldcat.org/affiliate/webservices/xisbn/app.jsp','YesNo'), ('XISBNDailyLimit','999','','The xISBN Web service is free for non-commercial use when usage does not exceed 1000 requests per day','Integer'), ('XSLTDetailsDisplay','default','','Enable XSL stylesheet control over details page display on intranet','Free'), -('XSLTResultsDisplay','default','','Enable XSL stylesheet control over results page display on intranet','Free'), ('yuipath','local','local|http://yui.yahooapis.com/2.5.1/build','Insert the path to YUI libraries, choose local if you use koha offline','Choice'), ('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'), ('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo') diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index bf21037..a7e3ac7 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -8589,6 +8589,13 @@ if ( CheckVersion($DBversion) ) { SetVersion($DBversion); } +$DBversion = "3.17.00.XXX"; +if (C4::Context->preference("Version") < TransformToNum($DBversion)) { + $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES ('MaxHoldsPerRecord', '1', 'The maximum number of holds allowed per bibliographic record per borrower', NULL, 'Integer')"); + print "Upgrade to $DBversion done (Bug 7710 - multiple holds per title)\n"; + SetVersion ($DBversion); +} + =head1 FUNCTIONS =head2 TableExists($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref index ec68eab..959de33 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -454,7 +454,12 @@ Circulation: - Patrons can only have - pref: maxreserves class: integer - - holds at once. + - total holds at a time. + - + - Patrons can only place + - pref: MaxHoldsPerRecord + class: integer + - holds on a single record at a time. - - pref: emailLibrarianWhenHoldIsPlaced choices: 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 cafc072..827edf2 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -62,7 +62,7 @@ function check() { } if (alreadyreserved > "0"){ - msg += (_("- This patron had already placed a hold on this item") + "\n" + _("Please cancel the previous hold first") + "\n"); + msg += (_("This patron has already placed the maximum number of holds for this record.") + "\n\n" + _("Please cancel a previous hold first.") + "\n"); } if (msg == "") return(true); @@ -247,7 +247,7 @@ function checkMultiHold() {
  • Too many holds: [% borrowerfirstname %] [% borrowersurname %] has too many holds.
  • [% END %] [% IF ( alreadyreserved ) %] -
  • [% borrowerfirstname %] [% borrowersurname %] already has a hold on this item
  • +
  • [% borrowerfirstname %] [% borrowersurname %] has already placed the maximum allowed number of holds on this record
  • [% END %] [% IF ( none_available ) %]
  • No items are available to be placed on hold
  • 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 d846355..57527af 100644 --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-reserve.tt +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-reserve.tt @@ -358,6 +358,8 @@ function select_first_available(id){ [% ELSE %] [% IF ( bibitemloo.already_patron_possession ) %]
    This title cannot be requested because it's already in your possession.
    + [% ELSIF bibitemloo.max_holds %] +
    This title cannot be requested. You have already placed the maximum number of requests allowed for this title.
    [% ELSE %]
    This title cannot be requested.
    [% END %] diff --git a/opac/opac-reserve.pl b/opac/opac-reserve.pl index 94ff1bd..27c10e6 100755 --- a/opac/opac-reserve.pl +++ b/opac/opac-reserve.pl @@ -38,7 +38,8 @@ use Koha::DateUtils; use Date::Calc qw/Today Date_to_Days/; # use Data::Dumper; -my $MAXIMUM_NUMBER_OF_RESERVES = C4::Context->preference("maxreserves"); +my $maxreserves = C4::Context->preference("maxreserves"); +my $max_holds_per_record = C4::Context->preference('MaxHoldsPerRecord'); my $query = new CGI; my ( $template, $borrowernumber, $cookie ) = get_template_and_user( @@ -183,7 +184,7 @@ foreach my $biblioNumber (@biblionumbers) { # if ( $query->param('place_reserve') ) { my $reserve_cnt = 0; - if ($MAXIMUM_NUMBER_OF_RESERVES) { + if ($maxreserves) { $reserve_cnt = GetReservesFromBorrowernumber( $borrowernumber ); } @@ -267,8 +268,8 @@ if ( $query->param('place_reserve') ) { } my $notes = $query->param('notes_'.$biblioNum)||''; - if ( $MAXIMUM_NUMBER_OF_RESERVES - && $reserve_cnt >= $MAXIMUM_NUMBER_OF_RESERVES ) + if ( $maxreserves + && $reserve_cnt >= $maxreserves ) { $canreserve = 0; } @@ -329,7 +330,7 @@ if ( $borr->{'debarred'} ) { my @reserves = GetReservesFromBorrowernumber( $borrowernumber ); $template->param( RESERVES => \@reserves ); -if ( $MAXIMUM_NUMBER_OF_RESERVES && (scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RESERVES) ) { +if ( $maxreserves && (scalar(@reserves) >= $maxreserves) ) { $template->param( message => 1 ); $noreserves = 1; $template->param( too_many_reserves => scalar(@reserves)); @@ -337,12 +338,10 @@ if ( $MAXIMUM_NUMBER_OF_RESERVES && (scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RES foreach my $res (@reserves) { foreach my $biblionumber (@biblionumbers) { if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) { -# $template->param( message => 1 ); -# $noreserves = 1; -# $template->param( already_reserved => 1 ); - $biblioDataHash{$biblionumber}->{already_reserved} = 1; + $biblioDataHash{$biblionumber}->{holds_count}++; } } + } unless ($noreserves) { @@ -386,6 +385,7 @@ foreach my $biblioNum (@biblionumbers) { $biblioLoopIter{reservecount} = $biblioData->{reservecount}; $biblioLoopIter{already_reserved} = $biblioData->{already_reserved}; $biblioLoopIter{mandatorynotes}=0; #FIXME: For future use + $biblioLoopIter{holds_count} = $biblioData->{holds_count}; if (!$itemLevelTypes && $biblioData->{itemtype}) { $biblioLoopIter{description} = $itemTypes->{$biblioData->{itemtype}}{description}; @@ -514,14 +514,17 @@ foreach my $biblioNum (@biblionumbers) { $policy_holdallowed = 0; } - if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) and ($itemLoopIter->{already_reserved} ne 1)) { + if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum) and ($itemLoopIter->{holds_count} < $max_holds_per_record )) { $itemLoopIter->{available} = 1; $numCopiesAvailable++; } $itemLoopIter->{imageurl} = getitemtypeimagelocation( 'opac', $itemTypes->{ $itemInfo->{itype} }{imageurl} ); - # Show serial enumeration when needed + # Don't allow multiple item level holds on the same item if borrowers can place multiple holds on one bib + $itemLoopIter->{available} = 0 if ( GetReservesFromBorrowernumber( $borrowernumber, undef, undef, $itemNum ) ); + + # Show serial enumeration when needed if ($itemLoopIter->{enumchron}) { $itemdata_enumchron = 1; } @@ -538,6 +541,11 @@ foreach my $biblioNum (@biblionumbers) { if ($biblioLoopIter{already_reserved}) { $biblioLoopIter{holdable} = undef; } + if ($biblioLoopIter{holds_count} >= $max_holds_per_record) { + $biblioLoopIter{holdable} = undef; + $anyholdable = undef; + $biblioLoopIter{max_holds} = 1; + } if(not CanBookBeReserved($borrowernumber,$biblioNum)){ $biblioLoopIter{holdable} = undef; } diff --git a/reserve/request.pl b/reserve/request.pl index 5bd9128..44b9a15 100755 --- a/reserve/request.pl +++ b/reserve/request.pl @@ -169,6 +169,7 @@ $template->param( messageborrower => $messageborrower ); # FIXME launch another time GetMember perhaps until my $borrowerinfo = GetMember( borrowernumber => $borrowernumber_hold ); +my $max_holds_per_record = C4::Context->preference('MaxHoldsPerRecord'); my @biblionumbers = (); my $biblionumbers = $input->param('biblionumbers'); @@ -212,15 +213,17 @@ foreach my $biblionumber (@biblionumbers) { } } - if ( $holds_count ) { - $alreadyreserved = 1; - $biblioloopiter{warn} = 1; - $biblioloopiter{alreadyres} = 1; + if ( $holds_count > $max_holds_per_record ) { + $alreadyreserved = 1; + $biblioloopiter{warn} = 1; + $biblioloopiter{alreadyres} = 1; + $biblioloopiter{maxreserves} = $max_holds_per_record; } $template->param( - alreadyreserved => $alreadyreserved, + alreadyreserved => $alreadyreserved, alreadypossession => $alreadypossession, + maxreserves => $maxreserves, ); # FIXME think @optionloop, is maybe obsolete, or must be switchable by a systeme preference fixed rank or not @@ -430,6 +433,12 @@ foreach my $biblionumber (@biblionumbers) { $itemdata_enumchron = 1; } + # Don't allow multiple item level holds on the same item if borrowers can place multiple holds on one bib + if ( GetReservesFromBorrowernumber( $borrowerinfo->{borrowernumber}, undef, undef, $itemnumber) ) { + $item->{available} = 0; + $item->{override} = 0; + } + push @{ $biblioitem->{itemloop} }, $item; } -- 1.7.2.5