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;
23 use OpenSRF::EX qw(:try);
26 use OpenSRF::Utils::Logger qw(:logger);
28 my $apputils = "OpenILS::Application::AppUtils";
32 __PACKAGE__->register_method(
33 method => "create_hold",
34 api_name => "open-ils.circ.holds.create",
36 Create a new hold for an item. From a permissions perspective,
37 the login session is used as the 'requestor' of the hold.
38 The hold recipient is determined by the 'usr' setting within
41 First we verify the requestion has holds request permissions.
42 Then we verify that the recipient is allowed to make the given hold.
43 If not, we see if the requestor has "override" capabilities. If not,
44 a permission exception is returned. If permissions allow, we cycle
45 through the set of holds objects and create.
47 If the recipient does not have permission to place multiple holds
48 on a single title and said operation is attempted, a permission
53 my( $self, $client, $login_session, @holds) = @_;
55 if(!@holds){return 0;}
56 my( $user, $evt ) = $apputils->checkses($login_session);
60 if(ref($holds[0]) eq 'ARRAY') {
62 } else { $holds = [ @holds ]; }
64 $logger->debug("Iterating over holds requests...");
66 for my $hold (@$holds) {
69 my $type = $hold->hold_type;
71 $logger->activity("User " . $user->id .
72 " creating new hold of type $type for user " . $hold->usr);
75 if($user->id ne $hold->usr) {
76 ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
86 # am I allowed to place holds for this user?
87 if($hold->requestor ne $hold->usr) {
88 $perm = _check_request_holds_perm($user->id, $user->home_ou);
89 if($perm) { return $perm; }
92 # is this user allowed to have holds of this type?
93 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
95 #if there is a requestor, see if the requestor has override privelages
96 if($hold->requestor ne $hold->usr) {
97 $perm = _check_request_holds_override($user->id, $user->home_ou);
98 if($perm) {return $perm;}
105 #enforce the fact that the login is the one requesting the hold
106 $hold->requestor($user->id);
108 my $resp = $apputils->simplereq(
110 'open-ils.storage.direct.action.hold_request.create', $hold );
113 return OpenSRF::EX::ERROR ("Error creating hold");
120 # makes sure that a user has permission to place the type of requested hold
121 # returns the Perm exception if not allowed, returns undef if all is well
122 sub _check_holds_perm {
123 my($type, $user_id, $org_id) = @_;
127 if($evt = $apputils->check_perms(
128 $user_id, $org_id, "MR_HOLDS")) {
132 } elsif ($type eq "T") {
133 if($evt = $apputils->check_perms(
134 $user_id, $org_id, "TITLE_HOLDS")) {
138 } elsif($type eq "V") {
139 if($evt = $apputils->check_perms(
140 $user_id, $org_id, "VOLUME_HOLDS")) {
144 } elsif($type eq "C") {
145 if($evt = $apputils->check_perms(
146 $user_id, $org_id, "COPY_HOLDS")) {
154 # tests if the given user is allowed to place holds on another's behalf
155 sub _check_request_holds_perm {
158 if(my $evt = $apputils->check_perms(
159 $user_id, $org_id, "REQUEST_HOLDS")) {
164 sub _check_request_holds_override {
167 if(my $evt = $apputils->check_perms(
168 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
174 __PACKAGE__->register_method(
175 method => "retrieve_holds",
176 api_name => "open-ils.circ.holds.retrieve",
178 Retrieves all the holds for the specified user id. The login session
179 is the requestor and if the requestor is different from the user, then
180 the requestor must have VIEW_HOLD permissions.
185 my($self, $client, $login_session, $user_id) = @_;
187 my( $user, $target, $evt ) = $apputils->checkses_requestor(
188 $login_session, $user_id, 'VIEW_HOLD' );
191 return $apputils->simplereq(
193 "open-ils.storage.direct.action.hold_request.search.atomic",
194 "usr" => $user_id , fulfillment_time => undef, { order_by => "request_time" });
198 __PACKAGE__->register_method(
199 method => "cancel_hold",
200 api_name => "open-ils.circ.hold.cancel",
202 Cancels the specified hold. The login session
203 is the requestor and if the requestor is different from the usr field
204 on the hold, the requestor must have CANCEL_HOLDS permissions.
205 the hold may be either the hold object or the hold id
209 my($self, $client, $login_session, $holdid) = @_;
212 my $user = $apputils->check_user_session($login_session);
213 my( $hold, $evt ) = $apputils->fetch_hold($holdid);
216 if($user->id ne $hold->usr) { #am I allowed to cancel this user's hold?
217 if($evt = $apputils->checkperms(
218 $user->id, $user->home_ou, 'CANCEL_HOLDS')) {
223 $logger->activity( "User " . $user->id .
224 " canceling hold $holdid for user " . $hold->usr );
226 return $apputils->simplereq(
228 "open-ils.storage.direct.action.hold_request.delete", $hold );
232 __PACKAGE__->register_method(
233 method => "update_hold",
234 api_name => "open-ils.circ.hold.update",
236 Updates the specified hold. The login session
237 is the requestor and if the requestor is different from the usr field
238 on the hold, the requestor must have UPDATE_HOLDS permissions.
242 my($self, $client, $login_session, $hold) = @_;
244 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
245 $login_session, $hold->usr, 'UPDATE_HOLD' );
248 $logger->activity('User ' + $requestor->id .
249 ' updating hold ' . $hold->id . ' for user ' . $target->id );
251 return $apputils->simplereq(
253 "open-ils.storage.direct.action.hold_request.update", $hold );
257 __PACKAGE__->register_method(
258 method => "retrieve_hold_status",
259 api_name => "open-ils.circ.hold.status.retrieve",
261 Calculates the current status of the hold.
262 the requestor must have VIEW_HOLD permissions if the hold is for a user
263 other than the requestor.
264 Returns -1 on error (for now)
265 Returns 1 for 'waiting for copy to become available'
266 Returns 2 for 'waiting for copy capture'
267 Returns 3 for 'in transit'
268 Returns 4 for 'arrived'
271 sub retrieve_hold_status {
272 my($self, $client, $login_session, $hold_id) = @_;
275 my( $requestor, $target, $hold, $copy, $transit, $evt );
277 ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
280 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
281 $login_session, $hold->usr, 'VIEW_HOLD' );
284 return 1 unless (defined($hold->current_copy));
286 ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
289 return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
291 ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
292 return 4 if(ref($transit) and defined($transit->dest_recv_time) );
294 return 3 if defined($hold->capture_time);
299 __PACKAGE__->register_method(
300 method => "capture_copy",
301 api_name => "open-ils.circ.hold.capture_copy.barcode",
303 Captures a copy to fulfil a hold
304 Params is login session and copy barcode
305 Optional param is 'flesh'. If set, we also return the
306 relevant copy and title
307 login mus have COPY_CHECKIN permissions (since this is essentially
312 my( $self, $client, $login_session, $params ) = @_;
313 my %params = %$params;
314 my $barcode = $params{barcode};
317 my( $user, $target, $copy, $hold, $evt );
319 ( $user, $evt ) = $apputils->checkses($login_session);
322 # am I allowed to checkin a copy?
323 $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
326 $logger->info("Capturing copy with barcode $barcode");
328 my $session = $apputils->start_db_session();
330 ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
333 $logger->debug("Capturing copy " . $copy->id);
335 ( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
338 warn "Found hold " . $hold->id . "\n";
339 $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
341 $hold->current_copy($copy->id);
342 $hold->capture_time("now");
345 my $stat = $session->request(
346 "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
347 if(!$stat) { throw OpenSRF::EX::ERROR
348 ("Error updating hold request " . $copy->id); }
350 $copy->status(8); #status on holds shelf
352 # if the staff member capturing this item is not at the pickup lib
353 if( $user->home_ou ne $hold->pickup_lib ) {
354 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
357 $copy->editor($user->id);
358 $copy->edit_date("now");
359 $stat = $session->request(
360 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
361 if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
363 my $payload = { hold => $hold };
364 $payload->{copy} = $copy if $params{flesh_copy};
366 if($params{flesh_record}) {
368 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
370 $record = $apputils->record_to_mvr($record);
371 $payload->{record} = $record;
374 $apputils->commit_db_session($session);
376 return OpenILS::Event->new('ROUTE_ITEM',
377 route_to => $hold->pickup_lib, payload => $payload );
380 sub _build_hold_transit {
381 my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
382 my $trans = Fieldmapper::action::hold_transit_copy->new;
384 $trans->hold($hold->id);
385 $trans->source($user->home_ou);
386 $trans->dest($hold->pickup_lib);
387 $trans->source_send_time("now");
388 $trans->target_copy($copy->id);
389 $trans->copy_status($copy->status);
391 my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
392 my ($stat) = $meth->run( $login_session, $trans, $session );
393 if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
394 else { $copy->status(6); } #status in transit
398 sub find_local_hold {
399 my( $class, $session, $copy, $user ) = @_;
400 return _find_local_hold_for_copy($session, $copy, $user);
403 sub _find_local_hold_for_copy {
408 my $evt = OpenILS::Event->new('HOLD_NOT_FOUND');
410 # first see if this copy has already been selected to fulfill a hold
411 my $hold = $session->request(
412 "open-ils.storage.direct.action.hold_request.search_where",
413 { current_copy => $copy->id, capture_time => undef } )->gather(1);
415 if($hold) {return $hold;}
417 $logger->debug("searching for local hold at org " .
418 $user->home_ou . " and copy " . $copy->id);
420 my $holdid = $session->request(
421 "open-ils.storage.action.hold_request.nearest_hold",
422 $user->home_ou, $copy->id )->gather(1);
424 return (undef, $evt) unless defined $holdid;
426 $logger->debug("Found hold id $holdid while ".
427 "searching nearest hold to " .$user->home_ou);
429 return $apputils->fetch_hold($holdid);
433 __PACKAGE__->register_method(
434 method => "create_hold_transit",
435 api_name => "open-ils.circ.hold_transit.create",
437 Creates a new transit object
440 sub create_hold_transit {
441 my( $self, $client, $login_session, $transit, $session ) = @_;
443 my( $user, $evt ) = $apputils->checkses($login_session);
445 $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
449 if($session) { $ses = $session; }
450 else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
452 return $ses->request(
453 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
457 sub fetch_open_hold_by_current_copy {
460 my $hold = $apputils->simplereq(
462 'open-ils.storage.direct.action.hold_request.search.atomic',
463 current_copy => $copyid , fulfillment_time => undef );
464 return $hold->[0] if ref($hold);
468 sub fetch_related_holds {
471 return $apputils->simplereq(
473 'open-ils.storage.direct.action.hold_request.search.atomic',
474 current_copy => $copyid , fulfillment_time => undef );