@@ -, +, @@ of titles --- a/C4/Reserves.pm +++ a/C4/Reserves.pm @@ -141,6 +141,10 @@ BEGIN { &GetReservesControlBranch IsItemOnHoldAndFound + + AddReserveGroup + DeleteReserveGroup + GetReserveGroupReserves ); @EXPORT_OK = qw( MergeHolds ); } @@ -165,7 +169,7 @@ sub AddReserve { my ( $branch, $borrowernumber, $biblionumber, $bibitems, $priority, $resdate, $expdate, $notes, - $title, $checkitem, $found + $title, $checkitem, $found, $reserve_group_id ) = @_; if ( Koha::Holds->search( { borrowernumber => $borrowernumber, biblionumber => $biblionumber } )->count() > 0 ) { @@ -205,7 +209,8 @@ sub AddReserve { itemnumber => $checkitem, found => $found, waitingdate => $waitingdate, - expirationdate => $expdate + expirationdate => $expdate, + reserve_group_id => $reserve_group_id, } )->store(); my $reserve_id = $hold->id(); @@ -1341,6 +1346,17 @@ sub ModReserveAffect { } $sth = $dbh->prepare($query); $sth->execute( $itemnumber, $borrowernumber,$biblionumber); + + if ($request->{reserve_group_id}) { + # Delete other reserves from same group + $dbh->do(q{ + DELETE FROM reserves + WHERE reserve_group_id = ? + AND reserve_id != ? + }, undef, $request->{reserve_group_id}, $request->{reserve_id}); + DeleteReserveGroup($request->{reserve_group_id}); + } + _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf ); _FixPriority( { biblionumber => $biblionumber } ); if ( C4::Context->preference("ReturnToShelvingCart") ) { @@ -1420,6 +1436,7 @@ sub GetReserveInfo { reminderdate, priority, found, + reserve_group_id, firstname, surname, phone, @@ -2450,6 +2467,60 @@ sub IsItemOnHoldAndFound { return $found; } +=head2 AddReserveGroup + + my $reserve_group_id = AddReserveGroup(); + +Creates a new reserve group and returns its ID + +=cut + +sub AddReserveGroup { + my $dbh = C4::Context->dbh; + + $dbh->do('INSERT INTO reserve_group VALUES ()'); + return $dbh->last_insert_id(undef, undef, 'reserve_group', undef); +} + +=head2 DeleteReserveGroup + + DeleteReserveGroup($reserve_group_id); + +Delete a reserve group. Reserves that belong to this group are not removed. + +=cut + +sub DeleteReserveGroup { + my ($reserve_group_id) = @_; + + my $dbh = C4::Context->dbh; + my $query = 'DELETE FROM reserve_group WHERE reserve_group_id = ?'; + $dbh->do($query, undef, $reserve_group_id); +} + +=head2 GetReserveGroupReserves + + my @reserves = GetReserveGroupReserves($reserve_group_id); + +Fetch all reserve from a reserve group. + +=cut + +sub GetReserveGroupReserves { + my ($reserve_group_id) = @_; + + my $dbh = C4::Context->dbh; + + my $reserves = $dbh->selectall_arrayref(q{ + SELECT * FROM reserves + WHERE reserve_group_id = ? + }, { Slice => {} }, $reserve_group_id); + + return () unless defined $reserves; + + return @$reserves; +} + =head1 AUTHOR Koha Development Team --- a/Koha/Template/Plugin/Biblio.pm +++ a/Koha/Template/Plugin/Biblio.pm @@ -32,4 +32,12 @@ sub HoldsCount { return $holds->count(); } +sub Get { + my ($self, $biblionumber) = @_; + + my $biblio = Koha::Biblios->find($biblionumber); + + return $biblio; +} + 1; --- a/circ/pendingreserves.pl +++ a/circ/pendingreserves.pl @@ -118,6 +118,7 @@ if ( $run_report ) { reminderdate, max(priority) as priority, reserves.found, + reserves.reserve_group_id, biblio.title, biblio.author, count(DISTINCT items.itemnumber) as icount, @@ -167,7 +168,8 @@ if ( $run_report ) { biblionumber => $data->{biblionumber}, statusw => ( $data->{found} eq "W" ), statusf => ( $data->{found} eq "F" ), - holdingbranches => [split('\|', $data->{l_holdingbranch})],, + reserve_group_id => $data->{reserve_group_id}, + holdingbranches => [split('\|', $data->{l_holdingbranch})], branch => $data->{l_branch}, itemcallnumber => $data->{l_itemcallnumber}, enumchron => $data->{l_enumchron}, --- a/installer/data/mysql/atomicupdate/bug_15516.sql +++ a/installer/data/mysql/atomicupdate/bug_15516.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS reserve_group; +CREATE TABLE reserve_group ( + reserve_group_id int unsigned NOT NULL AUTO_INCREMENT, + PRIMARY KEY (reserve_group_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +ALTER TABLE reserves ADD COLUMN reserve_group_id int unsigned NULL DEFAULT NULL; +ALTER TABLE reserves ADD CONSTRAINT reserves_ibfk_5 FOREIGN KEY (reserve_group_id) REFERENCES reserve_group (reserve_group_id) ON DELETE SET NULL ON UPDATE CASCADE; --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -1872,6 +1872,7 @@ CREATE TABLE `reserves` ( -- information related to holds/reserves in Koha `lowestPriority` tinyint(1) NOT NULL, `suspend` BOOLEAN NOT NULL DEFAULT 0, `suspend_until` DATETIME NULL DEFAULT NULL, + reserve_group_id int unsigned NULL DEFAULT NULL, PRIMARY KEY (`reserve_id`), KEY priorityfoundidx (priority,found), KEY `borrowernumber` (`borrowernumber`), @@ -1882,6 +1883,17 @@ CREATE TABLE `reserves` ( -- information related to holds/reserves in Koha CONSTRAINT `reserves_ibfk_2` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `reserves_ibfk_3` FOREIGN KEY (`itemnumber`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `reserves_ibfk_4` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) ON DELETE CASCADE ON UPDATE CASCADE + CONSTRAINT `reserves_ibfk_5` FOREIGN KEY (reserve_group_id) REFERENCES reserve_group (reserve_group_id) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table structure for table reserve_group +-- + +DROP TABLE IF EXISTS reserve_group; +CREATE TABLE reserve_group ( + reserve_group_id int unsigned NOT NULL AUTO_INCREMENT, + PRIMARY KEY(reserve_group_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- --- a/koha-tmpl/intranet-tmpl/prog/en/includes/reserve-group-modal.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/reserve-group-modal.inc @@ -0,0 +1,27 @@ + + + --- a/koha-tmpl/intranet-tmpl/prog/en/includes/strings.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/strings.inc @@ -38,5 +38,7 @@ var SUSPEND_HOLD_ERROR_DATE = _("Unable to suspend hold, invalid date"); var SUSPEND_HOLD_ERROR_NOT_FOUND = _("Unable to suspend hold, hold not found"); var RESUME_HOLD_ERROR_NOT_FOUND = _("Unable to resume, hold not found"); + var PART_OF_A = _("(part of a %s)"); + var RESERVE_GROUP = _("reserve group"); //]]> --- a/koha-tmpl/intranet-tmpl/prog/en/js/holds.js +++ a/koha-tmpl/intranet-tmpl/prog/en/js/holds.js @@ -42,6 +42,12 @@ $(document).ready(function() { title += " - " + oObj.itemnotes + "" } + if (oObj.reserve_group_id) { + title += '
'; + var link = '' + RESERVE_GROUP +'' + title += '' + PART_OF_A.format(link) + ''; + } + return title; } }, --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -1002,4 +1002,5 @@ No patron matched [% message %]

You have already submitted a barcode, please wait for the checkout to process...

+[% INCLUDE 'reserve-group-modal.inc' %] [% INCLUDE 'intranet-bottom.inc' %] --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/pendingreserves.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/pendingreserves.tt @@ -115,10 +115,16 @@ $(document).ready(function() { [% reserveloo.count %] [% reserveloo.rcount %] -

- [% INCLUDE 'biblio-default-view.inc' biblionumber = reserveloo.biblionumber %] - [% reserveloo.title |html %]

- [% IF ( reserveloo.author ) %]

by [% reserveloo.author %]

[% END %] +

+ [% INCLUDE 'biblio-default-view.inc' biblionumber = reserveloo.biblionumber %] + [% reserveloo.title |html %] +

+ [% IF ( reserveloo.author ) %] +

by [% reserveloo.author %]

+ [% END %] + [% IF reserveloo.reserve_group_id %] +

(part of a reserve group)

+ [% END %] [% ELSE %] " @@ -202,4 +208,5 @@ $(document).ready(function() { +[% INCLUDE 'reserve-group-modal.inc' %] [% INCLUDE 'intranet-bottom.inc' %] --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt @@ -543,4 +543,5 @@ function validate1(date) { [% INCLUDE 'circ-menu.inc' %] +[% INCLUDE 'reserve-group-modal.inc' %] [% INCLUDE 'intranet-bottom.inc' %] --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -424,13 +424,15 @@ function checkMultiHold() { Clear date - [% UNLESS ( multi_hold ) %]
  • - + [% IF multi_hold %] + + [% ELSE %] + + [% END %]
  • - [% END %] [% UNLESS ( multi_hold ) %] @@ -802,6 +804,9 @@ function checkMultiHold() { [% END %] [% END %] + [% IF reserveloo.reserve_group_id %] +
    (part of a reserve group)
    + [% END %] [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] @@ -862,4 +867,7 @@ function checkMultiHold() { + +[% INCLUDE 'reserve-group-modal.inc' %] + [% INCLUDE 'intranet-bottom.inc' %] --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/reserve-group.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/reserve-group.tt @@ -0,0 +1,30 @@ +[% USE Biblio %] +[% INCLUDE 'doc-head-open.inc' %] + Koha › Circulation › Holds › Holds group ([% reserve_group_id %]) + [% INCLUDE 'doc-head-close.inc' %] + + +

    Holds group ([% reserve_group_id %])

    +
    + [% IF reserves.size %] + + + + + + + + + + [% FOREACH reserve IN reserves %] + [% biblio = Biblio.Get(reserve.biblionumber) %] + + + + + + [% END %] + +
    TitlePriorityNotes
    [% biblio.title %][% reserve.priority %][% reserve.reservenotes %]
    + [% END %] + [% INCLUDE 'intranet-bottom.inc' %] --- a/reserve/placerequest.pl +++ a/reserve/placerequest.pl @@ -50,6 +50,7 @@ my $title=$input->param('title'); my $borrower=GetMember('borrowernumber'=>$borrowernumber); my $checkitem=$input->param('checkitem'); my $expirationdate = $input->param('expiration_date'); +my $requestany = $input->param('requestany'); my $multi_hold = $input->param('multi_hold'); my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ($biblionumber . '/'); @@ -79,6 +80,11 @@ if (defined $checkitem && $checkitem ne ''){ if ($type eq 'str8' && $borrower){ + my $reserve_group_id; + if ($multi_hold && $requestany) { + $reserve_group_id = AddReserveGroup(); + } + foreach my $biblionumber (keys %bibinfos) { my $count=@bibitems; @bibitems=sort @bibitems; @@ -104,8 +110,10 @@ if ($type eq 'str8' && $borrower){ if ($multi_hold) { my $bibinfo = $bibinfos{$biblionumber}; - AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,[$biblionumber], - $bibinfo->{rank},$startdate,$expirationdate,$notes,$bibinfo->{title},$checkitem,$found); + AddReserve($branch, $borrower->{'borrowernumber'}, $biblionumber, + [$biblionumber], $bibinfo->{rank}, $startdate, $expirationdate, + $notes, $bibinfo->{title}, $checkitem, $found, + $reserve_group_id); } else { # place a request on 1st available AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,\@realbi,$rank[0],$startdate,$expirationdate,$notes,$title,$checkitem,$found); --- a/reserve/request.pl +++ a/reserve/request.pl @@ -562,6 +562,7 @@ foreach my $biblionumber (@biblionumbers) { $reserve{'suspend'} = $res->suspend(); $reserve{'suspend_until'} = $res->suspend_until(); $reserve{'reserve_id'} = $res->reserve_id(); + $reserve{'reserve_group_id'} = $res->reserve_group_id; if ( C4::Context->preference('IndependentBranches') && $flags->{'superlibrarian'} != 1 ) { $reserve{'branchloop'} = [ GetBranchDetail( $res->branchcode() ) ]; --- a/reserve/reserve-group.pl +++ a/reserve/reserve-group.pl @@ -0,0 +1,42 @@ +#!/usr/bin/env perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use CGI qw( -utf8 ); + +use C4::Auth; +use C4::Output; +use C4::Reserves; + +my $cgi = new CGI; +my ($template, $loggedinuser, $cookie) = get_template_and_user({ + template_name => "reserve/reserve-group.tt", + query => $cgi, + type => "intranet", + authnotrequired => 0, + flagsrequired => { circulate => 'circulate_remaining_permissions' }, +}); + +my $reserve_group_id = $cgi->param('reserve_group_id'); +my @reserves = GetReserveGroupReserves($reserve_group_id); + +$template->param( + reserve_group_id => $reserve_group_id, + reserves => \@reserves, +); + +output_html_with_http_headers $cgi, $cookie, $template->output; --- a/svc/holds +++ a/svc/holds @@ -89,6 +89,7 @@ while ( my $h = $holds_rs->next() ) { waiting_at => $h->branch()->branchname(), waiting_here => $h->branch()->branchcode() eq $branch, priority => $h->priority(), + reserve_group_id => $h->reserve_group_id, subtitle => GetRecordValue( 'subtitle', GetMarcBiblio($biblionumber), GetFrameworkCode($biblionumber) --