]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
29f63964b2ac05511c39059be7306bdf2bf8cb9b
[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 Data::Dumper;
22 use OpenILS::EX;
23 use OpenSRF::EX qw(:try);
24 use OpenILS::Perm;
25 use OpenILS::Event;
26 use OpenSRF::Utils::Logger qw(:logger);
27
28 my $apputils = "OpenILS::Application::AppUtils";
29
30
31
32 __PACKAGE__->register_method(
33         method  => "create_hold",
34         api_name        => "open-ils.circ.holds.create",
35         notes           => <<NOTE);
36 Create a new hold for an item.  From a permissions perspective, 
37 the login session is used as the 'requestor' of the hold.  
38 The hold recipient is determined by the 'usr' setting within
39 the hold object.
40
41 First we verify the requestion has holds request permissions.
42 Then we verify that the recipient is allowed to make the given hold.
43 If not, we see if the requestor has "override" capabilities.  If not,
44 a permission exception is returned.  If permissions allow, we cycle
45 through the set of holds objects and create.
46
47 If the recipient does not have permission to place multiple holds
48 on a single title and said operation is attempted, a permission
49 exception is returned
50 NOTE
51
52 sub create_hold {
53         my( $self, $client, $login_session, @holds) = @_;
54
55         if(!@holds){return 0;}
56         my( $user, $evt ) = $apputils->checkses($login_session);
57         return $evt if $evt;
58
59         my $holds;
60         if(ref($holds[0]) eq 'ARRAY') {
61                 $holds = $holds[0];
62         } else { $holds = [ @holds ]; }
63
64         $logger->debug("Iterating over holds requests...");
65
66         for my $hold (@$holds) {
67
68                 if(!$hold){next};
69                 my $type = $hold->hold_type;
70
71                 $logger->activity("User " . $user->id . 
72                         " creating new hold of type $type for user " . $hold->usr);
73
74                 my $recipient;
75                 if($user->id ne $hold->usr) {
76                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
77                         return $evt if $evt;
78
79                 } else {
80                         $recipient = $user;
81                 }
82
83
84                 my $perm = undef;
85
86                 # am I allowed to place holds for this user?
87                 if($hold->requestor ne $hold->usr) {
88                         $perm = _check_request_holds_perm($user->id, $user->home_ou);
89                         if($perm) { return $perm; }
90                 }
91
92                 # is this user allowed to have holds of this type?
93                 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
94                 if($perm) { 
95                         #if there is a requestor, see if the requestor has override privelages
96                         if($hold->requestor ne $hold->usr) {
97                                 $perm = _check_request_holds_override($user->id, $user->home_ou);
98                                 if($perm) {return $perm;}
99
100                         } else {
101                                 return $perm; 
102                         }
103                 }
104
105                 #enforce the fact that the login is the one requesting the hold
106                 $hold->requestor($user->id); 
107
108                 my $resp = $apputils->simplereq(
109                         'open-ils.storage',
110                         'open-ils.storage.direct.action.hold_request.create', $hold );
111
112                 if(!$resp) { 
113                         return OpenSRF::EX::ERROR ("Error creating hold"); 
114                 }
115         }
116
117         return 1;
118 }
119
120 # makes sure that a user has permission to place the type of requested hold
121 # returns the Perm exception if not allowed, returns undef if all is well
122 sub _check_holds_perm {
123         my($type, $user_id, $org_id) = @_;
124
125         my $evt;
126         if($type eq "M") {
127                 if($evt = $apputils->check_perms(
128                         $user_id, $org_id, "MR_HOLDS")) {
129                         return $evt;
130                 } 
131
132         } elsif ($type eq "T") {
133                 if($evt = $apputils->check_perms(
134                         $user_id, $org_id, "TITLE_HOLDS")) {
135                         return $evt;
136                 }
137
138         } elsif($type eq "V") {
139                 if($evt = $apputils->check_perms(
140                         $user_id, $org_id, "VOLUME_HOLDS")) {
141                         return $evt;
142                 }
143
144         } elsif($type eq "C") {
145                 if($evt = $apputils->check_perms(
146                         $user_id, $org_id, "COPY_HOLDS")) {
147                         return $evt;
148                 }
149         }
150
151         return undef;
152 }
153
154 # tests if the given user is allowed to place holds on another's behalf
155 sub _check_request_holds_perm {
156         my $user_id = shift;
157         my $org_id = shift;
158         if(my $evt = $apputils->check_perms(
159                 $user_id, $org_id, "REQUEST_HOLDS")) {
160                 return $evt;
161         }
162 }
163
164 sub _check_request_holds_override {
165         my $user_id = shift;
166         my $org_id = shift;
167         if(my $evt = $apputils->check_perms(
168                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
169                 return $evt;
170         }
171 }
172
173
174 __PACKAGE__->register_method(
175         method  => "retrieve_holds",
176         api_name        => "open-ils.circ.holds.retrieve",
177         notes           => <<NOTE);
178 Retrieves all the holds for the specified user id.  The login session
179 is the requestor and if the requestor is different from the user, then
180 the requestor must have VIEW_HOLD permissions.
181 NOTE
182
183
184 sub retrieve_holds {
185         my($self, $client, $login_session, $user_id) = @_;
186
187         my( $user, $target, $evt ) = $apputils->checkses_requestor(
188                 $login_session, $user_id, 'VIEW_HOLD' );
189         return $evt if $evt;
190
191         return $apputils->simplereq(
192                 'open-ils.storage',
193                 "open-ils.storage.direct.action.hold_request.search.atomic",
194                 "usr" =>  $user_id , fulfillment_time => undef, { order_by => "request_time" });
195 }
196
197
198 __PACKAGE__->register_method(
199         method  => "cancel_hold",
200         api_name        => "open-ils.circ.hold.cancel",
201         notes           => <<"  NOTE");
202         Cancels the specified hold.  The login session
203         is the requestor and if the requestor is different from the usr field
204         on the hold, the requestor must have CANCEL_HOLDS permissions.
205         the hold may be either the hold object or the hold id
206         NOTE
207
208 sub cancel_hold {
209         my($self, $client, $login_session, $holdid) = @_;
210         
211
212         my $user = $apputils->check_user_session($login_session);
213         my( $hold, $evt ) = $apputils->fetch_hold($holdid);
214         return $evt if $evt;
215
216         if($user->id ne $hold->usr) { #am I allowed to cancel this user's hold?
217                 if($evt = $apputils->checkperms(
218                         $user->id, $user->home_ou, 'CANCEL_HOLDS')) {
219                         return $evt;
220                 }
221         }
222
223         $logger->activity( "User " . $user->id . 
224                 " canceling hold $holdid for user " . $hold->usr );
225
226         return $apputils->simplereq(
227                 'open-ils.storage',
228                 "open-ils.storage.direct.action.hold_request.delete", $hold );
229 }
230
231
232 __PACKAGE__->register_method(
233         method  => "update_hold",
234         api_name        => "open-ils.circ.hold.update",
235         notes           => <<"  NOTE");
236         Updates the specified hold.  The login session
237         is the requestor and if the requestor is different from the usr field
238         on the hold, the requestor must have UPDATE_HOLDS permissions.
239         NOTE
240
241 sub update_hold {
242         my($self, $client, $login_session, $hold) = @_;
243
244         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
245                 $login_session, $hold->usr, 'UPDATE_HOLD' );
246         return $evt if $evt;
247
248         $logger->activity('User ' + $requestor->id . 
249                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
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 = { copy => $copy, record => $title, hold => $hold, };
371
372         return OpenILS::Event->new('ROUTE_COPY', route_to => $hold->pickup_lib, payload => $payload );
373 }
374
375 sub _build_hold_transit {
376         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
377         my $trans = Fieldmapper::action::hold_transit_copy->new;
378
379         $trans->hold($hold->id);
380         $trans->source($user->home_ou);
381         $trans->dest($hold->pickup_lib);
382         $trans->source_send_time("now");
383         $trans->target_copy($copy->id);
384         $trans->copy_status($copy->status);
385
386         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
387         my ($stat) = $meth->run( $login_session, $trans, $session );
388         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
389         else { $copy->status(6); } #status in transit 
390 }
391
392
393 sub _find_local_hold_for_copy {
394
395         my $session = shift;
396         my $copy = shift;
397         my $user = shift;
398         my $evt = OpenILS::Event->new('HOLD_NOT_FOUND');
399
400         # first see if this copy has already been selected to fulfill a hold
401         my $hold  = $session->request(
402                 "open-ils.storage.direct.action.hold_request.search_where",
403                 { current_copy => $copy->id, capture_time => undef } )->gather(1);
404
405         $logger->debug("Hold found for copy " . $copy->id);
406
407         if($hold) {return $hold;}
408
409         $logger->debug("searching for local hold at org " . 
410                 $user->home_ou . " and copy " . $copy->id);
411
412         my $holdid = $session->request(
413                 "open-ils.storage.action.hold_request.nearest_hold",
414                 $user->home_ou, $copy->id )->gather(1);
415
416         return (undef, $evt) unless defined $holdid;
417
418         $logger->debug("Found hold id $holdid while ".
419                 "searching nearest hold to " .$user->home_ou);
420
421         return $apputils->fetch_hold($holdid);
422 }
423
424
425 __PACKAGE__->register_method(
426         method  => "create_hold_transit",
427         api_name        => "open-ils.circ.hold_transit.create",
428         notes           => <<"  NOTE");
429         Creates a new transit object
430         NOTE
431
432 sub create_hold_transit {
433         my( $self, $client, $login_session, $transit, $session ) = @_;
434
435         my( $user, $evt ) = $apputils->checkses($login_session);
436         return $evt if $evt;
437         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
438         return $evt if $evt;
439
440         my $ses;
441         if($session) { $ses = $session; } 
442         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
443
444         return $ses->request(
445                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
446 }
447
448
449 sub fetch_open_hold_by_current_copy {
450         my $class = shift;
451         my $copyid = shift;
452         my $hold = $apputils->simplereq(
453                 'open-ils.storage', 
454                 'open-ils.storage.direct.action.hold_request.search.atomic',
455                          current_copy =>  $copyid , fulfillment_time => undef );
456         return $hold->[0] if ref($hold);
457         return undef;
458 }
459
460 sub fetch_related_holds {
461         my $class = shift;
462         my $copyid = shift;
463         return $apputils->simplereq(
464                 'open-ils.storage', 
465                 'open-ils.storage.direct.action.hold_request.search.atomic',
466                          current_copy =>  $copyid , fulfillment_time => undef );
467 }
468
469
470 1;