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;
21 my $apputils = "OpenILS::Application::AppUtils";
23 use OpenSRF::EX qw(:try);
29 __PACKAGE__->register_method(
30 method => "create_hold",
31 api_name => "open-ils.circ.holds.create",
33 Create a new hold for an item. From a permissions perspective,
34 the login session is used as the 'requestor' of the hold.
35 The hold recipient is determined by the 'usr' setting within
38 First we verify the requestion has holds request permissions.
39 Then we verify that the recipient is allowed to make the given hold.
40 If not, we see if the requestor has "override" capabilities. If not,
41 a permission exception is returned. If permissions allow, we cycle
42 through the set of holds objects and create.
44 If the recipient does not have permission to place multiple holds
45 on a single title and said operation is attempted, a permission
50 my( $self, $client, $login_session, @holds) = @_;
52 if(!@holds){return 0;}
53 my( $user, $evt ) = $apputils->check_ses($login_session);
58 if(ref($holds[0]) eq 'ARRAY') {
60 } else { $holds = [ @holds ]; }
62 warn "Iterating over holds requests...\n";
64 for my $hold (@$holds) {
67 my $type = $hold->hold_type;
70 warn "Hold to create: " . Dumper($hold) . "\n";
73 if($user->id ne $hold->usr) {
74 $recipient = $apputils->fetch_user($hold->usr);
75 if(!$recipient) { return OpenILS::Event->new('USER_NOT_FOUND'); }
81 #enforce the fact that the login is the one requesting the hold
82 $hold->requestor($user->id);
86 # see if the requestor even has permission to request
87 if($hold->requestor ne $hold->usr) {
88 $perm = _check_request_holds_perm($user->id, $user->home_ou);
89 if($perm) { return $perm; }
92 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
94 #if there is a requestor, see if the requestor has override privelages
95 if($hold->requestor ne $hold->usr) {
96 $perm = _check_request_holds_override($user->id, $user->home_ou);
97 if($perm) {return $perm;}
104 #my $session = $apputils->start_db_session();
105 my $session = OpenSRF::AppSession->create("open-ils.storage");
106 my $method = "open-ils.storage.direct.action.hold_request.create";
107 warn "Sending hold request to storage... $method \n";
109 my $req = $session->request( $method, $hold );
111 my $resp = $req->gather(1);
112 $session->disconnect();
113 if(!$resp) { return OpenILS::EX->new("UNKNOWN")->ex(); }
114 # $apputils->commit_db_session($session);
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_HOLDS permissions.
185 my($self, $client, $login_session, $user_id) = @_;
187 my( $user, $target, $evt ) = $apputils->checkses_requestor(
188 $login_session, $user_id, 'VIEW_HOLDS' );
191 my $session = OpenSRF::AppSession->create("open-ils.storage");
192 my $req = $session->request(
193 "open-ils.storage.direct.action.hold_request.search.atomic",
194 "usr" => $user_id , fulfillment_time => undef, { order_by => "request_time" });
196 my $h = $req->gather(1);
197 $session->disconnect();
202 __PACKAGE__->register_method(
203 method => "cancel_hold",
204 api_name => "open-ils.circ.hold.cancel",
206 Cancels the specified hold. The login session
207 is the requestor and if the requestor is different from the usr field
208 on the hold, the requestor must have CANCEL_HOLDS permissions.
209 the hold may be either the hold object or the hold id
213 my($self, $client, $login_session, $hold) = @_;
215 my $user = $apputils->check_user_session($login_session);
217 my $session = OpenSRF::AppSession->create("open-ils.storage");
220 $hold = $session->request(
221 "open-ils.storage.direct.action.hold_request.retrieve", $hold)->gather(1);
224 if($user->id ne $hold->usr) {
225 if($apputils->check_user_perms($user->id, $user->home_ou, "CANCEL_HOLDS")) {
226 return OpenILS::Perm->new("CANCEL_HOLDS");
231 warn "Cancelling hold: " . Dumper($hold) . "\n";
233 my $req = $session->request(
234 "open-ils.storage.direct.action.hold_request.delete",
236 my $h = $req->gather(1);
238 warn "[$h] returned from hold_request delete\n";
239 $session->disconnect();
244 __PACKAGE__->register_method(
245 method => "update_hold",
246 api_name => "open-ils.circ.hold.update",
248 Updates the specified hold. The login session
249 is the requestor and if the requestor is different from the usr field
250 on the hold, the requestor must have UPDATE_HOLDS permissions.
254 my($self, $client, $login_session, $hold) = @_;
256 my $user = $apputils->check_user_session($login_session);
258 if($user->id ne $hold->usr) {
259 if($apputils->check_user_perms($user->id, $user->home_ou, "UPDATE_HOLDS")) {
260 return OpenILS::Perm->new("UPDATE_HOLDS");
265 warn "Updating hold: " . Dumper($hold) . "\n";
267 my $session = OpenSRF::AppSession->create("open-ils.storage");
268 my $req = $session->request(
269 "open-ils.storage.direct.action.hold_request.update", $hold );
270 my $h = $req->gather(1);
272 warn "[$h] returned from hold_request update\n";
273 $session->disconnect();
278 __PACKAGE__->register_method(
279 method => "retrieve_hold_status",
280 api_name => "open-ils.circ.hold.status.retrieve",
282 Calculates the current status of the hold.
283 the requestor must have VIEW_HOLDS permissions if the hold is for a user
284 other than the requestor.
285 Returns -1 on error (for now)
286 Returns 1 for 'waiting for copy to become available'
287 Returns 2 for 'waiting for copy capture'
288 Returns 3 for 'in transit'
289 Returns 4 for 'arrived'
292 sub retrieve_hold_status {
293 my($self, $client, $login_session, $hold_id) = @_;
295 my $user = $apputils->check_user_session($login_session);
297 my $session = OpenSRF::AppSession->create("open-ils.storage");
299 my $hold = $session->request(
300 "open-ils.storage.direct.action.hold_request.retrieve", $hold_id )->gather(1);
301 return -1 unless $hold; # should be an exception
304 if($user->id ne $hold->usr) {
305 if($apputils->check_user_perms($user->id, $user->home_ou, "VIEW_HOLDS")) {
306 return OpenILS::Perm->new("VIEW_HOLDS");
310 return 1 unless (defined($hold->current_copy));
312 #return 2 unless (defined($hold->capture_time));
314 my $copy = $session->request(
315 "open-ils.storage.direct.asset.copy.retrieve", $hold->current_copy )->gather(1);
316 return 1 unless $copy; # should be an exception
319 warn "Hold Copy in status check: " . Dumper($copy) . "\n\n";
321 return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
323 my $transit = _fetch_hold_transit($session, $hold->id);
324 return 4 if(ref($transit) and defined($transit->dest_recv_time) );
326 return 3 if defined($hold->capture_time);
332 sub _fetch_hold_transit {
335 return $session->request(
336 "open-ils.storage.direct.action.hold_transit_copy.search.hold",
337 $holdid )->gather(1);
340 __PACKAGE__->register_method(
341 method => "capture_copy",
342 api_name => "open-ils.circ.hold.capture_copy.barcode",
344 Captures a copy to fulfil a hold
345 Params is login session and copy barcode
346 Optional param is 'flesh'. If set, we also return the
347 relevant copy and title
348 login mus have COPY_CHECKIN permissions (since this is essentially
353 my( $self, $client, $login_session, $barcode, $flesh ) = @_;
355 warn "Capturing copy with barcode $barcode, flesh=$flesh \n";
357 my $user = $apputils->check_user_session($login_session);
359 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
360 return OpenILS::Perm->new("COPY_CHECKIN"); }
362 my $session = $apputils->start_db_session();
364 my $copy = $session->request(
365 "open-ils.storage.direct.asset.copy.search.barcode",
366 $barcode )->gather(1);
368 warn "Found copy $copy\n";
370 return OpenILS::EX->new("UNKNOWN_BARCODE")->ex unless $copy;
372 warn "Capturing copy " . $copy->id . "\n";
374 my $hold = _find_local_hold_for_copy($session, $copy, $user);
375 if(!$hold) {return OpenILS::EX->new("NO_HOLD_FOUND")->ex;}
377 warn "Found hold " . $hold->id . "\n";
379 $hold->current_copy($copy->id);
380 $hold->capture_time("now");
383 my $stat = $session->request(
384 "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
385 if(!$stat) { throw OpenSRF::EX ("Error updating hold request " . $copy->id); }
387 $copy->status(8); #status on holds shelf
389 # if the staff member capturing this item is not at the pickup lib
390 if( $user->home_ou ne $hold->pickup_lib ) {
391 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
394 $copy->editor($user->id);
395 $copy->edit_date("now");
396 $stat = $session->request(
397 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
398 if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
403 $title = $session->request(
404 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
405 $copy->id )->gather(1);
406 my $u = OpenILS::Utils::ModsParser->new();
407 $u->start_mods_batch( $title->marc() );
408 $title = $u->finish_mods_batch();
412 $apputils->commit_db_session($session);
416 route_to => $hold->pickup_lib,
423 sub _build_hold_transit {
424 my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
425 my $trans = Fieldmapper::action::hold_transit_copy->new;
427 $trans->hold($hold->id);
428 $trans->source($user->home_ou);
429 $trans->dest($hold->pickup_lib);
430 $trans->source_send_time("now");
431 $trans->target_copy($copy->id);
432 $trans->copy_status($copy->status);
434 my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
435 my ($stat) = $meth->run( $login_session, $trans, $session );
436 if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
437 else { $copy->status(6); } #status in transit
441 sub _find_local_hold_for_copy {
447 # first see if this copy has already been selected to fulfill a hold
448 my $hold = $session->request(
449 "open-ils.storage.direct.action.hold_request.search_where",
450 { current_copy => $copy->id, capture_time => undef } )->gather(1);
452 if($hold) {return $hold;}
454 warn "searching for local hold at org " . $user->home_ou . " and copy " . $copy->id . "\n";
456 my $holdid = $session->request(
457 "open-ils.storage.action.hold_request.nearest_hold",
458 $user->home_ou, $copy->id )->gather(1);
460 if(!$holdid) { return undef; }
462 warn "found hold id $holdid\n";
464 return $session->request(
465 "open-ils.storage.direct.action.hold_request.retrieve", $holdid )->gather(1);
470 __PACKAGE__->register_method(
471 method => "create_hold_transit",
472 api_name => "open-ils.circ.hold_transit.create",
474 Creates a new transit object
477 sub create_hold_transit {
478 my( $self, $client, $login_session, $transit, $session ) = @_;
480 my $user = $apputils->check_user_session($login_session);
481 if($apputils->check_user_perms($user->id, $user->home_ou, "CREATE_TRANSIT")) {
482 return OpenILS::Perm->new("CREATE_TRANSIT");
486 if($session) { $ses = $session; }
487 else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
489 return $ses->request(
490 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);