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";
33 __PACKAGE__->register_method(
34 method => "create_hold",
35 api_name => "open-ils.circ.holds.create",
37 Create a new hold for an item. From a permissions perspective,
38 the login session is used as the 'requestor' of the hold.
39 The hold recipient is determined by the 'usr' setting within
42 First we verify the requestion has holds request permissions.
43 Then we verify that the recipient is allowed to make the given hold.
44 If not, we see if the requestor has "override" capabilities. If not,
45 a permission exception is returned. If permissions allow, we cycle
46 through the set of holds objects and create.
48 If the recipient does not have permission to place multiple holds
49 on a single title and said operation is attempted, a permission
54 my( $self, $client, $login_session, @holds) = @_;
56 if(!@holds){return 0;}
57 my( $user, $evt ) = $apputils->checkses($login_session);
61 if(ref($holds[0]) eq 'ARRAY') {
63 } else { $holds = [ @holds ]; }
65 $logger->debug("Iterating over holds requests...");
67 for my $hold (@$holds) {
70 my $type = $hold->hold_type;
72 $logger->activity("User " . $user->id .
73 " creating new hold of type $type for user " . $hold->usr);
76 if($user->id ne $hold->usr) {
77 ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
87 # am I allowed to place holds for this user?
88 if($hold->requestor ne $hold->usr) {
89 $perm = _check_request_holds_perm($user->id, $user->home_ou);
90 if($perm) { return $perm; }
93 # is this user allowed to have holds of this type?
94 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
96 #if there is a requestor, see if the requestor has override privelages
97 if($hold->requestor ne $hold->usr) {
98 $perm = _check_request_holds_override($user->id, $user->home_ou);
99 if($perm) {return $perm;}
106 #enforce the fact that the login is the one requesting the hold
107 $hold->requestor($user->id);
109 my $resp = $apputils->simplereq(
111 'open-ils.storage.direct.action.hold_request.create', $hold );
114 return OpenSRF::EX::ERROR ("Error creating hold");
121 # makes sure that a user has permission to place the type of requested hold
122 # returns the Perm exception if not allowed, returns undef if all is well
123 sub _check_holds_perm {
124 my($type, $user_id, $org_id) = @_;
128 if($evt = $apputils->check_perms(
129 $user_id, $org_id, "MR_HOLDS")) {
133 } elsif ($type eq "T") {
134 if($evt = $apputils->check_perms(
135 $user_id, $org_id, "TITLE_HOLDS")) {
139 } elsif($type eq "V") {
140 if($evt = $apputils->check_perms(
141 $user_id, $org_id, "VOLUME_HOLDS")) {
145 } elsif($type eq "C") {
146 if($evt = $apputils->check_perms(
147 $user_id, $org_id, "COPY_HOLDS")) {
155 # tests if the given user is allowed to place holds on another's behalf
156 sub _check_request_holds_perm {
159 if(my $evt = $apputils->check_perms(
160 $user_id, $org_id, "REQUEST_HOLDS")) {
165 sub _check_request_holds_override {
168 if(my $evt = $apputils->check_perms(
169 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
175 __PACKAGE__->register_method(
176 method => "retrieve_holds",
177 api_name => "open-ils.circ.holds.retrieve",
179 Retrieves all the holds for the specified user id. The login session
180 is the requestor and if the requestor is different from the user, then
181 the requestor must have VIEW_HOLD permissions.
186 my($self, $client, $login_session, $user_id) = @_;
188 my( $user, $target, $evt ) = $apputils->checkses_requestor(
189 $login_session, $user_id, 'VIEW_HOLD' );
192 return $apputils->simplereq(
194 "open-ils.storage.direct.action.hold_request.search.atomic",
195 "usr" => $user_id , fulfillment_time => undef, { order_by => "request_time" });
199 __PACKAGE__->register_method(
200 method => "cancel_hold",
201 api_name => "open-ils.circ.hold.cancel",
203 Cancels the specified hold. The login session
204 is the requestor and if the requestor is different from the usr field
205 on the hold, the requestor must have CANCEL_HOLDS permissions.
206 the hold may be either the hold object or the hold id
210 my($self, $client, $login_session, $holdid) = @_;
213 my $user = $apputils->check_user_session($login_session);
214 my( $hold, $evt ) = $apputils->fetch_hold($holdid);
217 if($user->id ne $hold->usr) { #am I allowed to cancel this user's hold?
218 if($evt = $apputils->checkperms(
219 $user->id, $user->home_ou, 'CANCEL_HOLDS')) {
224 $logger->activity( "User " . $user->id .
225 " canceling hold $holdid for user " . $hold->usr );
227 return $apputils->simplereq(
229 "open-ils.storage.direct.action.hold_request.delete", $hold );
233 __PACKAGE__->register_method(
234 method => "update_hold",
235 api_name => "open-ils.circ.hold.update",
237 Updates the specified hold. The login session
238 is the requestor and if the requestor is different from the usr field
239 on the hold, the requestor must have UPDATE_HOLDS permissions.
243 my($self, $client, $login_session, $hold) = @_;
245 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
246 $login_session, $hold->usr, 'UPDATE_HOLD' );
249 $logger->activity('User ' + $requestor->id .
250 ' updating hold ' . $hold->id . ' for user ' . $target->id );
252 return $apputils->simplereq(
254 "open-ils.storage.direct.action.hold_request.update", $hold );
258 __PACKAGE__->register_method(
259 method => "retrieve_hold_status",
260 api_name => "open-ils.circ.hold.status.retrieve",
262 Calculates the current status of the hold.
263 the requestor must have VIEW_HOLD permissions if the hold is for a user
264 other than the requestor.
265 Returns -1 on error (for now)
266 Returns 1 for 'waiting for copy to become available'
267 Returns 2 for 'waiting for copy capture'
268 Returns 3 for 'in transit'
269 Returns 4 for 'arrived'
272 sub retrieve_hold_status {
273 my($self, $client, $login_session, $hold_id) = @_;
276 my( $requestor, $target, $hold, $copy, $transit, $evt );
278 ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
281 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
282 $login_session, $hold->usr, 'VIEW_HOLD' );
285 return 1 unless (defined($hold->current_copy));
287 ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
290 return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
292 ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
293 return 4 if(ref($transit) and defined($transit->dest_recv_time) );
295 return 3 if defined($hold->capture_time);
300 __PACKAGE__->register_method(
301 method => "capture_copy",
302 api_name => "open-ils.circ.hold.capture_copy.barcode",
304 Captures a copy to fulfil a hold
305 Params is login session and copy barcode
306 Optional param is 'flesh'. If set, we also return the
307 relevant copy and title
308 login mus have COPY_CHECKIN permissions (since this is essentially
313 my( $self, $client, $login_session, $params ) = @_;
314 my %params = %$params;
315 my $barcode = $params{barcode};
318 my( $user, $target, $copy, $hold, $evt );
320 ( $user, $evt ) = $apputils->checkses($login_session);
323 # am I allowed to checkin a copy?
324 $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
327 $logger->info("Capturing copy with barcode $barcode");
329 my $session = $apputils->start_db_session();
331 ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
334 $logger->debug("Capturing copy " . $copy->id);
336 ( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
339 warn "Found hold " . $hold->id . "\n";
340 $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
342 $hold->current_copy($copy->id);
343 $hold->capture_time("now");
346 my $stat = $session->request(
347 "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
348 if(!$stat) { throw OpenSRF::EX::ERROR
349 ("Error updating hold request " . $copy->id); }
351 $copy->status(8); #status on holds shelf
353 # if the staff member capturing this item is not at the pickup lib
354 if( $user->home_ou ne $hold->pickup_lib ) {
355 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
358 $copy->editor($user->id);
359 $copy->edit_date("now");
360 $stat = $session->request(
361 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
362 if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
364 my $payload = { hold => $hold };
365 $payload->{copy} = $copy if $params{flesh_copy};
367 if($params{flesh_record}) {
369 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
371 $record = $apputils->record_to_mvr($record);
372 $payload->{record} = $record;
375 $apputils->commit_db_session($session);
377 return OpenILS::Event->new('ROUTE_ITEM',
378 route_to => $hold->pickup_lib, payload => $payload );
381 sub _build_hold_transit {
382 my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
383 my $trans = Fieldmapper::action::hold_transit_copy->new;
385 $trans->hold($hold->id);
386 $trans->source($user->home_ou);
387 $trans->dest($hold->pickup_lib);
388 $trans->source_send_time("now");
389 $trans->target_copy($copy->id);
390 $trans->copy_status($copy->status);
392 my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
393 my ($stat) = $meth->run( $login_session, $trans, $session );
394 if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
395 else { $copy->status(6); } #status in transit
399 sub find_local_hold {
400 my( $class, $session, $copy, $user ) = @_;
401 return _find_local_hold_for_copy($session, $copy, $user);
404 sub _find_local_hold_for_copy {
409 my $evt = OpenILS::Event->new('HOLD_NOT_FOUND');
411 # first see if this copy has already been selected to fulfill a hold
412 my $hold = $session->request(
413 "open-ils.storage.direct.action.hold_request.search_where",
414 { current_copy => $copy->id, capture_time => undef } )->gather(1);
416 if($hold) {return $hold;}
418 $logger->debug("searching for local hold at org " .
419 $user->home_ou . " and copy " . $copy->id);
421 my $holdid = $session->request(
422 "open-ils.storage.action.hold_request.nearest_hold",
423 $user->home_ou, $copy->id )->gather(1);
425 return (undef, $evt) unless defined $holdid;
427 $logger->debug("Found hold id $holdid while ".
428 "searching nearest hold to " .$user->home_ou);
430 return $apputils->fetch_hold($holdid);
434 __PACKAGE__->register_method(
435 method => "create_hold_transit",
436 api_name => "open-ils.circ.hold_transit.create",
438 Creates a new transit object
441 sub create_hold_transit {
442 my( $self, $client, $login_session, $transit, $session ) = @_;
444 my( $user, $evt ) = $apputils->checkses($login_session);
446 $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
450 if($session) { $ses = $session; }
451 else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
453 return $ses->request(
454 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
458 sub fetch_open_hold_by_current_copy {
461 my $hold = $apputils->simplereq(
463 'open-ils.storage.direct.action.hold_request.search.atomic',
464 current_copy => $copyid , fulfillment_time => undef );
465 return $hold->[0] if ref($hold);
469 sub fetch_related_holds {
472 return $apputils->simplereq(
474 'open-ils.storage.direct.action.hold_request.search.atomic',
475 current_copy => $copyid , fulfillment_time => undef );
479 __PACKAGE__->register_method (
480 method => "hold_pull_list",
481 api_name => "open-ils.circ.hold_pull_list.retrieve",
483 Returns a list of hold ID's that need to be "pulled"
489 my( $self, $conn, $authtoken, $limit, $offset ) = @_;
490 my( $reqr, $evt ) = $U->checkses($authtoken);
493 # the perm locaiton shouldn't really matter here since holds
494 # will exist all over and VIEW_HOLDS should be universal
495 $evt = $U->check_perms($reqr->id, $reqr->home_ou, 'VIEW_HOLD');
498 return $U->storagereq(
499 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
500 $reqr->home_ou, $limit, $offset ); # XXX change to workstation