Filename | /mnt/catalyst/koha/C4/Reserves.pm |
Statements | Executed 36 statements in 18.7ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 13.3ms | 233ms | BEGIN@27 | C4::Reserves::
1 | 1 | 1 | 10.0ms | 28.7ms | BEGIN@29 | C4::Reserves::
1 | 1 | 1 | 5.26ms | 55.9ms | BEGIN@36 | C4::Reserves::
1 | 1 | 1 | 4.78ms | 5.63ms | BEGIN@31 | C4::Reserves::
1 | 1 | 1 | 3.63ms | 4.25ms | BEGIN@34 | C4::Reserves::
1 | 1 | 1 | 420µs | 432µs | BEGIN@24 | C4::Reserves::
1 | 1 | 1 | 12µs | 12µs | BEGIN@28 | C4::Reserves::
1 | 1 | 1 | 11µs | 11µs | BEGIN@89 | C4::Reserves::
1 | 1 | 1 | 11µs | 11µs | BEGIN@30 | C4::Reserves::
1 | 1 | 1 | 9µs | 30µs | BEGIN@37 | C4::Reserves::
1 | 1 | 1 | 9µs | 27µs | BEGIN@42 | C4::Reserves::
1 | 1 | 1 | 9µs | 26µs | BEGIN@38 | C4::Reserves::
1 | 1 | 1 | 8µs | 32µs | BEGIN@40 | C4::Reserves::
1 | 1 | 1 | 7µs | 9µs | BEGIN@26 | C4::Reserves::
1 | 1 | 1 | 6µs | 50µs | BEGIN@44 | C4::Reserves::
1 | 1 | 1 | 4µs | 4µ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 | CalculatePriority | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CanBookBeReserved | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CanItemBeReserved | C4::Reserves::
0 | 0 | 0 | 0s | 0s | CanReserveBeCanceledFromOpac | 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 | GetReserve | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveCount | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveFee | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveId | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveInfo | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReserveStatus | C4::Reserves::
0 | 0 | 0 | 0s | 0s | GetReservesControlBranch | 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 | 2 | 27µs | 2 | 443µ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 # 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 | ||||
26 | 2 | 19µs | 2 | 11µ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 # spent 9µs making 1 call to C4::Reserves::BEGIN@26
# spent 2µs making 1 call to C4::Context::import |
27 | 2 | 2.03ms | 2 | 234ms | # 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 # spent 233ms making 1 call to C4::Reserves::BEGIN@27
# spent 329µs making 1 call to Exporter::import |
28 | 2 | 28µs | 1 | 12µs | # spent 12µs within C4::Reserves::BEGIN@28 which was called:
# once (12µs+0s) by C4::Circulation::BEGIN@27 at line 28 # spent 12µs making 1 call to C4::Reserves::BEGIN@28 |
29 | 2 | 3.06ms | 2 | 28.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 # spent 28.7ms making 1 call to C4::Reserves::BEGIN@29
# spent 109µs making 1 call to Exporter::import |
30 | 2 | 27µs | 1 | 11µs | # spent 11µs within C4::Reserves::BEGIN@30 which was called:
# once (11µs+0s) by C4::Circulation::BEGIN@27 at line 30 # spent 11µs making 1 call to C4::Reserves::BEGIN@30 |
31 | 2 | 2.69ms | 2 | 5.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 # 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 | ||||
34 | 2 | 3.35ms | 1 | 4.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 # spent 4.25ms making 1 call to C4::Reserves::BEGIN@34 |
35 | 2 | 20µs | 1 | 4µs | # spent 4µs within C4::Reserves::BEGIN@35 which was called:
# once (4µs+0s) by C4::Circulation::BEGIN@27 at line 35 # spent 4µs making 1 call to C4::Reserves::BEGIN@35 |
36 | 2 | 1.95ms | 2 | 56.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 # spent 55.9ms making 1 call to C4::Reserves::BEGIN@36
# spent 114µs making 1 call to Exporter::import |
37 | 2 | 25µs | 2 | 50µ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 # spent 30µs making 1 call to C4::Reserves::BEGIN@37
# spent 20µs making 1 call to Exporter::import |
38 | 2 | 25µs | 2 | 44µ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 # spent 26µs making 1 call to C4::Reserves::BEGIN@38
# spent 18µs making 1 call to Exporter::import |
39 | |||||
40 | 2 | 22µs | 2 | 55µ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 # spent 32µs making 1 call to C4::Reserves::BEGIN@40
# spent 24µs making 1 call to Exporter::import |
41 | |||||
42 | 2 | 27µs | 2 | 44µ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 # spent 27µs making 1 call to C4::Reserves::BEGIN@42
# spent 18µs making 1 call to Exporter::import |
43 | |||||
44 | 2 | 91µs | 2 | 94µ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 # 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 | |||||
48 | C4::Reserves - Koha functions for dealing with reservation. | ||||
49 | |||||
50 | =head1 SYNOPSIS | ||||
51 | |||||
52 | use C4::Reserves; | ||||
53 | |||||
54 | =head1 DESCRIPTION | ||||
55 | |||||
56 | This 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 | ||||
90 | # set the version for version checking | ||||
91 | 1 | 900ns | $VERSION = 3.07.00.049; | ||
92 | 1 | 500ns | require Exporter; | ||
93 | 1 | 5µs | @ISA = qw(Exporter); | ||
94 | 1 | 2µ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 | ); | ||||
138 | 1 | 4µs | @EXPORT_OK = qw( MergeHolds ); | ||
139 | 1 | 5.31ms | 1 | 11µ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 | |||||
147 | sub 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 | |||||
260 | sub 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 | |||||
278 | This function gets the list of reservations for one C<$biblionumber>, | ||||
279 | returning an arrayref pointing to the reserves for C<$biblionumber>. | ||||
280 | |||||
281 | By default, only reserves whose start date falls before the current | ||||
282 | time are returned. To return all reserves, including future ones, | ||||
283 | the C<all_dates> parameter can be included and set to a true value. | ||||
284 | |||||
285 | If the C<itemnumber> parameter is supplied, reserves must be targeted | ||||
286 | to that item or not targeted to any item at all; otherwise, they | ||||
287 | are excluded from the list. | ||||
288 | |||||
289 | =cut | ||||
290 | |||||
291 | sub 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 | |||||
377 | Get the first reserve for a specific item number (based on priority). Returns the abovementioned values for that reserve. | ||||
378 | |||||
379 | The routine does not look at future reserves (read: item level holds), but DOES include future waits (a confirmed future hold). | ||||
380 | |||||
381 | =cut | ||||
382 | |||||
383 | sub 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 | |||||
403 | TODO :: Descritpion | ||||
404 | |||||
405 | =cut | ||||
406 | |||||
407 | sub 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 | |||||
439 | sub 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 | |||||
459 | This function return 1 if an item can be issued by this borrower. | ||||
460 | |||||
461 | =cut | ||||
462 | |||||
463 | sub 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 | |||||
571 | sub 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 | |||||
589 | this function returns the number of reservation for a borrower given on input arg. | ||||
590 | |||||
591 | =cut | ||||
592 | |||||
593 | sub 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 | |||||
613 | Check queued list of this document and check if this document must be transfered | ||||
614 | |||||
615 | =cut | ||||
616 | |||||
617 | sub 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 | |||||
661 | Calculate the fee for a reserve | ||||
662 | |||||
663 | =cut | ||||
664 | |||||
665 | sub 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 | |||||
762 | Get reserve list for a given branch | ||||
763 | |||||
764 | =cut | ||||
765 | |||||
766 | sub 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 | |||||
791 | sub 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 | |||||
824 | Take an itemnumber or a biblionumber and return the status of the reserve places on it. | ||||
825 | If 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. | ||||
833 | sub 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 | |||||
867 | Find a book in the reserves. | ||||
868 | |||||
869 | C<$itemnumber> is the book's item number. | ||||
870 | C<$lookahead> is the number of days to look in advance for future reserves. | ||||
871 | |||||
872 | As I understand it, C<&CheckReserves> looks for the given item in the | ||||
873 | reserves. If it is found, that's a match, and C<$status> is set to | ||||
874 | C<Waiting>. | ||||
875 | |||||
876 | Otherwise, it finds the most important item in the reserves with the | ||||
877 | same biblio number as this book (I'm not clear on this) and returns it | ||||
878 | with C<$status> set to C<Reserved>. | ||||
879 | |||||
880 | C<&CheckReserves> returns a two-element list: | ||||
881 | |||||
882 | C<$status> is either C<Waiting>, C<Reserved> (see above), or 0. | ||||
883 | |||||
884 | C<$reserve> is the reserve item that matched. It is a | ||||
885 | reference-to-hash whose keys are mostly the fields of the reserves | ||||
886 | table in the Koha database. | ||||
887 | |||||
888 | =cut | ||||
889 | |||||
890 | sub 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 | |||||
984 | Cancels all reserves with an expiration date from before today. | ||||
985 | |||||
986 | =cut | ||||
987 | |||||
988 | sub 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 | |||||
1027 | Unsuspends all suspended reserves with a suspend_until date from before today. | ||||
1028 | |||||
1029 | =cut | ||||
1030 | |||||
1031 | sub 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 | |||||
1045 | Cancels a reserve. | ||||
1046 | |||||
1047 | =cut | ||||
1048 | |||||
1049 | sub 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 | |||||
1099 | Change a hold request's priority or cancel it. | ||||
1100 | |||||
1101 | C<$rank> specifies the effect of the change. If C<$rank> | ||||
1102 | is 'W' or 'n', nothing happens. This corresponds to leaving a | ||||
1103 | request alone when changing its priority in the holds queue | ||||
1104 | for a bib. | ||||
1105 | |||||
1106 | If C<$rank> is 'del', the hold request is cancelled. | ||||
1107 | |||||
1108 | If C<$rank> is an integer greater than zero, the priority of | ||||
1109 | the request is set to that value. Since priority != 0 means | ||||
1110 | that the item is not waiting on the hold shelf, setting the | ||||
1111 | priority to a non-zero value also sets the request's found | ||||
1112 | status and waiting date to NULL. | ||||
1113 | |||||
1114 | The optional C<$itemnumber> parameter is used only when | ||||
1115 | C<$rank> is a non-zero integer; if supplied, the itemnumber | ||||
1116 | of the hold request is set accordingly; if omitted, the itemnumber | ||||
1117 | is cleared. | ||||
1118 | |||||
1119 | B<FIXME:> Note that the forgoing can have the effect of causing | ||||
1120 | item-level hold requests to turn into title-level requests. This | ||||
1121 | will be fixed once reserves has separate columns for requested | ||||
1122 | itemnumber and supplying itemnumber. | ||||
1123 | |||||
1124 | =cut | ||||
1125 | |||||
1126 | sub 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 | |||||
1172 | Fill a reserve. If I understand this correctly, this means that the | ||||
1173 | reserved book has been found and given to the patron who reserved it. | ||||
1174 | |||||
1175 | C<$reserve> specifies the reserve to fill. It is a reference-to-hash | ||||
1176 | whose keys are fields from the reserves table in the Koha database. | ||||
1177 | |||||
1178 | =cut | ||||
1179 | |||||
1180 | sub 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 | |||||
1239 | Update 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 | |||||
1247 | sub 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 | |||||
1266 | This function affect an item and a status for a given reserve | ||||
1267 | The itemnumber parameter is used to find the biblionumber. | ||||
1268 | with the biblionumber & the borrowernumber, we can affect the itemnumber | ||||
1269 | to the correct reserve. | ||||
1270 | |||||
1271 | if $transferToDo is not set, then the status is set to "Waiting" as well. | ||||
1272 | otherwise, a transfer is on the way, and the end of the transfer will | ||||
1273 | take care of the waiting status | ||||
1274 | |||||
1275 | =cut | ||||
1276 | |||||
1277 | sub 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 | |||||
1336 | function to cancel reserv,check other reserves, and transfer document if it's necessary | ||||
1337 | |||||
1338 | =cut | ||||
1339 | |||||
1340 | sub 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 | |||||
1358 | Reduce the values of queued list | ||||
1359 | |||||
1360 | =cut | ||||
1361 | |||||
1362 | sub 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 | |||||
1382 | Get item and borrower details for a current hold. | ||||
1383 | Current implementation this query should have a single result. | ||||
1384 | |||||
1385 | =cut | ||||
1386 | |||||
1387 | sub 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 | |||||
1435 | Checks whether a given item record is available for an | ||||
1436 | item-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 | |||||
1443 | Whether or not the item is currently on loan is | ||||
1444 | also checked - if the AllowOnShelfHolds system preference | ||||
1445 | is ON, an item can be requested even if it is currently | ||||
1446 | on loan to somebody else. If the system preference | ||||
1447 | is OFF, an item that is currently checked out cannot | ||||
1448 | be the target of an item-level hold request. | ||||
1449 | |||||
1450 | Note that IsAvailableForItemLevelRequest() does not | ||||
1451 | check if the staff operator is authorized to place | ||||
1452 | a request on the item - in particular, | ||||
1453 | this routine does not check IndependentBranches | ||||
1454 | and canreservefromotherbranches. | ||||
1455 | |||||
1456 | =cut | ||||
1457 | |||||
1458 | sub 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 | |||||
1507 | This function changes a reserve's priority up, down, to the top, or to the bottom. | ||||
1508 | Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed | ||||
1509 | |||||
1510 | =cut | ||||
1511 | |||||
1512 | sub 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 | |||||
1545 | This function sets the lowestPriority field to true if is false, and false if it is true. | ||||
1546 | |||||
1547 | =cut | ||||
1548 | |||||
1549 | sub 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 | |||||
1564 | This function sets the suspend field to true if is false, and false if it is true. | ||||
1565 | If the reserve is currently suspended with a suspend_until date, that date will | ||||
1566 | be cleared when it is unsuspended. | ||||
1567 | |||||
1568 | =cut | ||||
1569 | |||||
1570 | sub 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 | |||||
1608 | sub 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 | |||||
1660 | This routine adjusts the priority of a hold request and holds | ||||
1661 | on the same bib. | ||||
1662 | |||||
1663 | In the first form, where a reserve_id is passed, the priority of the | ||||
1664 | hold is set to supplied rank, and other holds for that bib are adjusted | ||||
1665 | accordingly. If the rank is "del", the hold is cancelled. If no rank | ||||
1666 | is supplied, all of the holds on that bib have their priority adjusted | ||||
1667 | as if the second form had been used. | ||||
1668 | |||||
1669 | In the second form, where a biblionumber is passed, the holds on that | ||||
1670 | bib (that are not captured) are sorted in order of increasing priority, | ||||
1671 | then have reserves.priority set so that the first non-captured hold | ||||
1672 | has its priority set to 1, the second non-captured hold has its priority | ||||
1673 | set to 2, and so forth. | ||||
1674 | |||||
1675 | In both cases, holds that have the lowestPriority flag on are have their | ||||
1676 | priority adjusted to ensure that they remain at the end of the line. | ||||
1677 | |||||
1678 | Note that the ignoreSetLowestRank parameter is meant to be used only | ||||
1679 | when _FixPriority calls itself. | ||||
1680 | |||||
1681 | =cut | ||||
1682 | |||||
1683 | sub _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 | |||||
1778 | Looks for an item-specific match first, then for a title-level match, returning the | ||||
1779 | first match found. If neither, then we look for a 3rd kind of match based on | ||||
1780 | reserve constraints. | ||||
1781 | Lookahead is the number of days to look in advance. | ||||
1782 | |||||
1783 | TODO: add more explanation about reserve constraints | ||||
1784 | |||||
1785 | C<&_Findgroupreserve> returns : | ||||
1786 | C<@results> is an array of references-to-hash whose keys are mostly | ||||
1787 | fields from the reserves table of the Koha database, plus | ||||
1788 | C<biblioitemnumber>. | ||||
1789 | |||||
1790 | =cut | ||||
1791 | |||||
1792 | sub _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 | |||||
1899 | Sends a notification to the patron that their hold has been filled (through | ||||
1900 | ModReserveAffect, _not_ ModReserveFill) | ||||
1901 | |||||
1902 | =cut | ||||
1903 | |||||
1904 | sub _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 | |||||
1991 | This increments the priority of all reserves after the one | ||||
1992 | with either the lowest date after C<$reservedate> | ||||
1993 | or the lowest priority after C<$priority>. | ||||
1994 | |||||
1995 | It effectively makes room for a new reserve to be inserted with a certain | ||||
1996 | priority, which is returned. | ||||
1997 | |||||
1998 | This is most useful when the reservedate can be set by the user. It allows | ||||
1999 | the new reserve to be placed before other reserves that have a later | ||||
2000 | reservedate. Since priority also is set by the form in reserves/request.pl | ||||
2001 | the sub accounts for that too. | ||||
2002 | |||||
2003 | =cut | ||||
2004 | |||||
2005 | sub _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 | |||||
2040 | Use when checking out an item to handle reserves | ||||
2041 | If $cancelreserve boolean is set to true, it will remove existing reserve | ||||
2042 | |||||
2043 | =cut | ||||
2044 | |||||
2045 | sub 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 | |||||
2093 | This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed | ||||
2094 | |||||
2095 | =cut | ||||
2096 | |||||
2097 | sub 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 | |||||
2147 | sub 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 | |||||
2201 | sub 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 | |||||
2236 | sub 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 | |||||
2274 | sub 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 | |||||
2291 | Calculate priority for a new reserve on biblionumber, placing it at | ||||
2292 | the end of the line of all holds whose start date falls before | ||||
2293 | the current system time and that are neither on the hold shelf | ||||
2294 | or in transit. | ||||
2295 | |||||
2296 | The reserve date parameter is optional; if it is supplied, the | ||||
2297 | priority is based on the set of holds whose start date falls before | ||||
2298 | the parameter value. | ||||
2299 | |||||
2300 | After calculation of this priority, it is recommended to call | ||||
2301 | _ShiftPriorityByDateAndPriority. Note that this is currently done in | ||||
2302 | AddReserves. | ||||
2303 | |||||
2304 | =cut | ||||
2305 | |||||
2306 | sub 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 | |||||
2334 | Koha Development Team <http://koha-community.org/> | ||||
2335 | |||||
2336 | =cut | ||||
2337 | |||||
2338 | 1 | 3µs | 1; |