]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
3e25b6a38075b09d7e0812484e1319f8f1b1db55
[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 my $apputils = "OpenILS::Application::AppUtils";
22 use OpenILS::EX;
23 use OpenSRF::EX qw(:try);
24 use OpenILS::Perm;
25 use OpenILS::Event;
26
27
28
29 __PACKAGE__->register_method(
30         method  => "create_hold",
31         api_name        => "open-ils.circ.holds.create",
32         notes           => <<NOTE);
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
36 the hold object.
37
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.
43
44 If the recipient does not have permission to place multiple holds
45 on a single title and said operation is attempted, a permission
46 exception is returned
47 NOTE
48
49 sub create_hold {
50         my( $self, $client, $login_session, @holds) = @_;
51
52         if(!@holds){return 0;}
53         my( $user, $evt ) = $apputils->check_ses($login_session);
54         return $evt if $evt;
55
56
57         my $holds;
58         if(ref($holds[0]) eq 'ARRAY') {
59                 $holds = $holds[0];
60         } else { $holds = [ @holds ]; }
61
62         warn "Iterating over holds requests...\n";
63
64         for my $hold (@$holds) {
65
66                 if(!$hold){next};
67                 my $type = $hold->hold_type;
68
69                 use Data::Dumper;
70                 warn "Hold to create: " . Dumper($hold) . "\n";
71
72                 my $recipient;
73                 if($user->id ne $hold->usr) {
74                         $recipient = $apputils->fetch_user($hold->usr);
75                         if(!$recipient) { return OpenILS::Event->new('USER_NOT_FOUND'); }
76
77                 } else {
78                         $recipient = $user;
79                 }
80
81                 #enforce the fact that the login is the one requesting the hold
82                 $hold->requestor($user->id); 
83
84                 my $perm = undef;
85
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; }
90                 }
91
92                 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
93                 if($perm) { 
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;}
98                         } else {
99                                 return $perm; 
100                         }
101                 }
102
103
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";
108
109                 my $req = $session->request( $method, $hold );
110
111                 my $resp = $req->gather(1);
112                 $session->disconnect();
113                 if(!$resp) { return OpenILS::EX->new("UNKNOWN")->ex(); }
114 #               $apputils->commit_db_session($session);
115         }
116
117         return 1;
118 }
119
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) = @_;
124
125         my $evt;
126         if($type eq "M") {
127                 if($evt = $apputils->check_perms(
128                         $user_id, $org_id, "MR_HOLDS")) {
129                         return $evt;
130                 } 
131
132         } elsif ($type eq "T") {
133                 if($evt = $apputils->check_perms(
134                         $user_id, $org_id, "TITLE_HOLDS")) {
135                         return $evt;
136                 }
137
138         } elsif($type eq "V") {
139                 if($evt = $apputils->check_perms(
140                         $user_id, $org_id, "VOLUME_HOLDS")) {
141                         return $evt;
142                 }
143
144         } elsif($type eq "C") {
145                 if($evt = $apputils->check_perms(
146                         $user_id, $org_id, "COPY_HOLDS")) {
147                         return $evt;
148                 }
149         }
150
151         return undef;
152 }
153
154 # tests if the given user is allowed to place holds on another's behalf
155 sub _check_request_holds_perm {
156         my $user_id = shift;
157         my $org_id = shift;
158         if(my $evt = $apputils->check_perms(
159                 $user_id, $org_id, "REQUEST_HOLDS")) {
160                 return $evt;
161         }
162 }
163
164 sub _check_request_holds_override {
165         my $user_id = shift;
166         my $org_id = shift;
167         if(my $evt = $apputils->check_perms(
168                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
169                 return $evt;
170         }
171 }
172
173
174 __PACKAGE__->register_method(
175         method  => "retrieve_holds",
176         api_name        => "open-ils.circ.holds.retrieve",
177         notes           => <<NOTE);
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.
181 NOTE
182
183
184 sub retrieve_holds {
185         my($self, $client, $login_session, $user_id) = @_;
186
187         my $user = $apputils->check_user_session($login_session);
188
189         if($user->id ne $user_id) {
190                 if($apputils->check_user_perms($user->id, $user->home_ou, "VIEW_HOLDS")) {
191                         return OpenILS::Perm->new("VIEW_HOLDS");
192                 }
193         }
194
195         my $session = OpenSRF::AppSession->create("open-ils.storage");
196         my $req = $session->request(
197                 "open-ils.storage.direct.action.hold_request.search.atomic",
198                 "usr" =>  $user_id , fulfillment_time => undef, { order_by => "request_time" });
199
200         my $h = $req->gather(1);
201         $session->disconnect();
202         return $h;
203 }
204
205
206 __PACKAGE__->register_method(
207         method  => "cancel_hold",
208         api_name        => "open-ils.circ.hold.cancel",
209         notes           => <<"  NOTE");
210         Cancels the specified hold.  The login session
211         is the requestor and if the requestor is different from the usr field
212         on the hold, the requestor must have CANCEL_HOLDS permissions.
213         the hold may be either the hold object or the hold id
214         NOTE
215
216 sub cancel_hold {
217         my($self, $client, $login_session, $hold) = @_;
218
219         my $user = $apputils->check_user_session($login_session);
220
221         my $session = OpenSRF::AppSession->create("open-ils.storage");
222         
223         if(!ref($hold)) {
224                 $hold = $session->request(
225                         "open-ils.storage.direct.action.hold_request.retrieve", $hold)->gather(1);
226         }
227
228         if($user->id ne $hold->usr) {
229                 if($apputils->check_user_perms($user->id, $user->home_ou, "CANCEL_HOLDS")) {
230                         return OpenILS::Perm->new("CANCEL_HOLDS");
231                 }
232         }
233
234         use Data::Dumper;
235         warn "Cancelling hold: " . Dumper($hold) . "\n";
236
237         my $req = $session->request(
238                 "open-ils.storage.direct.action.hold_request.delete",
239                 $hold );
240         my $h = $req->gather(1);
241
242         warn "[$h] returned from hold_request delete\n";
243         $session->disconnect();
244         return $h;
245 }
246
247
248 __PACKAGE__->register_method(
249         method  => "update_hold",
250         api_name        => "open-ils.circ.hold.update",
251         notes           => <<"  NOTE");
252         Updates the specified hold.  The login session
253         is the requestor and if the requestor is different from the usr field
254         on the hold, the requestor must have UPDATE_HOLDS permissions.
255         NOTE
256
257 sub update_hold {
258         my($self, $client, $login_session, $hold) = @_;
259
260         my $user = $apputils->check_user_session($login_session);
261
262         if($user->id ne $hold->usr) {
263                 if($apputils->check_user_perms($user->id, $user->home_ou, "UPDATE_HOLDS")) {
264                         return OpenILS::Perm->new("UPDATE_HOLDS");
265                 }
266         }
267
268         use Data::Dumper;
269         warn "Updating hold: " . Dumper($hold) . "\n";
270
271         my $session = OpenSRF::AppSession->create("open-ils.storage");
272         my $req = $session->request(
273                 "open-ils.storage.direct.action.hold_request.update", $hold );
274         my $h = $req->gather(1);
275
276         warn "[$h] returned from hold_request update\n";
277         $session->disconnect();
278         return $h;
279 }
280
281
282 __PACKAGE__->register_method(
283         method  => "retrieve_hold_status",
284         api_name        => "open-ils.circ.hold.status.retrieve",
285         notes           => <<"  NOTE");
286         Calculates the current status of the hold.
287         the requestor must have VIEW_HOLDS permissions if the hold is for a user
288         other than the requestor.
289         Returns -1  on error (for now)
290         Returns 1 for 'waiting for copy to become available'
291         Returns 2 for 'waiting for copy capture'
292         Returns 3 for 'in transit'
293         Returns 4 for 'arrived'
294         NOTE
295
296 sub retrieve_hold_status {
297         my($self, $client, $login_session, $hold_id) = @_;
298
299         my $user = $apputils->check_user_session($login_session);
300
301         my $session = OpenSRF::AppSession->create("open-ils.storage");
302
303         my $hold = $session->request(
304                 "open-ils.storage.direct.action.hold_request.retrieve", $hold_id )->gather(1);
305         return -1 unless $hold; # should be an exception
306
307
308         if($user->id ne $hold->usr) {
309                 if($apputils->check_user_perms($user->id, $user->home_ou, "VIEW_HOLDS")) {
310                         return OpenILS::Perm->new("VIEW_HOLDS");
311                 }
312         }
313         
314         return 1 unless (defined($hold->current_copy));
315
316         #return 2 unless (defined($hold->capture_time));
317
318         my $copy = $session->request(
319                 "open-ils.storage.direct.asset.copy.retrieve", $hold->current_copy )->gather(1);
320         return 1 unless $copy; # should be an exception
321
322         use Data::Dumper;
323         warn "Hold Copy in status check: " . Dumper($copy) . "\n\n";
324
325         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
326
327         my $transit = _fetch_hold_transit($session, $hold->id);
328         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
329
330         return 3 if defined($hold->capture_time);
331
332         return 2;
333 }
334
335
336 sub _fetch_hold_transit {
337         my $session = shift;
338         my $holdid = shift;
339         return $session->request(
340                 "open-ils.storage.direct.action.hold_transit_copy.search.hold",
341                 $holdid )->gather(1);
342 }
343
344 __PACKAGE__->register_method(
345         method  => "capture_copy",
346         api_name        => "open-ils.circ.hold.capture_copy.barcode",
347         notes           => <<"  NOTE");
348         Captures a copy to fulfil a hold
349         Params is login session and copy barcode
350         Optional param is 'flesh'.  If set, we also return the
351         relevant copy and title
352         login mus have COPY_CHECKIN permissions (since this is essentially
353         copy checkin)
354         NOTE
355
356 sub capture_copy {
357         my( $self, $client, $login_session, $barcode, $flesh ) = @_;
358
359         warn "Capturing copy with barcode $barcode, flesh=$flesh \n";
360
361         my $user = $apputils->check_user_session($login_session);
362
363         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
364                 return OpenILS::Perm->new("COPY_CHECKIN"); }
365
366         my $session = $apputils->start_db_session();
367
368         my $copy = $session->request(
369                 "open-ils.storage.direct.asset.copy.search.barcode",
370                 $barcode )->gather(1);
371
372         warn "Found copy $copy\n";
373
374         return OpenILS::EX->new("UNKNOWN_BARCODE")->ex unless $copy;
375
376         warn "Capturing copy " . $copy->id . "\n";
377
378         my $hold = _find_local_hold_for_copy($session, $copy, $user);
379         if(!$hold) {return OpenILS::EX->new("NO_HOLD_FOUND")->ex;}
380
381         warn "Found hold " . $hold->id . "\n";
382
383         $hold->current_copy($copy->id);
384         $hold->capture_time("now"); 
385
386         #update the hold
387         my $stat = $session->request(
388                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
389         if(!$stat) { throw OpenSRF::EX ("Error updating hold request " . $copy->id); }
390
391         $copy->status(8); #status on holds shelf
392
393         # if the staff member capturing this item is not at the pickup lib
394         if( $user->home_ou ne $hold->pickup_lib ) {
395                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
396         }
397
398         $copy->editor($user->id);
399         $copy->edit_date("now");
400         $stat = $session->request(
401                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
402         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
403
404         
405         my $title = undef;
406         if($flesh) {
407                 $title = $session->request(
408                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
409                         $copy->id )->gather(1);
410                 my $u = OpenILS::Utils::ModsParser->new();
411                 $u->start_mods_batch( $title->marc() );
412                 $title = $u->finish_mods_batch();
413
414         } 
415
416         $apputils->commit_db_session($session);
417
418         return { 
419                 copy => $copy,
420                 route_to => $hold->pickup_lib,
421                 record => $title,
422                 hold => $hold, 
423         };
424
425 }
426
427 sub _build_hold_transit {
428         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
429         my $trans = Fieldmapper::action::hold_transit_copy->new;
430
431         $trans->hold($hold->id);
432         $trans->source($user->home_ou);
433         $trans->dest($hold->pickup_lib);
434         $trans->source_send_time("now");
435         $trans->target_copy($copy->id);
436         $trans->copy_status($copy->status);
437
438         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
439         my ($stat) = $meth->run( $login_session, $trans, $session );
440         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
441         else { $copy->status(6); } #status in transit 
442 }
443
444
445 sub _find_local_hold_for_copy {
446
447         my $session = shift;
448         my $copy = shift;
449         my $user = shift;
450
451         # first see if this copy has already been selected to fulfill a hold
452         my $hold  = $session->request(
453                 "open-ils.storage.direct.action.hold_request.search_where",
454                 { current_copy => $copy->id, capture_time => undef } )->gather(1);
455
456         if($hold) {return $hold;}
457
458         warn "searching for local hold at org " . $user->home_ou . " and copy " . $copy->id . "\n";
459
460         my $holdid = $session->request(
461                 "open-ils.storage.action.hold_request.nearest_hold",
462                 $user->home_ou, $copy->id )->gather(1);
463
464         if(!$holdid) { return undef; }
465
466         warn "found hold id $holdid\n";
467
468         return $session->request(
469                 "open-ils.storage.direct.action.hold_request.retrieve", $holdid )->gather(1);
470
471 }
472
473
474 __PACKAGE__->register_method(
475         method  => "create_hold_transit",
476         api_name        => "open-ils.circ.hold_transit.create",
477         notes           => <<"  NOTE");
478         Creates a new transit object
479         NOTE
480
481 sub create_hold_transit {
482         my( $self, $client, $login_session, $transit, $session ) = @_;
483
484         my $user = $apputils->check_user_session($login_session);
485         if($apputils->check_user_perms($user->id, $user->home_ou, "CREATE_TRANSIT")) {
486                 return OpenILS::Perm->new("CREATE_TRANSIT");
487         }
488         
489         my $ses;
490         if($session) { $ses = $session; } 
491         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
492
493         return $ses->request(
494                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
495 }
496
497
498
499
500 1;