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