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