patched up some typos
[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->checkses($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, $target, $evt ) = $apputils->checkses_requestor(
188                 $login_session, $user_id, 'VIEW_HOLDS' );
189         return $evt if $evt;
190
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" });
195
196         my $h = $req->gather(1);
197         $session->disconnect();
198         return $h;
199 }
200
201
202 __PACKAGE__->register_method(
203         method  => "cancel_hold",
204         api_name        => "open-ils.circ.hold.cancel",
205         notes           => <<"  NOTE");
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
210         NOTE
211
212 sub cancel_hold {
213         my($self, $client, $login_session, $hold) = @_;
214
215         my $user = $apputils->check_user_session($login_session);
216
217         my $session = OpenSRF::AppSession->create("open-ils.storage");
218         
219         if(!ref($hold)) {
220                 $hold = $session->request(
221                         "open-ils.storage.direct.action.hold_request.retrieve", $hold)->gather(1);
222         }
223
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");
227                 }
228         }
229
230         use Data::Dumper;
231         warn "Cancelling hold: " . Dumper($hold) . "\n";
232
233         my $req = $session->request(
234                 "open-ils.storage.direct.action.hold_request.delete",
235                 $hold );
236         my $h = $req->gather(1);
237
238         warn "[$h] returned from hold_request delete\n";
239         $session->disconnect();
240         return $h;
241 }
242
243
244 __PACKAGE__->register_method(
245         method  => "update_hold",
246         api_name        => "open-ils.circ.hold.update",
247         notes           => <<"  NOTE");
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.
251         NOTE
252
253 sub update_hold {
254         my($self, $client, $login_session, $hold) = @_;
255
256         my $user = $apputils->check_user_session($login_session);
257
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");
261                 }
262         }
263
264         use Data::Dumper;
265         warn "Updating hold: " . Dumper($hold) . "\n";
266
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);
271
272         warn "[$h] returned from hold_request update\n";
273         $session->disconnect();
274         return $h;
275 }
276
277
278 __PACKAGE__->register_method(
279         method  => "retrieve_hold_status",
280         api_name        => "open-ils.circ.hold.status.retrieve",
281         notes           => <<"  NOTE");
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'
290         NOTE
291
292 sub retrieve_hold_status {
293         my($self, $client, $login_session, $hold_id) = @_;
294
295         my $user = $apputils->check_user_session($login_session);
296
297         my $session = OpenSRF::AppSession->create("open-ils.storage");
298
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
302
303
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");
307                 }
308         }
309         
310         return 1 unless (defined($hold->current_copy));
311
312         #return 2 unless (defined($hold->capture_time));
313
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
317
318         use Data::Dumper;
319         warn "Hold Copy in status check: " . Dumper($copy) . "\n\n";
320
321         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
322
323         my $transit = _fetch_hold_transit($session, $hold->id);
324         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
325
326         return 3 if defined($hold->capture_time);
327
328         return 2;
329 }
330
331
332 sub _fetch_hold_transit {
333         my $session = shift;
334         my $holdid = shift;
335         return $session->request(
336                 "open-ils.storage.direct.action.hold_transit_copy.search.hold",
337                 $holdid )->gather(1);
338 }
339
340 __PACKAGE__->register_method(
341         method  => "capture_copy",
342         api_name        => "open-ils.circ.hold.capture_copy.barcode",
343         notes           => <<"  NOTE");
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
349         copy checkin)
350         NOTE
351
352 sub capture_copy {
353         my( $self, $client, $login_session, $barcode, $flesh ) = @_;
354
355         warn "Capturing copy with barcode $barcode, flesh=$flesh \n";
356
357         my $user = $apputils->check_user_session($login_session);
358
359         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
360                 return OpenILS::Perm->new("COPY_CHECKIN"); }
361
362         my $session = $apputils->start_db_session();
363
364         my $copy = $session->request(
365                 "open-ils.storage.direct.asset.copy.search.barcode",
366                 $barcode )->gather(1);
367
368         warn "Found copy $copy\n";
369
370         return OpenILS::EX->new("UNKNOWN_BARCODE")->ex unless $copy;
371
372         warn "Capturing copy " . $copy->id . "\n";
373
374         my $hold = _find_local_hold_for_copy($session, $copy, $user);
375         if(!$hold) {return OpenILS::EX->new("NO_HOLD_FOUND")->ex;}
376
377         warn "Found hold " . $hold->id . "\n";
378
379         $hold->current_copy($copy->id);
380         $hold->capture_time("now"); 
381
382         #update the hold
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); }
386
387         $copy->status(8); #status on holds shelf
388
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 );
392         }
393
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); }
399
400         
401         my $title = undef;
402         if($flesh) {
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();
409
410         } 
411
412         $apputils->commit_db_session($session);
413
414         return { 
415                 copy => $copy,
416                 route_to => $hold->pickup_lib,
417                 record => $title,
418                 hold => $hold, 
419         };
420
421 }
422
423 sub _build_hold_transit {
424         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
425         my $trans = Fieldmapper::action::hold_transit_copy->new;
426
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);
433
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 
438 }
439
440
441 sub _find_local_hold_for_copy {
442
443         my $session = shift;
444         my $copy = shift;
445         my $user = shift;
446
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);
451
452         if($hold) {return $hold;}
453
454         warn "searching for local hold at org " . $user->home_ou . " and copy " . $copy->id . "\n";
455
456         my $holdid = $session->request(
457                 "open-ils.storage.action.hold_request.nearest_hold",
458                 $user->home_ou, $copy->id )->gather(1);
459
460         if(!$holdid) { return undef; }
461
462         warn "found hold id $holdid\n";
463
464         return $session->request(
465                 "open-ils.storage.direct.action.hold_request.retrieve", $holdid )->gather(1);
466
467 }
468
469
470 __PACKAGE__->register_method(
471         method  => "create_hold_transit",
472         api_name        => "open-ils.circ.hold_transit.create",
473         notes           => <<"  NOTE");
474         Creates a new transit object
475         NOTE
476
477 sub create_hold_transit {
478         my( $self, $client, $login_session, $transit, $session ) = @_;
479
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");
483         }
484         
485         my $ses;
486         if($session) { $ses = $session; } 
487         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
488
489         return $ses->request(
490                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
491 }
492
493
494
495
496 1;