@@ -, +, @@ --- C4/Circulation.pm | 2 +- C4/Reserves.pm | 176 ++++++++++++++++++++ admin/smart-rules.pl | 9 +- installer/data/mysql/en/mandatory/sysprefs.sql | 2 +- .../1-Obligatoire/unimarc_standard_systemprefs.sql | 2 +- installer/data/mysql/kohastructure.sql | 1 + installer/data/mysql/updatedatabase.pl | 16 ++ .../prog/en/modules/admin/smart-rules.tmpl | 3 + opac/opac-reserve.pl | 20 ++- reserve/request.pl | 22 ++- 10 files changed, 227 insertions(+), 26 deletions(-) --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -712,7 +712,7 @@ sub CanBookBeIssued { if ( $borrower->{'dateexpiry'} eq '0000-00-00') { $issuingimpossible{EXPIRED} = 1; } else { - my @expirydate= split /-/,$borrower->{'dateexpiry'}; + my @expirydate= split (/-/,$borrower->{'dateexpiry'}); if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 || Date_to_Days(Today) > Date_to_Days( @expirydate )) { $issuingimpossible{EXPIRED} = 1; --- a/C4/Reserves.pm +++ a/C4/Reserves.pm @@ -112,6 +112,8 @@ BEGIN { &ModReserveCancelAll &ModReserveMinusPriority + &CanBookBeReserved + &CanItemBeReserved &CheckReserves &CancelReserve @@ -326,6 +328,180 @@ sub GetReservesFromBorrowernumber { } #------------------------------------------------------------------------------------- +=item CanBookBeReserved + +$error = &CanBookBeReserved($borrowernumber, $biblionumber) + +=cut + +sub CanBookBeReserved{ + my ($borrowernumber, $biblionumber) = @_; + + my $dbh = C4::Context->dbh; + my $biblio = GetBiblioData($biblionumber); + my $borrower = C4::Members::GetMember($borrowernumber); + my $controlbranch = C4::Context->preference('ReservesControlBranch'); + my $itype = C4::Context->preference('item-level_itypes'); + my $reservesrights= 0; + my $reservescount = 0; + + # we retrieve the user rights + my @args; + my $rightsquery = "SELECT categorycode, itemtype, branchcode, reservesallowed + FROM issuingrules + WHERE categorycode = ?"; + push @args,$borrower->{categorycode}; + + if($controlbranch eq "ItemHomeLibrary"){ + $rightsquery .= " AND branchcode = '*'"; + }elsif($controlbranch eq "PatronLibrary"){ + $rightsquery .= " AND branchcode IN (?,'*')"; + push @args, $borrower->{branchcode}; + } + + if(not $itype){ + $rightsquery .= " AND itemtype IN (?,'*')"; + push @args, $biblio->{itemtype}; + }else{ + $rightsquery .= " AND itemtype = '*'"; + } + + $rightsquery .= " ORDER BY categorycode DESC, itemtype DESC, branchcode DESC"; + + my $sthrights = $dbh->prepare($rightsquery); + $sthrights->execute(@args); + + if(my $row = $sthrights->fetchrow_hashref()){ + $reservesrights = $row->{reservesallowed}; + } + + @args = (); + # we count how many reserves the borrower have + my $countquery = "SELECT count(*) as count + FROM reserves + LEFT JOIN items USING (itemnumber) + LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber) + LEFT JOIN borrowers USING (borrowernumber) + WHERE borrowernumber = ? + "; + push @args, $borrowernumber; + + if(not $itype){ + $countquery .= "AND itemtype = ?"; + push @args, $biblio->{itemtype}; + } + + if($controlbranch eq "PatronLibrary"){ + $countquery .= " AND borrowers.branchcode = ? "; + push @args, $borrower->{branchcode}; + } + + my $sthcount = $dbh->prepare($countquery); + $sthcount->execute(@args); + + if(my $row = $sthcount->fetchrow_hashref()){ + $reservescount = $row->{count}; + } + + if($reservescount < $reservesrights){ + return 1; + }else{ + return 0; + } + +} + +=item CanItemBeReserved + +$error = &CanItemBeReserved($borrowernumber, $itemnumber) + +this function return 1 if an item can be issued by this borrower. + +=cut + +sub CanItemBeReserved{ + my ($borrowernumber, $itemnumber) = @_; + + my $dbh = C4::Context->dbh; + my $allowedreserves = 0; + + my $controlbranch = C4::Context->preference('ReservesControlBranch'); + my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype"; + + # we retrieve borrowers and items informations # + my $item = GetItem($itemnumber); + my $borrower = C4::Members::GetMember($borrowernumber); + + # we retrieve user rights on this itemtype and branchcode + my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed + FROM issuingrules + WHERE (categorycode in (?,'*') ) + AND (itemtype IN (?,'*')) + AND (branchcode IN (?,'*')) + ORDER BY + categorycode DESC, + itemtype DESC, + branchcode DESC;" + ); + + my $querycount ="SELECT + count(*) as count + FROM reserves + LEFT JOIN items USING (itemnumber) + LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber) + LEFT JOIN borrowers USING (borrowernumber) + WHERE borrowernumber = ? + "; + + + my $itemtype = $item->{$itype}; + my $categorycode = $borrower->{categorycode}; + my $branchcode = ""; + my $branchfield = "reserves.branchcode"; + + if( $controlbranch eq "ItemHomeLibrary" ){ + $branchfield = "items.homebranch"; + $branchcode = $item->{homebranch}; + }elsif( $controlbranch eq "PatronLibrary" ){ + $branchfield = "borrowers.branchcode"; + $branchcode = $borrower->{branchcode}; + } + + # we retrieve rights + $sth->execute($categorycode, $itemtype, $branchcode); + if(my $rights = $sth->fetchrow_hashref()){ + $itemtype = $rights->{itemtype}; + $allowedreserves = $rights->{reservesallowed}; + }else{ + $itemtype = '*'; + } + + # we retrieve count + + $querycount .= "AND $branchfield = ?"; + + $querycount .= " AND $itype = ?" if ($itemtype ne "*"); + my $sthcount = $dbh->prepare($querycount); + + if($itemtype eq "*"){ + $sthcount->execute($borrowernumber, $branchcode); + }else{ + $sthcount->execute($borrowernumber, $branchcode, $itemtype); + } + + my $reservecount = "0"; + if(my $rowcount = $sthcount->fetchrow_hashref()){ + $reservecount = $rowcount->{count}; + } + + # we check if it's ok or not + if( $reservecount < $allowedreserves ){ + return 1; + }else{ + return 0; + } +} + =item GetReserveCount $number = &GetReserveCount($borrowernumber); --- a/admin/smart-rules.pl +++ a/admin/smart-rules.pl @@ -99,8 +99,8 @@ elsif ($op eq 'delete-branch-item') { # save the values entered elsif ($op eq 'add') { my $sth_search = $dbh->prepare("SELECT COUNT(*) AS total FROM issuingrules WHERE branchcode=? AND categorycode=? AND itemtype=?"); - my $sth_insert = $dbh->prepare("INSERT INTO issuingrules (branchcode, categorycode, itemtype, maxissueqty, renewalsallowed, issuelength, fine, firstremind, chargeperiod) VALUES(?,?,?,?,?,?,?,?,?)"); - my $sth_update=$dbh->prepare("UPDATE issuingrules SET fine=?, firstremind=?, chargeperiod=?, maxissueqty=?, renewalsallowed=?, issuelength=? WHERE branchcode=? AND categorycode=? AND itemtype=?"); + my $sth_insert = $dbh->prepare("INSERT INTO issuingrules (branchcode, categorycode, itemtype, maxissueqty, renewalsallowed, reservesallowed, issuelength, fine, firstremind, chargeperiod) VALUES(?,?,?,?,?,?,?,?,?,?)"); + my $sth_update=$dbh->prepare("UPDATE issuingrules SET fine=?, firstremind=?, chargeperiod=?, maxissueqty=?, renewalsallowed=?, reservesallowed=?, issuelength=? WHERE branchcode=? AND categorycode=? AND itemtype=?"); my $br = $branch; # branch my $bor = $input->param('categorycode'); # borrower category @@ -110,6 +110,7 @@ elsif ($op eq 'add') { my $chargeperiod = $input->param('chargeperiod'); my $maxissueqty = $input->param('maxissueqty'); my $renewalsallowed = $input->param('renewalsallowed'); + my $reservesallowed = $input->param('reservesallowed'); $maxissueqty =~ s/\s//g; $maxissueqty = undef if $maxissueqty !~ /^\d+/; my $issuelength = $input->param('issuelength'); @@ -118,9 +119,9 @@ elsif ($op eq 'add') { $sth_search->execute($br,$bor,$cat); my $res = $sth_search->fetchrow_hashref(); if ($res->{total}) { - $sth_update->execute($fine, $firstremind, $chargeperiod, $maxissueqty,$renewalsallowed,$issuelength,$br,$bor,$cat); + $sth_update->execute($fine, $firstremind, $chargeperiod, $maxissueqty,$renewalsallowed,$reservesallowed,$issuelength,$br,$bor,$cat); } else { - $sth_insert->execute($br,$bor,$cat,$maxissueqty,$renewalsallowed,$issuelength,$fine,$firstremind,$chargeperiod); + $sth_insert->execute($br,$bor,$cat,$maxissueqty,$renewalsallowed,$reservesallowed,$issuelength,$fine,$firstremind,$chargeperiod); } } elsif ($op eq "set-branch-defaults") { --- a/installer/data/mysql/en/mandatory/sysprefs.sql +++ a/installer/data/mysql/en/mandatory/sysprefs.sql @@ -56,7 +56,6 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('MARCOrgCode','OSt','Define MARC Organization Code - http://www.loc.gov/marc/organizations/orgshome.html','','free'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('MaxFine',9999,'Maximum fine a patron can have for a single late return','','Integer'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('maxoutstanding',5,'maximum amount withstanding to be able make holds','','Integer'); -INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('maxreserves',50,'Define maximum number of holds a patron can place','','Integer'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('memberofinstitution',0,'If ON, patrons can be linked to institutions',NULL,'YesNo'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('MIME','EXCEL','Define the default application for exporting report data','EXCEL|OPENOFFICE.ORG','Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('noissuescharge',5,'Define maximum amount withstanding before check outs are blocked','','Integer'); @@ -245,3 +244,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('v INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('viewLabeledMARC','0','Allow display of labeled MARC view of bibiographic records','','YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('viewMARC','1','Allow display of MARC view of bibiographic records','','YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('FilterBeforeOverdueReport','0','Do not run overdue report until filter selected','','YesNo'); +INSERT INTO systempreferences (variable,value, options, explanation, type) VALUES('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Branch checked for members reservations rights','Choice'); --- a/installer/data/mysql/fr-FR/1-Obligatoire/unimarc_standard_systemprefs.sql +++ a/installer/data/mysql/fr-FR/1-Obligatoire/unimarc_standard_systemprefs.sql @@ -57,7 +57,6 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('MARCOrgCode', '0', 'Ce paramètre définit votre code organisme MARC. Utilisé en MARC21. Voir - http://www.loc.gov/marc/organizations/orgshome.html', '', ''); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('MaxFine','9999','Amende maximum qu''un ahdérent peut avoir pour un retour en retard','','Integer'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('maxoutstanding', '5', 'Ce paramètre définit le montant maximal des dettes au dela duquel le lecteur ne peut plus faire de réservation', '', 'Integer'); -INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('maxreserves', '2', 'Ce paramètre définit le nombre maximal de réservations qu''un lecteur peut faire.', '', 'Integer'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('memberofinstitution', '0', 'Vos adhérents sont ils membres d''une institution ?', NULL, 'YesNo'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('MIME', 'OPENOFFICE.ORG', 'Ce paramètre définit l''application par défaut à ouvrir lorsqu''on télécharge un fichier (OpenOffice.org ou MS-Excel habituellement)', 'EXCEL|OPENOFFICE.ORG', 'Choice'); INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('noissuescharge', '5', 'Ce paramètre définit le montant maximal des dettes au delà duquel le lecteur ne peut plus emprunter', '', 'Integer'); @@ -247,3 +246,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('v INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('viewLabeledMARC','0','Autoriser l''affichage MARC labellis des notices bibliographiques','','YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('viewMARC','1','Autoriser l''affichage de la vue MARC des notices bibliographiques','','YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type)VALUES('FilterBeforeOverdueReport','0','Ne pas lancer le rapport sur les retards tant qu''il n''y a pas de filtre','','YesNo'); +INSERT INTO systempreferences (variable,value, options, explanation, type) VALUES('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Site de controle pour les reservations','Choice'); --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -1140,6 +1140,7 @@ CREATE TABLE `issuingrules` ( `maxissueqty` int(4) default NULL, `issuelength` int(4) default NULL, `renewalsallowed` smallint(6) NOT NULL default "0", + `reservesallowed` smallint(6) NOT NULL default "0", `branchcode` varchar(10) NOT NULL default '', PRIMARY KEY (`branchcode`,`categorycode`,`itemtype`), KEY `categorycode` (`categorycode`), --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -2493,6 +2493,22 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) { print "Upgrade to $DBversion done (Moving allowed renewals from itemtypes to issuingrule)\n"; } +$DBversion = '3.01.00.040'; +if (C4::Context->preference("Version") < TransformToNum($DBversion)) { + $dbh->do('ALTER TABLE issuingrules ADD COLUMN `reservesallowed` smallint(6) NOT NULL default "0" AFTER `renewalsallowed`;'); + + my $maxreserves = C4::Context->preference('maxreserves'); + $sth = $dbh->prepare('UPDATE issuingrules SET reservesallowed = ?;'); + $sth->execute($maxreserves); + + $dbh->do('DELETE FROM systempreferences WHERE variable = "maxreserves";'); + + $dbh->do("INSERT INTO systempreferences (variable,value, options, explanation, type) VALUES('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Branch checked for members reservations rights','Choice')"); + + SetVersion ($DBversion); + print "Upgrade to $DBversion done (Moving max allowed reserves from system preference to issuingrule)\n"; +} + =item DropAllForeignKeys($table) Drop all foreign keys of the table $table --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tmpl +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tmpl @@ -69,6 +69,7 @@ $(document).ready(function() { Fine Charging Interval Current Checkouts Allowed Renewals Allowed + Reserves Allowed Loan Period  @@ -95,6 +96,7 @@ $(document).ready(function() { time(s) + time(s) day(s) &categorycode=&branch=">Delete @@ -123,6 +125,7 @@ $(document).ready(function() { day(s) + day(s) "/> --- a/opac/opac-reserve.pl +++ a/opac/opac-reserve.pl @@ -32,8 +32,6 @@ use C4::Branch; # GetBranches use C4::Debug; # use Data::Dumper; -my $MAXIMUM_NUMBER_OF_RESERVES = C4::Context->preference("maxreserves"); - my $query = new CGI; my ( $template, $borrowernumber, $cookie ) = get_template_and_user( { @@ -190,11 +188,13 @@ if ( $query->param('place_reserve') ) { my $biblioData = $biblioDataHash{$biblioNum}; my $found; + my $canreserve = 0; # If a specific item was selected and the pickup branch is the same as the # holdingbranch, force the value $rank and $found. my $rank = $biblioData->{rank}; if ($itemNum ne ''){ + $canreserve = 1 if CanItemBeReserved($borrowernumber,$itemNum); $rank = '0' unless C4::Context->preference('ReservesNeedReturns'); my $item = GetItem($itemNum); if ( $item->{'holdingbranch'} eq $branch ){ @@ -203,12 +203,13 @@ if ( $query->param('place_reserve') ) { } else { # Inserts a null into the 'itemnumber' field of 'reserves' table. + $canreserve = 1 if CanBookBeReserved($borrowernumber,$biblioNum); $itemNum = undef; } # Here we actually do the reserveration. Stage 3. AddReserve($branch, $borrowernumber, $biblioNum, 'a', [$biblioNum], $rank, $notes, - $biblioData->{'title'}, $itemNum, $found); + $biblioData->{'title'}, $itemNum, $found) if $canreserve; } print $query->redirect("/cgi-bin/koha/opac-user.pl#opac-user-holds"); @@ -253,11 +254,8 @@ if ( $borr->{debarred} && ($borr->{debarred} eq 1) ) { my @reserves = GetReservesFromBorrowernumber( $borrowernumber ); $template->param( RESERVES => \@reserves ); -if ( scalar(@reserves) >= $MAXIMUM_NUMBER_OF_RESERVES ) { - $template->param( message => 1 ); - $noreserves = 1; - $template->param( too_many_reserves => scalar(@reserves)); -} + + foreach my $res (@reserves) { foreach my $biblionumber (@biblionumbers) { if ( $res->{'biblionumber'} == $biblionumber && $res->{'borrowernumber'} == $borrowernumber) { @@ -421,7 +419,7 @@ foreach my $biblioNum (@biblionumbers) { $policy_holdallowed = 0; } - if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed) { + if (IsAvailableForItemLevelRequest($itemNum) and $policy_holdallowed and CanItemBeReserved($borrowernumber,$itemNum)) { $itemLoopIter->{available} = 1; $numCopiesAvailable++; } @@ -447,6 +445,10 @@ foreach my $biblioNum (@biblionumbers) { $biblioLoopIter{holdable} = undef; } + if(not CanBookBeReserved($borrowernumber,$biblioNum)){ + $biblioLoopIter{holdable} = undef; + } + push @$biblioLoop, \%biblioLoopIter; } --- a/reserve/request.pl +++ a/reserve/request.pl @@ -88,6 +88,7 @@ my $borrowerslist; my $messageborrower; my $warnings; my $messages; +my $maxreserves; my $date = C4::Dates->today('iso'); @@ -115,24 +116,20 @@ if ($cardnumber) { my $diffbranch; my @getreservloop; my $count_reserv = 0; - my $maxreserves; # we check the reserves of the borrower, and if he can reserv a document # FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ... + + my $number_reserves = GetReserveCount( $borrowerinfo->{'borrowernumber'} ); - - if ( $number_reserves > C4::Context->preference('maxreserves') ) { - $warnings = 1; - $maxreserves = 1; - } - + # we check the date expiry of the borrower (only if there is an expiry date, otherwise, set to 1 (warn) my $expiry_date = $borrowerinfo->{dateexpiry}; my $expiry = 0; # flag set if patron account has expired if ($expiry_date and $expiry_date ne '0000-00-00' and - Date_to_Days(split /-/,$date) > Date_to_Days(split /-/,$expiry_date)) { + Date_to_Days(split (/-/,$date)) > Date_to_Days(split (/-/,$expiry_date))) { $messages = $expiry = 1; } @@ -157,7 +154,6 @@ if ($cardnumber) { borroweremailpro => $borrowerinfo->{'emailpro'}, borrowercategory => $borrowerinfo->{'category'}, borrowerreservs => $count_reserv, - maxreserves => $maxreserves, expiry => $expiry, diffbranch => $diffbranch, messages => $messages, @@ -213,6 +209,11 @@ if ($multihold) { my @biblioloop = (); foreach my $biblionumber (@biblionumbers) { + if ( not CanBookBeReserved($borrowerinfo->{borrowernumber}, $biblionumber) ) { + $warnings = 1; + $maxreserves = 1; + } + my %biblioloopiter = (); my $dat = GetBiblioData($biblionumber); @@ -399,7 +400,7 @@ foreach my $biblionumber (@biblionumbers) { $policy_holdallowed = 0; } - if (IsAvailableForItemLevelRequest($itemnumber) and not $item->{cantreserve}) { + if (IsAvailableForItemLevelRequest($itemnumber) and not $item->{cantreserve} and CanItemBeReserved($borrowerinfo->{borrowernumber}, $itemnumber) ) { if ( not $policy_holdallowed and C4::Context->preference( 'AllowHoldPolicyOverride' ) ) { $item->{override} = 1; $num_override++; @@ -545,6 +546,7 @@ foreach my $biblionumber (@biblionumbers) { $template->param( biblioloop => \@biblioloop ); $template->param( biblionumbers => $biblionumbers ); +$template->param( maxreserves => $maxreserves ); if ($multihold) { $template->param( multi_hold => 1 ); --