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