]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
typo
[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 my $U = $apputils;
30
31
32
33 __PACKAGE__->register_method(
34         method  => "create_hold",
35         api_name        => "open-ils.circ.holds.create",
36         notes           => <<NOTE);
37 Create a new hold for an item.  From a permissions perspective, 
38 the login session is used as the 'requestor' of the hold.  
39 The hold recipient is determined by the 'usr' setting within
40 the hold object.
41
42 First we verify the requestion has holds request permissions.
43 Then we verify that the recipient is allowed to make the given hold.
44 If not, we see if the requestor has "override" capabilities.  If not,
45 a permission exception is returned.  If permissions allow, we cycle
46 through the set of holds objects and create.
47
48 If the recipient does not have permission to place multiple holds
49 on a single title and said operation is attempted, a permission
50 exception is returned
51 NOTE
52
53 sub create_hold {
54         my( $self, $client, $login_session, @holds) = @_;
55
56         if(!@holds){return 0;}
57         my( $user, $evt ) = $apputils->checkses($login_session);
58         return $evt if $evt;
59
60         my $holds;
61         if(ref($holds[0]) eq 'ARRAY') {
62                 $holds = $holds[0];
63         } else { $holds = [ @holds ]; }
64
65         $logger->debug("Iterating over holds requests...");
66
67         for my $hold (@$holds) {
68
69                 if(!$hold){next};
70                 my $type = $hold->hold_type;
71
72                 $logger->activity("User " . $user->id . 
73                         " creating new hold of type $type for user " . $hold->usr);
74
75                 my $recipient;
76                 if($user->id ne $hold->usr) {
77                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
78                         return $evt if $evt;
79
80                 } else {
81                         $recipient = $user;
82                 }
83
84
85                 my $perm = undef;
86
87                 # am I allowed to place holds for this user?
88                 if($hold->requestor ne $hold->usr) {
89                         $perm = _check_request_holds_perm($user->id, $user->home_ou);
90                         if($perm) { return $perm; }
91                 }
92
93                 # is this user allowed to have holds of this type?
94                 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
95                 if($perm) { 
96                         #if there is a requestor, see if the requestor has override privelages
97                         if($hold->requestor ne $hold->usr) {
98                                 $perm = _check_request_holds_override($user->id, $user->home_ou);
99                                 if($perm) {return $perm;}
100
101                         } else {
102                                 return $perm; 
103                         }
104                 }
105
106                 #enforce the fact that the login is the one requesting the hold
107                 $hold->requestor($user->id); 
108
109                 my $resp = $apputils->simplereq(
110                         'open-ils.storage',
111                         'open-ils.storage.direct.action.hold_request.create', $hold );
112
113                 if(!$resp) { 
114                         return OpenSRF::EX::ERROR ("Error creating hold"); 
115                 }
116         }
117
118         return 1;
119 }
120
121 # makes sure that a user has permission to place the type of requested hold
122 # returns the Perm exception if not allowed, returns undef if all is well
123 sub _check_holds_perm {
124         my($type, $user_id, $org_id) = @_;
125
126         my $evt;
127         if($type eq "M") {
128                 if($evt = $apputils->check_perms(
129                         $user_id, $org_id, "MR_HOLDS")) {
130                         return $evt;
131                 } 
132
133         } elsif ($type eq "T") {
134                 if($evt = $apputils->check_perms(
135                         $user_id, $org_id, "TITLE_HOLDS")) {
136                         return $evt;
137                 }
138
139         } elsif($type eq "V") {
140                 if($evt = $apputils->check_perms(
141                         $user_id, $org_id, "VOLUME_HOLDS")) {
142                         return $evt;
143                 }
144
145         } elsif($type eq "C") {
146                 if($evt = $apputils->check_perms(
147                         $user_id, $org_id, "COPY_HOLDS")) {
148                         return $evt;
149                 }
150         }
151
152         return undef;
153 }
154
155 # tests if the given user is allowed to place holds on another's behalf
156 sub _check_request_holds_perm {
157         my $user_id = shift;
158         my $org_id = shift;
159         if(my $evt = $apputils->check_perms(
160                 $user_id, $org_id, "REQUEST_HOLDS")) {
161                 return $evt;
162         }
163 }
164
165 sub _check_request_holds_override {
166         my $user_id = shift;
167         my $org_id = shift;
168         if(my $evt = $apputils->check_perms(
169                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
170                 return $evt;
171         }
172 }
173
174
175 __PACKAGE__->register_method(
176         method  => "retrieve_holds",
177         api_name        => "open-ils.circ.holds.retrieve",
178         notes           => <<NOTE);
179 Retrieves all the holds, with hold transits attached, for the specified
180 user id.  The login session is the requestor and if the requestor is
181 different from the user, then the requestor must have VIEW_HOLD permissions.
182 NOTE
183
184
185 sub retrieve_holds {
186         my($self, $client, $login_session, $user_id) = @_;
187
188         my( $user, $target, $evt ) = $apputils->checkses_requestor(
189                 $login_session, $user_id, 'VIEW_HOLD' );
190         return $evt if $evt;
191
192         my $holds = $apputils->simplereq(
193                 'open-ils.storage',
194                 "open-ils.storage.direct.action.hold_request.search.atomic",
195                 "usr" =>  $user_id , fulfillment_time => undef, { order_by => "request_time" });
196         
197         for my $hold ( @$holds ) {
198                 $hold->transit(
199                         $apputils->simplereq(
200                                 'open-ils.storage',
201                                 "open-ils.storage.direct.action.hold_transit_copy.search.hold.atomic" => $hold->id,
202                                 { order_by => 'id desc', limit => 1 }
203                         )->[0]
204                 );
205         }
206
207         return $holds;
208 }
209
210 __PACKAGE__->register_method(
211         method  => "retrieve_holds_by_pickup_lib",
212         api_name        => "open-ils.circ.holds.retrieve_by_pickup_lib",
213         notes           => <<NOTE);
214 Retrieves all the holds, with hold transits attached, for the specified
215 pickup_ou id. 
216 NOTE
217
218
219 sub retrieve_holds_by_pickup_lib {
220         my($self, $client, $login_session, $ou_id) = @_;
221
222         #FIXME -- put an appropriate permission check here
223         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
224         #       $login_session, $user_id, 'VIEW_HOLD' );
225         #return $evt if $evt;
226
227         my $holds = $apputils->simplereq(
228                 'open-ils.storage',
229                 "open-ils.storage.direct.action.hold_request.search.atomic",
230                 "pickup_lib" =>  $ou_id , fulfillment_time => undef, { order_by => "request_time" });
231         
232         for my $hold ( @$holds ) {
233                 $hold->transit(
234                         $apputils->simplereq(
235                                 'open-ils.storage',
236                                 "open-ils.storage.direct.action.hold_transit_copy.search.hold.atomic" => $hold->id,
237                                 { order_by => 'id desc', limit => 1 }
238                         )->[0]
239                 );
240         }
241
242         return $holds;
243 }
244
245
246 __PACKAGE__->register_method(
247         method  => "cancel_hold",
248         api_name        => "open-ils.circ.hold.cancel",
249         notes           => <<"  NOTE");
250         Cancels the specified hold.  The login session
251         is the requestor and if the requestor is different from the usr field
252         on the hold, the requestor must have CANCEL_HOLDS permissions.
253         the hold may be either the hold object or the hold id
254         NOTE
255
256 sub cancel_hold {
257         my($self, $client, $login_session, $holdid) = @_;
258         my $hold;       
259
260         my($user, $evt) = $U->checkses($login_session);
261         return $evt if $evt;
262
263         ( $hold, $evt ) = $U->fetch_hold($holdid);
264         return $evt if $evt;
265
266         if($user->id ne $hold->usr) { #am I allowed to cancel this user's hold?
267                 if($evt = $apputils->check_perms(
268                         $user->id, $user->home_ou, 'CANCEL_HOLDS')) {
269                         return $evt;
270                 }
271         }
272
273         $logger->activity( "User " . $user->id . 
274                 " canceling hold $holdid for user " . $hold->usr );
275
276         return $apputils->simplereq(
277                 'open-ils.storage',
278                 "open-ils.storage.direct.action.hold_request.delete", $hold );
279 }
280
281
282 __PACKAGE__->register_method(
283         method  => "update_hold",
284         api_name        => "open-ils.circ.hold.update",
285         notes           => <<"  NOTE");
286         Updates the specified hold.  The login session
287         is the requestor and if the requestor is different from the usr field
288         on the hold, the requestor must have UPDATE_HOLDS permissions.
289         NOTE
290
291 sub update_hold {
292         my($self, $client, $login_session, $hold) = @_;
293
294         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
295                 $login_session, $hold->usr, 'UPDATE_HOLD' );
296         return $evt if $evt;
297
298         $logger->activity('User ' + $requestor->id . 
299                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
300
301         return $apputils->simplereq(
302                 'open-ils.storage',
303                 "open-ils.storage.direct.action.hold_request.update", $hold );
304 }
305
306
307 __PACKAGE__->register_method(
308         method  => "retrieve_hold_status",
309         api_name        => "open-ils.circ.hold.status.retrieve",
310         notes           => <<"  NOTE");
311         Calculates the current status of the hold.
312         the requestor must have VIEW_HOLD permissions if the hold is for a user
313         other than the requestor.
314         Returns -1  on error (for now)
315         Returns 1 for 'waiting for copy to become available'
316         Returns 2 for 'waiting for copy capture'
317         Returns 3 for 'in transit'
318         Returns 4 for 'arrived'
319         NOTE
320
321 sub retrieve_hold_status {
322         my($self, $client, $login_session, $hold_id) = @_;
323
324
325         my( $requestor, $target, $hold, $copy, $transit, $evt );
326
327         ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
328         return $evt if $evt;
329
330         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
331                 $login_session, $hold->usr, 'VIEW_HOLD' );
332         return $evt if $evt;
333
334         return 1 unless (defined($hold->current_copy));
335         
336         ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
337         return $evt if $evt;
338
339         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
340
341         ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
342         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
343
344         return 3 if defined($hold->capture_time);
345
346         return 2;
347 }
348
349
350
351
352
353 __PACKAGE__->register_method(
354         method  => "capture_copy",
355         api_name        => "open-ils.circ.hold.capture_copy.barcode",
356         notes           => <<"  NOTE");
357         Captures a copy to fulfil a hold
358         Params is login session and copy barcode
359         Optional param is 'flesh'.  If set, we also return the
360         relevant copy and title
361         login mus have COPY_CHECKIN permissions (since this is essentially
362         copy checkin)
363         NOTE
364
365 # XXX deprecate me XXX
366
367 sub capture_copy {
368         my( $self, $client, $login_session, $params ) = @_;
369         my %params = %$params;
370         my $barcode = $params{barcode};
371
372
373         my( $user, $target, $copy, $hold, $evt );
374
375         ( $user, $evt ) = $apputils->checkses($login_session);
376         return $evt if $evt;
377
378         # am I allowed to checkin a copy?
379         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
380         return $evt if $evt;
381
382         $logger->info("Capturing copy with barcode $barcode");
383
384         my $session = $apputils->start_db_session();
385
386         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
387         return $evt if $evt;
388
389         $logger->debug("Capturing copy " . $copy->id);
390
391         ( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
392         return $evt if $evt;
393
394         warn "Found hold " . $hold->id . "\n";
395         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
396
397         $hold->current_copy($copy->id);
398         $hold->capture_time("now"); 
399
400         #update the hold
401         my $stat = $session->request(
402                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
403         if(!$stat) { throw OpenSRF::EX::ERROR 
404                 ("Error updating hold request " . $copy->id); }
405
406         $copy->status(8); #status on holds shelf
407
408         # if the staff member capturing this item is not at the pickup lib
409         if( $user->home_ou ne $hold->pickup_lib ) {
410                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
411         }
412
413         $copy->editor($user->id);
414         $copy->edit_date("now");
415         $stat = $session->request(
416                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
417         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
418
419         my $payload = { hold => $hold };
420         $payload->{copy} = $copy if $params{flesh_copy};
421
422         if($params{flesh_record}) {
423                 my $record;
424                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
425                 return $evt if $evt;
426                 $record = $apputils->record_to_mvr($record);
427                 $payload->{record} = $record;
428         }
429
430         $apputils->commit_db_session($session);
431
432         return OpenILS::Event->new('ROUTE_ITEM', 
433                 route_to => $hold->pickup_lib, payload => $payload );
434 }
435
436 sub _build_hold_transit {
437         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
438         my $trans = Fieldmapper::action::hold_transit_copy->new;
439
440         $trans->hold($hold->id);
441         $trans->source($user->home_ou);
442         $trans->dest($hold->pickup_lib);
443         $trans->source_send_time("now");
444         $trans->target_copy($copy->id);
445         $trans->copy_status($copy->status);
446
447         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
448         my ($stat) = $meth->run( $login_session, $trans, $session );
449         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
450         else { $copy->status(6); } #status in transit 
451 }
452
453
454 sub find_local_hold {
455         my( $class, $session, $copy, $user ) = @_;
456         return _find_local_hold_for_copy($session, $copy, $user);
457 }
458
459 sub _find_local_hold_for_copy {
460
461         my $session = shift;
462         my $copy = shift;
463         my $user = shift;
464         my $evt = OpenILS::Event->new('HOLD_NOT_FOUND');
465
466         # first see if this copy has already been selected to fulfill a hold
467         my $hold  = $session->request(
468                 "open-ils.storage.direct.action.hold_request.search_where",
469                 { current_copy => $copy->id, capture_time => undef } )->gather(1);
470
471         if($hold) {return $hold;}
472
473         $logger->debug("searching for local hold at org " . 
474                 $user->home_ou . " and copy " . $copy->id);
475
476         my $holdid = $session->request(
477                 "open-ils.storage.action.hold_request.nearest_hold",
478                 $user->home_ou, $copy->id )->gather(1);
479
480         return (undef, $evt) unless defined $holdid;
481
482         $logger->debug("Found hold id $holdid while ".
483                 "searching nearest hold to " .$user->home_ou);
484
485         return $apputils->fetch_hold($holdid);
486 }
487
488
489 __PACKAGE__->register_method(
490         method  => "create_hold_transit",
491         api_name        => "open-ils.circ.hold_transit.create",
492         notes           => <<"  NOTE");
493         Creates a new transit object
494         NOTE
495
496 sub create_hold_transit {
497         my( $self, $client, $login_session, $transit, $session ) = @_;
498
499         my( $user, $evt ) = $apputils->checkses($login_session);
500         return $evt if $evt;
501         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
502         return $evt if $evt;
503
504         my $ses;
505         if($session) { $ses = $session; } 
506         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
507
508         return $ses->request(
509                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
510 }
511
512
513 sub fetch_open_hold_by_current_copy {
514         my $class = shift;
515         my $copyid = shift;
516         my $hold = $apputils->simplereq(
517                 'open-ils.storage', 
518                 'open-ils.storage.direct.action.hold_request.search.atomic',
519                          current_copy =>  $copyid , fulfillment_time => undef );
520         return $hold->[0] if ref($hold);
521         return undef;
522 }
523
524 sub fetch_related_holds {
525         my $class = shift;
526         my $copyid = shift;
527         return $apputils->simplereq(
528                 'open-ils.storage', 
529                 'open-ils.storage.direct.action.hold_request.search.atomic',
530                          current_copy =>  $copyid , fulfillment_time => undef );
531 }
532
533
534 __PACKAGE__->register_method (
535         method          => "hold_pull_list",
536         api_name                => "open-ils.circ.hold_pull_list.retrieve",
537         signature       => q/
538                 Returns a list of hold ID's that need to be "pulled"
539                 by a given location
540         /
541 );
542
543 sub hold_pull_list {
544         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
545         my( $reqr, $evt ) = $U->checkses($authtoken);
546         return $evt if $evt;
547
548         my $org = $reqr->ws_ou || $reqr->home_ou;
549         # the perm locaiton shouldn't really matter here since holds
550         # will exist all over and VIEW_HOLDS should be universal
551         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
552         return $evt if $evt;
553
554         return $U->storagereq(
555                 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
556                 $org, $limit, $offset ); # XXX change to workstation
557 }
558
559 __PACKAGE__->register_method (
560         method          => 'fetch_hold_notify',
561         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
562         signature       => q/ 
563                 Returns a list of hold notification objects based on hold id.
564                 @param authtoken The loggin session key
565                 @param holdid The id of the hold whose notifications we want to retrieve
566                 @return An array of hold notification objects, event on error.
567         /
568 );
569
570 sub fetch_hold_notify {
571         my( $self, $conn, $authtoken, $holdid ) = @_;
572         my( $requestor, $evt ) = $U->checkses($authtoken);
573         return $evt if $evt;
574         my ($hold, $patron);
575         ($hold, $evt) = $U->fetch_hold($holdid);
576         return $evt if $evt;
577         ($patron, $evt) = $U->fetch_user($hold->usr);
578         return $evt if $evt;
579
580         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
581         return $evt if $evt;
582
583         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
584         return $U->storagereq(
585                 'open-ils.storage.direct.action.hold_notification.search.hold.atomic', $holdid );
586 }
587
588
589 __PACKAGE__->register_method (
590         method          => 'create_hold_notify',
591         api_name                => 'open-ils.circ.hold_notification.create',
592         signature       => q/
593                 Creates a new hold notification object
594                 @param authtoken The login session key
595                 @param notification The hold notification object to create
596                 @return ID of the new object on success, Event on error
597                 /
598 );
599 sub create_hold_notify {
600         my( $self, $conn, $authtoken, $notification ) = @_;
601         my( $requestor, $evt ) = $U->checkses($authtoken);
602         return $evt if $evt;
603         my ($hold, $patron);
604         ($hold, $evt) = $U->fetch_hold($notification->hold);
605         return $evt if $evt;
606         ($patron, $evt) = $U->fetch_user($hold->usr);
607         return $evt if $evt;
608
609         # XXX perm depth probably doesn't matter here -- should always be consortium level
610         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
611         return $evt if $evt;
612
613         # Set the proper notifier 
614         $notification->notify_staff($requestor->id);
615         my $id = $U->storagereq(
616                 'open-ils.storage.direct.action.hold_notification.create', $notification );
617         return $U->DB_UPDATE_FAILED($notification) unless $id;
618         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
619         return $id;
620 }
621
622
623
624 1;