]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
disallowing holds if patron barred
[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 OpenSRF::EX qw(:try);
23 use OpenILS::Perm;
24 use OpenILS::Event;
25 use OpenSRF::Utils::Logger qw(:logger);
26 use OpenILS::Utils::Editor q/:funcs/;
27 use OpenILS::Utils::PermitHold;
28
29 my $apputils = "OpenILS::Application::AppUtils";
30 my $U = $apputils;
31
32
33
34 __PACKAGE__->register_method(
35         method  => "create_hold",
36         api_name        => "open-ils.circ.holds.create",
37         notes           => <<NOTE);
38 Create a new hold for an item.  From a permissions perspective, 
39 the login session is used as the 'requestor' of the hold.  
40 The hold recipient is determined by the 'usr' setting within
41 the hold object.
42
43 First we verify the requestion has holds request permissions.
44 Then we verify that the recipient is allowed to make the given hold.
45 If not, we see if the requestor has "override" capabilities.  If not,
46 a permission exception is returned.  If permissions allow, we cycle
47 through the set of holds objects and create.
48
49 If the recipient does not have permission to place multiple holds
50 on a single title and said operation is attempted, a permission
51 exception is returned
52 NOTE
53
54
55 __PACKAGE__->register_method(
56         method  => "create_hold",
57         api_name        => "open-ils.circ.holds.create.override",
58         signature       => q/
59                 If the recipient is not allowed to receive the requested hold,
60                 call this method to attempt the override
61                 @see open-ils.circ.holds.create
62         /
63 );
64
65 sub create_hold {
66         my( $self, $conn, $auth, @holds ) = @_;
67         my $e = new_editor(authtoken=>$auth);
68         return $e->event unless $e->checkauth;
69
70         my $holds = (ref($holds[0] eq 'ARRAY')) ? $holds[0] : [@holds];
71
72         for my $hold (@$holds) {
73
74                 next unless $hold;
75
76                 my $requestor = $e->requestor;
77                 my $recipient = $requestor;
78
79
80                 if( $requestor->id ne $hold->usr ) {
81                         # Make sure the requestor is allowed to place holds for 
82                         # the recipient if they are not the same people
83                         $recipient = $e->retrieve_actor_user($hold->usr) or return $e->event;
84                         $e->allowed('REQUEST_HOLDS', $recipient->home_ou) or return $e->event;
85                 }
86
87
88                 # Now make sure the recipient is allowed to receive the specified hold
89                 my $pevt;
90                 my $porg                = $recipient->home_ou;
91                 my $rid         = $e->requestor->id;
92                 my $t                   = $hold->hold_type;
93
94                 # See if a duplicate hold already exists
95                 my $sargs = {
96                         usr                     => $recipient->id, 
97                         hold_type       => $t, 
98                         fulfillment_time => undef, 
99                         target          => $hold->target
100                 };
101
102                 $sargs->{holdable_formats} = $hold->holdable_formats if $t eq 'M';
103                         
104                 # XXX Put multi-hold-per-title perm here for staff
105                 my $existing = $e->search_action_hold_request($sargs); 
106                 my $eevt = OpenILS::Event->new('HOLD_EXISTS') if @$existing;
107
108                 if( $t eq 'M' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'MR_HOLDS'); }
109                 if( $t eq 'T' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'TITLE_HOLDS');  }
110                 if( $t eq 'V' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'VOLUME_HOLDS'); }
111                 if( $t eq 'C' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'COPY_HOLDS'); }
112
113
114                 # COPY/VOLUME holds are allowed for staff, not overridable 
115                 # XXX We need overridable events for staff XXX
116
117                 if( $pevt ) {
118                         if( $self->api_name =~ /override/ ) {
119                                 # The recipient is not allowed to receive the requested hold 
120                                 # and the requestor has elected to override - 
121                                 # let's see if the requestor is allowed
122                                 return $e->event unless $e->allowed('REQUEST_HOLDS_OVERRIDE', $porg);
123                         } else {
124                                 return $pevt;
125                         }
126                 }
127
128                 if( $eevt ) {
129                         if( $self->api_name =~ /override/ ) {
130                                 return $e->event unless $e->allowed('CREATE_DUPLICATE_HOLDS', $porg);
131                         } else {
132                                 return $eevt;
133                         }
134                 }
135
136
137                 $hold->requestor($e->requestor->id); 
138                 $hold->selection_ou($recipient->home_ou) unless $hold->selection_ou;
139                 $e->create_action_hold_request($hold) or return $e->event;
140         }
141
142         return 1;
143 }
144
145 sub __create_hold {
146         my( $self, $client, $login_session, @holds) = @_;
147
148         if(!@holds){return 0;}
149         my( $user, $evt ) = $apputils->checkses($login_session);
150         return $evt if $evt;
151
152         my $holds;
153         if(ref($holds[0]) eq 'ARRAY') {
154                 $holds = $holds[0];
155         } else { $holds = [ @holds ]; }
156
157         $logger->debug("Iterating over holds requests...");
158
159         for my $hold (@$holds) {
160
161                 if(!$hold){next};
162                 my $type = $hold->hold_type;
163
164                 $logger->activity("User " . $user->id . 
165                         " creating new hold of type $type for user " . $hold->usr);
166
167                 my $recipient;
168                 if($user->id ne $hold->usr) {
169                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
170                         return $evt if $evt;
171
172                 } else {
173                         $recipient = $user;
174                 }
175
176
177                 my $perm = undef;
178
179                 # am I allowed to place holds for this user?
180                 if($hold->requestor ne $hold->usr) {
181                         $perm = _check_request_holds_perm($user->id, $user->home_ou);
182                         if($perm) { return $perm; }
183                 }
184
185                 # is this user allowed to have holds of this type?
186                 $perm = _check_holds_perm($type, $hold->requestor, $recipient->home_ou);
187                 if($perm) { 
188                         #if there is a requestor, see if the requestor has override privelages
189                         if($hold->requestor ne $hold->usr) {
190                                 $perm = _check_request_holds_override($user->id, $user->home_ou);
191                                 if($perm) {return $perm;}
192
193                         } else {
194                                 return $perm; 
195                         }
196                 }
197
198                 #enforce the fact that the login is the one requesting the hold
199                 $hold->requestor($user->id); 
200                 $hold->selection_ou($recipient->home_ou) unless $hold->selection_ou;
201
202                 my $resp = $apputils->simplereq(
203                         'open-ils.storage',
204                         'open-ils.storage.direct.action.hold_request.create', $hold );
205
206                 if(!$resp) { 
207                         return OpenSRF::EX::ERROR ("Error creating hold"); 
208                 }
209         }
210
211         return 1;
212 }
213
214 # makes sure that a user has permission to place the type of requested hold
215 # returns the Perm exception if not allowed, returns undef if all is well
216 sub _check_holds_perm {
217         my($type, $user_id, $org_id) = @_;
218
219         my $evt;
220         if($type eq "M") {
221                 if($evt = $apputils->check_perms(
222                         $user_id, $org_id, "MR_HOLDS")) {
223                         return $evt;
224                 } 
225
226         } elsif ($type eq "T") {
227                 if($evt = $apputils->check_perms(
228                         $user_id, $org_id, "TITLE_HOLDS")) {
229                         return $evt;
230                 }
231
232         } elsif($type eq "V") {
233                 if($evt = $apputils->check_perms(
234                         $user_id, $org_id, "VOLUME_HOLDS")) {
235                         return $evt;
236                 }
237
238         } elsif($type eq "C") {
239                 if($evt = $apputils->check_perms(
240                         $user_id, $org_id, "COPY_HOLDS")) {
241                         return $evt;
242                 }
243         }
244
245         return undef;
246 }
247
248 # tests if the given user is allowed to place holds on another's behalf
249 sub _check_request_holds_perm {
250         my $user_id = shift;
251         my $org_id = shift;
252         if(my $evt = $apputils->check_perms(
253                 $user_id, $org_id, "REQUEST_HOLDS")) {
254                 return $evt;
255         }
256 }
257
258 sub _check_request_holds_override {
259         my $user_id = shift;
260         my $org_id = shift;
261         if(my $evt = $apputils->check_perms(
262                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
263                 return $evt;
264         }
265 }
266
267 __PACKAGE__->register_method(
268         method  => "retrieve_holds_by_id",
269         api_name        => "open-ils.circ.holds.retrieve_by_id",
270         notes           => <<NOTE);
271 Retrieve the hold, with hold transits attached, for the specified id
272 The login session is the requestor and if the requestor is
273 different from the user, then the requestor must have VIEW_HOLD permissions.
274 NOTE
275
276
277 sub retrieve_holds_by_id {
278         my($self, $client, $login_session, $hold_id) = @_;
279
280         #FIXME
281         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
282         #       $login_session, $user_id, 'VIEW_HOLD' );
283         #return $evt if $evt;
284
285         my $holds = $apputils->simplereq(
286                 'open-ils.cstore',
287                 "open-ils.cstore.direct.action.hold_request.search.atomic",
288                 { id =>  $hold_id , fulfillment_time => undef }, { order_by => { ahr => "request_time" } });
289         
290         for my $hold ( @$holds ) {
291                 $hold->transit(
292                         $apputils->simplereq(
293                                 'open-ils.cstore',
294                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
295                                 { hold => $hold->id },
296                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
297                         )->[0]
298                 );
299         }
300
301         return $holds;
302 }
303
304
305 __PACKAGE__->register_method(
306         method  => "retrieve_holds",
307         api_name        => "open-ils.circ.holds.retrieve",
308         notes           => <<NOTE);
309 Retrieves all the holds, with hold transits attached, for the specified
310 user id.  The login session is the requestor and if the requestor is
311 different from the user, then the requestor must have VIEW_HOLD permissions.
312 NOTE
313
314
315 sub retrieve_holds {
316         my($self, $client, $login_session, $user_id) = @_;
317
318         my( $user, $target, $evt ) = $apputils->checkses_requestor(
319                 $login_session, $user_id, 'VIEW_HOLD' );
320         return $evt if $evt;
321
322         my $holds = $apputils->simplereq(
323                 'open-ils.cstore',
324                 "open-ils.cstore.direct.action.hold_request.search.atomic",
325                 { usr =>  $user_id , fulfillment_time => undef }, { order_by => { ahr => "request_time" } });
326         
327         for my $hold ( @$holds ) {
328                 $hold->transit(
329                         $apputils->simplereq(
330                                 'open-ils.cstore',
331                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
332                                 { hold => $hold->id },
333                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
334                         )->[0]
335                 );
336         }
337
338         return $holds;
339 }
340
341 __PACKAGE__->register_method(
342         method  => "retrieve_holds_by_pickup_lib",
343         api_name        => "open-ils.circ.holds.retrieve_by_pickup_lib",
344         notes           => <<NOTE);
345 Retrieves all the holds, with hold transits attached, for the specified
346 pickup_ou id. 
347 NOTE
348
349
350 sub retrieve_holds_by_pickup_lib {
351         my($self, $client, $login_session, $ou_id) = @_;
352
353         #FIXME -- put an appropriate permission check here
354         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
355         #       $login_session, $user_id, 'VIEW_HOLD' );
356         #return $evt if $evt;
357
358         my $holds = $apputils->simplereq(
359                 'open-ils.cstore',
360                 "open-ils.cstore.direct.action.hold_request.search.atomic",
361                 { pickup_lib =>  $ou_id , fulfillment_time => undef }, { order_by => { ahr => "request_time" } });
362         
363         for my $hold ( @$holds ) {
364                 $hold->transit(
365                         $apputils->simplereq(
366                                 'open-ils.cstore',
367                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
368                                 { hold => $hold->id },
369                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
370                         )->[0]
371                 );
372         }
373
374         return $holds;
375 }
376
377
378 __PACKAGE__->register_method(
379         method  => "cancel_hold",
380         api_name        => "open-ils.circ.hold.cancel",
381         notes           => <<"  NOTE");
382         Cancels the specified hold.  The login session
383         is the requestor and if the requestor is different from the usr field
384         on the hold, the requestor must have CANCEL_HOLDS permissions.
385         the hold may be either the hold object or the hold id
386         NOTE
387
388 sub cancel_hold {
389         my($self, $client, $login_session, $holdid) = @_;
390         my $hold;       
391
392         my($user, $evt) = $U->checkses($login_session);
393         return $evt if $evt;
394
395         ( $hold, $evt ) = $U->fetch_hold($holdid);
396         return $evt if $evt;
397
398         if($user->id ne $hold->usr) { #am I allowed to cancel this user's hold?
399                 if($evt = $apputils->check_perms(
400                         $user->id, $user->home_ou, 'CANCEL_HOLDS')) {
401                         return $evt;
402                 }
403         }
404
405         $logger->activity( "User " . $user->id . 
406                 " canceling hold $holdid for user " . $hold->usr );
407
408         return $apputils->simplereq(
409                 'open-ils.storage',
410                 "open-ils.storage.direct.action.hold_request.delete", $hold );
411 }
412
413
414 __PACKAGE__->register_method(
415         method  => "update_hold",
416         api_name        => "open-ils.circ.hold.update",
417         notes           => <<"  NOTE");
418         Updates the specified hold.  The login session
419         is the requestor and if the requestor is different from the usr field
420         on the hold, the requestor must have UPDATE_HOLDS permissions.
421         NOTE
422
423 sub update_hold {
424         my($self, $client, $login_session, $hold) = @_;
425
426         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
427                 $login_session, $hold->usr, 'UPDATE_HOLD' );
428         return $evt if $evt;
429
430         $logger->activity('User ' . $requestor->id . 
431                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
432
433         return $U->storagereq(
434                 "open-ils.storage.direct.action.hold_request.update", $hold );
435 }
436
437
438 __PACKAGE__->register_method(
439         method  => "retrieve_hold_status",
440         api_name        => "open-ils.circ.hold.status.retrieve",
441         notes           => <<"  NOTE");
442         Calculates the current status of the hold.
443         the requestor must have VIEW_HOLD permissions if the hold is for a user
444         other than the requestor.
445         Returns -1  on error (for now)
446         Returns 1 for 'waiting for copy to become available'
447         Returns 2 for 'waiting for copy capture'
448         Returns 3 for 'in transit'
449         Returns 4 for 'arrived'
450         NOTE
451
452 sub retrieve_hold_status {
453         my($self, $client, $login_session, $hold_id) = @_;
454
455
456         my( $requestor, $target, $hold, $copy, $transit, $evt );
457
458         ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
459         return $evt if $evt;
460
461         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
462                 $login_session, $hold->usr, 'VIEW_HOLD' );
463         return $evt if $evt;
464
465         return 1 unless (defined($hold->current_copy));
466         
467         ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
468         return $evt if $evt;
469
470         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
471
472         ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
473         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
474
475         return 3 if defined($hold->capture_time);
476
477         return 2;
478 }
479
480
481
482
483
484 __PACKAGE__->register_method(
485         method  => "capture_copy",
486         api_name        => "open-ils.circ.hold.capture_copy.barcode",
487         notes           => <<"  NOTE");
488         Captures a copy to fulfil a hold
489         Params is login session and copy barcode
490         Optional param is 'flesh'.  If set, we also return the
491         relevant copy and title
492         login mus have COPY_CHECKIN permissions (since this is essentially
493         copy checkin)
494         NOTE
495
496 # XXX deprecate me XXX
497
498 sub capture_copy {
499         my( $self, $client, $login_session, $params ) = @_;
500         my %params = %$params;
501         my $barcode = $params{barcode};
502
503
504         my( $user, $target, $copy, $hold, $evt );
505
506         ( $user, $evt ) = $apputils->checkses($login_session);
507         return $evt if $evt;
508
509         # am I allowed to checkin a copy?
510         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
511         return $evt if $evt;
512
513         $logger->info("Capturing copy with barcode $barcode");
514
515         my $session = $apputils->start_db_session();
516
517         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
518         return $evt if $evt;
519
520         $logger->debug("Capturing copy " . $copy->id);
521
522         #( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
523         ( $hold, $evt ) = $self->find_nearest_permitted_hold($session, $copy, $user);
524         return $evt if $evt;
525
526         warn "Found hold " . $hold->id . "\n";
527         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
528
529         $hold->current_copy($copy->id);
530         $hold->capture_time("now"); 
531
532         #update the hold
533         my $stat = $session->request(
534                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
535         if(!$stat) { throw OpenSRF::EX::ERROR 
536                 ("Error updating hold request " . $copy->id); }
537
538         $copy->status(8); #status on holds shelf
539
540         # if the staff member capturing this item is not at the pickup lib
541         if( $user->home_ou ne $hold->pickup_lib ) {
542                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
543         }
544
545         $copy->editor($user->id);
546         $copy->edit_date("now");
547         $stat = $session->request(
548                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
549         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
550
551         my $payload = { hold => $hold };
552         $payload->{copy} = $copy if $params{flesh_copy};
553
554         if($params{flesh_record}) {
555                 my $record;
556                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
557                 return $evt if $evt;
558                 $record = $apputils->record_to_mvr($record);
559                 $payload->{record} = $record;
560         }
561
562         $apputils->commit_db_session($session);
563
564         return OpenILS::Event->new('ROUTE_ITEM', 
565                 route_to => $hold->pickup_lib, payload => $payload );
566 }
567
568 sub _build_hold_transit {
569         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
570         my $trans = Fieldmapper::action::hold_transit_copy->new;
571
572         $trans->hold($hold->id);
573         $trans->source($user->home_ou);
574         $trans->dest($hold->pickup_lib);
575         $trans->source_send_time("now");
576         $trans->target_copy($copy->id);
577         $trans->copy_status($copy->status);
578
579         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
580         my ($stat) = $meth->run( $login_session, $trans, $session );
581         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
582         else { $copy->status(6); } #status in transit 
583 }
584
585
586 sub find_local_hold {
587         my( $class, $session, $copy, $user ) = @_;
588         #return _find_local_hold_for_copy($session, $copy, $user);
589         return $class->find_nearest_permitted_hold($session, $copy, $user);
590 }
591
592
593 =head deprecated
594 sub _find_local_hold_for_copy {
595
596         my $session = shift;
597         my $copy = shift;
598         my $user = shift;
599         my $evt = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
600
601         # first see if this copy has already been selected to fulfill a hold
602         my $hold  = $session->request(
603                 "open-ils.storage.direct.action.hold_request.search_where",
604                 { current_copy => $copy->id, capture_time => undef } )->gather(1);
605
606         if($hold) {return $hold;}
607
608         $logger->debug("searching for local hold at org " . 
609                 $user->ws_ou . " and copy " . $copy->id);
610
611         my $holdid = $session->request(
612                 "open-ils.storage.action.hold_request.nearest_hold",
613                 $user->ws_ou, $copy->id )->gather(1);
614
615         return (undef, $evt) unless defined $holdid;
616
617         $logger->debug("Found hold id $holdid while ".
618                 "searching nearest hold to " .$user->ws_ou);
619
620         return $apputils->fetch_hold($holdid);
621 }
622 =cut
623
624
625
626
627
628
629
630
631 __PACKAGE__->register_method(
632         method  => "create_hold_transit",
633         api_name        => "open-ils.circ.hold_transit.create",
634         notes           => <<"  NOTE");
635         Creates a new transit object
636         NOTE
637
638 sub create_hold_transit {
639         my( $self, $client, $login_session, $transit, $session ) = @_;
640
641         my( $user, $evt ) = $apputils->checkses($login_session);
642         return $evt if $evt;
643         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
644         return $evt if $evt;
645
646         my $ses;
647         if($session) { $ses = $session; } 
648         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
649
650         return $ses->request(
651                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
652 }
653
654
655 sub fetch_open_hold_by_current_copy {
656         my $class = shift;
657         my $copyid = shift;
658         my $hold = $apputils->simplereq(
659                 'open-ils.cstore', 
660                 'open-ils.cstore.direct.action.hold_request.search.atomic',
661                 { current_copy =>  $copyid , fulfillment_time => undef });
662         return $hold->[0] if ref($hold);
663         return undef;
664 }
665
666 sub fetch_related_holds {
667         my $class = shift;
668         my $copyid = shift;
669         return $apputils->simplereq(
670                 'open-ils.cstore', 
671                 'open-ils.cstore.direct.action.hold_request.search.atomic',
672                 { current_copy =>  $copyid , fulfillment_time => undef });
673 }
674
675
676 __PACKAGE__->register_method (
677         method          => "hold_pull_list",
678         api_name                => "open-ils.circ.hold_pull_list.retrieve",
679         signature       => q/
680                 Returns a list of hold ID's that need to be "pulled"
681                 by a given location
682         /
683 );
684
685 sub hold_pull_list {
686         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
687         my( $reqr, $evt ) = $U->checkses($authtoken);
688         return $evt if $evt;
689
690         my $org = $reqr->ws_ou || $reqr->home_ou;
691         # the perm locaiton shouldn't really matter here since holds
692         # will exist all over and VIEW_HOLDS should be universal
693         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
694         return $evt if $evt;
695
696         return $U->storagereq(
697                 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
698                 $org, $limit, $offset ); # XXX change to workstation
699 }
700
701 __PACKAGE__->register_method (
702         method          => 'fetch_hold_notify',
703         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
704         signature       => q/ 
705                 Returns a list of hold notification objects based on hold id.
706                 @param authtoken The loggin session key
707                 @param holdid The id of the hold whose notifications we want to retrieve
708                 @return An array of hold notification objects, event on error.
709         /
710 );
711
712 sub fetch_hold_notify {
713         my( $self, $conn, $authtoken, $holdid ) = @_;
714         my( $requestor, $evt ) = $U->checkses($authtoken);
715         return $evt if $evt;
716         my ($hold, $patron);
717         ($hold, $evt) = $U->fetch_hold($holdid);
718         return $evt if $evt;
719         ($patron, $evt) = $U->fetch_user($hold->usr);
720         return $evt if $evt;
721
722         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
723         return $evt if $evt;
724
725         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
726         return $U->cstorereq(
727                 'open-ils.cstore.direct.action.hold_notification.search.atomic', {hold => $holdid} );
728 }
729
730
731 __PACKAGE__->register_method (
732         method          => 'create_hold_notify',
733         api_name                => 'open-ils.circ.hold_notification.create',
734         signature       => q/
735                 Creates a new hold notification object
736                 @param authtoken The login session key
737                 @param notification The hold notification object to create
738                 @return ID of the new object on success, Event on error
739                 /
740 );
741 sub create_hold_notify {
742         my( $self, $conn, $authtoken, $notification ) = @_;
743         my( $requestor, $evt ) = $U->checkses($authtoken);
744         return $evt if $evt;
745         my ($hold, $patron);
746         ($hold, $evt) = $U->fetch_hold($notification->hold);
747         return $evt if $evt;
748         ($patron, $evt) = $U->fetch_user($hold->usr);
749         return $evt if $evt;
750
751         # XXX perm depth probably doesn't matter here -- should always be consortium level
752         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
753         return $evt if $evt;
754
755         # Set the proper notifier 
756         $notification->notify_staff($requestor->id);
757         my $id = $U->storagereq(
758                 'open-ils.storage.direct.action.hold_notification.create', $notification );
759         return $U->DB_UPDATE_FAILED($notification) unless $id;
760         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
761         return $id;
762 }
763
764
765 __PACKAGE__->register_method(
766         method  => 'reset_hold',
767         api_name        => 'open-ils.circ.hold.reset',
768         signature       => q/
769                 Un-captures and un-targets a hold, essentially returning
770                 it to the state it was in directly after it was placed,
771                 then attempts to re-target the hold
772                 @param authtoken The login session key
773                 @param holdid The id of the hold
774         /
775 );
776
777
778 sub reset_hold {
779         my( $self, $conn, $auth, $holdid ) = @_;
780         my $reqr;
781         my ($hold, $evt) = $U->fetch_hold($holdid);
782         return $evt if $evt;
783         ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD'); # XXX stronger permission
784         return $evt if $evt;
785         $evt = $self->_reset_hold($reqr, $hold);
786         return $evt if $evt;
787         return 1;
788 }
789
790 sub _reset_hold {
791         my ($self, $reqr, $hold, $session) = @_;
792
793         my $x;
794         if(!$session) {
795                 $x = 1;
796                 $session = $U->start_db_session();
797         }
798
799         $hold->clear_capture_time;
800         $hold->clear_current_copy;
801
802         return $U->DB_UPDATE_FAILED($hold) unless 
803                 $session->request(
804                         'open-ils.storage.direct.action.hold_request.update', $hold )->gather(1);
805
806         $session->request(
807                 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id )->gather(1);
808
809         $U->commit_db_session($session) unless $x;
810         return undef;
811 }
812
813
814 __PACKAGE__->register_method(
815         method => 'fetch_open_title_holds',
816         api_name        => 'open-ils.circ.open_holds.retrieve',
817         signature       => q/
818                 Returns a list ids of un-fulfilled holds for a given title id
819                 @param authtoken The login session key
820                 @param id the id of the item whose holds we want to retrieve
821                 @param type The hold type - M, T, V, C
822         /
823 );
824
825 sub fetch_open_title_holds {
826         my( $self, $conn, $auth, $id, $type, $org ) = @_;
827         my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
828         return $e->event unless $e->checkauth;
829
830         $type ||= "T";
831         $org ||= $e->requestor->ws_ou;
832
833 #       return $e->search_action_hold_request(
834 #               { target => $id, hold_type => $type, fulfillment_time => undef }, {idlist=>1});
835
836         # XXX make me return IDs in the future ^--
837         return $e->search_action_hold_request(
838                 { target => $id, hold_type => $type, fulfillment_time => undef });
839 }
840
841
842
843
844 __PACKAGE__->register_method(
845         method => 'fetch_captured_holds',
846         api_name        => 'open-ils.circ.captured_holds.on_shelf.retrieve',
847         signature       => q/
848                 Returns a list ids of un-fulfilled holds for a given title id
849                 @param authtoken The login session key
850                 @param org The org id of the location in question
851         /
852 );
853 sub fetch_captured_holds {
854         my( $self, $conn, $auth, $org ) = @_;
855
856         my $e = OpenILS::Utils::Editor->new(authtoken => $auth);
857         return $e->event unless $e->checkauth;
858         return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
859
860         $org ||= $e->requestor->ws_ou;
861
862         my $holds = $e->search_action_hold_request(
863                 { 
864                         capture_time            => { "!=" => undef },
865                         current_copy            => { "!=" => undef },
866                         fulfillment_time        => undef,
867                         pickup_lib                      => $org
868                 }
869         );
870
871         my @res;
872         my $stat = $U->copy_status_from_name('on holds shelf');
873         for my $h (@$holds) {
874                 my $copy = $e->retrieve_asset_copy($h->current_copy)
875                         or return $e->event;
876                 push( @res, $h ) if $copy->status == $stat->id; # eventually, push IDs here
877         }
878
879         return \@res;
880 }
881
882
883
884
885
886 __PACKAGE__->register_method(
887         method  => "check_title_hold",
888         api_name        => "open-ils.circ.title_hold.is_possible",
889         notes           => q/
890                 Determines if a hold were to be placed by a given user,
891                 whether or not said hold would have any potential copies
892                 to fulfill it.
893                 @param authtoken The login session key
894                 @param params A hash of named params including:
895                         patronid  - the id of the hold recipient
896                         titleid (brn) - the id of the title to be held
897                         depth   - the hold range depth (defaults to 0)
898         /);
899
900 sub check_title_hold {
901         my( $self, $client, $authtoken, $params ) = @_;
902
903         my %params              = %$params;
904         my $titleid             = $params{titleid} ||"";
905         my $mrid                        = $params{mrid} ||"";
906         my $depth               = $params{depth} || 0;
907         my $pickup_lib  = $params{pickup_lib};
908         my $hold_type   = $params{hold_type} || 'T';
909
910         my $e = new_editor(authtoken=>$authtoken);
911         return $e->event unless $e->checkauth;
912         my $patron = $e->retrieve_actor_user($params{patronid})
913                 or return $e->event;
914         return $e->event unless $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou);
915
916         return OpenILS::Event->new('PATRON_BARRED') 
917                 if $patron->barred and 
918                         ($patron->barred =~ /t/i or $patron->barred == 1);
919
920         my $rangelib    = $params{range_lib} || $patron->home_ou;
921
922         my $request_lib = $e->retrieve_actor_org_unit($e->requestor->ws_ou)
923                 or return $e->event;
924
925         if( $hold_type eq 'T' ) {
926                 return _check_title_hold_is_possible(
927                         $titleid, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
928         }
929
930         if( $hold_type eq 'M' ) {
931                 my $maps = $e->search_metabib_source_map({metarecord=>$mrid});
932                 my @recs = map { $_->source } @$maps;
933                 for my $rec (@recs) {
934                         return 1 if (_check_title_hold_is_possible(
935                                 $rec, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib));
936                 }
937         }
938 }
939
940
941
942 sub _check_title_hold_is_possible {
943         my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
944
945         my $limit       = 10;
946         my $offset      = 0;
947         my $title;
948
949         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
950
951         while( $title = $U->storagereq(
952                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
953                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
954
955                 last unless 
956                         ref($title) and 
957                         ref($title->call_numbers) and 
958                         @{$title->call_numbers};
959
960                 for my $cn (@{$title->call_numbers}) {
961         
962                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
963         
964                         for my $copy (@{$cn->copies}) {
965         
966                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
967         
968                                 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
969                                         {       patron                          => $patron, 
970                                                 requestor                       => $requestor, 
971                                                 copy                                    => $copy,
972                                                 title                                   => $title, 
973                                                 title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
974                                                 pickup_lib                      => $pickup_lib,
975                                                 request_lib                     => $request_lib 
976                                         } 
977                                 );
978         
979                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
980                         }
981                 }
982
983                 $offset += $limit;
984         }
985         return 0;
986 }
987
988
989
990 sub find_nearest_permitted_hold {
991
992         my $class       = shift;
993         my $session = shift;
994         my $copy                = shift;
995         my $user                = shift;
996         my $evt         = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
997
998         # first see if this copy has already been selected to fulfill a hold
999         my $hold  = $session->request(
1000                 "open-ils.storage.direct.action.hold_request.search_where",
1001                 { current_copy => $copy->id, capture_time => undef } )->gather(1);
1002
1003         if( $hold ) {
1004                 $logger->info("hold found which can be fulfilled by copy ".$copy->id);
1005                 return $hold;
1006         }
1007
1008         # We know this hold is permitted, so just return it
1009         return $hold if $hold;
1010
1011         $logger->debug("searching for potential holds at org ". 
1012                 $user->ws_ou." and copy ".$copy->id);
1013
1014         my $holds = $session->request(
1015                 "open-ils.storage.action.hold_request.nearest_hold.atomic",
1016                 $user->ws_ou, $copy->id, 5 )->gather(1);
1017
1018         return (undef, $evt) unless @$holds;
1019
1020         # for each potential hold, we have to run the permit script
1021         # to make sure the hold is actually permitted.
1022
1023         for my $holdid (@$holds) {
1024                 next unless $holdid;
1025                 $logger->info("Checking if hold $holdid is permitted for user ".$user->id);
1026
1027                 my ($hold) = $U->fetch_hold($holdid);
1028                 next unless $hold;
1029                 my ($reqr) = $U->fetch_user($hold->requestor);
1030
1031                 return ($hold) if OpenILS::Utils::PermitHold::permit_copy_hold(
1032                         {
1033                                 patron_id                       => $hold->usr,
1034                                 requestor                       => $reqr->id,
1035                                 copy                                    => $copy,
1036                                 pickup_lib                      => $hold->pickup_lib,
1037                                 request_lib                     => $hold->request_lib 
1038                         } 
1039                 );
1040         }
1041
1042         return (undef, $evt);
1043 }
1044
1045
1046
1047
1048
1049 1;