← Index
NYTProf Performance Profile   « line view »
For svc/members/upsert
  Run on Tue Jan 13 11:50:22 2015
Reported on Tue Jan 13 12:09:48 2015

Filename/mnt/catalyst/koha/C4/Reserves.pm
StatementsExecuted 36 statements in 18.7ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
11113.3ms233msC4::Reserves::::BEGIN@27C4::Reserves::BEGIN@27
11110.0ms28.7msC4::Reserves::::BEGIN@29C4::Reserves::BEGIN@29
1115.26ms55.9msC4::Reserves::::BEGIN@36C4::Reserves::BEGIN@36
1114.78ms5.63msC4::Reserves::::BEGIN@31C4::Reserves::BEGIN@31
1113.63ms4.25msC4::Reserves::::BEGIN@34C4::Reserves::BEGIN@34
111420µs432µsC4::Reserves::::BEGIN@24C4::Reserves::BEGIN@24
11112µs12µsC4::Reserves::::BEGIN@28C4::Reserves::BEGIN@28
11111µs11µsC4::Reserves::::BEGIN@89C4::Reserves::BEGIN@89
11111µs11µsC4::Reserves::::BEGIN@30C4::Reserves::BEGIN@30
1119µs30µsC4::Reserves::::BEGIN@37C4::Reserves::BEGIN@37
1119µs27µsC4::Reserves::::BEGIN@42C4::Reserves::BEGIN@42
1119µs26µsC4::Reserves::::BEGIN@38C4::Reserves::BEGIN@38
1118µs32µsC4::Reserves::::BEGIN@40C4::Reserves::BEGIN@40
1117µs9µsC4::Reserves::::BEGIN@26C4::Reserves::BEGIN@26
1116µs50µsC4::Reserves::::BEGIN@44C4::Reserves::BEGIN@44
1114µs4µsC4::Reserves::::BEGIN@35C4::Reserves::BEGIN@35
0000s0sC4::Reserves::::AddReserveC4::Reserves::AddReserve
0000s0sC4::Reserves::::AlterPriorityC4::Reserves::AlterPriority
0000s0sC4::Reserves::::AutoUnsuspendReservesC4::Reserves::AutoUnsuspendReserves
0000s0sC4::Reserves::::CalculatePriorityC4::Reserves::CalculatePriority
0000s0sC4::Reserves::::CanBookBeReservedC4::Reserves::CanBookBeReserved
0000s0sC4::Reserves::::CanItemBeReservedC4::Reserves::CanItemBeReserved
0000s0sC4::Reserves::::CanReserveBeCanceledFromOpacC4::Reserves::CanReserveBeCanceledFromOpac
0000s0sC4::Reserves::::CancelExpiredReservesC4::Reserves::CancelExpiredReserves
0000s0sC4::Reserves::::CancelReserveC4::Reserves::CancelReserve
0000s0sC4::Reserves::::CheckReservesC4::Reserves::CheckReserves
0000s0sC4::Reserves::::GetOtherReservesC4::Reserves::GetOtherReserves
0000s0sC4::Reserves::::GetReserveC4::Reserves::GetReserve
0000s0sC4::Reserves::::GetReserveCountC4::Reserves::GetReserveCount
0000s0sC4::Reserves::::GetReserveFeeC4::Reserves::GetReserveFee
0000s0sC4::Reserves::::GetReserveIdC4::Reserves::GetReserveId
0000s0sC4::Reserves::::GetReserveInfoC4::Reserves::GetReserveInfo
0000s0sC4::Reserves::::GetReserveStatusC4::Reserves::GetReserveStatus
0000s0sC4::Reserves::::GetReservesControlBranchC4::Reserves::GetReservesControlBranch
0000s0sC4::Reserves::::GetReservesForBranchC4::Reserves::GetReservesForBranch
0000s0sC4::Reserves::::GetReservesFromBiblionumberC4::Reserves::GetReservesFromBiblionumber
0000s0sC4::Reserves::::GetReservesFromBorrowernumberC4::Reserves::GetReservesFromBorrowernumber
0000s0sC4::Reserves::::GetReservesFromItemnumberC4::Reserves::GetReservesFromItemnumber
0000s0sC4::Reserves::::GetReservesToBranchC4::Reserves::GetReservesToBranch
0000s0sC4::Reserves::::IsAvailableForItemLevelRequestC4::Reserves::IsAvailableForItemLevelRequest
0000s0sC4::Reserves::::MergeHoldsC4::Reserves::MergeHolds
0000s0sC4::Reserves::::ModReserveC4::Reserves::ModReserve
0000s0sC4::Reserves::::ModReserveAffectC4::Reserves::ModReserveAffect
0000s0sC4::Reserves::::ModReserveCancelAllC4::Reserves::ModReserveCancelAll
0000s0sC4::Reserves::::ModReserveFillC4::Reserves::ModReserveFill
0000s0sC4::Reserves::::ModReserveMinusPriorityC4::Reserves::ModReserveMinusPriority
0000s0sC4::Reserves::::ModReserveStatusC4::Reserves::ModReserveStatus
0000s0sC4::Reserves::::MoveReserveC4::Reserves::MoveReserve
0000s0sC4::Reserves::::ReserveSlipC4::Reserves::ReserveSlip
0000s0sC4::Reserves::::RevertWaitingStatusC4::Reserves::RevertWaitingStatus
0000s0sC4::Reserves::::SuspendAllC4::Reserves::SuspendAll
0000s0sC4::Reserves::::ToggleLowestPriorityC4::Reserves::ToggleLowestPriority
0000s0sC4::Reserves::::ToggleSuspendC4::Reserves::ToggleSuspend
0000s0sC4::Reserves::::_FindgroupreserveC4::Reserves::_Findgroupreserve
0000s0sC4::Reserves::::_FixPriorityC4::Reserves::_FixPriority
0000s0sC4::Reserves::::_ShiftPriorityByDateAndPriorityC4::Reserves::_ShiftPriorityByDateAndPriority
0000s0sC4::Reserves::::_koha_notify_reserveC4::Reserves::_koha_notify_reserve
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1package C4::Reserves;
2
3# Copyright 2000-2002 Katipo Communications
4# 2006 SAN Ouest Provence
5# 2007-2010 BibLibre Paul POULAIN
6# 2011 Catalyst IT
7#
8# This file is part of Koha.
9#
10# Koha is free software; you can redistribute it and/or modify it under the
11# terms of the GNU General Public License as published by the Free Software
12# Foundation; either version 2 of the License, or (at your option) any later
13# version.
14#
15# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with Koha; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23
24227µs2443µs
# spent 432µs (420+12) within C4::Reserves::BEGIN@24 which was called: # once (420µs+12µs) by C4::Circulation::BEGIN@27 at line 24
use strict;
# spent 432µs making 1 call to C4::Reserves::BEGIN@24 # spent 12µs making 1 call to strict::import
25#use warnings; FIXME - Bug 2505
26219µs211µs
# spent 9µs (7+2) within C4::Reserves::BEGIN@26 which was called: # once (7µs+2µs) by C4::Circulation::BEGIN@27 at line 26
use C4::Context;
# spent 9µs making 1 call to C4::Reserves::BEGIN@26 # spent 2µs making 1 call to C4::Context::import
2722.03ms2234ms
# spent 233ms (13.3+220) within C4::Reserves::BEGIN@27 which was called: # once (13.3ms+220ms) by C4::Circulation::BEGIN@27 at line 27
use C4::Biblio;
# spent 233ms making 1 call to C4::Reserves::BEGIN@27 # spent 329µs making 1 call to Exporter::import
28228µs112µs
# spent 12µs within C4::Reserves::BEGIN@28 which was called: # once (12µs+0s) by C4::Circulation::BEGIN@27 at line 28
use C4::Members;
# spent 12µs making 1 call to C4::Reserves::BEGIN@28
2923.06ms228.8ms
# spent 28.7ms (10.0+18.7) within C4::Reserves::BEGIN@29 which was called: # once (10.0ms+18.7ms) by C4::Circulation::BEGIN@27 at line 29
use C4::Items;
# spent 28.7ms making 1 call to C4::Reserves::BEGIN@29 # spent 109µs making 1 call to Exporter::import
30227µs111µs
# spent 11µs within C4::Reserves::BEGIN@30 which was called: # once (11µs+0s) by C4::Circulation::BEGIN@27 at line 30
use C4::Circulation;
# spent 11µs making 1 call to C4::Reserves::BEGIN@30
3122.69ms25.76ms
# spent 5.63ms (4.78+852µs) within C4::Reserves::BEGIN@31 which was called: # once (4.78ms+852µs) by C4::Circulation::BEGIN@27 at line 31
use C4::Accounts;
# spent 5.63ms making 1 call to C4::Reserves::BEGIN@31 # spent 135µs making 1 call to Exporter::import
32
33# for _koha_notify_reserve
3423.35ms14.25ms
# spent 4.25ms (3.63+624µs) within C4::Reserves::BEGIN@34 which was called: # once (3.63ms+624µs) by C4::Circulation::BEGIN@27 at line 34
use C4::Members::Messaging;
# spent 4.25ms making 1 call to C4::Reserves::BEGIN@34
35220µs14µs
# spent 4µs within C4::Reserves::BEGIN@35 which was called: # once (4µs+0s) by C4::Circulation::BEGIN@27 at line 35
use C4::Members qw();
# spent 4µs making 1 call to C4::Reserves::BEGIN@35
3621.95ms256.0ms
# spent 55.9ms (5.26+50.6) within C4::Reserves::BEGIN@36 which was called: # once (5.26ms+50.6ms) by C4::Circulation::BEGIN@27 at line 36
use C4::Letters;
# spent 55.9ms making 1 call to C4::Reserves::BEGIN@36 # spent 114µs making 1 call to Exporter::import
37225µs250µs
# spent 30µs (9+21) within C4::Reserves::BEGIN@37 which was called: # once (9µs+21µs) by C4::Circulation::BEGIN@27 at line 37
use C4::Branch qw( GetBranchDetail );
# spent 30µs making 1 call to C4::Reserves::BEGIN@37 # spent 20µs making 1 call to Exporter::import
38225µs244µs
# spent 26µs (9+18) within C4::Reserves::BEGIN@38 which was called: # once (9µs+18µs) by C4::Circulation::BEGIN@27 at line 38
use C4::Dates qw( format_date_in_iso );
# spent 26µs making 1 call to C4::Reserves::BEGIN@38 # spent 18µs making 1 call to Exporter::import
39
40222µs255µs
# spent 32µs (8+24) within C4::Reserves::BEGIN@40 which was called: # once (8µs+24µs) by C4::Circulation::BEGIN@27 at line 40
use Koha::DateUtils;
# spent 32µs making 1 call to C4::Reserves::BEGIN@40 # spent 24µs making 1 call to Exporter::import
41
42227µs244µs
# spent 27µs (9+18) within C4::Reserves::BEGIN@42 which was called: # once (9µs+18µs) by C4::Circulation::BEGIN@27 at line 42
use List::MoreUtils qw( firstidx );
# spent 27µs making 1 call to C4::Reserves::BEGIN@42 # spent 18µs making 1 call to Exporter::import
43
44291µs294µs
# spent 50µs (6+44) within C4::Reserves::BEGIN@44 which was called: # once (6µs+44µs) by C4::Circulation::BEGIN@27 at line 44
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
# spent 50µs making 1 call to C4::Reserves::BEGIN@44 # spent 44µs making 1 call to vars::import
45
46=head1 NAME
47
48C4::Reserves - Koha functions for dealing with reservation.
49
50=head1 SYNOPSIS
51
52 use C4::Reserves;
53
54=head1 DESCRIPTION
55
56This modules provides somes functions to deal with reservations.
57
58 Reserves are stored in reserves table.
59 The following columns contains important values :
60 - priority >0 : then the reserve is at 1st stage, and not yet affected to any item.
61 =0 : then the reserve is being dealed
62 - found : NULL : means the patron requested the 1st available, and we haven't choosen the item
63 T(ransit) : the reserve is linked to an item but is in transit to the pickup branch
64 W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
65 F(inished) : the reserve has been completed, and is done
66 - itemnumber : empty : the reserve is still unaffected to an item
67 filled: the reserve is attached to an item
68 The complete workflow is :
69 ==== 1st use case ====
70 patron request a document, 1st available : P >0, F=NULL, I=NULL
71 a library having it run "transfertodo", and clic on the list
72 if there is no transfer to do, the reserve waiting
73 patron can pick it up P =0, F=W, I=filled
74 if there is a transfer to do, write in branchtransfer P =0, F=T, I=filled
75 The pickup library recieve the book, it check in P =0, F=W, I=filled
76 The patron borrow the book P =0, F=F, I=filled
77
78 ==== 2nd use case ====
79 patron requests a document, a given item,
80 If pickup is holding branch P =0, F=W, I=filled
81 If transfer needed, write in branchtransfer P =0, F=T, I=filled
82 The pickup library receive the book, it checks it in P =0, F=W, I=filled
83 The patron borrow the book P =0, F=F, I=filled
84
85=head1 FUNCTIONS
86
87=cut
88
89
# spent 11µs within C4::Reserves::BEGIN@89 which was called: # once (11µs+0s) by C4::Circulation::BEGIN@27 at line 139
BEGIN {
90 # set the version for version checking
911900ns $VERSION = 3.07.00.049;
921500ns require Exporter;
9315µs @ISA = qw(Exporter);
9412µs @EXPORT = qw(
95 &AddReserve
96
97 &GetReserve
98 &GetReservesFromItemnumber
99 &GetReservesFromBiblionumber
100 &GetReservesFromBorrowernumber
101 &GetReservesForBranch
102 &GetReservesToBranch
103 &GetReserveCount
104 &GetReserveFee
105 &GetReserveInfo
106 &GetReserveStatus
107
108 &GetOtherReserves
109
110 &ModReserveFill
111 &ModReserveAffect
112 &ModReserve
113 &ModReserveStatus
114 &ModReserveCancelAll
115 &ModReserveMinusPriority
116 &MoveReserve
117
118 &CheckReserves
119 &CanBookBeReserved
120 &CanItemBeReserved
121 &CanReserveBeCanceledFromOpac
122 &CancelReserve
123 &CancelExpiredReserves
124
125 &AutoUnsuspendReserves
126
127 &IsAvailableForItemLevelRequest
128
129 &AlterPriority
130 &ToggleLowestPriority
131
132 &ReserveSlip
133 &ToggleSuspend
134 &SuspendAll
135
136 &GetReservesControlBranch
137 );
13814µs @EXPORT_OK = qw( MergeHolds );
13915.31ms111µs}
# spent 11µs making 1 call to C4::Reserves::BEGIN@89
140
141=head2 AddReserve
142
143 AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
144
145=cut
146
147sub AddReserve {
148 my (
149 $branch, $borrowernumber, $biblionumber,
150 $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
151 $title, $checkitem, $found
152 ) = @_;
153 my $fee =
154 GetReserveFee($borrowernumber, $biblionumber, $constraint,
155 $bibitems );
156 my $dbh = C4::Context->dbh;
157 my $const = lc substr( $constraint, 0, 1 );
158 $resdate = format_date_in_iso( $resdate ) if ( $resdate );
159 $resdate = C4::Dates->today( 'iso' ) unless ( $resdate );
160 if ($expdate) {
161 $expdate = format_date_in_iso( $expdate );
162 } else {
163 undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
164 }
165 if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
166 # Make room in reserves for this before those of a later reserve date
167 $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
168 }
169 my $waitingdate;
170
171 # If the reserv had the waiting status, we had the value of the resdate
172 if ( $found eq 'W' ) {
173 $waitingdate = $resdate;
174 }
175
176 #eval {
177 # updates take place here
178 if ( $fee > 0 ) {
179 my $nextacctno = &getnextacctno( $borrowernumber );
180 my $query = qq/
181 INSERT INTO accountlines
182 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
183 VALUES
184 (?,?,now(),?,?,'Res',?)
185 /;
186 my $usth = $dbh->prepare($query);
187 $usth->execute( $borrowernumber, $nextacctno, $fee,
188 "Reserve Charge - $title", $fee );
189 }
190
191 #if ($const eq 'a'){
192 my $query = qq/
193 INSERT INTO reserves
194 (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
195 priority,reservenotes,itemnumber,found,waitingdate,expirationdate)
196 VALUES
197 (?,?,?,?,?,
198 ?,?,?,?,?,?)
199 /;
200 my $sth = $dbh->prepare($query);
201 $sth->execute(
202 $borrowernumber, $biblionumber, $resdate, $branch,
203 $const, $priority, $notes, $checkitem,
204 $found, $waitingdate, $expdate
205 );
206
207 # Send e-mail to librarian if syspref is active
208 if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){
209 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
210 my $branch_details = C4::Branch::GetBranchDetail($borrower->{branchcode});
211 if ( my $letter = C4::Letters::GetPreparedLetter (
212 module => 'reserves',
213 letter_code => 'HOLDPLACED',
214 branchcode => $branch,
215 tables => {
216 'branches' => $branch_details,
217 'borrowers' => $borrower,
218 'biblio' => $biblionumber,
219 'items' => $checkitem,
220 },
221 ) ) {
222
223 my $admin_email_address =$branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
224
225 C4::Letters::EnqueueLetter(
226 { letter => $letter,
227 borrowernumber => $borrowernumber,
228 message_transport_type => 'email',
229 from_address => $admin_email_address,
230 to_address => $admin_email_address,
231 }
232 );
233 }
234 }
235
236 #}
237 ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value?
238 $query = qq/
239 INSERT INTO reserveconstraints
240 (borrowernumber,biblionumber,reservedate,biblioitemnumber)
241 VALUES
242 (?,?,?,?)
243 /;
244 $sth = $dbh->prepare($query); # keep prepare outside the loop!
245 foreach (@$bibitems) {
246 $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
247 }
248
249 return; # FIXME: why not have a useful return value?
250}
251
252=head2 GetReserve
253
254 $res = GetReserve( $reserve_id );
255
256 Return the current reserve.
257
258=cut
259
260sub GetReserve {
261 my ($reserve_id) = @_;
262
263 my $dbh = C4::Context->dbh;
264 my $query = "SELECT * FROM reserves WHERE reserve_id = ?";
265 my $sth = $dbh->prepare( $query );
266 $sth->execute( $reserve_id );
267 return $sth->fetchrow_hashref();
268}
269
270=head2 GetReservesFromBiblionumber
271
272 my $reserves = GetReservesFromBiblionumber({
273 biblionumber => $biblionumber,
274 [ itemnumber => $itemnumber, ]
275 [ all_dates => 1|0 ]
276 });
277
278This function gets the list of reservations for one C<$biblionumber>,
279returning an arrayref pointing to the reserves for C<$biblionumber>.
280
281By default, only reserves whose start date falls before the current
282time are returned. To return all reserves, including future ones,
283the C<all_dates> parameter can be included and set to a true value.
284
285If the C<itemnumber> parameter is supplied, reserves must be targeted
286to that item or not targeted to any item at all; otherwise, they
287are excluded from the list.
288
289=cut
290
291sub GetReservesFromBiblionumber {
292 my ( $params ) = @_;
293 my $biblionumber = $params->{biblionumber} or return [];
294 my $itemnumber = $params->{itemnumber};
295 my $all_dates = $params->{all_dates} // 0;
296 my $dbh = C4::Context->dbh;
297
298 # Find the desired items in the reserves
299 my @params;
300 my $query = "
301 SELECT reserve_id,
302 branchcode,
303 timestamp AS rtimestamp,
304 priority,
305 biblionumber,
306 borrowernumber,
307 reservedate,
308 constrainttype,
309 found,
310 itemnumber,
311 reservenotes,
312 expirationdate,
313 lowestPriority,
314 suspend,
315 suspend_until
316 FROM reserves
317 WHERE biblionumber = ? ";
318 push( @params, $biblionumber );
319 unless ( $all_dates ) {
320 $query .= " AND reservedate <= CAST(NOW() AS DATE) ";
321 }
322 if ( $itemnumber ) {
323 $query .= " AND ( itemnumber IS NULL OR itemnumber = ? )";
324 push( @params, $itemnumber );
325 }
326 $query .= "ORDER BY priority";
327 my $sth = $dbh->prepare($query);
328 $sth->execute( @params );
329 my @results;
330 my $i = 0;
331 while ( my $data = $sth->fetchrow_hashref ) {
332
333 # FIXME - What is this doing? How do constraints work?
334 if ($data->{constrainttype} eq 'o') {
335 $query = '
336 SELECT biblioitemnumber
337 FROM reserveconstraints
338 WHERE biblionumber = ?
339 AND borrowernumber = ?
340 AND reservedate = ?
341 ';
342 my $csth = $dbh->prepare($query);
343 $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate});
344 my @bibitemno;
345 while ( my $bibitemnos = $csth->fetchrow_array ) {
346 push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref
347 }
348 my $count = scalar @bibitemno;
349
350 # if we have two or more different specific itemtypes
351 # reserved by same person on same day
352 my $bdata;
353 if ( $count > 1 ) {
354 $bdata = GetBiblioItemData( $bibitemno[$i] ); # FIXME: This doesn't make sense.
355 $i++; # $i can increase each pass, but the next @bibitemno might be smaller?
356 }
357 else {
358 # Look up the book we just found.
359 $bdata = GetBiblioItemData( $bibitemno[0] );
360 }
361 # Add the results of this latest search to the current
362 # results.
363 # FIXME - An 'each' would probably be more efficient.
364 foreach my $key ( keys %$bdata ) {
365 $data->{$key} = $bdata->{$key};
366 }
367 }
368 push @results, $data;
369 }
370 return \@results;
371}
372
373=head2 GetReservesFromItemnumber
374
375 ( $reservedate, $borrowernumber, $branchcode, $reserve_id, $waitingdate ) = GetReservesFromItemnumber($itemnumber);
376
377Get the first reserve for a specific item number (based on priority). Returns the abovementioned values for that reserve.
378
379The routine does not look at future reserves (read: item level holds), but DOES include future waits (a confirmed future hold).
380
381=cut
382
383sub GetReservesFromItemnumber {
384 my ( $itemnumber ) = @_;
385 my $dbh = C4::Context->dbh;
386 my $query = "
387 SELECT reservedate,borrowernumber,branchcode,reserve_id,waitingdate
388 FROM reserves
389 WHERE itemnumber=? AND ( reservedate <= CAST(now() AS date) OR
390 waitingdate IS NOT NULL )
391 ORDER BY priority
392 ";
393 my $sth_res = $dbh->prepare($query);
394 $sth_res->execute($itemnumber);
395 my ( $reservedate, $borrowernumber,$branchcode, $reserve_id, $wait ) = $sth_res->fetchrow_array;
396 return ( $reservedate, $borrowernumber, $branchcode, $reserve_id, $wait );
397}
398
399=head2 GetReservesFromBorrowernumber
400
401 $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
402
403TODO :: Descritpion
404
405=cut
406
407sub GetReservesFromBorrowernumber {
408 my ( $borrowernumber, $status ) = @_;
409 my $dbh = C4::Context->dbh;
410 my $sth;
411 if ($status) {
412 $sth = $dbh->prepare("
413 SELECT *
414 FROM reserves
415 WHERE borrowernumber=?
416 AND found =?
417 ORDER BY reservedate
418 ");
419 $sth->execute($borrowernumber,$status);
420 } else {
421 $sth = $dbh->prepare("
422 SELECT *
423 FROM reserves
424 WHERE borrowernumber=?
425 ORDER BY reservedate
426 ");
427 $sth->execute($borrowernumber);
428 }
429 my $data = $sth->fetchall_arrayref({});
430 return @$data;
431}
432#-------------------------------------------------------------------------------------
433=head2 CanBookBeReserved
434
435 $error = &CanBookBeReserved($borrowernumber, $biblionumber)
436
437=cut
438
439sub CanBookBeReserved{
440 my ($borrowernumber, $biblionumber) = @_;
441
442 my $items = GetItemnumbersForBiblio($biblionumber);
443 #get items linked via host records
444 my @hostitems = get_hostitemnumbers_of($biblionumber);
445 if (@hostitems){
446 push (@$items,@hostitems);
447 }
448
449 foreach my $item (@$items){
450 return 1 if CanItemBeReserved($borrowernumber, $item);
451 }
452 return 0;
453}
454
455=head2 CanItemBeReserved
456
457 $error = &CanItemBeReserved($borrowernumber, $itemnumber)
458
459This function return 1 if an item can be issued by this borrower.
460
461=cut
462
463sub CanItemBeReserved{
464 my ($borrowernumber, $itemnumber) = @_;
465
466 my $dbh = C4::Context->dbh;
467 my $ruleitemtype; # itemtype of the matching issuing rule
468 my $allowedreserves = 0;
469
470 # we retrieve borrowers and items informations #
471 # item->{itype} will come for biblioitems if necessery
472 my $item = GetItem($itemnumber);
473
474 # If an item is damaged and we don't allow holds on damaged items, we can stop right here
475 return 0 if ( $item->{damaged} && !C4::Context->preference('AllowHoldsOnDamagedItems') );
476
477 my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);
478
479 my $controlbranch = C4::Context->preference('ReservesControlBranch');
480 my $itemtypefield = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
481
482 # we retrieve user rights on this itemtype and branchcode
483 my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed
484 FROM issuingrules
485 WHERE (categorycode in (?,'*') )
486 AND (itemtype IN (?,'*'))
487 AND (branchcode IN (?,'*'))
488 ORDER BY
489 categorycode DESC,
490 itemtype DESC,
491 branchcode DESC;"
492 );
493
494 my $querycount ="SELECT
495 count(*) as count
496 FROM reserves
497 LEFT JOIN items USING (itemnumber)
498 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
499 LEFT JOIN borrowers USING (borrowernumber)
500 WHERE borrowernumber = ?
501 ";
502
503
504 my $branchcode = "";
505 my $branchfield = "reserves.branchcode";
506
507 if( $controlbranch eq "ItemHomeLibrary" ){
508 $branchfield = "items.homebranch";
509 $branchcode = $item->{homebranch};
510 }elsif( $controlbranch eq "PatronLibrary" ){
511 $branchfield = "borrowers.branchcode";
512 $branchcode = $borrower->{branchcode};
513 }
514
515 # we retrieve rights
516 $sth->execute($borrower->{'categorycode'}, $item->{'itype'}, $branchcode);
517 if(my $rights = $sth->fetchrow_hashref()){
518 $ruleitemtype = $rights->{itemtype};
519 $allowedreserves = $rights->{reservesallowed};
520 }else{
521 $ruleitemtype = '*';
522 }
523
524 # we retrieve count
525
526 $querycount .= "AND $branchfield = ?";
527
528 $querycount .= " AND $itemtypefield = ?" if ($ruleitemtype ne "*");
529 my $sthcount = $dbh->prepare($querycount);
530
531 if($ruleitemtype eq "*"){
532 $sthcount->execute($borrowernumber, $branchcode);
533 }else{
534 $sthcount->execute($borrowernumber, $branchcode, $ruleitemtype);
535 }
536
537 my $reservecount = "0";
538 if(my $rowcount = $sthcount->fetchrow_hashref()){
539 $reservecount = $rowcount->{count};
540 }
541
542 # we check if it's ok or not
543 if( $reservecount >= $allowedreserves ){
544 return 0;
545 }
546
547 # If reservecount is ok, we check item branch if IndependentBranches is ON
548 # and canreservefromotherbranches is OFF
549 if ( C4::Context->preference('IndependentBranches')
550 and !C4::Context->preference('canreservefromotherbranches') )
551 {
552 my $itembranch = $item->{homebranch};
553 if ($itembranch ne $borrower->{branchcode}) {
554 return 0;
555 }
556 }
557
558 return 1;
559}
560
561=head2 CanReserveBeCanceledFromOpac
562
563 $number = CanReserveBeCanceledFromOpac($reserve_id, $borrowernumber);
564
565 returns 1 if reserve can be cancelled by user from OPAC.
566 First check if reserve belongs to user, next checks if reserve is not in
567 transfer or waiting status
568
569=cut
570
571sub CanReserveBeCanceledFromOpac {
572 my ($reserve_id, $borrowernumber) = @_;
573
574 return unless $reserve_id and $borrowernumber;
575 my $reserve = GetReserve($reserve_id);
576
577 return 0 unless $reserve->{borrowernumber} == $borrowernumber;
578 return 0 if ( $reserve->{found} eq 'W' ) or ( $reserve->{found} eq 'T' );
579
580 return 1;
581
582}
583
584#--------------------------------------------------------------------------------
585=head2 GetReserveCount
586
587 $number = &GetReserveCount($borrowernumber);
588
589this function returns the number of reservation for a borrower given on input arg.
590
591=cut
592
593sub GetReserveCount {
594 my ($borrowernumber) = @_;
595
596 my $dbh = C4::Context->dbh;
597
598 my $query = "
599 SELECT COUNT(*) AS counter
600 FROM reserves
601 WHERE borrowernumber = ?
602 ";
603 my $sth = $dbh->prepare($query);
604 $sth->execute($borrowernumber);
605 my $row = $sth->fetchrow_hashref;
606 return $row->{counter};
607}
608
609=head2 GetOtherReserves
610
611 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
612
613Check queued list of this document and check if this document must be transfered
614
615=cut
616
617sub GetOtherReserves {
618 my ($itemnumber) = @_;
619 my $messages;
620 my $nextreservinfo;
621 my ( undef, $checkreserves, undef ) = CheckReserves($itemnumber);
622 if ($checkreserves) {
623 my $iteminfo = GetItem($itemnumber);
624 if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
625 $messages->{'transfert'} = $checkreserves->{'branchcode'};
626 #minus priorities of others reservs
627 ModReserveMinusPriority(
628 $itemnumber,
629 $checkreserves->{'reserve_id'},
630 );
631
632 #launch the subroutine dotransfer
633 C4::Items::ModItemTransfer(
634 $itemnumber,
635 $iteminfo->{'holdingbranch'},
636 $checkreserves->{'branchcode'}
637 ),
638 ;
639 }
640
641 #step 2b : case of a reservation on the same branch, set the waiting status
642 else {
643 $messages->{'waiting'} = 1;
644 ModReserveMinusPriority(
645 $itemnumber,
646 $checkreserves->{'reserve_id'},
647 );
648 ModReserveStatus($itemnumber,'W');
649 }
650
651 $nextreservinfo = $checkreserves->{'borrowernumber'};
652 }
653
654 return ( $messages, $nextreservinfo );
655}
656
657=head2 GetReserveFee
658
659 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
660
661Calculate the fee for a reserve
662
663=cut
664
665sub GetReserveFee {
666 my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
667
668 #check for issues;
669 my $dbh = C4::Context->dbh;
670 my $const = lc substr( $constraint, 0, 1 );
671 my $query = qq/
672 SELECT * FROM borrowers
673 LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
674 WHERE borrowernumber = ?
675 /;
676 my $sth = $dbh->prepare($query);
677 $sth->execute($borrowernumber);
678 my $data = $sth->fetchrow_hashref;
679 my $fee = $data->{'reservefee'};
680 my $cntitems = @- > $bibitems;
681
682 if ( $fee > 0 ) {
683
684 # check for items on issue
685 # first find biblioitem records
686 my @biblioitems;
687 my $sth1 = $dbh->prepare(
688 "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
689 WHERE (biblio.biblionumber = ?)"
690 );
691 $sth1->execute($biblionumber);
692 while ( my $data1 = $sth1->fetchrow_hashref ) {
693 if ( $const eq "a" ) {
694 push @biblioitems, $data1;
695 }
696 else {
697 my $found = 0;
698 my $x = 0;
699 while ( $x < $cntitems ) {
700 if ( @$bibitems->{'biblioitemnumber'} ==
701 $data->{'biblioitemnumber'} )
702 {
703 $found = 1;
704 }
705 $x++;
706 }
707 if ( $const eq 'o' ) {
708 if ( $found == 1 ) {
709 push @biblioitems, $data1;
710 }
711 }
712 else {
713 if ( $found == 0 ) {
714 push @biblioitems, $data1;
715 }
716 }
717 }
718 }
719 my $cntitemsfound = @biblioitems;
720 my $issues = 0;
721 my $x = 0;
722 my $allissued = 1;
723 while ( $x < $cntitemsfound ) {
724 my $bitdata = $biblioitems[$x];
725 my $sth2 = $dbh->prepare(
726 "SELECT * FROM items
727 WHERE biblioitemnumber = ?"
728 );
729 $sth2->execute( $bitdata->{'biblioitemnumber'} );
730 while ( my $itdata = $sth2->fetchrow_hashref ) {
731 my $sth3 = $dbh->prepare(
732 "SELECT * FROM issues
733 WHERE itemnumber = ?"
734 );
735 $sth3->execute( $itdata->{'itemnumber'} );
736 if ( my $isdata = $sth3->fetchrow_hashref ) {
737 }
738 else {
739 $allissued = 0;
740 }
741 }
742 $x++;
743 }
744 if ( $allissued == 0 ) {
745 my $rsth =
746 $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
747 $rsth->execute($biblionumber);
748 if ( my $rdata = $rsth->fetchrow_hashref ) {
749 }
750 else {
751 $fee = 0;
752 }
753 }
754 }
755 return $fee;
756}
757
758=head2 GetReservesToBranch
759
760 @transreserv = GetReservesToBranch( $frombranch );
761
762Get reserve list for a given branch
763
764=cut
765
766sub GetReservesToBranch {
767 my ( $frombranch ) = @_;
768 my $dbh = C4::Context->dbh;
769 my $sth = $dbh->prepare(
770 "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
771 FROM reserves
772 WHERE priority='0'
773 AND branchcode=?"
774 );
775 $sth->execute( $frombranch );
776 my @transreserv;
777 my $i = 0;
778 while ( my $data = $sth->fetchrow_hashref ) {
779 $transreserv[$i] = $data;
780 $i++;
781 }
782 return (@transreserv);
783}
784
785=head2 GetReservesForBranch
786
787 @transreserv = GetReservesForBranch($frombranch);
788
789=cut
790
791sub GetReservesForBranch {
792 my ($frombranch) = @_;
793 my $dbh = C4::Context->dbh;
794
795 my $query = "
796 SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
797 FROM reserves
798 WHERE priority='0'
799 AND found='W'
800 ";
801 $query .= " AND branchcode=? " if ( $frombranch );
802 $query .= "ORDER BY waitingdate" ;
803
804 my $sth = $dbh->prepare($query);
805 if ($frombranch){
806 $sth->execute($frombranch);
807 } else {
808 $sth->execute();
809 }
810
811 my @transreserv;
812 my $i = 0;
813 while ( my $data = $sth->fetchrow_hashref ) {
814 $transreserv[$i] = $data;
815 $i++;
816 }
817 return (@transreserv);
818}
819
820=head2 GetReserveStatus
821
822 $reservestatus = GetReserveStatus($itemnumber, $biblionumber);
823
824Take an itemnumber or a biblionumber and return the status of the reserve places on it.
825If several reserves exist, the reserve with the lower priority is given.
826
827=cut
828
829## FIXME: I don't think this does what it thinks it does.
830## It only ever checks the first reserve result, even though
831## multiple reserves for that bib can have the itemnumber set
832## the sub is only used once in the codebase.
833sub GetReserveStatus {
834 my ($itemnumber, $biblionumber) = @_;
835
836 my $dbh = C4::Context->dbh;
837
838 my ($sth, $found, $priority);
839 if ( $itemnumber ) {
840 $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
841 $sth->execute($itemnumber);
842 ($found, $priority) = $sth->fetchrow_array;
843 }
844
845 if ( $biblionumber and not defined $found and not defined $priority ) {
846 $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE biblionumber = ? order by priority LIMIT 1");
847 $sth->execute($biblionumber);
848 ($found, $priority) = $sth->fetchrow_array;
849 }
850
851 if(defined $found) {
852 return 'Waiting' if $found eq 'W' and $priority == 0;
853 return 'Finished' if $found eq 'F';
854 }
855
856 return 'Reserved' if $priority > 0;
857
858 return ''; # empty string here will remove need for checking undef, or less log lines
859}
860
861=head2 CheckReserves
862
863 ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber);
864 ($status, $reserve, $all_reserves) = &CheckReserves(undef, $barcode);
865 ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber,undef,$lookahead);
866
867Find a book in the reserves.
868
869C<$itemnumber> is the book's item number.
870C<$lookahead> is the number of days to look in advance for future reserves.
871
872As I understand it, C<&CheckReserves> looks for the given item in the
873reserves. If it is found, that's a match, and C<$status> is set to
874C<Waiting>.
875
876Otherwise, it finds the most important item in the reserves with the
877same biblio number as this book (I'm not clear on this) and returns it
878with C<$status> set to C<Reserved>.
879
880C<&CheckReserves> returns a two-element list:
881
882C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
883
884C<$reserve> is the reserve item that matched. It is a
885reference-to-hash whose keys are mostly the fields of the reserves
886table in the Koha database.
887
888=cut
889
890sub CheckReserves {
891 my ( $item, $barcode, $lookahead_days) = @_;
892 my $dbh = C4::Context->dbh;
893 my $sth;
894 my $select;
895 if (C4::Context->preference('item-level_itypes')){
896 $select = "
897 SELECT items.biblionumber,
898 items.biblioitemnumber,
899 itemtypes.notforloan,
900 items.notforloan AS itemnotforloan,
901 items.itemnumber,
902 items.damaged
903 FROM items
904 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
905 LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype
906 ";
907 }
908 else {
909 $select = "
910 SELECT items.biblionumber,
911 items.biblioitemnumber,
912 itemtypes.notforloan,
913 items.notforloan AS itemnotforloan,
914 items.itemnumber,
915 items.damaged
916 FROM items
917 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
918 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
919 ";
920 }
921
922 if ($item) {
923 $sth = $dbh->prepare("$select WHERE itemnumber = ?");
924 $sth->execute($item);
925 }
926 else {
927 $sth = $dbh->prepare("$select WHERE barcode = ?");
928 $sth->execute($barcode);
929 }
930 # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it.
931 my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber, $damaged ) = $sth->fetchrow_array;
932
933 return if ( $damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
934
935 return unless $itemnumber; # bail if we got nothing.
936
937 # if item is not for loan it cannot be reserved either.....
938 # execpt where items.notforloan < 0 : This indicates the item is holdable.
939 return if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
940
941 # Find this item in the reserves
942 my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber, $lookahead_days);
943
944 # $priority and $highest are used to find the most important item
945 # in the list returned by &_Findgroupreserve. (The lower $priority,
946 # the more important the item.)
947 # $highest is the most important item we've seen so far.
948 my $highest;
949 if (scalar @reserves) {
950 my $priority = 10000000;
951 foreach my $res (@reserves) {
952 if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
953 return ( "Waiting", $res, \@reserves ); # Found it
954 } else {
955 # See if this item is more important than what we've got so far
956 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
957 my $borrowerinfo=C4::Members::GetMember(borrowernumber => $res->{'borrowernumber'});
958 my $iteminfo=C4::Items::GetItem($itemnumber);
959 my $branch = GetReservesControlBranch( $iteminfo, $borrowerinfo );
960 my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'});
961 next if ($branchitemrule->{'holdallowed'} == 0);
962 next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
963 $priority = $res->{'priority'};
964 $highest = $res;
965 }
966 }
967 }
968 }
969
970 # If we get this far, then no exact match was found.
971 # We return the most important (i.e. next) reservation.
972 if ($highest) {
973 $highest->{'itemnumber'} = $item;
974 return ( "Reserved", $highest, \@reserves );
975 }
976
977 return ( '' );
978}
979
980=head2 CancelExpiredReserves
981
982 CancelExpiredReserves();
983
984Cancels all reserves with an expiration date from before today.
985
986=cut
987
988sub CancelExpiredReserves {
989
990 # Cancel reserves that have passed their expiration date.
991 my $dbh = C4::Context->dbh;
992 my $sth = $dbh->prepare( "
993 SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() )
994 AND expirationdate IS NOT NULL
995 AND found IS NULL
996 " );
997 $sth->execute();
998
999 while ( my $res = $sth->fetchrow_hashref() ) {
1000 CancelReserve({ reserve_id => $res->{'reserve_id'} });
1001 }
1002
1003 # Cancel reserves that have been waiting too long
1004 if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) {
1005 my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay");
1006 my $charge = C4::Context->preference("ExpireReservesMaxPickUpDelayCharge");
1007
1008 my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0";
1009 $sth = $dbh->prepare( $query );
1010 $sth->execute( $max_pickup_delay );
1011
1012 while (my $res = $sth->fetchrow_hashref ) {
1013 if ( $charge ) {
1014 manualinvoice($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge);
1015 }
1016
1017 CancelReserve({ reserve_id => $res->{'reserve_id'} });
1018 }
1019 }
1020
1021}
1022
1023=head2 AutoUnsuspendReserves
1024
1025 AutoUnsuspendReserves();
1026
1027Unsuspends all suspended reserves with a suspend_until date from before today.
1028
1029=cut
1030
1031sub AutoUnsuspendReserves {
1032
1033 my $dbh = C4::Context->dbh;
1034
1035 my $query = "UPDATE reserves SET suspend = 0, suspend_until = NULL WHERE DATE( suspend_until ) < DATE( CURDATE() )";
1036 my $sth = $dbh->prepare( $query );
1037 $sth->execute();
1038
1039}
1040
1041=head2 CancelReserve
1042
1043 CancelReserve({ reserve_id => $reserve_id, [ biblionumber => $biblionumber, borrowernumber => $borrrowernumber, itemnumber => $itemnumber ] });
1044
1045Cancels a reserve.
1046
1047=cut
1048
1049sub CancelReserve {
1050 my ( $params ) = @_;
1051
1052 my $reserve_id = $params->{'reserve_id'};
1053 $reserve_id = GetReserveId( $params ) unless ( $reserve_id );
1054
1055 return unless ( $reserve_id );
1056
1057 my $dbh = C4::Context->dbh;
1058
1059 my $reserve = GetReserve( $reserve_id );
1060
1061 my $query = "
1062 UPDATE reserves
1063 SET cancellationdate = now(),
1064 found = Null,
1065 priority = 0
1066 WHERE reserve_id = ?
1067 ";
1068 my $sth = $dbh->prepare($query);
1069 $sth->execute( $reserve_id );
1070
1071 $query = "
1072 INSERT INTO old_reserves
1073 SELECT * FROM reserves
1074 WHERE reserve_id = ?
1075 ";
1076 $sth = $dbh->prepare($query);
1077 $sth->execute( $reserve_id );
1078
1079 $query = "
1080 DELETE FROM reserves
1081 WHERE reserve_id = ?
1082 ";
1083 $sth = $dbh->prepare($query);
1084 $sth->execute( $reserve_id );
1085
1086 # now fix the priority on the others....
1087 _FixPriority({ biblionumber => $reserve->{biblionumber} });
1088}
1089
1090=head2 ModReserve
1091
1092 ModReserve({ rank => $rank,
1093 reserve_id => $reserve_id,
1094 branchcode => $branchcode
1095 [, itemnumber => $itemnumber ]
1096 [, biblionumber => $biblionumber, $borrowernumber => $borrowernumber ]
1097 });
1098
1099Change a hold request's priority or cancel it.
1100
1101C<$rank> specifies the effect of the change. If C<$rank>
1102is 'W' or 'n', nothing happens. This corresponds to leaving a
1103request alone when changing its priority in the holds queue
1104for a bib.
1105
1106If C<$rank> is 'del', the hold request is cancelled.
1107
1108If C<$rank> is an integer greater than zero, the priority of
1109the request is set to that value. Since priority != 0 means
1110that the item is not waiting on the hold shelf, setting the
1111priority to a non-zero value also sets the request's found
1112status and waiting date to NULL.
1113
1114The optional C<$itemnumber> parameter is used only when
1115C<$rank> is a non-zero integer; if supplied, the itemnumber
1116of the hold request is set accordingly; if omitted, the itemnumber
1117is cleared.
1118
1119B<FIXME:> Note that the forgoing can have the effect of causing
1120item-level hold requests to turn into title-level requests. This
1121will be fixed once reserves has separate columns for requested
1122itemnumber and supplying itemnumber.
1123
1124=cut
1125
1126sub ModReserve {
1127 my ( $params ) = @_;
1128
1129 my $rank = $params->{'rank'};
1130 my $reserve_id = $params->{'reserve_id'};
1131 my $branchcode = $params->{'branchcode'};
1132 my $itemnumber = $params->{'itemnumber'};
1133 my $suspend_until = $params->{'suspend_until'};
1134 my $borrowernumber = $params->{'borrowernumber'};
1135 my $biblionumber = $params->{'biblionumber'};
1136
1137 return if $rank eq "W";
1138 return if $rank eq "n";
1139
1140 return unless ( $reserve_id || ( $borrowernumber && ( $biblionumber || $itemnumber ) ) );
1141 $reserve_id = GetReserveId({ biblionumber => $biblionumber, borrowernumber => $borrowernumber, itemnumber => $itemnumber }) unless ( $reserve_id );
1142
1143 my $dbh = C4::Context->dbh;
1144 if ( $rank eq "del" ) {
1145 CancelReserve({ reserve_id => $reserve_id });
1146 }
1147 elsif ($rank =~ /^\d+/ and $rank > 0) {
1148 my $query = "
1149 UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1150 WHERE reserve_id = ?
1151 ";
1152 my $sth = $dbh->prepare($query);
1153 $sth->execute( $rank, $branchcode, $itemnumber, $reserve_id );
1154
1155 if ( defined( $suspend_until ) ) {
1156 if ( $suspend_until ) {
1157 $suspend_until = C4::Dates->new( $suspend_until )->output("iso");
1158 $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE reserve_id = ?", undef, ( $suspend_until, $reserve_id ) );
1159 } else {
1160 $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE reserve_id = ?", undef, ( $reserve_id ) );
1161 }
1162 }
1163
1164 _FixPriority({ reserve_id => $reserve_id, rank =>$rank });
1165 }
1166}
1167
1168=head2 ModReserveFill
1169
1170 &ModReserveFill($reserve);
1171
1172Fill a reserve. If I understand this correctly, this means that the
1173reserved book has been found and given to the patron who reserved it.
1174
1175C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1176whose keys are fields from the reserves table in the Koha database.
1177
1178=cut
1179
1180sub ModReserveFill {
1181 my ($res) = @_;
1182 my $dbh = C4::Context->dbh;
1183 # fill in a reserve record....
1184 my $reserve_id = $res->{'reserve_id'};
1185 my $biblionumber = $res->{'biblionumber'};
1186 my $borrowernumber = $res->{'borrowernumber'};
1187 my $resdate = $res->{'reservedate'};
1188
1189 # get the priority on this record....
1190 my $priority;
1191 my $query = "SELECT priority
1192 FROM reserves
1193 WHERE biblionumber = ?
1194 AND borrowernumber = ?
1195 AND reservedate = ?";
1196 my $sth = $dbh->prepare($query);
1197 $sth->execute( $biblionumber, $borrowernumber, $resdate );
1198 ($priority) = $sth->fetchrow_array;
1199
1200 # update the database...
1201 $query = "UPDATE reserves
1202 SET found = 'F',
1203 priority = 0
1204 WHERE biblionumber = ?
1205 AND reservedate = ?
1206 AND borrowernumber = ?
1207 ";
1208 $sth = $dbh->prepare($query);
1209 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1210
1211 # move to old_reserves
1212 $query = "INSERT INTO old_reserves
1213 SELECT * FROM reserves
1214 WHERE biblionumber = ?
1215 AND reservedate = ?
1216 AND borrowernumber = ?
1217 ";
1218 $sth = $dbh->prepare($query);
1219 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1220 $query = "DELETE FROM reserves
1221 WHERE biblionumber = ?
1222 AND reservedate = ?
1223 AND borrowernumber = ?
1224 ";
1225 $sth = $dbh->prepare($query);
1226 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1227
1228 # now fix the priority on the others (if the priority wasn't
1229 # already sorted!)....
1230 unless ( $priority == 0 ) {
1231 _FixPriority({ reserve_id => $reserve_id });
1232 }
1233}
1234
1235=head2 ModReserveStatus
1236
1237 &ModReserveStatus($itemnumber, $newstatus);
1238
1239Update the reserve status for the active (priority=0) reserve.
1240
1241$itemnumber is the itemnumber the reserve is on
1242
1243$newstatus is the new status.
1244
1245=cut
1246
1247sub ModReserveStatus {
1248
1249 #first : check if we have a reservation for this item .
1250 my ($itemnumber, $newstatus) = @_;
1251 my $dbh = C4::Context->dbh;
1252
1253 my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1254 my $sth_set = $dbh->prepare($query);
1255 $sth_set->execute( $newstatus, $itemnumber );
1256
1257 if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1258 CartToShelf( $itemnumber );
1259 }
1260}
1261
1262=head2 ModReserveAffect
1263
1264 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1265
1266This function affect an item and a status for a given reserve
1267The itemnumber parameter is used to find the biblionumber.
1268with the biblionumber & the borrowernumber, we can affect the itemnumber
1269to the correct reserve.
1270
1271if $transferToDo is not set, then the status is set to "Waiting" as well.
1272otherwise, a transfer is on the way, and the end of the transfer will
1273take care of the waiting status
1274
1275=cut
1276
1277sub ModReserveAffect {
1278 my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1279 my $dbh = C4::Context->dbh;
1280
1281 # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1282 # attached to $itemnumber
1283 my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1284 $sth->execute($itemnumber);
1285 my ($biblionumber) = $sth->fetchrow;
1286
1287 # get request - need to find out if item is already
1288 # waiting in order to not send duplicate hold filled notifications
1289 my $reserve_id = GetReserveId({
1290 borrowernumber => $borrowernumber,
1291 biblionumber => $biblionumber,
1292 });
1293 return unless defined $reserve_id;
1294 my $request = GetReserveInfo($reserve_id);
1295 my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1296
1297 # If we affect a reserve that has to be transfered, don't set to Waiting
1298 my $query;
1299 if ($transferToDo) {
1300 $query = "
1301 UPDATE reserves
1302 SET priority = 0,
1303 itemnumber = ?,
1304 found = 'T'
1305 WHERE borrowernumber = ?
1306 AND biblionumber = ?
1307 ";
1308 }
1309 else {
1310 # affect the reserve to Waiting as well.
1311 $query = "
1312 UPDATE reserves
1313 SET priority = 0,
1314 found = 'W',
1315 waitingdate = NOW(),
1316 itemnumber = ?
1317 WHERE borrowernumber = ?
1318 AND biblionumber = ?
1319 ";
1320 }
1321 $sth = $dbh->prepare($query);
1322 $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1323 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1324 _FixPriority( { biblionumber => $biblionumber } );
1325 if ( C4::Context->preference("ReturnToShelvingCart") ) {
1326 CartToShelf( $itemnumber );
1327 }
1328
1329 return;
1330}
1331
1332=head2 ModReserveCancelAll
1333
1334 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1335
1336function to cancel reserv,check other reserves, and transfer document if it's necessary
1337
1338=cut
1339
1340sub ModReserveCancelAll {
1341 my $messages;
1342 my $nextreservinfo;
1343 my ( $itemnumber, $borrowernumber ) = @_;
1344
1345 #step 1 : cancel the reservation
1346 my $CancelReserve = CancelReserve({ itemnumber => $itemnumber, borrowernumber => $borrowernumber });
1347
1348 #step 2 launch the subroutine of the others reserves
1349 ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1350
1351 return ( $messages, $nextreservinfo );
1352}
1353
1354=head2 ModReserveMinusPriority
1355
1356 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1357
1358Reduce the values of queued list
1359
1360=cut
1361
1362sub ModReserveMinusPriority {
1363 my ( $itemnumber, $reserve_id ) = @_;
1364
1365 #first step update the value of the first person on reserv
1366 my $dbh = C4::Context->dbh;
1367 my $query = "
1368 UPDATE reserves
1369 SET priority = 0 , itemnumber = ?
1370 WHERE reserve_id = ?
1371 ";
1372 my $sth_upd = $dbh->prepare($query);
1373 $sth_upd->execute( $itemnumber, $reserve_id );
1374 # second step update all others reserves
1375 _FixPriority({ reserve_id => $reserve_id, rank => '0' });
1376}
1377
1378=head2 GetReserveInfo
1379
1380 &GetReserveInfo($reserve_id);
1381
1382Get item and borrower details for a current hold.
1383Current implementation this query should have a single result.
1384
1385=cut
1386
1387sub GetReserveInfo {
1388 my ( $reserve_id ) = @_;
1389 my $dbh = C4::Context->dbh;
1390 my $strsth="SELECT
1391 reserve_id,
1392 reservedate,
1393 reservenotes,
1394 reserves.borrowernumber,
1395 reserves.biblionumber,
1396 reserves.branchcode,
1397 reserves.waitingdate,
1398 notificationdate,
1399 reminderdate,
1400 priority,
1401 found,
1402 firstname,
1403 surname,
1404 phone,
1405 email,
1406 address,
1407 address2,
1408 cardnumber,
1409 city,
1410 zipcode,
1411 biblio.title,
1412 biblio.author,
1413 items.holdingbranch,
1414 items.itemcallnumber,
1415 items.itemnumber,
1416 items.location,
1417 barcode,
1418 notes
1419 FROM reserves
1420 LEFT JOIN items USING(itemnumber)
1421 LEFT JOIN borrowers USING(borrowernumber)
1422 LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber)
1423 WHERE reserves.reserve_id = ?";
1424 my $sth = $dbh->prepare($strsth);
1425 $sth->execute($reserve_id);
1426
1427 my $data = $sth->fetchrow_hashref;
1428 return $data;
1429}
1430
1431=head2 IsAvailableForItemLevelRequest
1432
1433 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1434
1435Checks whether a given item record is available for an
1436item-level hold request. An item is available if
1437
1438* it is not lost AND
1439* it is not damaged AND
1440* it is not withdrawn AND
1441* does not have a not for loan value > 0
1442
1443Whether or not the item is currently on loan is
1444also checked - if the AllowOnShelfHolds system preference
1445is ON, an item can be requested even if it is currently
1446on loan to somebody else. If the system preference
1447is OFF, an item that is currently checked out cannot
1448be the target of an item-level hold request.
1449
1450Note that IsAvailableForItemLevelRequest() does not
1451check if the staff operator is authorized to place
1452a request on the item - in particular,
1453this routine does not check IndependentBranches
1454and canreservefromotherbranches.
1455
1456=cut
1457
1458sub IsAvailableForItemLevelRequest {
1459 my $itemnumber = shift;
1460
1461 my $item = GetItem($itemnumber);
1462
1463 # must check the notforloan setting of the itemtype
1464 # FIXME - a lot of places in the code do this
1465 # or something similar - need to be
1466 # consolidated
1467 my $dbh = C4::Context->dbh;
1468 my $notforloan_query;
1469 if (C4::Context->preference('item-level_itypes')) {
1470 $notforloan_query = "SELECT itemtypes.notforloan
1471 FROM items
1472 JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1473 WHERE itemnumber = ?";
1474 } else {
1475 $notforloan_query = "SELECT itemtypes.notforloan
1476 FROM items
1477 JOIN biblioitems USING (biblioitemnumber)
1478 JOIN itemtypes USING (itemtype)
1479 WHERE itemnumber = ?";
1480 }
1481 my $sth = $dbh->prepare($notforloan_query);
1482 $sth->execute($itemnumber);
1483 my $notforloan_per_itemtype = 0;
1484 if (my ($notforloan) = $sth->fetchrow_array) {
1485 $notforloan_per_itemtype = 1 if $notforloan;
1486 }
1487
1488 my $available_per_item = 1;
1489 $available_per_item = 0 if $item->{itemlost} or
1490 ( $item->{notforloan} > 0 ) or
1491 ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1492 $item->{withdrawn} or
1493 $notforloan_per_itemtype;
1494
1495
1496 if (C4::Context->preference('AllowOnShelfHolds')) {
1497 return $available_per_item;
1498 } else {
1499 return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "Waiting"));
1500 }
1501}
1502
1503=head2 AlterPriority
1504
1505 AlterPriority( $where, $reserve_id );
1506
1507This function changes a reserve's priority up, down, to the top, or to the bottom.
1508Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1509
1510=cut
1511
1512sub AlterPriority {
1513 my ( $where, $reserve_id ) = @_;
1514
1515 my $dbh = C4::Context->dbh;
1516
1517 my $reserve = GetReserve( $reserve_id );
1518
1519 if ( $reserve->{cancellationdate} ) {
1520 warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled (".$reserve->{cancellationdate}.')';
1521 return;
1522 }
1523
1524 if ( $where eq 'up' || $where eq 'down' ) {
1525
1526 my $priority = $reserve->{'priority'};
1527 $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1528 _FixPriority({ reserve_id => $reserve_id, rank => $priority })
1529
1530 } elsif ( $where eq 'top' ) {
1531
1532 _FixPriority({ reserve_id => $reserve_id, rank => '1' })
1533
1534 } elsif ( $where eq 'bottom' ) {
1535
1536 _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1537
1538 }
1539}
1540
1541=head2 ToggleLowestPriority
1542
1543 ToggleLowestPriority( $borrowernumber, $biblionumber );
1544
1545This function sets the lowestPriority field to true if is false, and false if it is true.
1546
1547=cut
1548
1549sub ToggleLowestPriority {
1550 my ( $reserve_id ) = @_;
1551
1552 my $dbh = C4::Context->dbh;
1553
1554 my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1555 $sth->execute( $reserve_id );
1556
1557 _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1558}
1559
1560=head2 ToggleSuspend
1561
1562 ToggleSuspend( $reserve_id );
1563
1564This function sets the suspend field to true if is false, and false if it is true.
1565If the reserve is currently suspended with a suspend_until date, that date will
1566be cleared when it is unsuspended.
1567
1568=cut
1569
1570sub ToggleSuspend {
1571 my ( $reserve_id, $suspend_until ) = @_;
1572
1573 $suspend_until = output_pref({ dt => dt_from_string( $suspend_until ), dateformat => 'iso' }) if ( $suspend_until );
1574
1575 my $do_until = ( $suspend_until ) ? '?' : 'NULL';
1576
1577 my $dbh = C4::Context->dbh;
1578
1579 my $sth = $dbh->prepare(
1580 "UPDATE reserves SET suspend = NOT suspend,
1581 suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE $do_until END
1582 WHERE reserve_id = ?
1583 ");
1584
1585 my @params;
1586 push( @params, $suspend_until ) if ( $suspend_until );
1587 push( @params, $reserve_id );
1588
1589 $sth->execute( @params );
1590}
1591
1592=head2 SuspendAll
1593
1594 SuspendAll(
1595 borrowernumber => $borrowernumber,
1596 [ biblionumber => $biblionumber, ]
1597 [ suspend_until => $suspend_until, ]
1598 [ suspend => $suspend ]
1599 );
1600
1601 This function accepts a set of hash keys as its parameters.
1602 It requires either borrowernumber or biblionumber, or both.
1603
1604 suspend_until is wholly optional.
1605
1606=cut
1607
1608sub SuspendAll {
1609 my %params = @_;
1610
1611 my $borrowernumber = $params{'borrowernumber'} || undef;
1612 my $biblionumber = $params{'biblionumber'} || undef;
1613 my $suspend_until = $params{'suspend_until'} || undef;
1614 my $suspend = defined( $params{'suspend'} ) ? $params{'suspend'} : 1;
1615
1616 $suspend_until = C4::Dates->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) );
1617
1618 return unless ( $borrowernumber || $biblionumber );
1619
1620 my ( $query, $sth, $dbh, @query_params );
1621
1622 $query = "UPDATE reserves SET suspend = ? ";
1623 push( @query_params, $suspend );
1624 if ( !$suspend ) {
1625 $query .= ", suspend_until = NULL ";
1626 } elsif ( $suspend_until ) {
1627 $query .= ", suspend_until = ? ";
1628 push( @query_params, $suspend_until );
1629 }
1630 $query .= " WHERE ";
1631 if ( $borrowernumber ) {
1632 $query .= " borrowernumber = ? ";
1633 push( @query_params, $borrowernumber );
1634 }
1635 $query .= " AND " if ( $borrowernumber && $biblionumber );
1636 if ( $biblionumber ) {
1637 $query .= " biblionumber = ? ";
1638 push( @query_params, $biblionumber );
1639 }
1640 $query .= " AND found IS NULL ";
1641
1642 $dbh = C4::Context->dbh;
1643 $sth = $dbh->prepare( $query );
1644 $sth->execute( @query_params );
1645}
1646
1647
1648=head2 _FixPriority
1649
1650 _FixPriority({
1651 reserve_id => $reserve_id,
1652 [rank => $rank,]
1653 [ignoreSetLowestRank => $ignoreSetLowestRank]
1654 });
1655
1656 or
1657
1658 _FixPriority({ biblionumber => $biblionumber});
1659
1660This routine adjusts the priority of a hold request and holds
1661on the same bib.
1662
1663In the first form, where a reserve_id is passed, the priority of the
1664hold is set to supplied rank, and other holds for that bib are adjusted
1665accordingly. If the rank is "del", the hold is cancelled. If no rank
1666is supplied, all of the holds on that bib have their priority adjusted
1667as if the second form had been used.
1668
1669In the second form, where a biblionumber is passed, the holds on that
1670bib (that are not captured) are sorted in order of increasing priority,
1671then have reserves.priority set so that the first non-captured hold
1672has its priority set to 1, the second non-captured hold has its priority
1673set to 2, and so forth.
1674
1675In both cases, holds that have the lowestPriority flag on are have their
1676priority adjusted to ensure that they remain at the end of the line.
1677
1678Note that the ignoreSetLowestRank parameter is meant to be used only
1679when _FixPriority calls itself.
1680
1681=cut
1682
1683sub _FixPriority {
1684 my ( $params ) = @_;
1685 my $reserve_id = $params->{reserve_id};
1686 my $rank = $params->{rank} // '';
1687 my $ignoreSetLowestRank = $params->{ignoreSetLowestRank};
1688 my $biblionumber = $params->{biblionumber};
1689
1690 my $dbh = C4::Context->dbh;
1691
1692 unless ( $biblionumber ) {
1693 my $res = GetReserve( $reserve_id );
1694 $biblionumber = $res->{biblionumber};
1695 }
1696
1697 if ( $rank eq "del" ) {
1698 CancelReserve({ reserve_id => $reserve_id });
1699 }
1700 elsif ( $rank eq "W" || $rank eq "0" ) {
1701
1702 # make sure priority for waiting or in-transit items is 0
1703 my $query = "
1704 UPDATE reserves
1705 SET priority = 0
1706 WHERE reserve_id = ?
1707 AND found IN ('W', 'T')
1708 ";
1709 my $sth = $dbh->prepare($query);
1710 $sth->execute( $reserve_id );
1711 }
1712 my @priority;
1713
1714 # get whats left
1715 my $query = "
1716 SELECT reserve_id, borrowernumber, reservedate, constrainttype
1717 FROM reserves
1718 WHERE biblionumber = ?
1719 AND ((found <> 'W' AND found <> 'T') OR found IS NULL)
1720 ORDER BY priority ASC
1721 ";
1722 my $sth = $dbh->prepare($query);
1723 $sth->execute( $biblionumber );
1724 while ( my $line = $sth->fetchrow_hashref ) {
1725 push( @priority, $line );
1726 }
1727
1728 # To find the matching index
1729 my $i;
1730 my $key = -1; # to allow for 0 to be a valid result
1731 for ( $i = 0 ; $i < @priority ; $i++ ) {
1732 if ( $reserve_id == $priority[$i]->{'reserve_id'} ) {
1733 $key = $i; # save the index
1734 last;
1735 }
1736 }
1737
1738 # if index exists in array then move it to new position
1739 if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1740 my $new_rank = $rank -
1741 1; # $new_rank is what you want the new index to be in the array
1742 my $moving_item = splice( @priority, $key, 1 );
1743 splice( @priority, $new_rank, 0, $moving_item );
1744 }
1745
1746 # now fix the priority on those that are left....
1747 $query = "
1748 UPDATE reserves
1749 SET priority = ?
1750 WHERE reserve_id = ?
1751 ";
1752 $sth = $dbh->prepare($query);
1753 for ( my $j = 0 ; $j < @priority ; $j++ ) {
1754 $sth->execute(
1755 $j + 1,
1756 $priority[$j]->{'reserve_id'}
1757 );
1758 }
1759
1760 $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1761 $sth->execute();
1762
1763 unless ( $ignoreSetLowestRank ) {
1764 while ( my $res = $sth->fetchrow_hashref() ) {
1765 _FixPriority({
1766 reserve_id => $res->{'reserve_id'},
1767 rank => '999999',
1768 ignoreSetLowestRank => 1
1769 });
1770 }
1771 }
1772}
1773
1774=head2 _Findgroupreserve
1775
1776 @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber, $lookahead);
1777
1778Looks for an item-specific match first, then for a title-level match, returning the
1779first match found. If neither, then we look for a 3rd kind of match based on
1780reserve constraints.
1781Lookahead is the number of days to look in advance.
1782
1783TODO: add more explanation about reserve constraints
1784
1785C<&_Findgroupreserve> returns :
1786C<@results> is an array of references-to-hash whose keys are mostly
1787fields from the reserves table of the Koha database, plus
1788C<biblioitemnumber>.
1789
1790=cut
1791
1792sub _Findgroupreserve {
1793 my ( $bibitem, $biblio, $itemnumber, $lookahead) = @_;
1794 my $dbh = C4::Context->dbh;
1795
1796 # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1797 # check for exact targetted match
1798 my $item_level_target_query = qq/
1799 SELECT reserves.biblionumber AS biblionumber,
1800 reserves.borrowernumber AS borrowernumber,
1801 reserves.reservedate AS reservedate,
1802 reserves.branchcode AS branchcode,
1803 reserves.cancellationdate AS cancellationdate,
1804 reserves.found AS found,
1805 reserves.reservenotes AS reservenotes,
1806 reserves.priority AS priority,
1807 reserves.timestamp AS timestamp,
1808 biblioitems.biblioitemnumber AS biblioitemnumber,
1809 reserves.itemnumber AS itemnumber,
1810 reserves.reserve_id AS reserve_id
1811 FROM reserves
1812 JOIN biblioitems USING (biblionumber)
1813 JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1814 WHERE found IS NULL
1815 AND priority > 0
1816 AND item_level_request = 1
1817 AND itemnumber = ?
1818 AND reservedate <= DATE_ADD(NOW(),INTERVAL ? DAY)
1819 AND suspend = 0
1820 /;
1821 my $sth = $dbh->prepare($item_level_target_query);
1822 $sth->execute($itemnumber, $lookahead||0);
1823 my @results;
1824 if ( my $data = $sth->fetchrow_hashref ) {
1825 push( @results, $data );
1826 }
1827 return @results if @results;
1828
1829 # check for title-level targetted match
1830 my $title_level_target_query = qq/
1831 SELECT reserves.biblionumber AS biblionumber,
1832 reserves.borrowernumber AS borrowernumber,
1833 reserves.reservedate AS reservedate,
1834 reserves.branchcode AS branchcode,
1835 reserves.cancellationdate AS cancellationdate,
1836 reserves.found AS found,
1837 reserves.reservenotes AS reservenotes,
1838 reserves.priority AS priority,
1839 reserves.timestamp AS timestamp,
1840 biblioitems.biblioitemnumber AS biblioitemnumber,
1841 reserves.itemnumber AS itemnumber,
1842 reserves.reserve_id AS reserve_id
1843 FROM reserves
1844 JOIN biblioitems USING (biblionumber)
1845 JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1846 WHERE found IS NULL
1847 AND priority > 0
1848 AND item_level_request = 0
1849 AND hold_fill_targets.itemnumber = ?
1850 AND reservedate <= DATE_ADD(NOW(),INTERVAL ? DAY)
1851 AND suspend = 0
1852 /;
1853 $sth = $dbh->prepare($title_level_target_query);
1854 $sth->execute($itemnumber, $lookahead||0);
1855 @results = ();
1856 if ( my $data = $sth->fetchrow_hashref ) {
1857 push( @results, $data );
1858 }
1859 return @results if @results;
1860
1861 my $query = qq/
1862 SELECT reserves.biblionumber AS biblionumber,
1863 reserves.borrowernumber AS borrowernumber,
1864 reserves.reservedate AS reservedate,
1865 reserves.waitingdate AS waitingdate,
1866 reserves.branchcode AS branchcode,
1867 reserves.cancellationdate AS cancellationdate,
1868 reserves.found AS found,
1869 reserves.reservenotes AS reservenotes,
1870 reserves.priority AS priority,
1871 reserves.timestamp AS timestamp,
1872 reserveconstraints.biblioitemnumber AS biblioitemnumber,
1873 reserves.itemnumber AS itemnumber,
1874 reserves.reserve_id AS reserve_id
1875 FROM reserves
1876 LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1877 WHERE reserves.biblionumber = ?
1878 AND ( ( reserveconstraints.biblioitemnumber = ?
1879 AND reserves.borrowernumber = reserveconstraints.borrowernumber
1880 AND reserves.reservedate = reserveconstraints.reservedate )
1881 OR reserves.constrainttype='a' )
1882 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1883 AND reserves.reservedate <= DATE_ADD(NOW(),INTERVAL ? DAY)
1884 AND suspend = 0
1885 /;
1886 $sth = $dbh->prepare($query);
1887 $sth->execute( $biblio, $bibitem, $itemnumber, $lookahead||0);
1888 @results = ();
1889 while ( my $data = $sth->fetchrow_hashref ) {
1890 push( @results, $data );
1891 }
1892 return @results;
1893}
1894
1895=head2 _koha_notify_reserve
1896
1897 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1898
1899Sends a notification to the patron that their hold has been filled (through
1900ModReserveAffect, _not_ ModReserveFill)
1901
1902=cut
1903
1904sub _koha_notify_reserve {
1905 my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1906
1907 my $dbh = C4::Context->dbh;
1908 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1909
1910 # Try to get the borrower's email address
1911 my $to_address = C4::Members::GetNoticeEmailAddress($borrowernumber);
1912
1913 my $letter_code;
1914 my $print_mode = 0;
1915 my $messagingprefs;
1916 if ( $to_address || $borrower->{'smsalertnumber'} ) {
1917 $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } );
1918 } else {
1919 $print_mode = 1;
1920 }
1921
1922 my $sth = $dbh->prepare("
1923 SELECT *
1924 FROM reserves
1925 WHERE borrowernumber = ?
1926 AND biblionumber = ?
1927 ");
1928 $sth->execute( $borrowernumber, $biblionumber );
1929 my $reserve = $sth->fetchrow_hashref;
1930 my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1931
1932 my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1933
1934 my %letter_params = (
1935 module => 'reserves',
1936 branchcode => $reserve->{branchcode},
1937 tables => {
1938 'branches' => $branch_details,
1939 'borrowers' => $borrower,
1940 'biblio' => $biblionumber,
1941 'reserves' => $reserve,
1942 'items', $reserve->{'itemnumber'},
1943 },
1944 substitute => { today => C4::Dates->new()->output() },
1945 );
1946
1947
1948 if ( $print_mode ) {
1949 $letter_params{ 'letter_code' } = 'HOLD_PRINT';
1950 my $letter = C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1951
1952 C4::Letters::EnqueueLetter( {
1953 letter => $letter,
1954 borrowernumber => $borrowernumber,
1955 message_transport_type => 'print',
1956 } );
1957
1958 return;
1959 }
1960
1961 if ( $to_address && defined $messagingprefs->{transports}->{'email'} ) {
1962 $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'email'};
1963 my $letter = C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1964
1965 C4::Letters::EnqueueLetter(
1966 { letter => $letter,
1967 borrowernumber => $borrowernumber,
1968 message_transport_type => 'email',
1969 from_address => $admin_email_address,
1970 }
1971 );
1972 }
1973
1974 if ( $borrower->{'smsalertnumber'} && defined $messagingprefs->{transports}->{'sms'} ) {
1975 $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'sms'};
1976 my $letter = C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1977
1978 C4::Letters::EnqueueLetter(
1979 { letter => $letter,
1980 borrowernumber => $borrowernumber,
1981 message_transport_type => 'sms',
1982 }
1983 );
1984 }
1985}
1986
1987=head2 _ShiftPriorityByDateAndPriority
1988
1989 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1990
1991This increments the priority of all reserves after the one
1992with either the lowest date after C<$reservedate>
1993or the lowest priority after C<$priority>.
1994
1995It effectively makes room for a new reserve to be inserted with a certain
1996priority, which is returned.
1997
1998This is most useful when the reservedate can be set by the user. It allows
1999the new reserve to be placed before other reserves that have a later
2000reservedate. Since priority also is set by the form in reserves/request.pl
2001the sub accounts for that too.
2002
2003=cut
2004
2005sub _ShiftPriorityByDateAndPriority {
2006 my ( $biblio, $resdate, $new_priority ) = @_;
2007
2008 my $dbh = C4::Context->dbh;
2009 my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
2010 my $sth = $dbh->prepare( $query );
2011 $sth->execute( $biblio, $resdate, $new_priority );
2012 my $min_priority = $sth->fetchrow;
2013 # if no such matches are found, $new_priority remains as original value
2014 $new_priority = $min_priority if ( $min_priority );
2015
2016 # Shift the priority up by one; works in conjunction with the next SQL statement
2017 $query = "UPDATE reserves
2018 SET priority = priority+1
2019 WHERE biblionumber = ?
2020 AND borrowernumber = ?
2021 AND reservedate = ?
2022 AND found IS NULL";
2023 my $sth_update = $dbh->prepare( $query );
2024
2025 # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
2026 $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
2027 $sth = $dbh->prepare( $query );
2028 $sth->execute( $new_priority, $biblio );
2029 while ( my $row = $sth->fetchrow_hashref ) {
2030 $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
2031 }
2032
2033 return $new_priority; # so the caller knows what priority they wind up receiving
2034}
2035
2036=head2 MoveReserve
2037
2038 MoveReserve( $itemnumber, $borrowernumber, $cancelreserve )
2039
2040Use when checking out an item to handle reserves
2041If $cancelreserve boolean is set to true, it will remove existing reserve
2042
2043=cut
2044
2045sub MoveReserve {
2046 my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_;
2047
2048 my ( $restype, $res, $all_reserves ) = CheckReserves( $itemnumber );
2049 return unless $res;
2050
2051 my $biblionumber = $res->{biblionumber};
2052 my $biblioitemnumber = $res->{biblioitemnumber};
2053
2054 if ($res->{borrowernumber} == $borrowernumber) {
2055 ModReserveFill($res);
2056 }
2057 else {
2058 # warn "Reserved";
2059 # The item is reserved by someone else.
2060 # Find this item in the reserves
2061
2062 my $borr_res;
2063 foreach (@$all_reserves) {
2064 $_->{'borrowernumber'} == $borrowernumber or next;
2065 $_->{'biblionumber'} == $biblionumber or next;
2066
2067 $borr_res = $_;
2068 last;
2069 }
2070
2071 if ( $borr_res ) {
2072 # The item is reserved by the current patron
2073 ModReserveFill($borr_res);
2074 }
2075
2076 if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
2077 RevertWaitingStatus({ itemnumber => $itemnumber });
2078 }
2079 elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
2080 CancelReserve({
2081 biblionumber => $res->{'biblionumber'},
2082 itemnumber => $res->{'itemnumber'},
2083 borrowernumber => $res->{'borrowernumber'}
2084 });
2085 }
2086 }
2087}
2088
2089=head2 MergeHolds
2090
2091 MergeHolds($dbh,$to_biblio, $from_biblio);
2092
2093This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
2094
2095=cut
2096
2097sub MergeHolds {
2098 my ( $dbh, $to_biblio, $from_biblio ) = @_;
2099 my $sth = $dbh->prepare(
2100 "SELECT count(*) as reserve_count FROM reserves WHERE biblionumber = ?"
2101 );
2102 $sth->execute($from_biblio);
2103 if ( my $data = $sth->fetchrow_hashref() ) {
2104
2105 # holds exist on old record, if not we don't need to do anything
2106 $sth = $dbh->prepare(
2107 "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2108 $sth->execute( $to_biblio, $from_biblio );
2109
2110 # Reorder by date
2111 # don't reorder those already waiting
2112
2113 $sth = $dbh->prepare(
2114"SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
2115 );
2116 my $upd_sth = $dbh->prepare(
2117"UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2118 AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
2119 );
2120 $sth->execute( $to_biblio, 'W', 'T' );
2121 my $priority = 1;
2122 while ( my $reserve = $sth->fetchrow_hashref() ) {
2123 $upd_sth->execute(
2124 $priority, $to_biblio,
2125 $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
2126 $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
2127 );
2128 $priority++;
2129 }
2130 }
2131}
2132
2133=head2 RevertWaitingStatus
2134
2135 $success = RevertWaitingStatus({ itemnumber => $itemnumber });
2136
2137 Reverts a 'waiting' hold back to a regular hold with a priority of 1.
2138
2139 Caveat: Any waiting hold fixed with RevertWaitingStatus will be an
2140 item level hold, even if it was only a bibliolevel hold to
2141 begin with. This is because we can no longer know if a hold
2142 was item-level or bib-level after a hold has been set to
2143 waiting status.
2144
2145=cut
2146
2147sub RevertWaitingStatus {
2148 my ( $params ) = @_;
2149 my $itemnumber = $params->{'itemnumber'};
2150
2151 return unless ( $itemnumber );
2152
2153 my $dbh = C4::Context->dbh;
2154
2155 ## Get the waiting reserve we want to revert
2156 my $query = "
2157 SELECT * FROM reserves
2158 WHERE itemnumber = ?
2159 AND found IS NOT NULL
2160 ";
2161 my $sth = $dbh->prepare( $query );
2162 $sth->execute( $itemnumber );
2163 my $reserve = $sth->fetchrow_hashref();
2164
2165 ## Increment the priority of all other non-waiting
2166 ## reserves for this bib record
2167 $query = "
2168 UPDATE reserves
2169 SET
2170 priority = priority + 1
2171 WHERE
2172 biblionumber = ?
2173 AND
2174 priority > 0
2175 ";
2176 $sth = $dbh->prepare( $query );
2177 $sth->execute( $reserve->{'biblionumber'} );
2178
2179 ## Fix up the currently waiting reserve
2180 $query = "
2181 UPDATE reserves
2182 SET
2183 priority = 1,
2184 found = NULL,
2185 waitingdate = NULL
2186 WHERE
2187 reserve_id = ?
2188 ";
2189 $sth = $dbh->prepare( $query );
2190 return $sth->execute( $reserve->{'reserve_id'} );
2191}
2192
2193=head2 GetReserveId
2194
2195 $reserve_id = GetReserveId({ biblionumber => $biblionumber, borrowernumber => $borrowernumber [, itemnumber => $itemnumber ] });
2196
2197 Returnes the first reserve id that matches the given criteria
2198
2199=cut
2200
2201sub GetReserveId {
2202 my ( $params ) = @_;
2203
2204 return unless ( ( $params->{'biblionumber'} || $params->{'itemnumber'} ) && $params->{'borrowernumber'} );
2205
2206 my $dbh = C4::Context->dbh();
2207
2208 my $sql = "SELECT reserve_id FROM reserves WHERE ";
2209
2210 my @params;
2211 my @limits;
2212 foreach my $key ( keys %$params ) {
2213 if ( defined( $params->{$key} ) ) {
2214 push( @limits, "$key = ?" );
2215 push( @params, $params->{$key} );
2216 }
2217 }
2218
2219 $sql .= join( " AND ", @limits );
2220
2221 my $sth = $dbh->prepare( $sql );
2222 $sth->execute( @params );
2223 my $row = $sth->fetchrow_hashref();
2224
2225 return $row->{'reserve_id'};
2226}
2227
2228=head2 ReserveSlip
2229
2230 ReserveSlip($branchcode, $borrowernumber, $biblionumber)
2231
2232 Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
2233
2234=cut
2235
2236sub ReserveSlip {
2237 my ($branch, $borrowernumber, $biblionumber) = @_;
2238
2239# return unless ( C4::Context->boolean_preference('printreserveslips') );
2240
2241 my $reserve_id = GetReserveId({
2242 biblionumber => $biblionumber,
2243 borrowernumber => $borrowernumber
2244 }) or return;
2245 my $reserve = GetReserveInfo($reserve_id) or return;
2246
2247 return C4::Letters::GetPreparedLetter (
2248 module => 'circulation',
2249 letter_code => 'RESERVESLIP',
2250 branchcode => $branch,
2251 tables => {
2252 'reserves' => $reserve,
2253 'branches' => $reserve->{branchcode},
2254 'borrowers' => $reserve->{borrowernumber},
2255 'biblio' => $reserve->{biblionumber},
2256 'items' => $reserve->{itemnumber},
2257 },
2258 );
2259}
2260
2261=head2 GetReservesControlBranch
2262
2263 my $reserves_control_branch = GetReservesControlBranch($item, $borrower);
2264
2265 Return the branchcode to be used to determine which reserves
2266 policy applies to a transaction.
2267
2268 C<$item> is a hashref for an item. Only 'homebranch' is used.
2269
2270 C<$borrower> is a hashref to borrower. Only 'branchcode' is used.
2271
2272=cut
2273
2274sub GetReservesControlBranch {
2275 my ( $item, $borrower ) = @_;
2276
2277 my $reserves_control = C4::Context->preference('ReservesControlBranch');
2278
2279 my $branchcode =
2280 ( $reserves_control eq 'ItemHomeLibrary' ) ? $item->{'homebranch'}
2281 : ( $reserves_control eq 'PatronLibrary' ) ? $borrower->{'branchcode'}
2282 : undef;
2283
2284 return $branchcode;
2285}
2286
2287=head2 CalculatePriority
2288
2289 my $p = CalculatePriority($biblionumber, $resdate);
2290
2291Calculate priority for a new reserve on biblionumber, placing it at
2292the end of the line of all holds whose start date falls before
2293the current system time and that are neither on the hold shelf
2294or in transit.
2295
2296The reserve date parameter is optional; if it is supplied, the
2297priority is based on the set of holds whose start date falls before
2298the parameter value.
2299
2300After calculation of this priority, it is recommended to call
2301_ShiftPriorityByDateAndPriority. Note that this is currently done in
2302AddReserves.
2303
2304=cut
2305
2306sub CalculatePriority {
2307 my ( $biblionumber, $resdate ) = @_;
2308
2309 my $sql = q{
2310 SELECT COUNT(*) FROM reserves
2311 WHERE biblionumber = ?
2312 AND priority > 0
2313 AND (found IS NULL OR found = '')
2314 };
2315 #skip found==W or found==T (waiting or transit holds)
2316 if( $resdate ) {
2317 $sql.= ' AND ( reservedate <= ? )';
2318 }
2319 else {
2320 $sql.= ' AND ( reservedate < NOW() )';
2321 }
2322 my $dbh = C4::Context->dbh();
2323 my @row = $dbh->selectrow_array(
2324 $sql,
2325 undef,
2326 $resdate ? ($biblionumber, $resdate) : ($biblionumber)
2327 );
2328
2329 return @row ? $row[0]+1 : 1;
2330}
2331
2332=head1 AUTHOR
2333
2334Koha Development Team <http://koha-community.org/>
2335
2336=cut
2337
233813µs1;