]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
b6b9c9a17dcb8a37fe2c0d2d0910a515ccbc1c5a
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Holds.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2005  Georgia Public Library Service 
3 # Bill Erickson <highfalutin@gmail.com>
4
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
15
16
17 package OpenILS::Application::Circ::Holds;
18 use base qw/OpenSRF::Application/;
19 use strict; use warnings;
20 use OpenILS::Application::AppUtils;
21 use OpenILS::EX;
22 use OpenSRF::EX qw(:try);
23 use OpenILS::Perm;
24 use OpenILS::Event;
25 use OpenSRF::Utils::Logger qw(:logger);
26
27 my $apputils = "OpenILS::Application::AppUtils";
28
29
30
31 __PACKAGE__->register_method(
32         method  => "create_hold",
33         api_name        => "open-ils.circ.holds.create",
34         notes           => <<NOTE);
35 Create a new hold for an item.  From a permissions perspective, 
36 the login session is used as the 'requestor' of the hold.  
37 The hold recipient is determined by the 'usr' setting within
38 the hold object.
39
40 First we verify the requestion has holds request permissions.
41 Then we verify that the recipient is allowed to make the given hold.
42 If not, we see if the requestor has "override" capabilities.  If not,
43 a permission exception is returned.  If permissions allow, we cycle
44 through the set of holds objects and create.
45
46 If the recipient does not have permission to place multiple holds
47 on a single title and said operation is attempted, a permission
48 exception is returned
49 NOTE
50
51 sub create_hold {
52         my( $self, $client, $login_session, @holds) = @_;
53
54         if(!@holds){return 0;}
55         my( $user, $evt ) = $apputils->checkses($login_session);
56         return $evt if $evt;
57
58         my $holds;
59         if(ref($holds[0]) eq 'ARRAY') {
60                 $holds = $holds[0];
61         } else { $holds = [ @holds ]; }
62
63         $logger->debug("Iterating over holds requests...");
64
65         for my $hold (@$holds) {
66
67                 if(!$hold){next};
68                 my $type = $hold->hold_type;
69
70                 $logger->activity("User " . $user->id . 
71                         " creating new hold of type $type for user " . $hold->usr);
72
73                 my $recipient;
74                 if($user->id ne $hold->usr) {
75                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
76                         return $evt if $evt;
77
78                 } else {
79                         $recipient = $user;
80                 }
81
82
83                 my $perm = undef;
84
85                 # am I allowed to place holds for this user?
86                 if($hold->requestor ne $hold->usr) {
87                         $perm = _check_request_holds_perm($user->id, $user->home_ou);
88                         if($perm) { return $perm; }
89                 }
90
91                 # is this user allowed to have holds of this type?
92                 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
93                 if($perm) { 
94                         #if there is a requestor, see if the requestor has override privelages
95                         if($hold->requestor ne $hold->usr) {
96                                 $perm = _check_request_holds_override($user->id, $user->home_ou);
97                                 if($perm) {return $perm;}
98
99                         } else {
100                                 return $perm; 
101                         }
102                 }
103
104                 #enforce the fact that the login is the one requesting the hold
105                 $hold->requestor($user->id); 
106
107                 my $resp = $apputils->simplereq(
108                         'open-ils.storage',
109                         'open-ils.storage.direct.action.hold_request.create', $hold );
110
111                 if(!$resp) { 
112                         return OpenSRF::EX::ERROR ("Error creating hold"); 
113                 }
114         }
115
116         return 1;
117 }
118
119 # makes sure that a user has permission to place the type of requested hold
120 # returns the Perm exception if not allowed, returns undef if all is well
121 sub _check_holds_perm {
122         my($type, $user_id, $org_id) = @_;
123
124         my $evt;
125         if($type eq "M") {
126                 if($evt = $apputils->check_perms(
127                         $user_id, $org_id, "MR_HOLDS")) {
128                         return $evt;
129                 } 
130
131         } elsif ($type eq "T") {
132                 if($evt = $apputils->check_perms(
133                         $user_id, $org_id, "TITLE_HOLDS")) {
134                         return $evt;
135                 }
136
137         } elsif($type eq "V") {
138                 if($evt = $apputils->check_perms(
139                         $user_id, $org_id, "VOLUME_HOLDS")) {
140                         return $evt;
141                 }
142
143         } elsif($type eq "C") {
144                 if($evt = $apputils->check_perms(
145                         $user_id, $org_id, "COPY_HOLDS")) {
146                         return $evt;
147                 }
148         }
149
150         return undef;
151 }
152
153 # tests if the given user is allowed to place holds on another's behalf
154 sub _check_request_holds_perm {
155         my $user_id = shift;
156         my $org_id = shift;
157         if(my $evt = $apputils->check_perms(
158                 $user_id, $org_id, "REQUEST_HOLDS")) {
159                 return $evt;
160         }
161 }
162
163 sub _check_request_holds_override {
164         my $user_id = shift;
165         my $org_id = shift;
166         if(my $evt = $apputils->check_perms(
167                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
168                 return $evt;
169         }
170 }
171
172
173 __PACKAGE__->register_method(
174         method  => "retrieve_holds",
175         api_name        => "open-ils.circ.holds.retrieve",
176         notes           => <<NOTE);
177 Retrieves all the holds for the specified user id.  The login session
178 is the requestor and if the requestor is different from the user, then
179 the requestor must have VIEW_HOLD permissions.
180 NOTE
181
182
183 sub retrieve_holds {
184         my($self, $client, $login_session, $user_id) = @_;
185
186         my( $user, $target, $evt ) = $apputils->checkses_requestor(
187                 $login_session, $user_id, 'VIEW_HOLD' );
188         return $evt if $evt;
189
190         return $apputils->simplereq(
191                 'open-ils.storage',
192                 "open-ils.storage.direct.action.hold_request.search.atomic",
193                 "usr" =>  $user_id , fulfillment_time => undef, { order_by => "request_time" });
194 }
195
196
197 __PACKAGE__->register_method(
198         method  => "cancel_hold",
199         api_name        => "open-ils.circ.hold.cancel",
200         notes           => <<"  NOTE");
201         Cancels the specified hold.  The login session
202         is the requestor and if the requestor is different from the usr field
203         on the hold, the requestor must have CANCEL_HOLDS permissions.
204         the hold may be either the hold object or the hold id
205         NOTE
206
207 sub cancel_hold {
208         my($self, $client, $login_session, $holdid) = @_;
209         
210
211         my $user = $apputils->check_user_session($login_session);
212         my( $hold, $evt ) = $apputils->fetch_hold($holdid);
213         return $evt if $evt;
214
215         if($user->id ne $hold->usr) { #am I allowed to cancel this user's hold?
216                 if($evt = $apputils->checkperms(
217                         $user->id, $user->home_ou, 'CANCEL_HOLDS')) {
218                         return $evt;
219                 }
220         }
221
222         $logger->activity( "User " . $user->id . 
223                 " canceling hold $holdid for user " . $hold->usr );
224
225         return $apputils->simplereq(
226                 'open-ils.storage',
227                 "open-ils.storage.direct.action.hold_request.delete", $hold );
228 }
229
230
231 __PACKAGE__->register_method(
232         method  => "update_hold",
233         api_name        => "open-ils.circ.hold.update",
234         notes           => <<"  NOTE");
235         Updates the specified hold.  The login session
236         is the requestor and if the requestor is different from the usr field
237         on the hold, the requestor must have UPDATE_HOLDS permissions.
238         NOTE
239
240 sub update_hold {
241         my($self, $client, $login_session, $hold) = @_;
242
243         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
244                 $login_session, $hold->usr, 'UPDATE_HOLD' );
245         return $evt if $evt;
246
247         $logger->activity('User ' + $requestor->id . 
248                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
249         use Data::Dumper;
250
251         return $apputils->simplereq(
252                 'open-ils.storage',
253                 "open-ils.storage.direct.action.hold_request.update", $hold );
254 }
255
256
257 __PACKAGE__->register_method(
258         method  => "retrieve_hold_status",
259         api_name        => "open-ils.circ.hold.status.retrieve",
260         notes           => <<"  NOTE");
261         Calculates the current status of the hold.
262         the requestor must have VIEW_HOLD permissions if the hold is for a user
263         other than the requestor.
264         Returns -1  on error (for now)
265         Returns 1 for 'waiting for copy to become available'
266         Returns 2 for 'waiting for copy capture'
267         Returns 3 for 'in transit'
268         Returns 4 for 'arrived'
269         NOTE
270
271 sub retrieve_hold_status {
272         my($self, $client, $login_session, $hold_id) = @_;
273
274
275         my( $requestor, $target, $hold, $copy, $transit, $evt );
276
277         ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
278         return $evt if $evt;
279
280         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
281                 $login_session, $hold->usr, 'VIEW_HOLD' );
282         return $evt if $evt;
283
284         return 1 unless (defined($hold->current_copy));
285         
286         ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
287         return $evt if $evt;
288
289         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
290
291         ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
292         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
293
294         return 3 if defined($hold->capture_time);
295
296         return 2;
297 }
298
299 __PACKAGE__->register_method(
300         method  => "capture_copy",
301         api_name        => "open-ils.circ.hold.capture_copy.barcode",
302         notes           => <<"  NOTE");
303         Captures a copy to fulfil a hold
304         Params is login session and copy barcode
305         Optional param is 'flesh'.  If set, we also return the
306         relevant copy and title
307         login mus have COPY_CHECKIN permissions (since this is essentially
308         copy checkin)
309         NOTE
310
311 sub capture_copy {
312         my( $self, $client, $login_session, $barcode, $flesh ) = @_;
313
314         my( $user, $target, $copy, $hold, $evt );
315
316         ( $user, $evt ) = $apputils->checkses($login_session);
317         return $evt if $evt;
318
319         # am I allowed to checkin a copy?
320         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
321         return $evt if $evt;
322
323         $logger->info("Capturing copy with barcode $barcode, flesh=$flesh");
324
325         my $session = $apputils->start_db_session();
326
327         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
328         return $evt if $evt;
329
330         $logger->debug("Capturing copy " . $copy->id);
331
332         ( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
333         return $evt if $evt;
334
335         warn "Found hold " . $hold->id . "\n";
336         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
337
338         $hold->current_copy($copy->id);
339         $hold->capture_time("now"); 
340
341         #update the hold
342         my $stat = $session->request(
343                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
344         if(!$stat) { throw OpenSRF::EX::ERROR 
345                 ("Error updating hold request " . $copy->id); }
346
347         $copy->status(8); #status on holds shelf
348
349         # if the staff member capturing this item is not at the pickup lib
350         if( $user->home_ou ne $hold->pickup_lib ) {
351                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
352         }
353
354         $copy->editor($user->id);
355         $copy->edit_date("now");
356         $stat = $session->request(
357                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
358         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
359
360         
361         my $title = undef;
362         if($flesh) {
363                 ($title, $evt) = $apputils->fetch_record_by_copy( $copy->id );
364                 return $evt if $evt;
365                 $title = $apputils->record_to_mvr($title);
366         } 
367
368         $apputils->commit_db_session($session);
369
370         my $payload = { 
371                 copy => $copy,
372                 route_to => $hold->pickup_lib,
373                 record => $title,
374                 hold => $hold, 
375         };
376
377         return OpenILS::Event->new('SUCCESS', payload => $payload );
378 }
379
380 sub _build_hold_transit {
381         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
382         my $trans = Fieldmapper::action::hold_transit_copy->new;
383
384         $trans->hold($hold->id);
385         $trans->source($user->home_ou);
386         $trans->dest($hold->pickup_lib);
387         $trans->source_send_time("now");
388         $trans->target_copy($copy->id);
389         $trans->copy_status($copy->status);
390
391         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
392         my ($stat) = $meth->run( $login_session, $trans, $session );
393         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
394         else { $copy->status(6); } #status in transit 
395 }
396
397
398 sub _find_local_hold_for_copy {
399
400         my $session = shift;
401         my $copy = shift;
402         my $user = shift;
403         my $evt = OpenILS::Event->new('HOLD_NOT_FOUND');
404
405         # first see if this copy has already been selected to fulfill a hold
406         my $hold  = $session->request(
407                 "open-ils.storage.direct.action.hold_request.search_where",
408                 { current_copy => $copy->id, capture_time => undef } )->gather(1);
409
410         $logger->debug("Hold found for copy " . $copy->id);
411
412         if($hold) {return $hold;}
413
414         $logger->debug("searching for local hold at org " . 
415                 $user->home_ou . " and copy " . $copy->id);
416
417         my $holdid = $session->request(
418                 "open-ils.storage.action.hold_request.nearest_hold",
419                 $user->home_ou, $copy->id )->gather(1);
420
421         return (undef, $evt) unless defined $holdid;
422
423         $logger->debug("Found hold id $holdid while ".
424                 "searching nearest hold to " .$user->home_ou);
425
426         return $apputils->fetch_hold($holdid);
427 }
428
429
430 __PACKAGE__->register_method(
431         method  => "create_hold_transit",
432         api_name        => "open-ils.circ.hold_transit.create",
433         notes           => <<"  NOTE");
434         Creates a new transit object
435         NOTE
436
437 sub create_hold_transit {
438         my( $self, $client, $login_session, $transit, $session ) = @_;
439
440         my( $user, $evt ) = $apputils->checkses($login_session);
441         return $evt if $evt;
442         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
443         return $evt if $evt;
444
445         my $ses;
446         if($session) { $ses = $session; } 
447         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
448
449         return $ses->request(
450                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
451 }
452
453
454
455
456 1;