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