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