1 # ---------------------------------------------------------------
2 # Copyright (C) 2005 Georgia Public Library Service
3 # Bill Erickson <highfalutin@gmail.com>
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.
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 # ---------------------------------------------------------------
17 package OpenILS::Application::Circ::Holds;
18 use base qw/OpenSRF::Application/;
19 use strict; use warnings;
20 use OpenILS::Application::AppUtils;
22 use OpenSRF::EX qw(:try);
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/;
30 my $apputils = "OpenILS::Application::AppUtils";
35 __PACKAGE__->register_method(
36 method => "create_hold",
37 api_name => "open-ils.circ.holds.create",
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
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.
50 If the recipient does not have permission to place multiple holds
51 on a single title and said operation is attempted, a permission
56 __PACKAGE__->register_method(
57 method => "create_hold",
58 api_name => "open-ils.circ.holds.create.override",
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
67 my( $self, $conn, $auth, @holds ) = @_;
68 my $e = new_editor(authtoken=>$auth, xact=>1);
69 return $e->event unless $e->checkauth;
71 my $override = 1 if $self->api_name =~ /override/;
73 my $holds = (ref($holds[0] eq 'ARRAY')) ? $holds[0] : [@holds];
75 for my $hold (@$holds) {
80 my $requestor = $e->requestor;
81 my $recipient = $requestor;
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;
92 # Now make sure the recipient is allowed to receive the specified hold
94 my $porg = $recipient->home_ou;
95 my $rid = $e->requestor->id;
96 my $t = $hold->hold_type;
98 # See if a duplicate hold already exists
100 usr => $recipient->id,
102 fulfillment_time => undef,
103 target => $hold->target,
104 cancel_time => undef,
107 $sargs->{holdable_formats} = $hold->holdable_formats if $t eq 'M';
109 my $existing = $e->search_action_hold_request($sargs);
110 push( @events, OpenILS::Event->new('HOLD_EXISTS')) if @$existing;
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'); }
117 return $pevt if $pevt;
121 for my $evt (@events) {
123 my $name = $evt->{textcode};
124 return $e->event unless $e->allowed("$name.override", $porg);
134 # return $e->event unless $e->allowed('CREATE_DUPLICATE_HOLDS', $porg);
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;
151 my( $self, $client, $login_session, @holds) = @_;
153 if(!@holds){return 0;}
154 my( $user, $evt ) = $apputils->checkses($login_session);
158 if(ref($holds[0]) eq 'ARRAY') {
160 } else { $holds = [ @holds ]; }
162 $logger->debug("Iterating over holds requests...");
164 for my $hold (@$holds) {
167 my $type = $hold->hold_type;
169 $logger->activity("User " . $user->id .
170 " creating new hold of type $type for user " . $hold->usr);
173 if($user->id ne $hold->usr) {
174 ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
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; }
190 # is this user allowed to have holds of this type?
191 $perm = _check_holds_perm($type, $hold->requestor, $recipient->home_ou);
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;}
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;
207 my $resp = $apputils->simplereq(
209 'open-ils.storage.direct.action.hold_request.create', $hold );
212 return OpenSRF::EX::ERROR ("Error creating hold");
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) = @_;
226 if($evt = $apputils->check_perms(
227 $user_id, $org_id, "MR_HOLDS")) {
231 } elsif ($type eq "T") {
232 if($evt = $apputils->check_perms(
233 $user_id, $org_id, "TITLE_HOLDS")) {
237 } elsif($type eq "V") {
238 if($evt = $apputils->check_perms(
239 $user_id, $org_id, "VOLUME_HOLDS")) {
243 } elsif($type eq "C") {
244 if($evt = $apputils->check_perms(
245 $user_id, $org_id, "COPY_HOLDS")) {
253 # tests if the given user is allowed to place holds on another's behalf
254 sub _check_request_holds_perm {
257 if(my $evt = $apputils->check_perms(
258 $user_id, $org_id, "REQUEST_HOLDS")) {
263 sub _check_request_holds_override {
266 if(my $evt = $apputils->check_perms(
267 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
272 __PACKAGE__->register_method(
273 method => "retrieve_holds_by_id",
274 api_name => "open-ils.circ.holds.retrieve_by_id",
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.
282 sub retrieve_holds_by_id {
283 my($self, $client, $login_session, $hold_id) = @_;
286 #my( $user, $target, $evt ) = $apputils->checkses_requestor(
287 # $login_session, $user_id, 'VIEW_HOLD' );
288 #return $evt if $evt;
290 my $holds = $apputils->simplereq(
292 "open-ils.cstore.direct.action.hold_request.search.atomic",
293 { id => $hold_id , fulfillment_time => undef }, { order_by => { ahr => "request_time" } });
295 for my $hold ( @$holds ) {
297 $apputils->simplereq(
299 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
300 { hold => $hold->id },
301 { order_by => { ahtc => 'id desc' }, limit => 1 }
310 __PACKAGE__->register_method(
311 method => "retrieve_holds",
312 api_name => "open-ils.circ.holds.retrieve",
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.
321 my($self, $client, $login_session, $user_id) = @_;
323 my( $user, $target, $evt ) = $apputils->checkses_requestor(
324 $login_session, $user_id, 'VIEW_HOLD' );
327 my $holds = $apputils->simplereq(
329 "open-ils.cstore.direct.action.hold_request.search.atomic",
332 fulfillment_time => undef,
333 cancel_time => undef,
335 { order_by => { ahr => "request_time" } }
338 for my $hold ( @$holds ) {
340 $apputils->simplereq(
342 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
343 { hold => $hold->id },
344 { order_by => { ahtc => 'id desc' }, limit => 1 }
352 __PACKAGE__->register_method(
353 method => "retrieve_holds_by_pickup_lib",
354 api_name => "open-ils.circ.holds.retrieve_by_pickup_lib",
356 Retrieves all the holds, with hold transits attached, for the specified
361 sub retrieve_holds_by_pickup_lib {
362 my($self, $client, $login_session, $ou_id) = @_;
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;
369 my $holds = $apputils->simplereq(
371 "open-ils.cstore.direct.action.hold_request.search.atomic",
373 pickup_lib => $ou_id ,
374 fulfillment_time => undef,
377 { order_by => { ahr => "request_time" } });
379 for my $hold ( @$holds ) {
381 $apputils->simplereq(
383 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
384 { hold => $hold->id },
385 { order_by => { ahtc => 'id desc' }, limit => 1 }
394 __PACKAGE__->register_method(
395 method => "cancel_hold",
396 api_name => "open-ils.circ.hold.cancel",
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
405 my($self, $client, $auth, $holdid) = @_;
407 my $e = new_editor(authtoken=>$auth, xact=>1);
408 return $e->event unless $e->checkauth;
410 my $hold = $e->retrieve_action_hold_request($holdid)
413 if( $e->requestor->id ne $hold->usr ) {
414 return $e->event unless $e->allowed('CANCEL_HOLDS');
417 return 1 if $hold->cancel_time;
419 # If the hold is captured, reset the copy status
420 if( $hold->capture_time and $hold->current_copy ) {
422 my $copy = $e->retrieve_asset_copy($hold->current_copy)
425 if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
426 $logger->info("setting copy to status 'reshelving' on hold cancel");
427 $copy->status(OILS_COPY_STATUS_RESHELVING);
428 $copy->editor($e->requestor->id);
429 $copy->edit_date('now');
430 $e->update_asset_copy($copy) or return $e->event;
434 $hold->cancel_time('now');
435 $e->update_action_hold_request($hold)
443 __PACKAGE__->register_method(
444 method => "update_hold",
445 api_name => "open-ils.circ.hold.update",
447 Updates the specified hold. The login session
448 is the requestor and if the requestor is different from the usr field
449 on the hold, the requestor must have UPDATE_HOLDS permissions.
453 my($self, $client, $login_session, $hold) = @_;
455 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
456 $login_session, $hold->usr, 'UPDATE_HOLD' );
459 $logger->activity('User ' . $requestor->id .
460 ' updating hold ' . $hold->id . ' for user ' . $target->id );
462 return $U->storagereq(
463 "open-ils.storage.direct.action.hold_request.update", $hold );
467 __PACKAGE__->register_method(
468 method => "retrieve_hold_status",
469 api_name => "open-ils.circ.hold.status.retrieve",
471 Calculates the current status of the hold.
472 the requestor must have VIEW_HOLD permissions if the hold is for a user
473 other than the requestor.
474 Returns -1 on error (for now)
475 Returns 1 for 'waiting for copy to become available'
476 Returns 2 for 'waiting for copy capture'
477 Returns 3 for 'in transit'
478 Returns 4 for 'arrived'
481 sub retrieve_hold_status {
482 my($self, $client, $login_session, $hold_id) = @_;
485 my( $requestor, $target, $hold, $copy, $transit, $evt );
487 ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
490 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
491 $login_session, $hold->usr, 'VIEW_HOLD' );
494 return 1 unless (defined($hold->current_copy));
496 ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
499 return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
501 ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
502 return 4 if(ref($transit) and defined($transit->dest_recv_time) );
504 return 3 if defined($hold->capture_time);
514 __PACKAGE__->register_method(
515 method => "capture_copy",
516 api_name => "open-ils.circ.hold.capture_copy.barcode",
518 Captures a copy to fulfil a hold
519 Params is login session and copy barcode
520 Optional param is 'flesh'. If set, we also return the
521 relevant copy and title
522 login mus have COPY_CHECKIN permissions (since this is essentially
526 # XXX deprecate me XXX
529 my( $self, $client, $login_session, $params ) = @_;
530 my %params = %$params;
531 my $barcode = $params{barcode};
534 my( $user, $target, $copy, $hold, $evt );
536 ( $user, $evt ) = $apputils->checkses($login_session);
539 # am I allowed to checkin a copy?
540 $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
543 $logger->info("Capturing copy with barcode $barcode");
545 my $session = $apputils->start_db_session();
547 ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
550 $logger->debug("Capturing copy " . $copy->id);
552 #( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
553 ( $hold, $evt ) = $self->find_nearest_permitted_hold($session, $copy, $user);
556 warn "Found hold " . $hold->id . "\n";
557 $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
559 $hold->current_copy($copy->id);
560 $hold->capture_time("now");
563 my $stat = $session->request(
564 "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
565 if(!$stat) { throw OpenSRF::EX::ERROR
566 ("Error updating hold request " . $copy->id); }
568 $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); #status on holds shelf
570 # if the staff member capturing this item is not at the pickup lib
571 if( $user->home_ou ne $hold->pickup_lib ) {
572 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
575 $copy->editor($user->id);
576 $copy->edit_date("now");
577 $stat = $session->request(
578 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
579 if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
581 my $payload = { hold => $hold };
582 $payload->{copy} = $copy if $params{flesh_copy};
584 if($params{flesh_record}) {
586 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
588 $record = $apputils->record_to_mvr($record);
589 $payload->{record} = $record;
592 $apputils->commit_db_session($session);
594 return OpenILS::Event->new('ROUTE_ITEM',
595 route_to => $hold->pickup_lib, payload => $payload );
598 sub _build_hold_transit {
599 my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
600 my $trans = Fieldmapper::action::hold_transit_copy->new;
602 $trans->hold($hold->id);
603 $trans->source($user->home_ou);
604 $trans->dest($hold->pickup_lib);
605 $trans->source_send_time("now");
606 $trans->target_copy($copy->id);
607 $trans->copy_status($copy->status);
609 my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
610 my ($stat) = $meth->run( $login_session, $trans, $session );
611 if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
612 else { $copy->status(6); } #status in transit
617 __PACKAGE__->register_method(
618 method => "create_hold_transit",
619 api_name => "open-ils.circ.hold_transit.create",
621 Creates a new transit object
624 sub create_hold_transit {
625 my( $self, $client, $login_session, $transit, $session ) = @_;
627 my( $user, $evt ) = $apputils->checkses($login_session);
629 $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
633 if($session) { $ses = $session; }
634 else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
636 return $ses->request(
637 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
643 sub find_local_hold {
644 my( $class, $session, $copy, $user ) = @_;
645 return $class->find_nearest_permitted_hold($session, $copy, $user);
653 sub fetch_open_hold_by_current_copy {
656 my $hold = $apputils->simplereq(
658 'open-ils.cstore.direct.action.hold_request.search.atomic',
659 { current_copy => $copyid , cancel_time => undef, fulfillment_time => undef });
660 return $hold->[0] if ref($hold);
664 sub fetch_related_holds {
667 return $apputils->simplereq(
669 'open-ils.cstore.direct.action.hold_request.search.atomic',
670 { current_copy => $copyid , cancel_time => undef, fulfillment_time => undef });
674 __PACKAGE__->register_method (
675 method => "hold_pull_list",
676 api_name => "open-ils.circ.hold_pull_list.retrieve",
678 Returns a list of hold ID's that need to be "pulled"
684 my( $self, $conn, $authtoken, $limit, $offset ) = @_;
685 my( $reqr, $evt ) = $U->checkses($authtoken);
688 my $org = $reqr->ws_ou || $reqr->home_ou;
689 # the perm locaiton shouldn't really matter here since holds
690 # will exist all over and VIEW_HOLDS should be universal
691 $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
694 return $U->storagereq(
695 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
696 $org, $limit, $offset );
699 __PACKAGE__->register_method (
700 method => 'fetch_hold_notify',
701 api_name => 'open-ils.circ.hold_notification.retrieve_by_hold',
703 Returns a list of hold notification objects based on hold id.
704 @param authtoken The loggin session key
705 @param holdid The id of the hold whose notifications we want to retrieve
706 @return An array of hold notification objects, event on error.
710 sub fetch_hold_notify {
711 my( $self, $conn, $authtoken, $holdid ) = @_;
712 my( $requestor, $evt ) = $U->checkses($authtoken);
715 ($hold, $evt) = $U->fetch_hold($holdid);
717 ($patron, $evt) = $U->fetch_user($hold->usr);
720 $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
723 $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
724 return $U->cstorereq(
725 'open-ils.cstore.direct.action.hold_notification.search.atomic', {hold => $holdid} );
729 __PACKAGE__->register_method (
730 method => 'create_hold_notify',
731 api_name => 'open-ils.circ.hold_notification.create',
733 Creates a new hold notification object
734 @param authtoken The login session key
735 @param notification The hold notification object to create
736 @return ID of the new object on success, Event on error
739 sub create_hold_notify {
740 my( $self, $conn, $authtoken, $notification ) = @_;
741 my( $requestor, $evt ) = $U->checkses($authtoken);
744 ($hold, $evt) = $U->fetch_hold($notification->hold);
746 ($patron, $evt) = $U->fetch_user($hold->usr);
749 # XXX perm depth probably doesn't matter here -- should always be consortium level
750 $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
753 # Set the proper notifier
754 $notification->notify_staff($requestor->id);
755 my $id = $U->storagereq(
756 'open-ils.storage.direct.action.hold_notification.create', $notification );
757 return $U->DB_UPDATE_FAILED($notification) unless $id;
758 $logger->info("User ".$requestor->id." successfully created new hold notification $id");
763 __PACKAGE__->register_method(
764 method => 'reset_hold',
765 api_name => 'open-ils.circ.hold.reset',
767 Un-captures and un-targets a hold, essentially returning
768 it to the state it was in directly after it was placed,
769 then attempts to re-target the hold
770 @param authtoken The login session key
771 @param holdid The id of the hold
777 my( $self, $conn, $auth, $holdid ) = @_;
779 my ($hold, $evt) = $U->fetch_hold($holdid);
781 ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD'); # XXX stronger permission
783 $evt = $self->_reset_hold($reqr, $hold);
789 my ($self, $reqr, $hold, $session) = @_;
794 $session = $U->start_db_session();
797 $hold->clear_capture_time;
798 $hold->clear_current_copy;
800 return $U->DB_UPDATE_FAILED($hold) unless
802 'open-ils.storage.direct.action.hold_request.update', $hold )->gather(1);
805 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id )->gather(1);
807 $U->commit_db_session($session) unless $x;
812 __PACKAGE__->register_method(
813 method => 'fetch_open_title_holds',
814 api_name => 'open-ils.circ.open_holds.retrieve',
816 Returns a list ids of un-fulfilled holds for a given title id
817 @param authtoken The login session key
818 @param id the id of the item whose holds we want to retrieve
819 @param type The hold type - M, T, V, C
823 sub fetch_open_title_holds {
824 my( $self, $conn, $auth, $id, $type, $org ) = @_;
825 my $e = new_editor( authtoken => $auth );
826 return $e->event unless $e->checkauth;
829 $org ||= $e->requestor->ws_ou;
831 # return $e->search_action_hold_request(
832 # { target => $id, hold_type => $type, fulfillment_time => undef }, {idlist=>1});
834 # XXX make me return IDs in the future ^--
835 return $e->search_action_hold_request(
836 { target => $id, cancel_time => undef, hold_type => $type, fulfillment_time => undef });
842 __PACKAGE__->register_method(
843 method => 'fetch_captured_holds',
844 api_name => 'open-ils.circ.captured_holds.on_shelf.retrieve',
846 Returns a list ids of un-fulfilled holds for a given title id
847 @param authtoken The login session key
848 @param org The org id of the location in question
851 sub fetch_captured_holds {
852 my( $self, $conn, $auth, $org ) = @_;
854 my $e = new_editor(authtoken => $auth);
855 return $e->event unless $e->checkauth;
856 return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
858 $org ||= $e->requestor->ws_ou;
860 my $holds = $e->search_action_hold_request(
862 capture_time => { "!=" => undef },
863 current_copy => { "!=" => undef },
864 fulfillment_time => undef,
866 cancel_time => undef,
871 my $stat = OILS_COPY_STATUS_ON_HOLDS_SHELF;
872 for my $h (@$holds) {
873 my $copy = $e->retrieve_asset_copy($h->current_copy)
875 push( @res, $h ) if $copy->status == $stat->id; # eventually, push IDs here
885 __PACKAGE__->register_method(
886 method => "check_title_hold",
887 api_name => "open-ils.circ.title_hold.is_possible",
889 Determines if a hold were to be placed by a given user,
890 whether or not said hold would have any potential copies
892 @param authtoken The login session key
893 @param params A hash of named params including:
894 patronid - the id of the hold recipient
895 titleid (brn) - the id of the title to be held
896 depth - the hold range depth (defaults to 0)
899 sub check_title_hold {
900 my( $self, $client, $authtoken, $params ) = @_;
902 my %params = %$params;
903 my $titleid = $params{titleid} ||"";
904 my $mrid = $params{mrid} ||"";
905 my $depth = $params{depth} || 0;
906 my $pickup_lib = $params{pickup_lib};
907 my $hold_type = $params{hold_type} || 'T';
909 my $e = new_editor(authtoken=>$authtoken);
910 return $e->event unless $e->checkauth;
911 my $patron = $e->retrieve_actor_user($params{patronid})
913 return $e->event unless $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou);
915 return OpenILS::Event->new('PATRON_BARRED')
916 if $patron->barred and
917 ($patron->barred =~ /t/i or $patron->barred == 1);
919 my $rangelib = $params{range_lib} || $patron->home_ou;
921 my $request_lib = $e->retrieve_actor_org_unit($e->requestor->ws_ou)
924 if( $hold_type eq 'T' ) {
925 return _check_title_hold_is_possible(
926 $titleid, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
929 if( $hold_type eq 'M' ) {
930 my $maps = $e->search_metabib_source_map({metarecord=>$mrid});
931 my @recs = map { $_->source } @$maps;
932 for my $rec (@recs) {
933 return 1 if (_check_title_hold_is_possible(
934 $rec, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib));
941 sub _check_title_hold_is_possible {
942 my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
948 $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
950 while( $title = $U->storagereq(
951 'open-ils.storage.biblio.record_entry.ranged_tree',
952 $titleid, $rangelib, $depth, $limit, $offset ) ) {
956 ref($title->call_numbers) and
957 @{$title->call_numbers};
959 for my $cn (@{$title->call_numbers}) {
961 $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
963 for my $copy (@{$cn->copies}) {
965 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
967 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
969 requestor => $requestor,
972 title_descriptor => $title->fixed_fields, # this is fleshed into the title object
973 pickup_lib => $pickup_lib,
974 request_lib => $request_lib
978 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
989 sub find_nearest_permitted_hold {
995 my $evt = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
997 # first see if this copy has already been selected to fulfill a hold
998 my $hold = $session->request(
999 "open-ils.storage.direct.action.hold_request.search_where",
1000 { current_copy => $copy->id, cancel_time => undef, capture_time => undef } )->gather(1);
1003 $logger->info("hold found which can be fulfilled by copy ".$copy->id);
1007 # We know this hold is permitted, so just return it
1008 return $hold if $hold;
1010 $logger->debug("searching for potential holds at org ".
1011 $user->ws_ou." and copy ".$copy->id);
1013 my $holds = $session->request(
1014 "open-ils.storage.action.hold_request.nearest_hold.atomic",
1015 $user->ws_ou, $copy->id, 5 )->gather(1);
1017 return (undef, $evt) unless @$holds;
1019 # for each potential hold, we have to run the permit script
1020 # to make sure the hold is actually permitted.
1022 for my $holdid (@$holds) {
1023 next unless $holdid;
1024 $logger->info("Checking if hold $holdid is permitted for user ".$user->id);
1026 my ($hold) = $U->fetch_hold($holdid);
1028 my ($reqr) = $U->fetch_user($hold->requestor);
1030 return ($hold) if OpenILS::Utils::PermitHold::permit_copy_hold(
1032 patron_id => $hold->usr,
1033 requestor => $reqr->id,
1035 pickup_lib => $hold->pickup_lib,
1036 request_lib => $hold->request_lib
1041 return (undef, $evt);
1045 #__PACKAGE__->register_method(