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