]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
more constants updates
[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
277 The login session is the requestor and if the requestor is
278 different from the user, then the requestor must have VIEW_HOLD permissions.
279 NOTE
280
281
282 sub retrieve_holds_by_id {
283         my($self, $client, $login_session, $hold_id) = @_;
284
285         #FIXME
286         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
287         #       $login_session, $user_id, 'VIEW_HOLD' );
288         #return $evt if $evt;
289
290         my $holds = $apputils->simplereq(
291                 'open-ils.cstore',
292                 "open-ils.cstore.direct.action.hold_request.search.atomic",
293                 { id =>  $hold_id , fulfillment_time => undef }, { order_by => { ahr => "request_time" } });
294         
295         for my $hold ( @$holds ) {
296                 $hold->transit(
297                         $apputils->simplereq(
298                                 'open-ils.cstore',
299                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
300                                 { hold => $hold->id },
301                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
302                         )->[0]
303                 );
304         }
305
306         return $holds;
307 }
308
309
310 __PACKAGE__->register_method(
311         method  => "retrieve_holds",
312         api_name        => "open-ils.circ.holds.retrieve",
313         notes           => <<NOTE);
314 Retrieves all the holds, with hold transits attached, for the specified
315 user id.  The login session is the requestor and if the requestor is
316 different from the user, then the requestor must have VIEW_HOLD permissions.
317 NOTE
318
319
320 sub retrieve_holds {
321         my($self, $client, $login_session, $user_id) = @_;
322
323         my( $user, $target, $evt ) = $apputils->checkses_requestor(
324                 $login_session, $user_id, 'VIEW_HOLD' );
325         return $evt if $evt;
326
327         my $holds = $apputils->simplereq(
328                 'open-ils.cstore',
329                 "open-ils.cstore.direct.action.hold_request.search.atomic",
330                 { 
331                         usr =>  $user_id , 
332                         fulfillment_time => undef,
333                         cancel_time => undef,
334                 }, 
335                 { order_by => { ahr => "request_time" } }
336         );
337         
338         for my $hold ( @$holds ) {
339                 $hold->transit(
340                         $apputils->simplereq(
341                                 'open-ils.cstore',
342                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
343                                 { hold => $hold->id },
344                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
345                         )->[0]
346                 );
347         }
348
349         return $holds;
350 }
351
352 __PACKAGE__->register_method(
353         method  => "retrieve_holds_by_pickup_lib",
354         api_name        => "open-ils.circ.holds.retrieve_by_pickup_lib",
355         notes           => <<NOTE);
356 Retrieves all the holds, with hold transits attached, for the specified
357 pickup_ou id. 
358 NOTE
359
360
361 sub retrieve_holds_by_pickup_lib {
362         my($self, $client, $login_session, $ou_id) = @_;
363
364         #FIXME -- put an appropriate permission check here
365         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
366         #       $login_session, $user_id, 'VIEW_HOLD' );
367         #return $evt if $evt;
368
369         my $holds = $apputils->simplereq(
370                 'open-ils.cstore',
371                 "open-ils.cstore.direct.action.hold_request.search.atomic",
372                 { 
373                         pickup_lib =>  $ou_id , 
374                         fulfillment_time => undef,
375                         cancel_time => undef
376                 }, 
377                 { order_by => { ahr => "request_time" } });
378         
379         for my $hold ( @$holds ) {
380                 $hold->transit(
381                         $apputils->simplereq(
382                                 'open-ils.cstore',
383                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
384                                 { hold => $hold->id },
385                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
386                         )->[0]
387                 );
388         }
389
390         return $holds;
391 }
392
393
394 __PACKAGE__->register_method(
395         method  => "cancel_hold",
396         api_name        => "open-ils.circ.hold.cancel",
397         notes           => <<"  NOTE");
398         Cancels the specified hold.  The login session
399         is the requestor and if the requestor is different from the usr field
400         on the hold, the requestor must have CANCEL_HOLDS permissions.
401         the hold may be either the hold object or the hold id
402         NOTE
403
404 sub cancel_hold {
405         my($self, $client, $auth, $holdid) = @_;
406
407         my $e = new_editor(authtoken=>$auth, xact=>1);
408         return $e->event unless $e->checkauth;
409
410         my $hold = $e->retrieve_action_hold_request($holdid)
411                 or return $e->event;
412
413         if( $e->requestor->id ne $hold->usr ) {
414                 return $e->event unless $e->allowed('CANCEL_HOLDS');
415         }
416
417         return 1 if $hold->cancel_time;
418
419         # If the hold is captured, reset the copy status
420         if( $hold->capture_time and $hold->current_copy ) {
421
422                 my $copy = $e->retrieve_asset_copy($hold->current_copy)
423                         or return $e->event;
424
425                 if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
426                         $logger->info("setting copy to status 'reshelving' on hold cancel");
427                         $copy->status(OILS_COPY_STATUS_RESHELVING);
428                         $copy->editor($e->requestor->id);
429                         $copy->edit_date('now');
430                         $e->update_asset_copy($copy) or return $e->event;
431                 }
432         }
433
434         $hold->cancel_time('now');
435         $e->update_action_hold_request($hold)
436                 or return $e->event;
437
438         $e->commit;
439         return 1;
440 }
441
442
443 __PACKAGE__->register_method(
444         method  => "update_hold",
445         api_name        => "open-ils.circ.hold.update",
446         notes           => <<"  NOTE");
447         Updates the specified hold.  The login session
448         is the requestor and if the requestor is different from the usr field
449         on the hold, the requestor must have UPDATE_HOLDS permissions.
450         NOTE
451
452 sub update_hold {
453         my($self, $client, $login_session, $hold) = @_;
454
455         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
456                 $login_session, $hold->usr, 'UPDATE_HOLD' );
457         return $evt if $evt;
458
459         $logger->activity('User ' . $requestor->id . 
460                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
461
462         return $U->storagereq(
463                 "open-ils.storage.direct.action.hold_request.update", $hold );
464 }
465
466
467 __PACKAGE__->register_method(
468         method  => "retrieve_hold_status",
469         api_name        => "open-ils.circ.hold.status.retrieve",
470         notes           => <<"  NOTE");
471         Calculates the current status of the hold.
472         the requestor must have VIEW_HOLD permissions if the hold is for a user
473         other than the requestor.
474         Returns -1  on error (for now)
475         Returns 1 for 'waiting for copy to become available'
476         Returns 2 for 'waiting for copy capture'
477         Returns 3 for 'in transit'
478         Returns 4 for 'arrived'
479         NOTE
480
481 sub retrieve_hold_status {
482         my($self, $client, $login_session, $hold_id) = @_;
483
484
485         my( $requestor, $target, $hold, $copy, $transit, $evt );
486
487         ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
488         return $evt if $evt;
489
490         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
491                 $login_session, $hold->usr, 'VIEW_HOLD' );
492         return $evt if $evt;
493
494         return 1 unless (defined($hold->current_copy));
495         
496         ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
497         return $evt if $evt;
498
499         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
500
501         ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
502         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
503
504         return 3 if defined($hold->capture_time);
505
506         return 2;
507 }
508
509
510
511
512
513 =head DEPRECATED
514 __PACKAGE__->register_method(
515         method  => "capture_copy",
516         api_name        => "open-ils.circ.hold.capture_copy.barcode",
517         notes           => <<"  NOTE");
518         Captures a copy to fulfil a hold
519         Params is login session and copy barcode
520         Optional param is 'flesh'.  If set, we also return the
521         relevant copy and title
522         login mus have COPY_CHECKIN permissions (since this is essentially
523         copy checkin)
524         NOTE
525
526 # XXX deprecate me XXX
527
528 sub capture_copy {
529         my( $self, $client, $login_session, $params ) = @_;
530         my %params = %$params;
531         my $barcode = $params{barcode};
532
533
534         my( $user, $target, $copy, $hold, $evt );
535
536         ( $user, $evt ) = $apputils->checkses($login_session);
537         return $evt if $evt;
538
539         # am I allowed to checkin a copy?
540         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
541         return $evt if $evt;
542
543         $logger->info("Capturing copy with barcode $barcode");
544
545         my $session = $apputils->start_db_session();
546
547         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
548         return $evt if $evt;
549
550         $logger->debug("Capturing copy " . $copy->id);
551
552         #( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
553         ( $hold, $evt ) = $self->find_nearest_permitted_hold($session, $copy, $user);
554         return $evt if $evt;
555
556         warn "Found hold " . $hold->id . "\n";
557         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
558
559         $hold->current_copy($copy->id);
560         $hold->capture_time("now"); 
561
562         #update the hold
563         my $stat = $session->request(
564                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
565         if(!$stat) { throw OpenSRF::EX::ERROR 
566                 ("Error updating hold request " . $copy->id); }
567
568         $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); #status on holds shelf
569
570         # if the staff member capturing this item is not at the pickup lib
571         if( $user->home_ou ne $hold->pickup_lib ) {
572                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
573         }
574
575         $copy->editor($user->id);
576         $copy->edit_date("now");
577         $stat = $session->request(
578                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
579         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
580
581         my $payload = { hold => $hold };
582         $payload->{copy} = $copy if $params{flesh_copy};
583
584         if($params{flesh_record}) {
585                 my $record;
586                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
587                 return $evt if $evt;
588                 $record = $apputils->record_to_mvr($record);
589                 $payload->{record} = $record;
590         }
591
592         $apputils->commit_db_session($session);
593
594         return OpenILS::Event->new('ROUTE_ITEM', 
595                 route_to => $hold->pickup_lib, payload => $payload );
596 }
597
598 sub _build_hold_transit {
599         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
600         my $trans = Fieldmapper::action::hold_transit_copy->new;
601
602         $trans->hold($hold->id);
603         $trans->source($user->home_ou);
604         $trans->dest($hold->pickup_lib);
605         $trans->source_send_time("now");
606         $trans->target_copy($copy->id);
607         $trans->copy_status($copy->status);
608
609         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
610         my ($stat) = $meth->run( $login_session, $trans, $session );
611         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
612         else { $copy->status(6); } #status in transit 
613 }
614
615
616
617 __PACKAGE__->register_method(
618         method  => "create_hold_transit",
619         api_name        => "open-ils.circ.hold_transit.create",
620         notes           => <<"  NOTE");
621         Creates a new transit object
622         NOTE
623
624 sub create_hold_transit {
625         my( $self, $client, $login_session, $transit, $session ) = @_;
626
627         my( $user, $evt ) = $apputils->checkses($login_session);
628         return $evt if $evt;
629         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
630         return $evt if $evt;
631
632         my $ses;
633         if($session) { $ses = $session; } 
634         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
635
636         return $ses->request(
637                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
638 }
639
640 =cut
641
642
643 sub find_local_hold {
644         my( $class, $session, $copy, $user ) = @_;
645         return $class->find_nearest_permitted_hold($session, $copy, $user);
646 }
647
648
649
650
651
652
653 sub fetch_open_hold_by_current_copy {
654         my $class = shift;
655         my $copyid = shift;
656         my $hold = $apputils->simplereq(
657                 'open-ils.cstore', 
658                 'open-ils.cstore.direct.action.hold_request.search.atomic',
659                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
660         return $hold->[0] if ref($hold);
661         return undef;
662 }
663
664 sub fetch_related_holds {
665         my $class = shift;
666         my $copyid = shift;
667         return $apputils->simplereq(
668                 'open-ils.cstore', 
669                 'open-ils.cstore.direct.action.hold_request.search.atomic',
670                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
671 }
672
673
674 __PACKAGE__->register_method (
675         method          => "hold_pull_list",
676         api_name                => "open-ils.circ.hold_pull_list.retrieve",
677         signature       => q/
678                 Returns a list of hold ID's that need to be "pulled"
679                 by a given location
680         /
681 );
682
683 sub hold_pull_list {
684         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
685         my( $reqr, $evt ) = $U->checkses($authtoken);
686         return $evt if $evt;
687
688         my $org = $reqr->ws_ou || $reqr->home_ou;
689         # the perm locaiton shouldn't really matter here since holds
690         # will exist all over and VIEW_HOLDS should be universal
691         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
692         return $evt if $evt;
693
694         return $U->storagereq(
695                 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
696                 $org, $limit, $offset ); 
697 }
698
699 __PACKAGE__->register_method (
700         method          => 'fetch_hold_notify',
701         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
702         signature       => q/ 
703                 Returns a list of hold notification objects based on hold id.
704                 @param authtoken The loggin session key
705                 @param holdid The id of the hold whose notifications we want to retrieve
706                 @return An array of hold notification objects, event on error.
707         /
708 );
709
710 sub fetch_hold_notify {
711         my( $self, $conn, $authtoken, $holdid ) = @_;
712         my( $requestor, $evt ) = $U->checkses($authtoken);
713         return $evt if $evt;
714         my ($hold, $patron);
715         ($hold, $evt) = $U->fetch_hold($holdid);
716         return $evt if $evt;
717         ($patron, $evt) = $U->fetch_user($hold->usr);
718         return $evt if $evt;
719
720         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
721         return $evt if $evt;
722
723         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
724         return $U->cstorereq(
725                 'open-ils.cstore.direct.action.hold_notification.search.atomic', {hold => $holdid} );
726 }
727
728
729 __PACKAGE__->register_method (
730         method          => 'create_hold_notify',
731         api_name                => 'open-ils.circ.hold_notification.create',
732         signature       => q/
733                 Creates a new hold notification object
734                 @param authtoken The login session key
735                 @param notification The hold notification object to create
736                 @return ID of the new object on success, Event on error
737                 /
738 );
739 sub create_hold_notify {
740         my( $self, $conn, $authtoken, $notification ) = @_;
741         my( $requestor, $evt ) = $U->checkses($authtoken);
742         return $evt if $evt;
743         my ($hold, $patron);
744         ($hold, $evt) = $U->fetch_hold($notification->hold);
745         return $evt if $evt;
746         ($patron, $evt) = $U->fetch_user($hold->usr);
747         return $evt if $evt;
748
749         # XXX perm depth probably doesn't matter here -- should always be consortium level
750         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
751         return $evt if $evt;
752
753         # Set the proper notifier 
754         $notification->notify_staff($requestor->id);
755         my $id = $U->storagereq(
756                 'open-ils.storage.direct.action.hold_notification.create', $notification );
757         return $U->DB_UPDATE_FAILED($notification) unless $id;
758         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
759         return $id;
760 }
761
762
763 __PACKAGE__->register_method(
764         method  => 'reset_hold',
765         api_name        => 'open-ils.circ.hold.reset',
766         signature       => q/
767                 Un-captures and un-targets a hold, essentially returning
768                 it to the state it was in directly after it was placed,
769                 then attempts to re-target the hold
770                 @param authtoken The login session key
771                 @param holdid The id of the hold
772         /
773 );
774
775
776 sub reset_hold {
777         my( $self, $conn, $auth, $holdid ) = @_;
778         my $reqr;
779         my ($hold, $evt) = $U->fetch_hold($holdid);
780         return $evt if $evt;
781         ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD'); # XXX stronger permission
782         return $evt if $evt;
783         $evt = $self->_reset_hold($reqr, $hold);
784         return $evt if $evt;
785         return 1;
786 }
787
788 sub _reset_hold {
789         my ($self, $reqr, $hold, $session) = @_;
790
791         my $x;
792         if(!$session) {
793                 $x = 1;
794                 $session = $U->start_db_session();
795         }
796
797         $hold->clear_capture_time;
798         $hold->clear_current_copy;
799
800         return $U->DB_UPDATE_FAILED($hold) unless 
801                 $session->request(
802                         'open-ils.storage.direct.action.hold_request.update', $hold )->gather(1);
803
804         $session->request(
805                 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id )->gather(1);
806
807         $U->commit_db_session($session) unless $x;
808         return undef;
809 }
810
811
812 __PACKAGE__->register_method(
813         method => 'fetch_open_title_holds',
814         api_name        => 'open-ils.circ.open_holds.retrieve',
815         signature       => q/
816                 Returns a list ids of un-fulfilled holds for a given title id
817                 @param authtoken The login session key
818                 @param id the id of the item whose holds we want to retrieve
819                 @param type The hold type - M, T, V, C
820         /
821 );
822
823 sub fetch_open_title_holds {
824         my( $self, $conn, $auth, $id, $type, $org ) = @_;
825         my $e = new_editor( authtoken => $auth );
826         return $e->event unless $e->checkauth;
827
828         $type ||= "T";
829         $org ||= $e->requestor->ws_ou;
830
831 #       return $e->search_action_hold_request(
832 #               { target => $id, hold_type => $type, fulfillment_time => undef }, {idlist=>1});
833
834         # XXX make me return IDs in the future ^--
835         return $e->search_action_hold_request(
836                 { target => $id, cancel_time => undef, hold_type => $type, fulfillment_time => undef });
837 }
838
839
840
841
842 __PACKAGE__->register_method(
843         method => 'fetch_captured_holds',
844         api_name        => 'open-ils.circ.captured_holds.on_shelf.retrieve',
845         signature       => q/
846                 Returns a list ids of un-fulfilled holds for a given title id
847                 @param authtoken The login session key
848                 @param org The org id of the location in question
849         /
850 );
851 sub fetch_captured_holds {
852         my( $self, $conn, $auth, $org ) = @_;
853
854         my $e = new_editor(authtoken => $auth);
855         return $e->event unless $e->checkauth;
856         return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
857
858         $org ||= $e->requestor->ws_ou;
859
860         my $holds = $e->search_action_hold_request(
861                 { 
862                         capture_time            => { "!=" => undef },
863                         current_copy            => { "!=" => undef },
864                         fulfillment_time        => undef,
865                         pickup_lib                      => $org,
866                         cancel_time                     => undef,
867                 }
868         );
869
870         my @res;
871         my $stat = OILS_COPY_STATUS_ON_HOLDS_SHELF;
872         for my $h (@$holds) {
873                 my $copy = $e->retrieve_asset_copy($h->current_copy)
874                         or return $e->event;
875                 push( @res, $h ) if $copy->status == $stat->id; # eventually, push IDs here
876         }
877
878         return \@res;
879 }
880
881
882
883
884
885 __PACKAGE__->register_method(
886         method  => "check_title_hold",
887         api_name        => "open-ils.circ.title_hold.is_possible",
888         notes           => q/
889                 Determines if a hold were to be placed by a given user,
890                 whether or not said hold would have any potential copies
891                 to fulfill it.
892                 @param authtoken The login session key
893                 @param params A hash of named params including:
894                         patronid  - the id of the hold recipient
895                         titleid (brn) - the id of the title to be held
896                         depth   - the hold range depth (defaults to 0)
897         /);
898
899 sub check_title_hold {
900         my( $self, $client, $authtoken, $params ) = @_;
901
902         my %params              = %$params;
903         my $titleid             = $params{titleid} ||"";
904         my $mrid                        = $params{mrid} ||"";
905         my $depth               = $params{depth} || 0;
906         my $pickup_lib  = $params{pickup_lib};
907         my $hold_type   = $params{hold_type} || 'T';
908
909         my $e = new_editor(authtoken=>$authtoken);
910         return $e->event unless $e->checkauth;
911         my $patron = $e->retrieve_actor_user($params{patronid})
912                 or return $e->event;
913         return $e->event unless $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou);
914
915         return OpenILS::Event->new('PATRON_BARRED') 
916                 if $patron->barred and 
917                         ($patron->barred =~ /t/i or $patron->barred == 1);
918
919         my $rangelib    = $params{range_lib} || $patron->home_ou;
920
921         my $request_lib = $e->retrieve_actor_org_unit($e->requestor->ws_ou)
922                 or return $e->event;
923
924         if( $hold_type eq 'T' ) {
925                 return _check_title_hold_is_possible(
926                         $titleid, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
927         }
928
929         if( $hold_type eq 'M' ) {
930                 my $maps = $e->search_metabib_source_map({metarecord=>$mrid});
931                 my @recs = map { $_->source } @$maps;
932                 for my $rec (@recs) {
933                         return 1 if (_check_title_hold_is_possible(
934                                 $rec, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib));
935                 }
936         }
937 }
938
939
940
941 sub _check_title_hold_is_possible {
942         my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
943
944         my $limit       = 10;
945         my $offset      = 0;
946         my $title;
947
948         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
949
950         while( $title = $U->storagereq(
951                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
952                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
953
954                 last unless 
955                         ref($title) and 
956                         ref($title->call_numbers) and 
957                         @{$title->call_numbers};
958
959                 for my $cn (@{$title->call_numbers}) {
960         
961                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
962         
963                         for my $copy (@{$cn->copies}) {
964         
965                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
966         
967                                 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
968                                         {       patron                          => $patron, 
969                                                 requestor                       => $requestor, 
970                                                 copy                                    => $copy,
971                                                 title                                   => $title, 
972                                                 title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
973                                                 pickup_lib                      => $pickup_lib,
974                                                 request_lib                     => $request_lib 
975                                         } 
976                                 );
977         
978                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
979                         }
980                 }
981
982                 $offset += $limit;
983         }
984         return 0;
985 }
986
987
988
989 sub find_nearest_permitted_hold {
990
991         my $class       = shift;
992         my $session = shift;
993         my $copy                = shift;
994         my $user                = shift;
995         my $evt         = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
996
997         # first see if this copy has already been selected to fulfill a hold
998         my $hold  = $session->request(
999                 "open-ils.storage.direct.action.hold_request.search_where",
1000                 { current_copy => $copy->id, cancel_time => undef, capture_time => undef } )->gather(1);
1001
1002         if( $hold ) {
1003                 $logger->info("hold found which can be fulfilled by copy ".$copy->id);
1004                 return $hold;
1005         }
1006
1007         # We know this hold is permitted, so just return it
1008         return $hold if $hold;
1009
1010         $logger->debug("searching for potential holds at org ". 
1011                 $user->ws_ou." and copy ".$copy->id);
1012
1013         my $holds = $session->request(
1014                 "open-ils.storage.action.hold_request.nearest_hold.atomic",
1015                 $user->ws_ou, $copy->id, 5 )->gather(1);
1016
1017         return (undef, $evt) unless @$holds;
1018
1019         # for each potential hold, we have to run the permit script
1020         # to make sure the hold is actually permitted.
1021
1022         for my $holdid (@$holds) {
1023                 next unless $holdid;
1024                 $logger->info("Checking if hold $holdid is permitted for user ".$user->id);
1025
1026                 my ($hold) = $U->fetch_hold($holdid);
1027                 next unless $hold;
1028                 my ($reqr) = $U->fetch_user($hold->requestor);
1029
1030                 return ($hold) if OpenILS::Utils::PermitHold::permit_copy_hold(
1031                         {
1032                                 patron_id                       => $hold->usr,
1033                                 requestor                       => $reqr->id,
1034                                 copy                                    => $copy,
1035                                 pickup_lib                      => $hold->pickup_lib,
1036                                 request_lib                     => $hold->request_lib 
1037                         } 
1038                 );
1039         }
1040
1041         return (undef, $evt);
1042 }
1043
1044
1045 #__PACKAGE__->register_method(
1046 #);
1047
1048
1049
1050
1051
1052 1;