Filename | /usr/share/koha/lib/C4/Reserves.pm |
Statements | Executed 787 statements in 123ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 22.4ms | 185ms | BEGIN@27 | C4::Reserves::
1 | 1 | 1 | 14.7ms | 18.7ms | BEGIN@29 | C4::Reserves::
64 | 2 | 2 | 6.58ms | 184ms | GetReserveStatus | C4::Reserves::
1 | 1 | 1 | 4.77ms | 47.0ms | BEGIN@36 | C4::Reserves::
1 | 1 | 1 | 4.17ms | 4.87ms | BEGIN@31 | C4::Reserves::
1 | 1 | 1 | 1.11ms | 1.23ms | BEGIN@34 | C4::Reserves::
1 | 1 | 1 | 37µs | 43µs | BEGIN@28 | C4::Reserves::
1 | 1 | 1 | 26µs | 31µs | BEGIN@30 | C4::Reserves::
1 | 1 | 1 | 23µs | 23µs | BEGIN@89 | C4::Reserves::
1 | 1 | 1 | 21µs | 59µs | BEGIN@42 | C4::Reserves::
1 | 1 | 1 | 18µs | 23µs | BEGIN@24 | C4::Reserves::
1 | 1 | 1 | 15µs | 57µs | BEGIN@37 | C4::Reserves::
1 | 1 | 1 | 13µs | 46µs | BEGIN@38 | C4::Reserves::
1 | 1 | 1 | 13µs | 74µs | BEGIN@40 | C4::Reserves::
1 | 1 | 1 | 12µs | 142µs | BEGIN@44 | C4::Reserves::
1 | 1 | 1 | 10µs | 12µs | BEGIN@26 | C4::Reserves::
1 | 1 | 1 | 6µs | 6µs | BEGIN@35 | C4::Reserves::
0 | 0 | 0 | 0s | 0s | AddReserve | C4::Reserves::
0 | 0 | 0 | 0s | 0s | AlterPriority | C4::Reserves::
0 | 0 | 0 | 0s | 0s | AutoUnsuspendReserves | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CanBookBeReserved | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CanItemBeReserved | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CancelExpiredReserves | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CancelReserve | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CheckReserves | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetOtherReserves | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveCount | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveFee | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveInfo | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReservesForBranch | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReservesFromBiblionumber | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReservesFromBorrowernumber | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReservesFromItemnumber | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReservesToBranch | C4::Reserves::
0 | 0 | 0 | 0s | 0s | IsAvailableForItemLevelRequest | C4::Reserves::
0 | 0 | 0 | 0s | 0s | MergeHolds | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ModReserve | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ModReserveAffect | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ModReserveCancelAll | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ModReserveFill | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ModReserveMinusPriority | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ModReserveStatus | C4::Reserves::
0 | 0 | 0 | 0s | 0s | MoveReserve | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ReserveSlip | C4::Reserves::
0 | 0 | 0 | 0s | 0s | RevertWaitingStatus | C4::Reserves::
0 | 0 | 0 | 0s | 0s | SuspendAll | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ToggleLowestPriority | C4::Reserves::
0 | 0 | 0 | 0s | 0s | ToggleSuspend | C4::Reserves::
0 | 0 | 0 | 0s | 0s | _Findgroupreserve | C4::Reserves::
0 | 0 | 0 | 0s | 0s | _FixPriority | C4::Reserves::
0 | 0 | 0 | 0s | 0s | _ShiftPriorityByDateAndPriority | C4::Reserves::
0 | 0 | 0 | 0s | 0s | _koha_notify_reserve | C4::Reserves::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package 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 | |||||
24 | 3 | 30µs | 2 | 27µs | # spent 23µs (18+4) within C4::Reserves::BEGIN@24 which was called:
# once (18µs+4µs) by C4::Circulation::BEGIN@27 at line 24 # spent 23µs making 1 call to C4::Reserves::BEGIN@24
# spent 4µs making 1 call to strict::import |
25 | #use warnings; FIXME - Bug 2505 | ||||
26 | 3 | 26µs | 2 | 14µs | # spent 12µs (10+2) within C4::Reserves::BEGIN@26 which was called:
# once (10µs+2µs) by C4::Circulation::BEGIN@27 at line 26 # spent 12µs making 1 call to C4::Reserves::BEGIN@26
# spent 2µs making 1 call to C4::Context::import |
27 | 3 | 171µs | 2 | 187ms | # spent 185ms (22.4+163) within C4::Reserves::BEGIN@27 which was called:
# once (22.4ms+163ms) by C4::Circulation::BEGIN@27 at line 27 # spent 185ms making 1 call to C4::Reserves::BEGIN@27
# spent 1.40ms making 1 call to Exporter::import |
28 | 3 | 72µs | 2 | 49µs | # spent 43µs (37+6) within C4::Reserves::BEGIN@28 which was called:
# once (37µs+6µs) by C4::Circulation::BEGIN@27 at line 28 # spent 43µs making 1 call to C4::Reserves::BEGIN@28
# spent 6µs making 1 call to UNIVERSAL::import |
29 | 3 | 234µs | 2 | 19.0ms | # spent 18.7ms (14.7+3.94) within C4::Reserves::BEGIN@29 which was called:
# once (14.7ms+3.94ms) by C4::Circulation::BEGIN@27 at line 29 # spent 18.7ms making 1 call to C4::Reserves::BEGIN@29
# spent 309µs making 1 call to Exporter::import |
30 | 3 | 42µs | 2 | 35µs | # spent 31µs (26+4) within C4::Reserves::BEGIN@30 which was called:
# once (26µs+4µs) by C4::Circulation::BEGIN@27 at line 30 # spent 31µs making 1 call to C4::Reserves::BEGIN@30
# spent 4µs making 1 call to UNIVERSAL::import |
31 | 3 | 155µs | 2 | 5.09ms | # spent 4.87ms (4.17+701µs) within C4::Reserves::BEGIN@31 which was called:
# once (4.17ms+701µs) by C4::Circulation::BEGIN@27 at line 31 # spent 4.87ms making 1 call to C4::Reserves::BEGIN@31
# spent 216µs making 1 call to Exporter::import |
32 | |||||
33 | # for _koha_notify_reserve | ||||
34 | 3 | 151µs | 2 | 1.23ms | # spent 1.23ms (1.11+125µs) within C4::Reserves::BEGIN@34 which was called:
# once (1.11ms+125µs) by C4::Circulation::BEGIN@27 at line 34 # spent 1.23ms making 1 call to C4::Reserves::BEGIN@34
# spent 3µs making 1 call to UNIVERSAL::import |
35 | 3 | 26µs | 1 | 6µs | # spent 6µs within C4::Reserves::BEGIN@35 which was called:
# once (6µs+0s) by C4::Circulation::BEGIN@27 at line 35 # spent 6µs making 1 call to C4::Reserves::BEGIN@35 |
36 | 3 | 146µs | 2 | 47.3ms | # spent 47.0ms (4.77+42.2) within C4::Reserves::BEGIN@36 which was called:
# once (4.77ms+42.2ms) by C4::Circulation::BEGIN@27 at line 36 # spent 47.0ms making 1 call to C4::Reserves::BEGIN@36
# spent 284µs making 1 call to Exporter::import |
37 | 3 | 35µs | 2 | 100µs | # spent 57µs (15+43) within C4::Reserves::BEGIN@37 which was called:
# once (15µs+43µs) by C4::Circulation::BEGIN@27 at line 37 # spent 57µs making 1 call to C4::Reserves::BEGIN@37
# spent 43µs making 1 call to Exporter::import |
38 | 3 | 34µs | 2 | 79µs | # spent 46µs (13+33) within C4::Reserves::BEGIN@38 which was called:
# once (13µs+33µs) by C4::Circulation::BEGIN@27 at line 38 # spent 46µs making 1 call to C4::Reserves::BEGIN@38
# spent 33µs making 1 call to Exporter::import |
39 | |||||
40 | 3 | 39µs | 2 | 134µs | # spent 74µs (13+61) within C4::Reserves::BEGIN@40 which was called:
# once (13µs+61µs) by C4::Circulation::BEGIN@27 at line 40 # spent 74µs making 1 call to C4::Reserves::BEGIN@40
# spent 61µs making 1 call to Exporter::import |
41 | |||||
42 | 3 | 60µs | 2 | 97µs | # spent 59µs (21+38) within C4::Reserves::BEGIN@42 which was called:
# once (21µs+38µs) by C4::Circulation::BEGIN@27 at line 42 # spent 59µs making 1 call to C4::Reserves::BEGIN@42
# spent 38µs making 1 call to Exporter::import |
43 | |||||
44 | 3 | 139µs | 2 | 271µs | # spent 142µs (12+129) within C4::Reserves::BEGIN@44 which was called:
# once (12µs+129µs) by C4::Circulation::BEGIN@27 at line 44 # spent 142µs making 1 call to C4::Reserves::BEGIN@44
# spent 129µs making 1 call to vars::import |
45 | |||||
46 | =head1 NAME | ||||
47 | |||||
- - | |||||
89 | # spent 23µs within C4::Reserves::BEGIN@89 which was called:
# once (23µs+0s) by C4::Circulation::BEGIN@27 at line 135 | ||||
90 | # set the version for version checking | ||||
91 | 5 | 23µs | $VERSION = 3.07.00.049; | ||
92 | require Exporter; | ||||
93 | @ISA = qw(Exporter); | ||||
94 | @EXPORT = qw( | ||||
95 | &AddReserve | ||||
96 | |||||
97 | &GetReservesFromItemnumber | ||||
98 | &GetReservesFromBiblionumber | ||||
99 | &GetReservesFromBorrowernumber | ||||
100 | &GetReservesForBranch | ||||
101 | &GetReservesToBranch | ||||
102 | &GetReserveCount | ||||
103 | &GetReserveFee | ||||
104 | &GetReserveInfo | ||||
105 | &GetReserveStatus | ||||
106 | |||||
107 | &GetOtherReserves | ||||
108 | |||||
109 | &ModReserveFill | ||||
110 | &ModReserveAffect | ||||
111 | &ModReserve | ||||
112 | &ModReserveStatus | ||||
113 | &ModReserveCancelAll | ||||
114 | &ModReserveMinusPriority | ||||
115 | &MoveReserve | ||||
116 | |||||
117 | &CheckReserves | ||||
118 | &CanBookBeReserved | ||||
119 | &CanItemBeReserved | ||||
120 | &CancelReserve | ||||
121 | &CancelExpiredReserves | ||||
122 | |||||
123 | &AutoUnsuspendReserves | ||||
124 | |||||
125 | &IsAvailableForItemLevelRequest | ||||
126 | |||||
127 | &AlterPriority | ||||
128 | &ToggleLowestPriority | ||||
129 | |||||
130 | &ReserveSlip | ||||
131 | &ToggleSuspend | ||||
132 | &SuspendAll | ||||
133 | ); | ||||
134 | @EXPORT_OK = qw( MergeHolds ); | ||||
135 | 1 | 6.08ms | 1 | 23µs | } # spent 23µs making 1 call to C4::Reserves::BEGIN@89 |
136 | |||||
137 | =head2 AddReserve | ||||
138 | |||||
- - | |||||
143 | sub AddReserve { | ||||
144 | my ( | ||||
145 | $branch, $borrowernumber, $biblionumber, | ||||
146 | $constraint, $bibitems, $priority, $resdate, $expdate, $notes, | ||||
147 | $title, $checkitem, $found | ||||
148 | ) = @_; | ||||
149 | my $fee = | ||||
150 | GetReserveFee($borrowernumber, $biblionumber, $constraint, | ||||
151 | $bibitems ); | ||||
152 | my $dbh = C4::Context->dbh; | ||||
153 | my $const = lc substr( $constraint, 0, 1 ); | ||||
154 | $resdate = format_date_in_iso( $resdate ) if ( $resdate ); | ||||
155 | $resdate = C4::Dates->today( 'iso' ) unless ( $resdate ); | ||||
156 | if ($expdate) { | ||||
157 | $expdate = format_date_in_iso( $expdate ); | ||||
158 | } else { | ||||
159 | undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00' | ||||
160 | } | ||||
161 | if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) { | ||||
162 | # Make room in reserves for this before those of a later reserve date | ||||
163 | $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority ); | ||||
164 | } | ||||
165 | my $waitingdate; | ||||
166 | |||||
167 | # If the reserv had the waiting status, we had the value of the resdate | ||||
168 | if ( $found eq 'W' ) { | ||||
169 | $waitingdate = $resdate; | ||||
170 | } | ||||
171 | |||||
172 | #eval { | ||||
173 | # updates take place here | ||||
174 | if ( $fee > 0 ) { | ||||
175 | my $nextacctno = &getnextacctno( $borrowernumber ); | ||||
176 | my $query = qq/ | ||||
177 | INSERT INTO accountlines | ||||
178 | (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding) | ||||
179 | VALUES | ||||
180 | (?,?,now(),?,?,'Res',?) | ||||
181 | /; | ||||
182 | my $usth = $dbh->prepare($query); | ||||
183 | $usth->execute( $borrowernumber, $nextacctno, $fee, | ||||
184 | "Reserve Charge - $title", $fee ); | ||||
185 | } | ||||
186 | |||||
187 | #if ($const eq 'a'){ | ||||
188 | my $query = qq/ | ||||
189 | INSERT INTO reserves | ||||
190 | (borrowernumber,biblionumber,reservedate,branchcode,constrainttype, | ||||
191 | priority,reservenotes,itemnumber,found,waitingdate,expirationdate) | ||||
192 | VALUES | ||||
193 | (?,?,?,?,?, | ||||
194 | ?,?,?,?,?,?) | ||||
195 | /; | ||||
196 | my $sth = $dbh->prepare($query); | ||||
197 | $sth->execute( | ||||
198 | $borrowernumber, $biblionumber, $resdate, $branch, | ||||
199 | $const, $priority, $notes, $checkitem, | ||||
200 | $found, $waitingdate, $expdate | ||||
201 | ); | ||||
202 | |||||
203 | # Send e-mail to librarian if syspref is active | ||||
204 | if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){ | ||||
205 | my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber); | ||||
206 | my $branch_details = C4::Branch::GetBranchDetail($borrower->{branchcode}); | ||||
207 | if ( my $letter = C4::Letters::GetPreparedLetter ( | ||||
208 | module => 'reserves', | ||||
209 | letter_code => 'HOLDPLACED', | ||||
210 | branchcode => $branch, | ||||
211 | tables => { | ||||
212 | 'branches' => $branch_details, | ||||
213 | 'borrowers' => $borrower, | ||||
214 | 'biblio' => $biblionumber, | ||||
215 | }, | ||||
216 | ) ) { | ||||
217 | |||||
218 | my $admin_email_address =$branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress'); | ||||
219 | |||||
220 | C4::Letters::EnqueueLetter( | ||||
221 | { letter => $letter, | ||||
222 | borrowernumber => $borrowernumber, | ||||
223 | message_transport_type => 'email', | ||||
224 | from_address => $admin_email_address, | ||||
225 | to_address => $admin_email_address, | ||||
226 | } | ||||
227 | ); | ||||
228 | } | ||||
229 | } | ||||
230 | |||||
231 | #} | ||||
232 | ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value? | ||||
233 | $query = qq/ | ||||
234 | INSERT INTO reserveconstraints | ||||
235 | (borrowernumber,biblionumber,reservedate,biblioitemnumber) | ||||
236 | VALUES | ||||
237 | (?,?,?,?) | ||||
238 | /; | ||||
239 | $sth = $dbh->prepare($query); # keep prepare outside the loop! | ||||
240 | foreach (@$bibitems) { | ||||
241 | $sth->execute($borrowernumber, $biblionumber, $resdate, $_); | ||||
242 | } | ||||
243 | |||||
244 | return; # FIXME: why not have a useful return value? | ||||
245 | } | ||||
246 | |||||
247 | =head2 GetReservesFromBiblionumber | ||||
248 | |||||
- - | |||||
256 | sub GetReservesFromBiblionumber { | ||||
257 | my ($biblionumber) = shift or return (0, []); | ||||
258 | my ($all_dates) = shift; | ||||
259 | my $dbh = C4::Context->dbh; | ||||
260 | |||||
261 | # Find the desired items in the reserves | ||||
262 | my $query = " | ||||
263 | SELECT branchcode, | ||||
264 | timestamp AS rtimestamp, | ||||
265 | priority, | ||||
266 | biblionumber, | ||||
267 | borrowernumber, | ||||
268 | reservedate, | ||||
269 | constrainttype, | ||||
270 | found, | ||||
271 | itemnumber, | ||||
272 | reservenotes, | ||||
273 | expirationdate, | ||||
274 | lowestPriority, | ||||
275 | suspend, | ||||
276 | suspend_until | ||||
277 | FROM reserves | ||||
278 | WHERE biblionumber = ? "; | ||||
279 | unless ( $all_dates ) { | ||||
280 | $query .= "AND reservedate <= CURRENT_DATE()"; | ||||
281 | } | ||||
282 | $query .= "ORDER BY priority"; | ||||
283 | my $sth = $dbh->prepare($query); | ||||
284 | $sth->execute($biblionumber); | ||||
285 | my @results; | ||||
286 | my $i = 0; | ||||
287 | while ( my $data = $sth->fetchrow_hashref ) { | ||||
288 | |||||
289 | # FIXME - What is this doing? How do constraints work? | ||||
290 | if ($data->{constrainttype} eq 'o') { | ||||
291 | $query = ' | ||||
292 | SELECT biblioitemnumber | ||||
293 | FROM reserveconstraints | ||||
294 | WHERE biblionumber = ? | ||||
295 | AND borrowernumber = ? | ||||
296 | AND reservedate = ? | ||||
297 | '; | ||||
298 | my $csth = $dbh->prepare($query); | ||||
299 | $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate}); | ||||
300 | my @bibitemno; | ||||
301 | while ( my $bibitemnos = $csth->fetchrow_array ) { | ||||
302 | push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref | ||||
303 | } | ||||
304 | my $count = scalar @bibitemno; | ||||
305 | |||||
306 | # if we have two or more different specific itemtypes | ||||
307 | # reserved by same person on same day | ||||
308 | my $bdata; | ||||
309 | if ( $count > 1 ) { | ||||
310 | $bdata = GetBiblioItemData( $bibitemno[$i] ); # FIXME: This doesn't make sense. | ||||
311 | $i++; # $i can increase each pass, but the next @bibitemno might be smaller? | ||||
312 | } | ||||
313 | else { | ||||
314 | # Look up the book we just found. | ||||
315 | $bdata = GetBiblioItemData( $bibitemno[0] ); | ||||
316 | } | ||||
317 | # Add the results of this latest search to the current | ||||
318 | # results. | ||||
319 | # FIXME - An 'each' would probably be more efficient. | ||||
320 | foreach my $key ( keys %$bdata ) { | ||||
321 | $data->{$key} = $bdata->{$key}; | ||||
322 | } | ||||
323 | } | ||||
324 | push @results, $data; | ||||
325 | } | ||||
326 | return ( $#results + 1, \@results ); | ||||
327 | } | ||||
328 | |||||
329 | =head2 GetReservesFromItemnumber | ||||
330 | |||||
- - | |||||
337 | sub GetReservesFromItemnumber { | ||||
338 | my ( $itemnumber, $all_dates ) = @_; | ||||
339 | my $dbh = C4::Context->dbh; | ||||
340 | my $query = " | ||||
341 | SELECT reservedate,borrowernumber,branchcode | ||||
342 | FROM reserves | ||||
343 | WHERE itemnumber=? | ||||
344 | "; | ||||
345 | unless ( $all_dates ) { | ||||
346 | $query .= " AND reservedate <= CURRENT_DATE()"; | ||||
347 | } | ||||
348 | my $sth_res = $dbh->prepare($query); | ||||
349 | $sth_res->execute($itemnumber); | ||||
350 | my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array; | ||||
351 | return ( $reservedate, $borrowernumber, $branchcode ); | ||||
352 | } | ||||
353 | |||||
354 | =head2 GetReservesFromBorrowernumber | ||||
355 | |||||
- - | |||||
362 | sub GetReservesFromBorrowernumber { | ||||
363 | my ( $borrowernumber, $status ) = @_; | ||||
364 | my $dbh = C4::Context->dbh; | ||||
365 | my $sth; | ||||
366 | if ($status) { | ||||
367 | $sth = $dbh->prepare(" | ||||
368 | SELECT * | ||||
369 | FROM reserves | ||||
370 | WHERE borrowernumber=? | ||||
371 | AND found =? | ||||
372 | ORDER BY reservedate | ||||
373 | "); | ||||
374 | $sth->execute($borrowernumber,$status); | ||||
375 | } else { | ||||
376 | $sth = $dbh->prepare(" | ||||
377 | SELECT * | ||||
378 | FROM reserves | ||||
379 | WHERE borrowernumber=? | ||||
380 | ORDER BY reservedate | ||||
381 | "); | ||||
382 | $sth->execute($borrowernumber); | ||||
383 | } | ||||
384 | my $data = $sth->fetchall_arrayref({}); | ||||
385 | return @$data; | ||||
386 | } | ||||
387 | #------------------------------------------------------------------------------------- | ||||
388 | =head2 CanBookBeReserved | ||||
389 | |||||
- - | |||||
394 | sub CanBookBeReserved{ | ||||
395 | my ($borrowernumber, $biblionumber) = @_; | ||||
396 | |||||
397 | my $items = GetItemnumbersForBiblio($biblionumber); | ||||
398 | #get items linked via host records | ||||
399 | my @hostitems = get_hostitemnumbers_of($biblionumber); | ||||
400 | if (@hostitems){ | ||||
401 | push (@$items,@hostitems); | ||||
402 | } | ||||
403 | |||||
404 | foreach my $item (@$items){ | ||||
405 | return 1 if CanItemBeReserved($borrowernumber, $item); | ||||
406 | } | ||||
407 | return 0; | ||||
408 | } | ||||
409 | |||||
410 | =head2 CanItemBeReserved | ||||
411 | |||||
- - | |||||
418 | sub CanItemBeReserved{ | ||||
419 | my ($borrowernumber, $itemnumber) = @_; | ||||
420 | |||||
421 | my $dbh = C4::Context->dbh; | ||||
422 | my $allowedreserves = 0; | ||||
423 | |||||
424 | my $controlbranch = C4::Context->preference('ReservesControlBranch'); | ||||
425 | my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype"; | ||||
426 | |||||
427 | # we retrieve borrowers and items informations # | ||||
428 | my $item = GetItem($itemnumber); | ||||
429 | my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber); | ||||
430 | |||||
431 | # we retrieve user rights on this itemtype and branchcode | ||||
432 | my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed | ||||
433 | FROM issuingrules | ||||
434 | WHERE (categorycode in (?,'*') ) | ||||
435 | AND (itemtype IN (?,'*')) | ||||
436 | AND (branchcode IN (?,'*')) | ||||
437 | ORDER BY | ||||
438 | categorycode DESC, | ||||
439 | itemtype DESC, | ||||
440 | branchcode DESC;" | ||||
441 | ); | ||||
442 | |||||
443 | my $querycount ="SELECT | ||||
444 | count(*) as count | ||||
445 | FROM reserves | ||||
446 | LEFT JOIN items USING (itemnumber) | ||||
447 | LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber) | ||||
448 | LEFT JOIN borrowers USING (borrowernumber) | ||||
449 | WHERE borrowernumber = ? | ||||
450 | "; | ||||
451 | |||||
452 | |||||
453 | my $itemtype = $item->{$itype}; | ||||
454 | my $categorycode = $borrower->{categorycode}; | ||||
455 | my $branchcode = ""; | ||||
456 | my $branchfield = "reserves.branchcode"; | ||||
457 | |||||
458 | if( $controlbranch eq "ItemHomeLibrary" ){ | ||||
459 | $branchfield = "items.homebranch"; | ||||
460 | $branchcode = $item->{homebranch}; | ||||
461 | }elsif( $controlbranch eq "PatronLibrary" ){ | ||||
462 | $branchfield = "borrowers.branchcode"; | ||||
463 | $branchcode = $borrower->{branchcode}; | ||||
464 | } | ||||
465 | |||||
466 | # we retrieve rights | ||||
467 | $sth->execute($categorycode, $itemtype, $branchcode); | ||||
468 | if(my $rights = $sth->fetchrow_hashref()){ | ||||
469 | $itemtype = $rights->{itemtype}; | ||||
470 | $allowedreserves = $rights->{reservesallowed}; | ||||
471 | }else{ | ||||
472 | $itemtype = '*'; | ||||
473 | } | ||||
474 | |||||
475 | # we retrieve count | ||||
476 | |||||
477 | $querycount .= "AND $branchfield = ?"; | ||||
478 | |||||
479 | $querycount .= " AND $itype = ?" if ($itemtype ne "*"); | ||||
480 | my $sthcount = $dbh->prepare($querycount); | ||||
481 | |||||
482 | if($itemtype eq "*"){ | ||||
483 | $sthcount->execute($borrowernumber, $branchcode); | ||||
484 | }else{ | ||||
485 | $sthcount->execute($borrowernumber, $branchcode, $itemtype); | ||||
486 | } | ||||
487 | |||||
488 | my $reservecount = "0"; | ||||
489 | if(my $rowcount = $sthcount->fetchrow_hashref()){ | ||||
490 | $reservecount = $rowcount->{count}; | ||||
491 | } | ||||
492 | |||||
493 | # we check if it's ok or not | ||||
494 | if( $reservecount < $allowedreserves ){ | ||||
495 | return 1; | ||||
496 | }else{ | ||||
497 | return 0; | ||||
498 | } | ||||
499 | } | ||||
500 | #-------------------------------------------------------------------------------- | ||||
501 | =head2 GetReserveCount | ||||
502 | |||||
- - | |||||
509 | sub GetReserveCount { | ||||
510 | my ($borrowernumber) = @_; | ||||
511 | |||||
512 | my $dbh = C4::Context->dbh; | ||||
513 | |||||
514 | my $query = ' | ||||
515 | SELECT COUNT(*) AS counter | ||||
516 | FROM reserves | ||||
517 | WHERE borrowernumber = ? | ||||
518 | '; | ||||
519 | my $sth = $dbh->prepare($query); | ||||
520 | $sth->execute($borrowernumber); | ||||
521 | my $row = $sth->fetchrow_hashref; | ||||
522 | return $row->{counter}; | ||||
523 | } | ||||
524 | |||||
525 | =head2 GetOtherReserves | ||||
526 | |||||
- - | |||||
533 | sub GetOtherReserves { | ||||
534 | my ($itemnumber) = @_; | ||||
535 | my $messages; | ||||
536 | my $nextreservinfo; | ||||
537 | my ( undef, $checkreserves, undef ) = CheckReserves($itemnumber); | ||||
538 | if ($checkreserves) { | ||||
539 | my $iteminfo = GetItem($itemnumber); | ||||
540 | if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) { | ||||
541 | $messages->{'transfert'} = $checkreserves->{'branchcode'}; | ||||
542 | #minus priorities of others reservs | ||||
543 | ModReserveMinusPriority( | ||||
544 | $itemnumber, | ||||
545 | $checkreserves->{'borrowernumber'}, | ||||
546 | $iteminfo->{'biblionumber'} | ||||
547 | ); | ||||
548 | |||||
549 | #launch the subroutine dotransfer | ||||
550 | C4::Items::ModItemTransfer( | ||||
551 | $itemnumber, | ||||
552 | $iteminfo->{'holdingbranch'}, | ||||
553 | $checkreserves->{'branchcode'} | ||||
554 | ), | ||||
555 | ; | ||||
556 | } | ||||
557 | |||||
558 | #step 2b : case of a reservation on the same branch, set the waiting status | ||||
559 | else { | ||||
560 | $messages->{'waiting'} = 1; | ||||
561 | ModReserveMinusPriority( | ||||
562 | $itemnumber, | ||||
563 | $checkreserves->{'borrowernumber'}, | ||||
564 | $iteminfo->{'biblionumber'} | ||||
565 | ); | ||||
566 | ModReserveStatus($itemnumber,'W'); | ||||
567 | } | ||||
568 | |||||
569 | $nextreservinfo = $checkreserves->{'borrowernumber'}; | ||||
570 | } | ||||
571 | |||||
572 | return ( $messages, $nextreservinfo ); | ||||
573 | } | ||||
574 | |||||
575 | =head2 GetReserveFee | ||||
576 | |||||
- - | |||||
583 | sub GetReserveFee { | ||||
584 | my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_; | ||||
585 | |||||
586 | #check for issues; | ||||
587 | my $dbh = C4::Context->dbh; | ||||
588 | my $const = lc substr( $constraint, 0, 1 ); | ||||
589 | my $query = qq/ | ||||
590 | SELECT * FROM borrowers | ||||
591 | LEFT JOIN categories ON borrowers.categorycode = categories.categorycode | ||||
592 | WHERE borrowernumber = ? | ||||
593 | /; | ||||
594 | my $sth = $dbh->prepare($query); | ||||
595 | $sth->execute($borrowernumber); | ||||
596 | my $data = $sth->fetchrow_hashref; | ||||
597 | $sth->finish(); | ||||
598 | my $fee = $data->{'reservefee'}; | ||||
599 | my $cntitems = @- > $bibitems; | ||||
600 | |||||
601 | if ( $fee > 0 ) { | ||||
602 | |||||
603 | # check for items on issue | ||||
604 | # first find biblioitem records | ||||
605 | my @biblioitems; | ||||
606 | my $sth1 = $dbh->prepare( | ||||
607 | "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber | ||||
608 | WHERE (biblio.biblionumber = ?)" | ||||
609 | ); | ||||
610 | $sth1->execute($biblionumber); | ||||
611 | while ( my $data1 = $sth1->fetchrow_hashref ) { | ||||
612 | if ( $const eq "a" ) { | ||||
613 | push @biblioitems, $data1; | ||||
614 | } | ||||
615 | else { | ||||
616 | my $found = 0; | ||||
617 | my $x = 0; | ||||
618 | while ( $x < $cntitems ) { | ||||
619 | if ( @$bibitems->{'biblioitemnumber'} == | ||||
620 | $data->{'biblioitemnumber'} ) | ||||
621 | { | ||||
622 | $found = 1; | ||||
623 | } | ||||
624 | $x++; | ||||
625 | } | ||||
626 | if ( $const eq 'o' ) { | ||||
627 | if ( $found == 1 ) { | ||||
628 | push @biblioitems, $data1; | ||||
629 | } | ||||
630 | } | ||||
631 | else { | ||||
632 | if ( $found == 0 ) { | ||||
633 | push @biblioitems, $data1; | ||||
634 | } | ||||
635 | } | ||||
636 | } | ||||
637 | } | ||||
638 | $sth1->finish; | ||||
639 | my $cntitemsfound = @biblioitems; | ||||
640 | my $issues = 0; | ||||
641 | my $x = 0; | ||||
642 | my $allissued = 1; | ||||
643 | while ( $x < $cntitemsfound ) { | ||||
644 | my $bitdata = $biblioitems[$x]; | ||||
645 | my $sth2 = $dbh->prepare( | ||||
646 | "SELECT * FROM items | ||||
647 | WHERE biblioitemnumber = ?" | ||||
648 | ); | ||||
649 | $sth2->execute( $bitdata->{'biblioitemnumber'} ); | ||||
650 | while ( my $itdata = $sth2->fetchrow_hashref ) { | ||||
651 | my $sth3 = $dbh->prepare( | ||||
652 | "SELECT * FROM issues | ||||
653 | WHERE itemnumber = ?" | ||||
654 | ); | ||||
655 | $sth3->execute( $itdata->{'itemnumber'} ); | ||||
656 | if ( my $isdata = $sth3->fetchrow_hashref ) { | ||||
657 | } | ||||
658 | else { | ||||
659 | $allissued = 0; | ||||
660 | } | ||||
661 | } | ||||
662 | $x++; | ||||
663 | } | ||||
664 | if ( $allissued == 0 ) { | ||||
665 | my $rsth = | ||||
666 | $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?"); | ||||
667 | $rsth->execute($biblionumber); | ||||
668 | if ( my $rdata = $rsth->fetchrow_hashref ) { | ||||
669 | } | ||||
670 | else { | ||||
671 | $fee = 0; | ||||
672 | } | ||||
673 | } | ||||
674 | } | ||||
675 | return $fee; | ||||
676 | } | ||||
677 | |||||
678 | =head2 GetReservesToBranch | ||||
679 | |||||
- - | |||||
686 | sub GetReservesToBranch { | ||||
687 | my ( $frombranch ) = @_; | ||||
688 | my $dbh = C4::Context->dbh; | ||||
689 | my $sth = $dbh->prepare( | ||||
690 | "SELECT borrowernumber,reservedate,itemnumber,timestamp | ||||
691 | FROM reserves | ||||
692 | WHERE priority='0' | ||||
693 | AND branchcode=?" | ||||
694 | ); | ||||
695 | $sth->execute( $frombranch ); | ||||
696 | my @transreserv; | ||||
697 | my $i = 0; | ||||
698 | while ( my $data = $sth->fetchrow_hashref ) { | ||||
699 | $transreserv[$i] = $data; | ||||
700 | $i++; | ||||
701 | } | ||||
702 | return (@transreserv); | ||||
703 | } | ||||
704 | |||||
705 | =head2 GetReservesForBranch | ||||
706 | |||||
- - | |||||
711 | sub GetReservesForBranch { | ||||
712 | my ($frombranch) = @_; | ||||
713 | my $dbh = C4::Context->dbh; | ||||
714 | my $query = "SELECT borrowernumber,reservedate,itemnumber,waitingdate | ||||
715 | FROM reserves | ||||
716 | WHERE priority='0' | ||||
717 | AND found='W' "; | ||||
718 | if ($frombranch){ | ||||
719 | $query .= " AND branchcode=? "; | ||||
720 | } | ||||
721 | $query .= "ORDER BY waitingdate" ; | ||||
722 | my $sth = $dbh->prepare($query); | ||||
723 | if ($frombranch){ | ||||
724 | $sth->execute($frombranch); | ||||
725 | } | ||||
726 | else { | ||||
727 | $sth->execute(); | ||||
728 | } | ||||
729 | my @transreserv; | ||||
730 | my $i = 0; | ||||
731 | while ( my $data = $sth->fetchrow_hashref ) { | ||||
732 | $transreserv[$i] = $data; | ||||
733 | $i++; | ||||
734 | } | ||||
735 | return (@transreserv); | ||||
736 | } | ||||
737 | |||||
738 | =head2 GetReserveStatus | ||||
739 | |||||
- - | |||||
747 | # spent 184ms (6.58+178) within C4::Reserves::GetReserveStatus which was called 64 times, avg 2.88ms/call:
# 32 times (4.15ms+113ms) by C4::Search::searchResults at line 1926 of /usr/share/koha/lib/C4/Search.pm, avg 3.67ms/call
# 32 times (2.43ms+64.6ms) by C4::XSLT::buildKohaItemsNamespace at line 267 of /usr/share/koha/lib/C4/XSLT.pm, avg 2.09ms/call | ||||
748 | 448 | 3.63ms | my ($itemnumber, $biblionumber) = @_; | ||
749 | |||||
750 | 64 | 61.0ms | my $dbh = C4::Context->dbh; # spent 61.0ms making 64 calls to C4::Context::dbh, avg 954µs/call | ||
751 | |||||
752 | my ($sth, $found, $priority); | ||||
753 | 192 | 72.1ms | if ( $itemnumber ) { | ||
754 | 1 | 397µs | 128 | 11.5ms | $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1"); # spent 6.26ms making 64 calls to DBI::db::prepare, avg 98µs/call
# spent 5.20ms making 64 calls to DBD::mysql::db::prepare, avg 81µs/call |
755 | 64 | 68.5ms | $sth->execute($itemnumber); # spent 68.5ms making 64 calls to DBI::st::execute, avg 1.07ms/call | ||
756 | 64 | 951µs | ($found, $priority) = $sth->fetchrow_array; # spent 951µs making 64 calls to DBI::st::fetchrow_array, avg 15µs/call | ||
757 | } | ||||
758 | |||||
759 | 96 | 37.9ms | if ( $biblionumber and not defined $found and not defined $priority ) { | ||
760 | 1 | 1.20ms | 64 | 5.58ms | $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE biblionumber = ? order by priority LIMIT 1"); # spent 2.99ms making 32 calls to DBI::db::prepare, avg 93µs/call
# spent 2.59ms making 32 calls to DBD::mysql::db::prepare, avg 81µs/call |
761 | 128 | 36.8ms | $sth->execute($biblionumber); # spent 36.4ms making 32 calls to DBI::st::execute, avg 1.14ms/call
# spent 277µs making 64 calls to DBI::common::DESTROY, avg 4µs/call
# spent 75µs making 32 calls to DBD::_mem::common::DESTROY, avg 2µs/call | ||
762 | 32 | 403µs | ($found, $priority) = $sth->fetchrow_array; # spent 403µs making 32 calls to DBI::st::fetchrow_array, avg 13µs/call | ||
763 | } | ||||
764 | |||||
765 | if(defined $found) { | ||||
766 | return 'Waiting' if $found eq 'W' and $priority == 0; | ||||
767 | return 'Finished' if $found eq 'F'; | ||||
768 | return 'Reserved' if $priority > 0; | ||||
769 | } | ||||
770 | return ''; | ||||
771 | #empty string here will remove need for checking undef, or less log lines | ||||
772 | } | ||||
773 | |||||
774 | =head2 CheckReserves | ||||
775 | |||||
- - | |||||
801 | sub CheckReserves { | ||||
802 | my ( $item, $barcode ) = @_; | ||||
803 | my $dbh = C4::Context->dbh; | ||||
804 | my $sth; | ||||
805 | my $select; | ||||
806 | if (C4::Context->preference('item-level_itypes')){ | ||||
807 | $select = " | ||||
808 | SELECT items.biblionumber, | ||||
809 | items.biblioitemnumber, | ||||
810 | itemtypes.notforloan, | ||||
811 | items.notforloan AS itemnotforloan, | ||||
812 | items.itemnumber | ||||
813 | FROM items | ||||
814 | LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber | ||||
815 | LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype | ||||
816 | "; | ||||
817 | } | ||||
818 | else { | ||||
819 | $select = " | ||||
820 | SELECT items.biblionumber, | ||||
821 | items.biblioitemnumber, | ||||
822 | itemtypes.notforloan, | ||||
823 | items.notforloan AS itemnotforloan, | ||||
824 | items.itemnumber | ||||
825 | FROM items | ||||
826 | LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber | ||||
827 | LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype | ||||
828 | "; | ||||
829 | } | ||||
830 | |||||
831 | if ($item) { | ||||
832 | $sth = $dbh->prepare("$select WHERE itemnumber = ?"); | ||||
833 | $sth->execute($item); | ||||
834 | } | ||||
835 | else { | ||||
836 | $sth = $dbh->prepare("$select WHERE barcode = ?"); | ||||
837 | $sth->execute($barcode); | ||||
838 | } | ||||
839 | # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it. | ||||
840 | my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array; | ||||
841 | |||||
842 | return ( '' ) unless $itemnumber; # bail if we got nothing. | ||||
843 | |||||
844 | # if item is not for loan it cannot be reserved either..... | ||||
845 | # execpt where items.notforloan < 0 : This indicates the item is holdable. | ||||
846 | return ( '' ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype; | ||||
847 | |||||
848 | # Find this item in the reserves | ||||
849 | my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber ); | ||||
850 | |||||
851 | # $priority and $highest are used to find the most important item | ||||
852 | # in the list returned by &_Findgroupreserve. (The lower $priority, | ||||
853 | # the more important the item.) | ||||
854 | # $highest is the most important item we've seen so far. | ||||
855 | my $highest; | ||||
856 | if (scalar @reserves) { | ||||
857 | my $priority = 10000000; | ||||
858 | foreach my $res (@reserves) { | ||||
859 | if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) { | ||||
860 | return ( "Waiting", $res, \@reserves ); # Found it | ||||
861 | } else { | ||||
862 | # See if this item is more important than what we've got so far | ||||
863 | if ( $res->{'priority'} && $res->{'priority'} < $priority ) { | ||||
864 | my $borrowerinfo=C4::Members::GetMember(borrowernumber => $res->{'borrowernumber'}); | ||||
865 | my $iteminfo=C4::Items::GetItem($itemnumber); | ||||
866 | my $branch=C4::Circulation::_GetCircControlBranch($iteminfo,$borrowerinfo); | ||||
867 | my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'}); | ||||
868 | next if ($branchitemrule->{'holdallowed'} == 0); | ||||
869 | next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'})); | ||||
870 | $priority = $res->{'priority'}; | ||||
871 | $highest = $res; | ||||
872 | } | ||||
873 | } | ||||
874 | } | ||||
875 | } | ||||
876 | |||||
877 | # If we get this far, then no exact match was found. | ||||
878 | # We return the most important (i.e. next) reservation. | ||||
879 | if ($highest) { | ||||
880 | $highest->{'itemnumber'} = $item; | ||||
881 | return ( "Reserved", $highest, \@reserves ); | ||||
882 | } | ||||
883 | |||||
884 | return ( '' ); | ||||
885 | } | ||||
886 | |||||
887 | =head2 CancelExpiredReserves | ||||
888 | |||||
- - | |||||
895 | sub CancelExpiredReserves { | ||||
896 | |||||
897 | # Cancel reserves that have passed their expiration date. | ||||
898 | my $dbh = C4::Context->dbh; | ||||
899 | my $sth = $dbh->prepare( " | ||||
900 | SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() ) | ||||
901 | AND expirationdate IS NOT NULL | ||||
902 | AND found IS NULL | ||||
903 | " ); | ||||
904 | $sth->execute(); | ||||
905 | |||||
906 | while ( my $res = $sth->fetchrow_hashref() ) { | ||||
907 | CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} ); | ||||
908 | } | ||||
909 | |||||
910 | # Cancel reserves that have been waiting too long | ||||
911 | if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) { | ||||
912 | my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay"); | ||||
913 | my $charge = C4::Context->preference("ExpireReservesMaxPickUpDelayCharge"); | ||||
914 | |||||
915 | my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0"; | ||||
916 | $sth = $dbh->prepare( $query ); | ||||
917 | $sth->execute( $max_pickup_delay ); | ||||
918 | |||||
919 | while (my $res = $sth->fetchrow_hashref ) { | ||||
920 | if ( $charge ) { | ||||
921 | manualinvoice($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge); | ||||
922 | } | ||||
923 | |||||
924 | CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} ); | ||||
925 | } | ||||
926 | } | ||||
927 | |||||
928 | } | ||||
929 | |||||
930 | =head2 AutoUnsuspendReserves | ||||
931 | |||||
- - | |||||
938 | sub AutoUnsuspendReserves { | ||||
939 | |||||
940 | my $dbh = C4::Context->dbh; | ||||
941 | |||||
942 | my $query = "UPDATE reserves SET suspend = 0, suspend_until = NULL WHERE DATE( suspend_until ) < DATE( CURDATE() )"; | ||||
943 | my $sth = $dbh->prepare( $query ); | ||||
944 | $sth->execute(); | ||||
945 | |||||
946 | } | ||||
947 | |||||
948 | =head2 CancelReserve | ||||
949 | |||||
- - | |||||
966 | sub CancelReserve { | ||||
967 | my ( $biblio, $item, $borr ) = @_; | ||||
968 | my $dbh = C4::Context->dbh; | ||||
969 | if ( $item and $borr ) { | ||||
970 | # removing a waiting reserve record.... | ||||
971 | # update the database... | ||||
972 | my $query = " | ||||
973 | UPDATE reserves | ||||
974 | SET cancellationdate = now(), | ||||
975 | found = Null, | ||||
976 | priority = 0 | ||||
977 | WHERE itemnumber = ? | ||||
978 | AND borrowernumber = ? | ||||
979 | "; | ||||
980 | my $sth = $dbh->prepare($query); | ||||
981 | $sth->execute( $item, $borr ); | ||||
982 | $sth->finish; | ||||
983 | $query = " | ||||
984 | INSERT INTO old_reserves | ||||
985 | SELECT * FROM reserves | ||||
986 | WHERE itemnumber = ? | ||||
987 | AND borrowernumber = ? | ||||
988 | "; | ||||
989 | $sth = $dbh->prepare($query); | ||||
990 | $sth->execute( $item, $borr ); | ||||
991 | $query = " | ||||
992 | DELETE FROM reserves | ||||
993 | WHERE itemnumber = ? | ||||
994 | AND borrowernumber = ? | ||||
995 | "; | ||||
996 | $sth = $dbh->prepare($query); | ||||
997 | $sth->execute( $item, $borr ); | ||||
998 | } | ||||
999 | else { | ||||
1000 | # removing a reserve record.... | ||||
1001 | # get the prioritiy on this record.... | ||||
1002 | my $priority; | ||||
1003 | my $query = qq/ | ||||
1004 | SELECT priority FROM reserves | ||||
1005 | WHERE biblionumber = ? | ||||
1006 | AND borrowernumber = ? | ||||
1007 | AND cancellationdate IS NULL | ||||
1008 | AND itemnumber IS NULL | ||||
1009 | /; | ||||
1010 | my $sth = $dbh->prepare($query); | ||||
1011 | $sth->execute( $biblio, $borr ); | ||||
1012 | ($priority) = $sth->fetchrow_array; | ||||
1013 | $sth->finish; | ||||
1014 | $query = qq/ | ||||
1015 | UPDATE reserves | ||||
1016 | SET cancellationdate = now(), | ||||
1017 | found = Null, | ||||
1018 | priority = 0 | ||||
1019 | WHERE biblionumber = ? | ||||
1020 | AND borrowernumber = ? | ||||
1021 | /; | ||||
1022 | |||||
1023 | # update the database, removing the record... | ||||
1024 | $sth = $dbh->prepare($query); | ||||
1025 | $sth->execute( $biblio, $borr ); | ||||
1026 | $sth->finish; | ||||
1027 | |||||
1028 | $query = qq/ | ||||
1029 | INSERT INTO old_reserves | ||||
1030 | SELECT * FROM reserves | ||||
1031 | WHERE biblionumber = ? | ||||
1032 | AND borrowernumber = ? | ||||
1033 | /; | ||||
1034 | $sth = $dbh->prepare($query); | ||||
1035 | $sth->execute( $biblio, $borr ); | ||||
1036 | |||||
1037 | $query = qq/ | ||||
1038 | DELETE FROM reserves | ||||
1039 | WHERE biblionumber = ? | ||||
1040 | AND borrowernumber = ? | ||||
1041 | /; | ||||
1042 | $sth = $dbh->prepare($query); | ||||
1043 | $sth->execute( $biblio, $borr ); | ||||
1044 | |||||
1045 | # now fix the priority on the others.... | ||||
1046 | _FixPriority( $biblio, $borr ); | ||||
1047 | } | ||||
1048 | } | ||||
1049 | |||||
1050 | =head2 ModReserve | ||||
1051 | |||||
- - | |||||
1081 | sub ModReserve { | ||||
1082 | #subroutine to update a reserve | ||||
1083 | my ( $rank, $biblio, $borrower, $branch , $itemnumber, $suspend_until) = @_; | ||||
1084 | return if $rank eq "W"; | ||||
1085 | return if $rank eq "n"; | ||||
1086 | my $dbh = C4::Context->dbh; | ||||
1087 | if ( $rank eq "del" ) { | ||||
1088 | my $query = qq/ | ||||
1089 | UPDATE reserves | ||||
1090 | SET cancellationdate=now() | ||||
1091 | WHERE biblionumber = ? | ||||
1092 | AND borrowernumber = ? | ||||
1093 | /; | ||||
1094 | my $sth = $dbh->prepare($query); | ||||
1095 | $sth->execute( $biblio, $borrower ); | ||||
1096 | $sth->finish; | ||||
1097 | $query = qq/ | ||||
1098 | INSERT INTO old_reserves | ||||
1099 | SELECT * | ||||
1100 | FROM reserves | ||||
1101 | WHERE biblionumber = ? | ||||
1102 | AND borrowernumber = ? | ||||
1103 | /; | ||||
1104 | $sth = $dbh->prepare($query); | ||||
1105 | $sth->execute( $biblio, $borrower ); | ||||
1106 | $query = qq/ | ||||
1107 | DELETE FROM reserves | ||||
1108 | WHERE biblionumber = ? | ||||
1109 | AND borrowernumber = ? | ||||
1110 | /; | ||||
1111 | $sth = $dbh->prepare($query); | ||||
1112 | $sth->execute( $biblio, $borrower ); | ||||
1113 | |||||
1114 | } | ||||
1115 | elsif ($rank =~ /^\d+/ and $rank > 0) { | ||||
1116 | my $query = " | ||||
1117 | UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL | ||||
1118 | WHERE biblionumber = ? | ||||
1119 | AND borrowernumber = ? | ||||
1120 | "; | ||||
1121 | my $sth = $dbh->prepare($query); | ||||
1122 | $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower); | ||||
1123 | $sth->finish; | ||||
1124 | |||||
1125 | if ( defined( $suspend_until ) ) { | ||||
1126 | if ( $suspend_until ) { | ||||
1127 | $suspend_until = C4::Dates->new( $suspend_until )->output("iso"); | ||||
1128 | $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $suspend_until, $biblio, $borrower ) ); | ||||
1129 | } else { | ||||
1130 | $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $biblio, $borrower ) ); | ||||
1131 | } | ||||
1132 | } | ||||
1133 | |||||
1134 | _FixPriority( $biblio, $borrower, $rank); | ||||
1135 | } | ||||
1136 | } | ||||
1137 | |||||
1138 | =head2 ModReserveFill | ||||
1139 | |||||
- - | |||||
1150 | sub ModReserveFill { | ||||
1151 | my ($res) = @_; | ||||
1152 | my $dbh = C4::Context->dbh; | ||||
1153 | # fill in a reserve record.... | ||||
1154 | my $biblionumber = $res->{'biblionumber'}; | ||||
1155 | my $borrowernumber = $res->{'borrowernumber'}; | ||||
1156 | my $resdate = $res->{'reservedate'}; | ||||
1157 | |||||
1158 | # get the priority on this record.... | ||||
1159 | my $priority; | ||||
1160 | my $query = "SELECT priority | ||||
1161 | FROM reserves | ||||
1162 | WHERE biblionumber = ? | ||||
1163 | AND borrowernumber = ? | ||||
1164 | AND reservedate = ?"; | ||||
1165 | my $sth = $dbh->prepare($query); | ||||
1166 | $sth->execute( $biblionumber, $borrowernumber, $resdate ); | ||||
1167 | ($priority) = $sth->fetchrow_array; | ||||
1168 | $sth->finish; | ||||
1169 | |||||
1170 | # update the database... | ||||
1171 | $query = "UPDATE reserves | ||||
1172 | SET found = 'F', | ||||
1173 | priority = 0 | ||||
1174 | WHERE biblionumber = ? | ||||
1175 | AND reservedate = ? | ||||
1176 | AND borrowernumber = ? | ||||
1177 | "; | ||||
1178 | $sth = $dbh->prepare($query); | ||||
1179 | $sth->execute( $biblionumber, $resdate, $borrowernumber ); | ||||
1180 | $sth->finish; | ||||
1181 | |||||
1182 | # move to old_reserves | ||||
1183 | $query = "INSERT INTO old_reserves | ||||
1184 | SELECT * FROM reserves | ||||
1185 | WHERE biblionumber = ? | ||||
1186 | AND reservedate = ? | ||||
1187 | AND borrowernumber = ? | ||||
1188 | "; | ||||
1189 | $sth = $dbh->prepare($query); | ||||
1190 | $sth->execute( $biblionumber, $resdate, $borrowernumber ); | ||||
1191 | $query = "DELETE FROM reserves | ||||
1192 | WHERE biblionumber = ? | ||||
1193 | AND reservedate = ? | ||||
1194 | AND borrowernumber = ? | ||||
1195 | "; | ||||
1196 | $sth = $dbh->prepare($query); | ||||
1197 | $sth->execute( $biblionumber, $resdate, $borrowernumber ); | ||||
1198 | |||||
1199 | # now fix the priority on the others (if the priority wasn't | ||||
1200 | # already sorted!).... | ||||
1201 | unless ( $priority == 0 ) { | ||||
1202 | _FixPriority( $biblionumber, $borrowernumber ); | ||||
1203 | } | ||||
1204 | } | ||||
1205 | |||||
1206 | =head2 ModReserveStatus | ||||
1207 | |||||
- - | |||||
1218 | sub ModReserveStatus { | ||||
1219 | |||||
1220 | #first : check if we have a reservation for this item . | ||||
1221 | my ($itemnumber, $newstatus) = @_; | ||||
1222 | my $dbh = C4::Context->dbh; | ||||
1223 | |||||
1224 | my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0"; | ||||
1225 | my $sth_set = $dbh->prepare($query); | ||||
1226 | $sth_set->execute( $newstatus, $itemnumber ); | ||||
1227 | |||||
1228 | if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) { | ||||
1229 | CartToShelf( $itemnumber ); | ||||
1230 | } | ||||
1231 | } | ||||
1232 | |||||
1233 | =head2 ModReserveAffect | ||||
1234 | |||||
- - | |||||
1248 | sub ModReserveAffect { | ||||
1249 | my ( $itemnumber, $borrowernumber,$transferToDo ) = @_; | ||||
1250 | my $dbh = C4::Context->dbh; | ||||
1251 | |||||
1252 | # we want to attach $itemnumber to $borrowernumber, find the biblionumber | ||||
1253 | # attached to $itemnumber | ||||
1254 | my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?"); | ||||
1255 | $sth->execute($itemnumber); | ||||
1256 | my ($biblionumber) = $sth->fetchrow; | ||||
1257 | |||||
1258 | # get request - need to find out if item is already | ||||
1259 | # waiting in order to not send duplicate hold filled notifications | ||||
1260 | my $request = GetReserveInfo($borrowernumber, $biblionumber); | ||||
1261 | my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0; | ||||
1262 | |||||
1263 | # If we affect a reserve that has to be transfered, don't set to Waiting | ||||
1264 | my $query; | ||||
1265 | if ($transferToDo) { | ||||
1266 | $query = " | ||||
1267 | UPDATE reserves | ||||
1268 | SET priority = 0, | ||||
1269 | itemnumber = ?, | ||||
1270 | found = 'T' | ||||
1271 | WHERE borrowernumber = ? | ||||
1272 | AND biblionumber = ? | ||||
1273 | "; | ||||
1274 | } | ||||
1275 | else { | ||||
1276 | # affect the reserve to Waiting as well. | ||||
1277 | $query = " | ||||
1278 | UPDATE reserves | ||||
1279 | SET priority = 0, | ||||
1280 | found = 'W', | ||||
1281 | waitingdate = NOW(), | ||||
1282 | itemnumber = ? | ||||
1283 | WHERE borrowernumber = ? | ||||
1284 | AND biblionumber = ? | ||||
1285 | "; | ||||
1286 | } | ||||
1287 | $sth = $dbh->prepare($query); | ||||
1288 | $sth->execute( $itemnumber, $borrowernumber,$biblionumber); | ||||
1289 | _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf ); | ||||
1290 | |||||
1291 | if ( C4::Context->preference("ReturnToShelvingCart") ) { | ||||
1292 | CartToShelf( $itemnumber ); | ||||
1293 | } | ||||
1294 | |||||
1295 | return; | ||||
1296 | } | ||||
1297 | |||||
1298 | =head2 ModReserveCancelAll | ||||
1299 | |||||
- - | |||||
1306 | sub ModReserveCancelAll { | ||||
1307 | my $messages; | ||||
1308 | my $nextreservinfo; | ||||
1309 | my ( $itemnumber, $borrowernumber ) = @_; | ||||
1310 | |||||
1311 | #step 1 : cancel the reservation | ||||
1312 | my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber ); | ||||
1313 | |||||
1314 | #step 2 launch the subroutine of the others reserves | ||||
1315 | ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber); | ||||
1316 | |||||
1317 | return ( $messages, $nextreservinfo ); | ||||
1318 | } | ||||
1319 | |||||
1320 | =head2 ModReserveMinusPriority | ||||
1321 | |||||
- - | |||||
1328 | sub ModReserveMinusPriority { | ||||
1329 | my ( $itemnumber, $borrowernumber, $biblionumber ) = @_; | ||||
1330 | |||||
1331 | #first step update the value of the first person on reserv | ||||
1332 | my $dbh = C4::Context->dbh; | ||||
1333 | my $query = " | ||||
1334 | UPDATE reserves | ||||
1335 | SET priority = 0 , itemnumber = ? | ||||
1336 | WHERE borrowernumber=? | ||||
1337 | AND biblionumber=? | ||||
1338 | "; | ||||
1339 | my $sth_upd = $dbh->prepare($query); | ||||
1340 | $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber ); | ||||
1341 | # second step update all others reservs | ||||
1342 | _FixPriority($biblionumber, $borrowernumber, '0'); | ||||
1343 | } | ||||
1344 | |||||
1345 | =head2 GetReserveInfo | ||||
1346 | |||||
- - | |||||
1354 | sub GetReserveInfo { | ||||
1355 | my ( $borrowernumber, $biblionumber ) = @_; | ||||
1356 | my $dbh = C4::Context->dbh; | ||||
1357 | my $strsth="SELECT | ||||
1358 | reservedate, | ||||
1359 | reservenotes, | ||||
1360 | reserves.borrowernumber, | ||||
1361 | reserves.biblionumber, | ||||
1362 | reserves.branchcode, | ||||
1363 | reserves.waitingdate, | ||||
1364 | notificationdate, | ||||
1365 | reminderdate, | ||||
1366 | priority, | ||||
1367 | found, | ||||
1368 | firstname, | ||||
1369 | surname, | ||||
1370 | phone, | ||||
1371 | email, | ||||
1372 | address, | ||||
1373 | address2, | ||||
1374 | cardnumber, | ||||
1375 | city, | ||||
1376 | zipcode, | ||||
1377 | biblio.title, | ||||
1378 | biblio.author, | ||||
1379 | items.holdingbranch, | ||||
1380 | items.itemcallnumber, | ||||
1381 | items.itemnumber, | ||||
1382 | items.location, | ||||
1383 | barcode, | ||||
1384 | notes | ||||
1385 | FROM reserves | ||||
1386 | LEFT JOIN items USING(itemnumber) | ||||
1387 | LEFT JOIN borrowers USING(borrowernumber) | ||||
1388 | LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber) | ||||
1389 | WHERE | ||||
1390 | reserves.borrowernumber=? | ||||
1391 | AND reserves.biblionumber=?"; | ||||
1392 | my $sth = $dbh->prepare($strsth); | ||||
1393 | $sth->execute($borrowernumber,$biblionumber); | ||||
1394 | |||||
1395 | my $data = $sth->fetchrow_hashref; | ||||
1396 | return $data; | ||||
1397 | |||||
1398 | } | ||||
1399 | |||||
1400 | =head2 IsAvailableForItemLevelRequest | ||||
1401 | |||||
- - | |||||
1427 | sub IsAvailableForItemLevelRequest { | ||||
1428 | my $itemnumber = shift; | ||||
1429 | |||||
1430 | my $item = GetItem($itemnumber); | ||||
1431 | |||||
1432 | # must check the notforloan setting of the itemtype | ||||
1433 | # FIXME - a lot of places in the code do this | ||||
1434 | # or something similar - need to be | ||||
1435 | # consolidated | ||||
1436 | my $dbh = C4::Context->dbh; | ||||
1437 | my $notforloan_query; | ||||
1438 | if (C4::Context->preference('item-level_itypes')) { | ||||
1439 | $notforloan_query = "SELECT itemtypes.notforloan | ||||
1440 | FROM items | ||||
1441 | JOIN itemtypes ON (itemtypes.itemtype = items.itype) | ||||
1442 | WHERE itemnumber = ?"; | ||||
1443 | } else { | ||||
1444 | $notforloan_query = "SELECT itemtypes.notforloan | ||||
1445 | FROM items | ||||
1446 | JOIN biblioitems USING (biblioitemnumber) | ||||
1447 | JOIN itemtypes USING (itemtype) | ||||
1448 | WHERE itemnumber = ?"; | ||||
1449 | } | ||||
1450 | my $sth = $dbh->prepare($notforloan_query); | ||||
1451 | $sth->execute($itemnumber); | ||||
1452 | my $notforloan_per_itemtype = 0; | ||||
1453 | if (my ($notforloan) = $sth->fetchrow_array) { | ||||
1454 | $notforloan_per_itemtype = 1 if $notforloan; | ||||
1455 | } | ||||
1456 | |||||
1457 | my $available_per_item = 1; | ||||
1458 | $available_per_item = 0 if $item->{itemlost} or | ||||
1459 | ( $item->{notforloan} > 0 ) or | ||||
1460 | ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or | ||||
1461 | $item->{wthdrawn} or | ||||
1462 | $notforloan_per_itemtype; | ||||
1463 | |||||
1464 | |||||
1465 | if (C4::Context->preference('AllowOnShelfHolds')) { | ||||
1466 | return $available_per_item; | ||||
1467 | } else { | ||||
1468 | return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "Waiting")); | ||||
1469 | } | ||||
1470 | } | ||||
1471 | |||||
1472 | =head2 AlterPriority | ||||
1473 | |||||
- - | |||||
1481 | sub AlterPriority { | ||||
1482 | my ( $where, $borrowernumber, $biblionumber ) = @_; | ||||
1483 | |||||
1484 | my $dbh = C4::Context->dbh; | ||||
1485 | |||||
1486 | ## Find this reserve | ||||
1487 | my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL'); | ||||
1488 | $sth->execute( $biblionumber, $borrowernumber ); | ||||
1489 | my $reserve = $sth->fetchrow_hashref(); | ||||
1490 | $sth->finish(); | ||||
1491 | |||||
1492 | if ( $where eq 'up' || $where eq 'down' ) { | ||||
1493 | |||||
1494 | my $priority = $reserve->{'priority'}; | ||||
1495 | $priority = $where eq 'up' ? $priority - 1 : $priority + 1; | ||||
1496 | _FixPriority( $biblionumber, $borrowernumber, $priority ) | ||||
1497 | |||||
1498 | } elsif ( $where eq 'top' ) { | ||||
1499 | |||||
1500 | _FixPriority( $biblionumber, $borrowernumber, '1' ) | ||||
1501 | |||||
1502 | } elsif ( $where eq 'bottom' ) { | ||||
1503 | |||||
1504 | _FixPriority( $biblionumber, $borrowernumber, '999999' ) | ||||
1505 | |||||
1506 | } | ||||
1507 | } | ||||
1508 | |||||
1509 | =head2 ToggleLowestPriority | ||||
1510 | |||||
- - | |||||
1517 | sub ToggleLowestPriority { | ||||
1518 | my ( $borrowernumber, $biblionumber ) = @_; | ||||
1519 | |||||
1520 | my $dbh = C4::Context->dbh; | ||||
1521 | |||||
1522 | my $sth = $dbh->prepare( | ||||
1523 | "UPDATE reserves SET lowestPriority = NOT lowestPriority | ||||
1524 | WHERE biblionumber = ? | ||||
1525 | AND borrowernumber = ?" | ||||
1526 | ); | ||||
1527 | $sth->execute( | ||||
1528 | $biblionumber, | ||||
1529 | $borrowernumber, | ||||
1530 | ); | ||||
1531 | $sth->finish; | ||||
1532 | |||||
1533 | _FixPriority( $biblionumber, $borrowernumber, '999999' ); | ||||
1534 | } | ||||
1535 | |||||
1536 | =head2 ToggleSuspend | ||||
1537 | |||||
- - | |||||
1546 | sub ToggleSuspend { | ||||
1547 | my ( $borrowernumber, $biblionumber, $suspend_until ) = @_; | ||||
1548 | |||||
1549 | $suspend_until = output_pref( dt_from_string( $suspend_until ), 'iso' ) if ( $suspend_until ); | ||||
1550 | |||||
1551 | my $do_until = ( $suspend_until ) ? '?' : 'NULL'; | ||||
1552 | |||||
1553 | my $dbh = C4::Context->dbh; | ||||
1554 | |||||
1555 | my $sth = $dbh->prepare( | ||||
1556 | "UPDATE reserves SET suspend = NOT suspend, | ||||
1557 | suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE $do_until END | ||||
1558 | WHERE biblionumber = ? | ||||
1559 | AND borrowernumber = ? | ||||
1560 | "); | ||||
1561 | |||||
1562 | my @params; | ||||
1563 | push( @params, $suspend_until ) if ( $suspend_until ); | ||||
1564 | push( @params, $biblionumber ); | ||||
1565 | push( @params, $borrowernumber ); | ||||
1566 | |||||
1567 | $sth->execute( @params ); | ||||
1568 | $sth->finish; | ||||
1569 | } | ||||
1570 | |||||
1571 | =head2 SuspendAll | ||||
1572 | |||||
- - | |||||
1587 | sub SuspendAll { | ||||
1588 | my %params = @_; | ||||
1589 | |||||
1590 | my $borrowernumber = $params{'borrowernumber'} || undef; | ||||
1591 | my $biblionumber = $params{'biblionumber'} || undef; | ||||
1592 | my $suspend_until = $params{'suspend_until'} || undef; | ||||
1593 | my $suspend = defined( $params{'suspend'} ) ? $params{'suspend'} : 1; | ||||
1594 | |||||
1595 | $suspend_until = C4::Dates->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) ); | ||||
1596 | |||||
1597 | return unless ( $borrowernumber || $biblionumber ); | ||||
1598 | |||||
1599 | my ( $query, $sth, $dbh, @query_params ); | ||||
1600 | |||||
1601 | $query = "UPDATE reserves SET suspend = ? "; | ||||
1602 | push( @query_params, $suspend ); | ||||
1603 | if ( !$suspend ) { | ||||
1604 | $query .= ", suspend_until = NULL "; | ||||
1605 | } elsif ( $suspend_until ) { | ||||
1606 | $query .= ", suspend_until = ? "; | ||||
1607 | push( @query_params, $suspend_until ); | ||||
1608 | } | ||||
1609 | $query .= " WHERE "; | ||||
1610 | if ( $borrowernumber ) { | ||||
1611 | $query .= " borrowernumber = ? "; | ||||
1612 | push( @query_params, $borrowernumber ); | ||||
1613 | } | ||||
1614 | $query .= " AND " if ( $borrowernumber && $biblionumber ); | ||||
1615 | if ( $biblionumber ) { | ||||
1616 | $query .= " biblionumber = ? "; | ||||
1617 | push( @query_params, $biblionumber ); | ||||
1618 | } | ||||
1619 | $query .= " AND found IS NULL "; | ||||
1620 | |||||
1621 | $dbh = C4::Context->dbh; | ||||
1622 | $sth = $dbh->prepare( $query ); | ||||
1623 | $sth->execute( @query_params ); | ||||
1624 | $sth->finish; | ||||
1625 | } | ||||
1626 | |||||
1627 | |||||
1628 | =head2 _FixPriority | ||||
1629 | |||||
- - | |||||
1641 | sub _FixPriority { | ||||
1642 | my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_; | ||||
1643 | my $dbh = C4::Context->dbh; | ||||
1644 | if ( $rank eq "del" ) { | ||||
1645 | CancelReserve( $biblio, undef, $borrowernumber ); | ||||
1646 | } | ||||
1647 | if ( $rank eq "W" || $rank eq "0" ) { | ||||
1648 | |||||
1649 | # make sure priority for waiting or in-transit items is 0 | ||||
1650 | my $query = qq/ | ||||
1651 | UPDATE reserves | ||||
1652 | SET priority = 0 | ||||
1653 | WHERE biblionumber = ? | ||||
1654 | AND borrowernumber = ? | ||||
1655 | AND found IN ('W', 'T') | ||||
1656 | /; | ||||
1657 | my $sth = $dbh->prepare($query); | ||||
1658 | $sth->execute( $biblio, $borrowernumber ); | ||||
1659 | } | ||||
1660 | my @priority; | ||||
1661 | my @reservedates; | ||||
1662 | |||||
1663 | # get whats left | ||||
1664 | # FIXME adding a new security in returned elements for changing priority, | ||||
1665 | # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve) | ||||
1666 | # This is wrong a waiting reserve has W set | ||||
1667 | # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs | ||||
1668 | my $query = qq/ | ||||
1669 | SELECT borrowernumber, reservedate, constrainttype | ||||
1670 | FROM reserves | ||||
1671 | WHERE biblionumber = ? | ||||
1672 | AND ((found <> 'W' AND found <> 'T') or found is NULL) | ||||
1673 | ORDER BY priority ASC | ||||
1674 | /; | ||||
1675 | my $sth = $dbh->prepare($query); | ||||
1676 | $sth->execute($biblio); | ||||
1677 | while ( my $line = $sth->fetchrow_hashref ) { | ||||
1678 | push( @reservedates, $line ); | ||||
1679 | push( @priority, $line ); | ||||
1680 | } | ||||
1681 | |||||
1682 | # To find the matching index | ||||
1683 | my $i; | ||||
1684 | my $key = -1; # to allow for 0 to be a valid result | ||||
1685 | for ( $i = 0 ; $i < @priority ; $i++ ) { | ||||
1686 | if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) { | ||||
1687 | $key = $i; # save the index | ||||
1688 | last; | ||||
1689 | } | ||||
1690 | } | ||||
1691 | |||||
1692 | # if index exists in array then move it to new position | ||||
1693 | if ( $key > -1 && $rank ne 'del' && $rank > 0 ) { | ||||
1694 | my $new_rank = $rank - | ||||
1695 | 1; # $new_rank is what you want the new index to be in the array | ||||
1696 | my $moving_item = splice( @priority, $key, 1 ); | ||||
1697 | splice( @priority, $new_rank, 0, $moving_item ); | ||||
1698 | } | ||||
1699 | |||||
1700 | # now fix the priority on those that are left.... | ||||
1701 | $query = " | ||||
1702 | UPDATE reserves | ||||
1703 | SET priority = ? | ||||
1704 | WHERE biblionumber = ? | ||||
1705 | AND borrowernumber = ? | ||||
1706 | AND reservedate = ? | ||||
1707 | AND found IS NULL | ||||
1708 | "; | ||||
1709 | $sth = $dbh->prepare($query); | ||||
1710 | for ( my $j = 0 ; $j < @priority ; $j++ ) { | ||||
1711 | $sth->execute( | ||||
1712 | $j + 1, $biblio, | ||||
1713 | $priority[$j]->{'borrowernumber'}, | ||||
1714 | $priority[$j]->{'reservedate'} | ||||
1715 | ); | ||||
1716 | $sth->finish; | ||||
1717 | } | ||||
1718 | |||||
1719 | $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" ); | ||||
1720 | $sth->execute(); | ||||
1721 | |||||
1722 | unless ( $ignoreSetLowestRank ) { | ||||
1723 | while ( my $res = $sth->fetchrow_hashref() ) { | ||||
1724 | _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 ); | ||||
1725 | } | ||||
1726 | } | ||||
1727 | } | ||||
1728 | |||||
1729 | =head2 _Findgroupreserve | ||||
1730 | |||||
- - | |||||
1746 | sub _Findgroupreserve { | ||||
1747 | my ( $bibitem, $biblio, $itemnumber ) = @_; | ||||
1748 | my $dbh = C4::Context->dbh; | ||||
1749 | |||||
1750 | # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var. | ||||
1751 | # check for exact targetted match | ||||
1752 | my $item_level_target_query = qq/ | ||||
1753 | SELECT reserves.biblionumber AS biblionumber, | ||||
1754 | reserves.borrowernumber AS borrowernumber, | ||||
1755 | reserves.reservedate AS reservedate, | ||||
1756 | reserves.branchcode AS branchcode, | ||||
1757 | reserves.cancellationdate AS cancellationdate, | ||||
1758 | reserves.found AS found, | ||||
1759 | reserves.reservenotes AS reservenotes, | ||||
1760 | reserves.priority AS priority, | ||||
1761 | reserves.timestamp AS timestamp, | ||||
1762 | biblioitems.biblioitemnumber AS biblioitemnumber, | ||||
1763 | reserves.itemnumber AS itemnumber | ||||
1764 | FROM reserves | ||||
1765 | JOIN biblioitems USING (biblionumber) | ||||
1766 | JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber) | ||||
1767 | WHERE found IS NULL | ||||
1768 | AND priority > 0 | ||||
1769 | AND item_level_request = 1 | ||||
1770 | AND itemnumber = ? | ||||
1771 | AND reservedate <= CURRENT_DATE() | ||||
1772 | AND suspend = 0 | ||||
1773 | /; | ||||
1774 | my $sth = $dbh->prepare($item_level_target_query); | ||||
1775 | $sth->execute($itemnumber); | ||||
1776 | my @results; | ||||
1777 | if ( my $data = $sth->fetchrow_hashref ) { | ||||
1778 | push( @results, $data ); | ||||
1779 | } | ||||
1780 | return @results if @results; | ||||
1781 | |||||
1782 | # check for title-level targetted match | ||||
1783 | my $title_level_target_query = qq/ | ||||
1784 | SELECT reserves.biblionumber AS biblionumber, | ||||
1785 | reserves.borrowernumber AS borrowernumber, | ||||
1786 | reserves.reservedate AS reservedate, | ||||
1787 | reserves.branchcode AS branchcode, | ||||
1788 | reserves.cancellationdate AS cancellationdate, | ||||
1789 | reserves.found AS found, | ||||
1790 | reserves.reservenotes AS reservenotes, | ||||
1791 | reserves.priority AS priority, | ||||
1792 | reserves.timestamp AS timestamp, | ||||
1793 | biblioitems.biblioitemnumber AS biblioitemnumber, | ||||
1794 | reserves.itemnumber AS itemnumber | ||||
1795 | FROM reserves | ||||
1796 | JOIN biblioitems USING (biblionumber) | ||||
1797 | JOIN hold_fill_targets USING (biblionumber, borrowernumber) | ||||
1798 | WHERE found IS NULL | ||||
1799 | AND priority > 0 | ||||
1800 | AND item_level_request = 0 | ||||
1801 | AND hold_fill_targets.itemnumber = ? | ||||
1802 | AND reservedate <= CURRENT_DATE() | ||||
1803 | AND suspend = 0 | ||||
1804 | /; | ||||
1805 | $sth = $dbh->prepare($title_level_target_query); | ||||
1806 | $sth->execute($itemnumber); | ||||
1807 | @results = (); | ||||
1808 | if ( my $data = $sth->fetchrow_hashref ) { | ||||
1809 | push( @results, $data ); | ||||
1810 | } | ||||
1811 | return @results if @results; | ||||
1812 | |||||
1813 | my $query = qq/ | ||||
1814 | SELECT reserves.biblionumber AS biblionumber, | ||||
1815 | reserves.borrowernumber AS borrowernumber, | ||||
1816 | reserves.reservedate AS reservedate, | ||||
1817 | reserves.waitingdate AS waitingdate, | ||||
1818 | reserves.branchcode AS branchcode, | ||||
1819 | reserves.cancellationdate AS cancellationdate, | ||||
1820 | reserves.found AS found, | ||||
1821 | reserves.reservenotes AS reservenotes, | ||||
1822 | reserves.priority AS priority, | ||||
1823 | reserves.timestamp AS timestamp, | ||||
1824 | reserveconstraints.biblioitemnumber AS biblioitemnumber, | ||||
1825 | reserves.itemnumber AS itemnumber | ||||
1826 | FROM reserves | ||||
1827 | LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber | ||||
1828 | WHERE reserves.biblionumber = ? | ||||
1829 | AND ( ( reserveconstraints.biblioitemnumber = ? | ||||
1830 | AND reserves.borrowernumber = reserveconstraints.borrowernumber | ||||
1831 | AND reserves.reservedate = reserveconstraints.reservedate ) | ||||
1832 | OR reserves.constrainttype='a' ) | ||||
1833 | AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?) | ||||
1834 | AND reserves.reservedate <= CURRENT_DATE() | ||||
1835 | AND suspend = 0 | ||||
1836 | /; | ||||
1837 | $sth = $dbh->prepare($query); | ||||
1838 | $sth->execute( $biblio, $bibitem, $itemnumber ); | ||||
1839 | @results = (); | ||||
1840 | while ( my $data = $sth->fetchrow_hashref ) { | ||||
1841 | push( @results, $data ); | ||||
1842 | } | ||||
1843 | return @results; | ||||
1844 | } | ||||
1845 | |||||
1846 | =head2 _koha_notify_reserve | ||||
1847 | |||||
- - | |||||
1855 | sub _koha_notify_reserve { | ||||
1856 | my ($itemnumber, $borrowernumber, $biblionumber) = @_; | ||||
1857 | |||||
1858 | my $dbh = C4::Context->dbh; | ||||
1859 | my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber); | ||||
1860 | |||||
1861 | # Try to get the borrower's email address | ||||
1862 | my $to_address = C4::Members::GetNoticeEmailAddress($borrowernumber); | ||||
1863 | |||||
1864 | my $letter_code; | ||||
1865 | my $print_mode = 0; | ||||
1866 | my $messagingprefs; | ||||
1867 | if ( $to_address || $borrower->{'smsalertnumber'} ) { | ||||
1868 | $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } ); | ||||
1869 | } else { | ||||
1870 | $print_mode = 1; | ||||
1871 | } | ||||
1872 | |||||
1873 | my $sth = $dbh->prepare(" | ||||
1874 | SELECT * | ||||
1875 | FROM reserves | ||||
1876 | WHERE borrowernumber = ? | ||||
1877 | AND biblionumber = ? | ||||
1878 | "); | ||||
1879 | $sth->execute( $borrowernumber, $biblionumber ); | ||||
1880 | my $reserve = $sth->fetchrow_hashref; | ||||
1881 | my $branch_details = GetBranchDetail( $reserve->{'branchcode'} ); | ||||
1882 | |||||
1883 | my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress'); | ||||
1884 | |||||
1885 | my %letter_params = ( | ||||
1886 | module => 'reserves', | ||||
1887 | branchcode => $reserve->{branchcode}, | ||||
1888 | tables => { | ||||
1889 | 'branches' => $branch_details, | ||||
1890 | 'borrowers' => $borrower, | ||||
1891 | 'biblio' => $biblionumber, | ||||
1892 | 'reserves' => $reserve, | ||||
1893 | 'items', $reserve->{'itemnumber'}, | ||||
1894 | }, | ||||
1895 | substitute => { today => C4::Dates->new()->output() }, | ||||
1896 | ); | ||||
1897 | |||||
1898 | |||||
1899 | if ( $print_mode ) { | ||||
1900 | $letter_params{ 'letter_code' } = 'HOLD_PRINT'; | ||||
1901 | my $letter = C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module"; | ||||
1902 | |||||
1903 | C4::Letters::EnqueueLetter( { | ||||
1904 | letter => $letter, | ||||
1905 | borrowernumber => $borrowernumber, | ||||
1906 | message_transport_type => 'print', | ||||
1907 | } ); | ||||
1908 | |||||
1909 | return; | ||||
1910 | } | ||||
1911 | |||||
1912 | if ( $to_address && defined $messagingprefs->{transports}->{'email'} ) { | ||||
1913 | $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'email'}; | ||||
1914 | my $letter = C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module"; | ||||
1915 | |||||
1916 | C4::Letters::EnqueueLetter( | ||||
1917 | { letter => $letter, | ||||
1918 | borrowernumber => $borrowernumber, | ||||
1919 | message_transport_type => 'email', | ||||
1920 | from_address => $admin_email_address, | ||||
1921 | } | ||||
1922 | ); | ||||
1923 | } | ||||
1924 | |||||
1925 | if ( $borrower->{'smsalertnumber'} && defined $messagingprefs->{transports}->{'sms'} ) { | ||||
1926 | $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'sms'}; | ||||
1927 | my $letter = C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module"; | ||||
1928 | |||||
1929 | C4::Letters::EnqueueLetter( | ||||
1930 | { letter => $letter, | ||||
1931 | borrowernumber => $borrowernumber, | ||||
1932 | message_transport_type => 'sms', | ||||
1933 | } | ||||
1934 | ); | ||||
1935 | } | ||||
1936 | } | ||||
1937 | |||||
1938 | =head2 _ShiftPriorityByDateAndPriority | ||||
1939 | |||||
- - | |||||
1956 | sub _ShiftPriorityByDateAndPriority { | ||||
1957 | my ( $biblio, $resdate, $new_priority ) = @_; | ||||
1958 | |||||
1959 | my $dbh = C4::Context->dbh; | ||||
1960 | my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1"; | ||||
1961 | my $sth = $dbh->prepare( $query ); | ||||
1962 | $sth->execute( $biblio, $resdate, $new_priority ); | ||||
1963 | my $min_priority = $sth->fetchrow; | ||||
1964 | # if no such matches are found, $new_priority remains as original value | ||||
1965 | $new_priority = $min_priority if ( $min_priority ); | ||||
1966 | |||||
1967 | # Shift the priority up by one; works in conjunction with the next SQL statement | ||||
1968 | $query = "UPDATE reserves | ||||
1969 | SET priority = priority+1 | ||||
1970 | WHERE biblionumber = ? | ||||
1971 | AND borrowernumber = ? | ||||
1972 | AND reservedate = ? | ||||
1973 | AND found IS NULL"; | ||||
1974 | my $sth_update = $dbh->prepare( $query ); | ||||
1975 | |||||
1976 | # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least | ||||
1977 | $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC"; | ||||
1978 | $sth = $dbh->prepare( $query ); | ||||
1979 | $sth->execute( $new_priority, $biblio ); | ||||
1980 | while ( my $row = $sth->fetchrow_hashref ) { | ||||
1981 | $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} ); | ||||
1982 | } | ||||
1983 | |||||
1984 | return $new_priority; # so the caller knows what priority they wind up receiving | ||||
1985 | } | ||||
1986 | |||||
1987 | =head2 MoveReserve | ||||
1988 | |||||
- - | |||||
1996 | sub MoveReserve { | ||||
1997 | my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_; | ||||
1998 | |||||
1999 | my ( $restype, $res, $all_reserves ) = CheckReserves( $itemnumber ); | ||||
2000 | return unless $res; | ||||
2001 | |||||
2002 | my $biblionumber = $res->{biblionumber}; | ||||
2003 | my $biblioitemnumber = $res->{biblioitemnumber}; | ||||
2004 | |||||
2005 | if ($res->{borrowernumber} == $borrowernumber) { | ||||
2006 | ModReserveFill($res); | ||||
2007 | } | ||||
2008 | else { | ||||
2009 | # warn "Reserved"; | ||||
2010 | # The item is reserved by someone else. | ||||
2011 | # Find this item in the reserves | ||||
2012 | |||||
2013 | my $borr_res; | ||||
2014 | foreach (@$all_reserves) { | ||||
2015 | $_->{'borrowernumber'} == $borrowernumber or next; | ||||
2016 | $_->{'biblionumber'} == $biblionumber or next; | ||||
2017 | |||||
2018 | $borr_res = $_; | ||||
2019 | last; | ||||
2020 | } | ||||
2021 | |||||
2022 | if ( $borr_res ) { | ||||
2023 | # The item is reserved by the current patron | ||||
2024 | ModReserveFill($borr_res); | ||||
2025 | } | ||||
2026 | |||||
2027 | if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1 | ||||
2028 | RevertWaitingStatus({ itemnumber => $itemnumber }); | ||||
2029 | } | ||||
2030 | elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item | ||||
2031 | CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'}); | ||||
2032 | CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'}); | ||||
2033 | } | ||||
2034 | } | ||||
2035 | } | ||||
2036 | |||||
2037 | =head2 MergeHolds | ||||
2038 | |||||
- - | |||||
2045 | sub MergeHolds { | ||||
2046 | my ( $dbh, $to_biblio, $from_biblio ) = @_; | ||||
2047 | my $sth = $dbh->prepare( | ||||
2048 | "SELECT count(*) as reservenumber FROM reserves WHERE biblionumber = ?" | ||||
2049 | ); | ||||
2050 | $sth->execute($from_biblio); | ||||
2051 | if ( my $data = $sth->fetchrow_hashref() ) { | ||||
2052 | |||||
2053 | # holds exist on old record, if not we don't need to do anything | ||||
2054 | $sth = $dbh->prepare( | ||||
2055 | "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?"); | ||||
2056 | $sth->execute( $to_biblio, $from_biblio ); | ||||
2057 | |||||
2058 | # Reorder by date | ||||
2059 | # don't reorder those already waiting | ||||
2060 | |||||
2061 | $sth = $dbh->prepare( | ||||
2062 | "SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC" | ||||
2063 | ); | ||||
2064 | my $upd_sth = $dbh->prepare( | ||||
2065 | "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ? | ||||
2066 | AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) " | ||||
2067 | ); | ||||
2068 | $sth->execute( $to_biblio, 'W', 'T' ); | ||||
2069 | my $priority = 1; | ||||
2070 | while ( my $reserve = $sth->fetchrow_hashref() ) { | ||||
2071 | $upd_sth->execute( | ||||
2072 | $priority, $to_biblio, | ||||
2073 | $reserve->{'borrowernumber'}, $reserve->{'reservedate'}, | ||||
2074 | $reserve->{'constrainttype'}, $reserve->{'itemnumber'} | ||||
2075 | ); | ||||
2076 | $priority++; | ||||
2077 | } | ||||
2078 | } | ||||
2079 | } | ||||
2080 | |||||
2081 | =head2 RevertWaitingStatus | ||||
2082 | |||||
- - | |||||
2095 | sub RevertWaitingStatus { | ||||
2096 | my ( $params ) = @_; | ||||
2097 | my $itemnumber = $params->{'itemnumber'}; | ||||
2098 | |||||
2099 | return unless ( $itemnumber ); | ||||
2100 | |||||
2101 | my $dbh = C4::Context->dbh; | ||||
2102 | |||||
2103 | ## Get the waiting reserve we want to revert | ||||
2104 | my $query = " | ||||
2105 | SELECT * FROM reserves | ||||
2106 | WHERE itemnumber = ? | ||||
2107 | AND found IS NOT NULL | ||||
2108 | "; | ||||
2109 | my $sth = $dbh->prepare( $query ); | ||||
2110 | $sth->execute( $itemnumber ); | ||||
2111 | my $reserve = $sth->fetchrow_hashref(); | ||||
2112 | |||||
2113 | ## Increment the priority of all other non-waiting | ||||
2114 | ## reserves for this bib record | ||||
2115 | $query = " | ||||
2116 | UPDATE reserves | ||||
2117 | SET | ||||
2118 | priority = priority + 1 | ||||
2119 | WHERE | ||||
2120 | biblionumber = ? | ||||
2121 | AND | ||||
2122 | priority > 0 | ||||
2123 | "; | ||||
2124 | $sth = $dbh->prepare( $query ); | ||||
2125 | $sth->execute( $reserve->{'biblionumber'} ); | ||||
2126 | |||||
2127 | ## Fix up the currently waiting reserve | ||||
2128 | $query = " | ||||
2129 | UPDATE reserves | ||||
2130 | SET | ||||
2131 | priority = 1, | ||||
2132 | found = NULL, | ||||
2133 | waitingdate = NULL | ||||
2134 | WHERE | ||||
2135 | reserve_id = ? | ||||
2136 | "; | ||||
2137 | $sth = $dbh->prepare( $query ); | ||||
2138 | return $sth->execute( $reserve->{'reserve_id'} ); | ||||
2139 | } | ||||
2140 | |||||
2141 | =head2 ReserveSlip | ||||
2142 | |||||
- - | |||||
2149 | sub ReserveSlip { | ||||
2150 | my ($branch, $borrowernumber, $biblionumber) = @_; | ||||
2151 | |||||
2152 | # return unless ( C4::Context->boolean_preference('printreserveslips') ); | ||||
2153 | |||||
2154 | my $reserve = GetReserveInfo($borrowernumber,$biblionumber ) | ||||
2155 | or return; | ||||
2156 | |||||
2157 | return C4::Letters::GetPreparedLetter ( | ||||
2158 | module => 'circulation', | ||||
2159 | letter_code => 'RESERVESLIP', | ||||
2160 | branchcode => $branch, | ||||
2161 | tables => { | ||||
2162 | 'reserves' => $reserve, | ||||
2163 | 'branches' => $reserve->{branchcode}, | ||||
2164 | 'borrowers' => $reserve->{borrowernumber}, | ||||
2165 | 'biblio' => $reserve->{biblionumber}, | ||||
2166 | 'items' => $reserve->{itemnumber}, | ||||
2167 | }, | ||||
2168 | ); | ||||
2169 | } | ||||
2170 | |||||
2171 | =head1 AUTHOR | ||||
2172 | |||||
- - | |||||
2177 | 1 | 4µs | 1; |