25bc1f00a03b6698777c7a59eef7edd8e5d4fc5d
[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 OpenILS::EX;
23 use OpenSRF::EX qw(:try);
24 use OpenILS::Perm;
25 use OpenILS::Event;
26 use OpenSRF::Utils::Logger qw(:logger);
27
28 my $apputils = "OpenILS::Application::AppUtils";
29 my $U = $apputils;
30
31
32
33 __PACKAGE__->register_method(
34         method  => "create_hold",
35         api_name        => "open-ils.circ.holds.create",
36         notes           => <<NOTE);
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
40 the hold object.
41
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.
47
48 If the recipient does not have permission to place multiple holds
49 on a single title and said operation is attempted, a permission
50 exception is returned
51 NOTE
52
53 sub create_hold {
54         my( $self, $client, $login_session, @holds) = @_;
55
56         if(!@holds){return 0;}
57         my( $user, $evt ) = $apputils->checkses($login_session);
58         return $evt if $evt;
59
60         my $holds;
61         if(ref($holds[0]) eq 'ARRAY') {
62                 $holds = $holds[0];
63         } else { $holds = [ @holds ]; }
64
65         $logger->debug("Iterating over holds requests...");
66
67         for my $hold (@$holds) {
68
69                 if(!$hold){next};
70                 my $type = $hold->hold_type;
71
72                 $logger->activity("User " . $user->id . 
73                         " creating new hold of type $type for user " . $hold->usr);
74
75                 my $recipient;
76                 if($user->id ne $hold->usr) {
77                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
78                         return $evt if $evt;
79
80                 } else {
81                         $recipient = $user;
82                 }
83
84
85                 my $perm = undef;
86
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; }
91                 }
92
93                 # is this user allowed to have holds of this type?
94                 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
95                 if($perm) { 
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;}
100
101                         } else {
102                                 return $perm; 
103                         }
104                 }
105
106                 #enforce the fact that the login is the one requesting the hold
107                 $hold->requestor($user->id); 
108
109                 my $resp = $apputils->simplereq(
110                         'open-ils.storage',
111                         'open-ils.storage.direct.action.hold_request.create', $hold );
112
113                 if(!$resp) { 
114                         return OpenSRF::EX::ERROR ("Error creating hold"); 
115                 }
116         }
117
118         return 1;
119 }
120
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) = @_;
125
126         my $evt;
127         if($type eq "M") {
128                 if($evt = $apputils->check_perms(
129                         $user_id, $org_id, "MR_HOLDS")) {
130                         return $evt;
131                 } 
132
133         } elsif ($type eq "T") {
134                 if($evt = $apputils->check_perms(
135                         $user_id, $org_id, "TITLE_HOLDS")) {
136                         return $evt;
137                 }
138
139         } elsif($type eq "V") {
140                 if($evt = $apputils->check_perms(
141                         $user_id, $org_id, "VOLUME_HOLDS")) {
142                         return $evt;
143                 }
144
145         } elsif($type eq "C") {
146                 if($evt = $apputils->check_perms(
147                         $user_id, $org_id, "COPY_HOLDS")) {
148                         return $evt;
149                 }
150         }
151
152         return undef;
153 }
154
155 # tests if the given user is allowed to place holds on another's behalf
156 sub _check_request_holds_perm {
157         my $user_id = shift;
158         my $org_id = shift;
159         if(my $evt = $apputils->check_perms(
160                 $user_id, $org_id, "REQUEST_HOLDS")) {
161                 return $evt;
162         }
163 }
164
165 sub _check_request_holds_override {
166         my $user_id = shift;
167         my $org_id = shift;
168         if(my $evt = $apputils->check_perms(
169                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
170                 return $evt;
171         }
172 }
173
174
175 __PACKAGE__->register_method(
176         method  => "retrieve_holds",
177         api_name        => "open-ils.circ.holds.retrieve",
178         notes           => <<NOTE);
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.
182 NOTE
183
184
185 sub retrieve_holds {
186         my($self, $client, $login_session, $user_id) = @_;
187
188         my( $user, $target, $evt ) = $apputils->checkses_requestor(
189                 $login_session, $user_id, 'VIEW_HOLD' );
190         return $evt if $evt;
191
192         return $apputils->simplereq(
193                 'open-ils.storage',
194                 "open-ils.storage.direct.action.hold_request.search.atomic",
195                 "usr" =>  $user_id , fulfillment_time => undef, { order_by => "request_time" });
196 }
197
198
199 __PACKAGE__->register_method(
200         method  => "cancel_hold",
201         api_name        => "open-ils.circ.hold.cancel",
202         notes           => <<"  NOTE");
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
207         NOTE
208
209 sub cancel_hold {
210         my($self, $client, $login_session, $holdid) = @_;
211         
212
213         my $user = $apputils->check_user_session($login_session);
214         my( $hold, $evt ) = $apputils->fetch_hold($holdid);
215         return $evt if $evt;
216
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')) {
220                         return $evt;
221                 }
222         }
223
224         $logger->activity( "User " . $user->id . 
225                 " canceling hold $holdid for user " . $hold->usr );
226
227         return $apputils->simplereq(
228                 'open-ils.storage',
229                 "open-ils.storage.direct.action.hold_request.delete", $hold );
230 }
231
232
233 __PACKAGE__->register_method(
234         method  => "update_hold",
235         api_name        => "open-ils.circ.hold.update",
236         notes           => <<"  NOTE");
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.
240         NOTE
241
242 sub update_hold {
243         my($self, $client, $login_session, $hold) = @_;
244
245         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
246                 $login_session, $hold->usr, 'UPDATE_HOLD' );
247         return $evt if $evt;
248
249         $logger->activity('User ' + $requestor->id . 
250                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
251
252         return $apputils->simplereq(
253                 'open-ils.storage',
254                 "open-ils.storage.direct.action.hold_request.update", $hold );
255 }
256
257
258 __PACKAGE__->register_method(
259         method  => "retrieve_hold_status",
260         api_name        => "open-ils.circ.hold.status.retrieve",
261         notes           => <<"  NOTE");
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'
270         NOTE
271
272 sub retrieve_hold_status {
273         my($self, $client, $login_session, $hold_id) = @_;
274
275
276         my( $requestor, $target, $hold, $copy, $transit, $evt );
277
278         ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
279         return $evt if $evt;
280
281         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
282                 $login_session, $hold->usr, 'VIEW_HOLD' );
283         return $evt if $evt;
284
285         return 1 unless (defined($hold->current_copy));
286         
287         ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
288         return $evt if $evt;
289
290         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
291
292         ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
293         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
294
295         return 3 if defined($hold->capture_time);
296
297         return 2;
298 }
299
300 __PACKAGE__->register_method(
301         method  => "capture_copy",
302         api_name        => "open-ils.circ.hold.capture_copy.barcode",
303         notes           => <<"  NOTE");
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
309         copy checkin)
310         NOTE
311
312 sub capture_copy {
313         my( $self, $client, $login_session, $params ) = @_;
314         my %params = %$params;
315         my $barcode = $params{barcode};
316
317
318         my( $user, $target, $copy, $hold, $evt );
319
320         ( $user, $evt ) = $apputils->checkses($login_session);
321         return $evt if $evt;
322
323         # am I allowed to checkin a copy?
324         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
325         return $evt if $evt;
326
327         $logger->info("Capturing copy with barcode $barcode");
328
329         my $session = $apputils->start_db_session();
330
331         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
332         return $evt if $evt;
333
334         $logger->debug("Capturing copy " . $copy->id);
335
336         ( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
337         return $evt if $evt;
338
339         warn "Found hold " . $hold->id . "\n";
340         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
341
342         $hold->current_copy($copy->id);
343         $hold->capture_time("now"); 
344
345         #update the hold
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); }
350
351         $copy->status(8); #status on holds shelf
352
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 );
356         }
357
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); }
363
364         my $payload = { hold => $hold };
365         $payload->{copy} = $copy if $params{flesh_copy};
366
367         if($params{flesh_record}) {
368                 my $record;
369                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
370                 return $evt if $evt;
371                 $record = $apputils->record_to_mvr($record);
372                 $payload->{record} = $record;
373         }
374
375         $apputils->commit_db_session($session);
376
377         return OpenILS::Event->new('ROUTE_ITEM', 
378                 route_to => $hold->pickup_lib, payload => $payload );
379 }
380
381 sub _build_hold_transit {
382         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
383         my $trans = Fieldmapper::action::hold_transit_copy->new;
384
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);
391
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 
396 }
397
398
399 sub find_local_hold {
400         my( $class, $session, $copy, $user ) = @_;
401         return _find_local_hold_for_copy($session, $copy, $user);
402 }
403
404 sub _find_local_hold_for_copy {
405
406         my $session = shift;
407         my $copy = shift;
408         my $user = shift;
409         my $evt = OpenILS::Event->new('HOLD_NOT_FOUND');
410
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);
415
416         if($hold) {return $hold;}
417
418         $logger->debug("searching for local hold at org " . 
419                 $user->home_ou . " and copy " . $copy->id);
420
421         my $holdid = $session->request(
422                 "open-ils.storage.action.hold_request.nearest_hold",
423                 $user->home_ou, $copy->id )->gather(1);
424
425         return (undef, $evt) unless defined $holdid;
426
427         $logger->debug("Found hold id $holdid while ".
428                 "searching nearest hold to " .$user->home_ou);
429
430         return $apputils->fetch_hold($holdid);
431 }
432
433
434 __PACKAGE__->register_method(
435         method  => "create_hold_transit",
436         api_name        => "open-ils.circ.hold_transit.create",
437         notes           => <<"  NOTE");
438         Creates a new transit object
439         NOTE
440
441 sub create_hold_transit {
442         my( $self, $client, $login_session, $transit, $session ) = @_;
443
444         my( $user, $evt ) = $apputils->checkses($login_session);
445         return $evt if $evt;
446         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
447         return $evt if $evt;
448
449         my $ses;
450         if($session) { $ses = $session; } 
451         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
452
453         return $ses->request(
454                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
455 }
456
457
458 sub fetch_open_hold_by_current_copy {
459         my $class = shift;
460         my $copyid = shift;
461         my $hold = $apputils->simplereq(
462                 'open-ils.storage', 
463                 'open-ils.storage.direct.action.hold_request.search.atomic',
464                          current_copy =>  $copyid , fulfillment_time => undef );
465         return $hold->[0] if ref($hold);
466         return undef;
467 }
468
469 sub fetch_related_holds {
470         my $class = shift;
471         my $copyid = shift;
472         return $apputils->simplereq(
473                 'open-ils.storage', 
474                 'open-ils.storage.direct.action.hold_request.search.atomic',
475                          current_copy =>  $copyid , fulfillment_time => undef );
476 }
477
478
479 __PACKAGE__->register_method (
480         method          => "hold_pull_list",
481         api_name                => "open-ils.circ.hold_pull_list.retrieve",
482         signature       => q/
483                 Returns a list of hold ID's that need to be "pulled"
484                 by a given location
485         /
486 );
487
488 sub hold_pull_list {
489         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
490         my( $reqr, $evt ) = $U->checkses($authtoken);
491         return $evt if $evt;
492
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');
496         return $evt if $evt;
497
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
501 }
502
503
504
505 1;