]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
added log and note
[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 OpenSRF::EX qw(:try);
23 use OpenILS::Perm;
24 use OpenILS::Event;
25 use OpenSRF::Utils::Logger qw(:logger);
26 use OpenILS::Utils::CStoreEditor q/:funcs/;
27 use OpenILS::Utils::PermitHold;
28 use OpenSRF::Utils::SettingsClient;
29 use OpenILS::Const qw/:const/;
30
31 my $apputils = "OpenILS::Application::AppUtils";
32 my $U = $apputils;
33
34
35
36 __PACKAGE__->register_method(
37         method  => "create_hold",
38         api_name        => "open-ils.circ.holds.create",
39         notes           => <<NOTE);
40 Create a new hold for an item.  From a permissions perspective, 
41 the login session is used as the 'requestor' of the hold.  
42 The hold recipient is determined by the 'usr' setting within
43 the hold object.
44
45 First we verify the requestion has holds request permissions.
46 Then we verify that the recipient is allowed to make the given hold.
47 If not, we see if the requestor has "override" capabilities.  If not,
48 a permission exception is returned.  If permissions allow, we cycle
49 through the set of holds objects and create.
50
51 If the recipient does not have permission to place multiple holds
52 on a single title and said operation is attempted, a permission
53 exception is returned
54 NOTE
55
56
57 __PACKAGE__->register_method(
58         method  => "create_hold",
59         api_name        => "open-ils.circ.holds.create.override",
60         signature       => q/
61                 If the recipient is not allowed to receive the requested hold,
62                 call this method to attempt the override
63                 @see open-ils.circ.holds.create
64         /
65 );
66
67 sub create_hold {
68         my( $self, $conn, $auth, @holds ) = @_;
69         my $e = new_editor(authtoken=>$auth, xact=>1);
70         return $e->event unless $e->checkauth;
71
72         my $override = 1 if $self->api_name =~ /override/;
73
74         my $holds = (ref($holds[0] eq 'ARRAY')) ? $holds[0] : [@holds];
75
76         for my $hold (@$holds) {
77
78                 next unless $hold;
79                 my @events;
80
81                 my $requestor = $e->requestor;
82                 my $recipient = $requestor;
83
84
85                 if( $requestor->id ne $hold->usr ) {
86                         # Make sure the requestor is allowed to place holds for 
87                         # the recipient if they are not the same people
88                         $recipient = $e->retrieve_actor_user($hold->usr) or return $e->event;
89                         $e->allowed('REQUEST_HOLDS', $recipient->home_ou) or return $e->event;
90                 }
91
92
93                 # Now make sure the recipient is allowed to receive the specified hold
94                 my $pevt;
95                 my $porg                = $recipient->home_ou;
96                 my $rid         = $e->requestor->id;
97                 my $t                   = $hold->hold_type;
98
99                 # See if a duplicate hold already exists
100                 my $sargs = {
101                         usr                     => $recipient->id, 
102                         hold_type       => $t, 
103                         fulfillment_time => undef, 
104                         target          => $hold->target,
105                         cancel_time     => undef,
106                 };
107
108                 $sargs->{holdable_formats} = $hold->holdable_formats if $t eq 'M';
109                         
110                 my $existing = $e->search_action_hold_request($sargs); 
111                 push( @events, OpenILS::Event->new('HOLD_EXISTS')) if @$existing;
112
113                 if( $t eq 'M' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'MR_HOLDS'); }
114                 if( $t eq 'T' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'TITLE_HOLDS');  }
115                 if( $t eq 'V' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'VOLUME_HOLDS'); }
116                 if( $t eq 'C' ) { $pevt = $e->event unless $e->checkperm($rid, $porg, 'COPY_HOLDS'); }
117
118                 return $pevt if $pevt;
119
120                 if( @events ) {
121                         if( $override ) {
122                                 for my $evt (@events) {
123                                         next unless $evt;
124                                         my $name = $evt->{textcode};
125                                         return $e->event unless $e->allowed("$name.override", $porg);
126                                 }
127                         } else {
128                                 return \@events;
129                         }
130                 }
131
132
133 #               if( $eevt ) {
134 #                       if( $override ) {
135 #                               return $e->event unless $e->allowed('CREATE_DUPLICATE_HOLDS', $porg);
136 #                       } else {
137 #                               return $eevt;
138 #                       }
139 #               }
140
141
142                 $hold->requestor($e->requestor->id); 
143                 $hold->selection_ou($recipient->home_ou) unless $hold->selection_ou;
144                 $e->create_action_hold_request($hold) or return $e->event;
145         }
146
147         $e->commit;
148         return 1;
149 }
150
151 sub __create_hold {
152         my( $self, $client, $login_session, @holds) = @_;
153
154         if(!@holds){return 0;}
155         my( $user, $evt ) = $apputils->checkses($login_session);
156         return $evt if $evt;
157
158         my $holds;
159         if(ref($holds[0]) eq 'ARRAY') {
160                 $holds = $holds[0];
161         } else { $holds = [ @holds ]; }
162
163         $logger->debug("Iterating over holds requests...");
164
165         for my $hold (@$holds) {
166
167                 if(!$hold){next};
168                 my $type = $hold->hold_type;
169
170                 $logger->activity("User " . $user->id . 
171                         " creating new hold of type $type for user " . $hold->usr);
172
173                 my $recipient;
174                 if($user->id ne $hold->usr) {
175                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
176                         return $evt if $evt;
177
178                 } else {
179                         $recipient = $user;
180                 }
181
182
183                 my $perm = undef;
184
185                 # am I allowed to place holds for this user?
186                 if($hold->requestor ne $hold->usr) {
187                         $perm = _check_request_holds_perm($user->id, $user->home_ou);
188                         if($perm) { return $perm; }
189                 }
190
191                 # is this user allowed to have holds of this type?
192                 $perm = _check_holds_perm($type, $hold->requestor, $recipient->home_ou);
193                 if($perm) { 
194                         #if there is a requestor, see if the requestor has override privelages
195                         if($hold->requestor ne $hold->usr) {
196                                 $perm = _check_request_holds_override($user->id, $user->home_ou);
197                                 if($perm) {return $perm;}
198
199                         } else {
200                                 return $perm; 
201                         }
202                 }
203
204                 #enforce the fact that the login is the one requesting the hold
205                 $hold->requestor($user->id); 
206                 $hold->selection_ou($recipient->home_ou) unless $hold->selection_ou;
207
208                 my $resp = $apputils->simplereq(
209                         'open-ils.storage',
210                         'open-ils.storage.direct.action.hold_request.create', $hold );
211
212                 if(!$resp) { 
213                         return OpenSRF::EX::ERROR ("Error creating hold"); 
214                 }
215         }
216
217         return 1;
218 }
219
220 # makes sure that a user has permission to place the type of requested hold
221 # returns the Perm exception if not allowed, returns undef if all is well
222 sub _check_holds_perm {
223         my($type, $user_id, $org_id) = @_;
224
225         my $evt;
226         if($type eq "M") {
227                 if($evt = $apputils->check_perms(
228                         $user_id, $org_id, "MR_HOLDS")) {
229                         return $evt;
230                 } 
231
232         } elsif ($type eq "T") {
233                 if($evt = $apputils->check_perms(
234                         $user_id, $org_id, "TITLE_HOLDS")) {
235                         return $evt;
236                 }
237
238         } elsif($type eq "V") {
239                 if($evt = $apputils->check_perms(
240                         $user_id, $org_id, "VOLUME_HOLDS")) {
241                         return $evt;
242                 }
243
244         } elsif($type eq "C") {
245                 if($evt = $apputils->check_perms(
246                         $user_id, $org_id, "COPY_HOLDS")) {
247                         return $evt;
248                 }
249         }
250
251         return undef;
252 }
253
254 # tests if the given user is allowed to place holds on another's behalf
255 sub _check_request_holds_perm {
256         my $user_id = shift;
257         my $org_id = shift;
258         if(my $evt = $apputils->check_perms(
259                 $user_id, $org_id, "REQUEST_HOLDS")) {
260                 return $evt;
261         }
262 }
263
264 sub _check_request_holds_override {
265         my $user_id = shift;
266         my $org_id = shift;
267         if(my $evt = $apputils->check_perms(
268                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
269                 return $evt;
270         }
271 }
272
273 __PACKAGE__->register_method(
274         method  => "retrieve_holds_by_id",
275         api_name        => "open-ils.circ.holds.retrieve_by_id",
276         notes           => <<NOTE);
277 Retrieve the hold, with hold transits attached, for the specified id The login session is the requestor and if the requestor is
278 different from the user, then the requestor must have VIEW_HOLD permissions.
279 NOTE
280
281
282 sub retrieve_holds_by_id {
283         my($self, $client, $login_session, $hold_id) = @_;
284
285         #FIXME
286         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
287         #       $login_session, $user_id, 'VIEW_HOLD' );
288         #return $evt if $evt;
289
290         my $holds = $apputils->simplereq(
291                 'open-ils.cstore',
292                 "open-ils.cstore.direct.action.hold_request.search.atomic",
293                 { id =>  $hold_id , fulfillment_time => undef }, 
294                 { order_by => { ahr => "request_time" } }
295         );
296
297         flesh_hold_transits($holds);
298         return $holds;
299 }
300
301
302 __PACKAGE__->register_method(
303         method  => "retrieve_holds",
304         api_name        => "open-ils.circ.holds.retrieve",
305         notes           => <<NOTE);
306 Retrieves all the holds, with hold transits attached, for the specified
307 user id.  The login session is the requestor and if the requestor is
308 different from the user, then the requestor must have VIEW_HOLD permissions.
309 NOTE
310
311
312 sub retrieve_holds {
313         my($self, $client, $login_session, $user_id) = @_;
314
315         my( $user, $target, $evt ) = $apputils->checkses_requestor(
316                 $login_session, $user_id, 'VIEW_HOLD' );
317         return $evt if $evt;
318
319         my $holds = $apputils->simplereq(
320                 'open-ils.cstore',
321                 "open-ils.cstore.direct.action.hold_request.search.atomic",
322                 { 
323                         usr =>  $user_id , 
324                         fulfillment_time => undef,
325                         cancel_time => undef,
326                 }, 
327                 { order_by => { ahr => "request_time" } }
328         );
329         
330         for my $hold ( @$holds ) {
331                 $hold->transit(
332                         $apputils->simplereq(
333                                 'open-ils.cstore',
334                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
335                                 { hold => $hold->id },
336                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
337                         )->[0]
338                 );
339         }
340
341         return $holds;
342 }
343
344 __PACKAGE__->register_method(
345         method  => "retrieve_holds_by_pickup_lib",
346         api_name        => "open-ils.circ.holds.retrieve_by_pickup_lib",
347         notes           => <<NOTE);
348 Retrieves all the holds, with hold transits attached, for the specified
349 pickup_ou id. 
350 NOTE
351
352
353 sub retrieve_holds_by_pickup_lib {
354         my($self, $client, $login_session, $ou_id) = @_;
355
356         #FIXME -- put an appropriate permission check here
357         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
358         #       $login_session, $user_id, 'VIEW_HOLD' );
359         #return $evt if $evt;
360
361         my $holds = $apputils->simplereq(
362                 'open-ils.cstore',
363                 "open-ils.cstore.direct.action.hold_request.search.atomic",
364                 { 
365                         pickup_lib =>  $ou_id , 
366                         fulfillment_time => undef,
367                         cancel_time => undef
368                 }, 
369                 { order_by => { ahr => "request_time" } });
370
371
372         flesh_hold_transits($holds);
373         return $holds;
374 }
375
376
377 __PACKAGE__->register_method(
378         method  => "cancel_hold",
379         api_name        => "open-ils.circ.hold.cancel",
380         notes           => <<"  NOTE");
381         Cancels the specified hold.  The login session
382         is the requestor and if the requestor is different from the usr field
383         on the hold, the requestor must have CANCEL_HOLDS permissions.
384         the hold may be either the hold object or the hold id
385         NOTE
386
387 sub cancel_hold {
388         my($self, $client, $auth, $holdid) = @_;
389
390         my $e = new_editor(authtoken=>$auth, xact=>1);
391         return $e->event unless $e->checkauth;
392
393         my $hold = $e->retrieve_action_hold_request($holdid)
394                 or return $e->event;
395
396         if( $e->requestor->id ne $hold->usr ) {
397                 return $e->event unless $e->allowed('CANCEL_HOLDS');
398         }
399
400         return 1 if $hold->cancel_time;
401
402         # If the hold is captured, reset the copy status
403         if( $hold->capture_time and $hold->current_copy ) {
404
405                 my $copy = $e->retrieve_asset_copy($hold->current_copy)
406                         or return $e->event;
407
408                 if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
409                         $logger->info("setting copy to status 'reshelving' on hold cancel");
410                         $copy->status(OILS_COPY_STATUS_RESHELVING);
411                         $copy->editor($e->requestor->id);
412                         $copy->edit_date('now');
413                         $e->update_asset_copy($copy) or return $e->event;
414                 }
415         }
416
417         $hold->cancel_time('now');
418         $e->update_action_hold_request($hold)
419                 or return $e->event;
420
421         $self->delete_hold_copy_maps($e, $hold->id);
422
423         $e->commit;
424         return 1;
425 }
426
427 sub delete_hold_copy_maps {
428         my $class = shift;
429         my $editor = shift;
430         my $holdid = shift;
431
432         my $maps = $editor->search_action_hold_copy_map({hold=>$holdid});
433         for(@$maps) {
434                 $editor->delete_action_hold_copy_map($_) 
435                         or return $editor->event;
436         }
437         return undef;
438 }
439
440
441 __PACKAGE__->register_method(
442         method  => "update_hold",
443         api_name        => "open-ils.circ.hold.update",
444         notes           => <<"  NOTE");
445         Updates the specified hold.  The login session
446         is the requestor and if the requestor is different from the usr field
447         on the hold, the requestor must have UPDATE_HOLDS permissions.
448         NOTE
449
450 sub update_hold {
451         my($self, $client, $login_session, $hold) = @_;
452
453         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
454                 $login_session, $hold->usr, 'UPDATE_HOLD' );
455         return $evt if $evt;
456
457         $logger->activity('User ' . $requestor->id . 
458                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
459
460         return $U->storagereq(
461                 "open-ils.storage.direct.action.hold_request.update", $hold );
462 }
463
464
465 __PACKAGE__->register_method(
466         method  => "retrieve_hold_status",
467         api_name        => "open-ils.circ.hold.status.retrieve",
468         notes           => <<"  NOTE");
469         Calculates the current status of the hold.
470         the requestor must have VIEW_HOLD permissions if the hold is for a user
471         other than the requestor.
472         Returns -1  on error (for now)
473         Returns 1 for 'waiting for copy to become available'
474         Returns 2 for 'waiting for copy capture'
475         Returns 3 for 'in transit'
476         Returns 4 for 'arrived'
477         NOTE
478
479 sub retrieve_hold_status {
480         my($self, $client, $auth, $hold_id) = @_;
481
482         my $e = new_editor(authtoken => $auth);
483         return $e->event unless $e->checkauth;
484         my $hold = $e->retrieve_action_hold_request($hold_id)
485                 or return $e->event;
486         return $e->event unless $e->allowed('VIEW_HOLD');
487
488         return 1 unless $hold->current_copy;
489         return 2 unless $hold->capture_time;
490
491         my $copy = $e->retrieve_asset_copy($hold->current_copy)
492                 or return $e->event;
493
494         return 3 if $copy->status == OILS_COPY_STATUS_IN_TRANSIT;
495         return 4 if $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
496
497         return -1;
498 }
499
500
501
502
503
504 =head DEPRECATED
505 __PACKAGE__->register_method(
506         method  => "capture_copy",
507         api_name        => "open-ils.circ.hold.capture_copy.barcode",
508         notes           => <<"  NOTE");
509         Captures a copy to fulfil a hold
510         Params is login session and copy barcode
511         Optional param is 'flesh'.  If set, we also return the
512         relevant copy and title
513         login mus have COPY_CHECKIN permissions (since this is essentially
514         copy checkin)
515         NOTE
516
517 # XXX deprecate me XXX
518
519 sub capture_copy {
520         my( $self, $client, $login_session, $params ) = @_;
521         my %params = %$params;
522         my $barcode = $params{barcode};
523
524
525         my( $user, $target, $copy, $hold, $evt );
526
527         ( $user, $evt ) = $apputils->checkses($login_session);
528         return $evt if $evt;
529
530         # am I allowed to checkin a copy?
531         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
532         return $evt if $evt;
533
534         $logger->info("Capturing copy with barcode $barcode");
535
536         my $session = $apputils->start_db_session();
537
538         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
539         return $evt if $evt;
540
541         $logger->debug("Capturing copy " . $copy->id);
542
543         #( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
544         ( $hold, $evt ) = $self->find_nearest_permitted_hold($session, $copy, $user);
545         return $evt if $evt;
546
547         warn "Found hold " . $hold->id . "\n";
548         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
549
550         $hold->current_copy($copy->id);
551         $hold->capture_time("now"); 
552
553         #update the hold
554         my $stat = $session->request(
555                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
556         if(!$stat) { throw OpenSRF::EX::ERROR 
557                 ("Error updating hold request " . $copy->id); }
558
559         $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); #status on holds shelf
560
561         # if the staff member capturing this item is not at the pickup lib
562         if( $user->home_ou ne $hold->pickup_lib ) {
563                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
564         }
565
566         $copy->editor($user->id);
567         $copy->edit_date("now");
568         $stat = $session->request(
569                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
570         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
571
572         my $payload = { hold => $hold };
573         $payload->{copy} = $copy if $params{flesh_copy};
574
575         if($params{flesh_record}) {
576                 my $record;
577                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
578                 return $evt if $evt;
579                 $record = $apputils->record_to_mvr($record);
580                 $payload->{record} = $record;
581         }
582
583         $apputils->commit_db_session($session);
584
585         return OpenILS::Event->new('ROUTE_ITEM', 
586                 route_to => $hold->pickup_lib, payload => $payload );
587 }
588
589 sub _build_hold_transit {
590         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
591         my $trans = Fieldmapper::action::hold_transit_copy->new;
592
593         $trans->hold($hold->id);
594         $trans->source($user->home_ou);
595         $trans->dest($hold->pickup_lib);
596         $trans->source_send_time("now");
597         $trans->target_copy($copy->id);
598         $trans->copy_status($copy->status);
599
600         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
601         my ($stat) = $meth->run( $login_session, $trans, $session );
602         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
603         else { $copy->status(6); } #status in transit 
604 }
605
606
607
608 __PACKAGE__->register_method(
609         method  => "create_hold_transit",
610         api_name        => "open-ils.circ.hold_transit.create",
611         notes           => <<"  NOTE");
612         Creates a new transit object
613         NOTE
614
615 sub create_hold_transit {
616         my( $self, $client, $login_session, $transit, $session ) = @_;
617
618         my( $user, $evt ) = $apputils->checkses($login_session);
619         return $evt if $evt;
620         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
621         return $evt if $evt;
622
623         my $ses;
624         if($session) { $ses = $session; } 
625         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
626
627         return $ses->request(
628                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
629 }
630
631 =cut
632
633
634 sub find_local_hold {
635         my( $class, $session, $copy, $user ) = @_;
636         return $class->find_nearest_permitted_hold($session, $copy, $user);
637 }
638
639
640
641
642
643
644 sub fetch_open_hold_by_current_copy {
645         my $class = shift;
646         my $copyid = shift;
647         my $hold = $apputils->simplereq(
648                 'open-ils.cstore', 
649                 'open-ils.cstore.direct.action.hold_request.search.atomic',
650                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
651         return $hold->[0] if ref($hold);
652         return undef;
653 }
654
655 sub fetch_related_holds {
656         my $class = shift;
657         my $copyid = shift;
658         return $apputils->simplereq(
659                 'open-ils.cstore', 
660                 'open-ils.cstore.direct.action.hold_request.search.atomic',
661                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
662 }
663
664
665 __PACKAGE__->register_method (
666         method          => "hold_pull_list",
667         api_name                => "open-ils.circ.hold_pull_list.retrieve",
668         signature       => q/
669                 Returns a list of hold ID's that need to be "pulled"
670                 by a given location
671         /
672 );
673
674 sub hold_pull_list {
675         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
676         my( $reqr, $evt ) = $U->checkses($authtoken);
677         return $evt if $evt;
678
679         my $org = $reqr->ws_ou || $reqr->home_ou;
680         # the perm locaiton shouldn't really matter here since holds
681         # will exist all over and VIEW_HOLDS should be universal
682         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
683         return $evt if $evt;
684
685         return $U->storagereq(
686                 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
687                 $org, $limit, $offset ); 
688 }
689
690 __PACKAGE__->register_method (
691         method          => 'fetch_hold_notify',
692         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
693         signature       => q/ 
694                 Returns a list of hold notification objects based on hold id.
695                 @param authtoken The loggin session key
696                 @param holdid The id of the hold whose notifications we want to retrieve
697                 @return An array of hold notification objects, event on error.
698         /
699 );
700
701 sub fetch_hold_notify {
702         my( $self, $conn, $authtoken, $holdid ) = @_;
703         my( $requestor, $evt ) = $U->checkses($authtoken);
704         return $evt if $evt;
705         my ($hold, $patron);
706         ($hold, $evt) = $U->fetch_hold($holdid);
707         return $evt if $evt;
708         ($patron, $evt) = $U->fetch_user($hold->usr);
709         return $evt if $evt;
710
711         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
712         return $evt if $evt;
713
714         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
715         return $U->cstorereq(
716                 'open-ils.cstore.direct.action.hold_notification.search.atomic', {hold => $holdid} );
717 }
718
719
720 __PACKAGE__->register_method (
721         method          => 'create_hold_notify',
722         api_name                => 'open-ils.circ.hold_notification.create',
723         signature       => q/
724                 Creates a new hold notification object
725                 @param authtoken The login session key
726                 @param notification The hold notification object to create
727                 @return ID of the new object on success, Event on error
728                 /
729 );
730 sub create_hold_notify {
731         my( $self, $conn, $authtoken, $notification ) = @_;
732         my( $requestor, $evt ) = $U->checkses($authtoken);
733         return $evt if $evt;
734         my ($hold, $patron);
735         ($hold, $evt) = $U->fetch_hold($notification->hold);
736         return $evt if $evt;
737         ($patron, $evt) = $U->fetch_user($hold->usr);
738         return $evt if $evt;
739
740         # XXX perm depth probably doesn't matter here -- should always be consortium level
741         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
742         return $evt if $evt;
743
744         # Set the proper notifier 
745         $notification->notify_staff($requestor->id);
746         my $id = $U->storagereq(
747                 'open-ils.storage.direct.action.hold_notification.create', $notification );
748         return $U->DB_UPDATE_FAILED($notification) unless $id;
749         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
750         return $id;
751 }
752
753
754 __PACKAGE__->register_method(
755         method  => 'reset_hold',
756         api_name        => 'open-ils.circ.hold.reset',
757         signature       => q/
758                 Un-captures and un-targets a hold, essentially returning
759                 it to the state it was in directly after it was placed,
760                 then attempts to re-target the hold
761                 @param authtoken The login session key
762                 @param holdid The id of the hold
763         /
764 );
765
766
767 sub reset_hold {
768         my( $self, $conn, $auth, $holdid ) = @_;
769         my $reqr;
770         my ($hold, $evt) = $U->fetch_hold($holdid);
771         return $evt if $evt;
772         ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD'); # XXX stronger permission
773         return $evt if $evt;
774         $evt = $self->_reset_hold($reqr, $hold);
775         return $evt if $evt;
776         return 1;
777 }
778
779 sub _reset_hold {
780         my ($self, $reqr, $hold) = @_;
781
782         my $e = new_editor(xact =>1, requestor => $reqr);
783
784         $logger->info("reseting hold ".$hold->id);
785
786         if( $hold->capture_time and $hold->current_copy ) {
787
788                 my $copy = $e->retrieve_asset_copy($hold->current_copy)
789                         or return $e->event;
790
791                 if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
792                         $logger->info("setting copy to status 'reshelving' on hold retarget");
793                         $copy->status(OILS_COPY_STATUS_RESHELVING);
794                         $copy->editor($e->requestor->id);
795                         $copy->edit_date('now');
796                         $e->update_asset_copy($copy) or return $e->event;
797
798                 } elsif( $copy->status == OILS_COPY_STATUS_IN_TRANSIT ) {
799                         $logger->warn("reseting hold that is in transit: ".$hold->id);
800                         # is this allowed?      
801                 }
802         }
803
804         $hold->clear_capture_time;
805         $hold->clear_current_copy;
806
807         $e->update_action_hold_request($hold) or return $e->event;
808
809         $e->commit;
810
811         $U->storagereq(
812                 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id );
813
814         return undef;
815 }
816
817
818 __PACKAGE__->register_method(
819         method => 'fetch_open_title_holds',
820         api_name        => 'open-ils.circ.open_holds.retrieve',
821         signature       => q/
822                 Returns a list ids of un-fulfilled holds for a given title id
823                 @param authtoken The login session key
824                 @param id the id of the item whose holds we want to retrieve
825                 @param type The hold type - M, T, V, C
826         /
827 );
828
829 sub fetch_open_title_holds {
830         my( $self, $conn, $auth, $id, $type, $org ) = @_;
831         my $e = new_editor( authtoken => $auth );
832         return $e->event unless $e->checkauth;
833
834         $type ||= "T";
835         $org ||= $e->requestor->ws_ou;
836
837 #       return $e->search_action_hold_request(
838 #               { target => $id, hold_type => $type, fulfillment_time => undef }, {idlist=>1});
839
840         # XXX make me return IDs in the future ^--
841         my $holds = $e->search_action_hold_request(
842                 { 
843                         target                          => $id, 
844                         cancel_time                     => undef, 
845                         hold_type                       => $type, 
846                         fulfillment_time        => undef 
847                 }
848         );
849
850         flesh_hold_transits($holds);
851         return $holds;
852 }
853
854
855 sub flesh_hold_transits {
856         my $holds = shift;
857         for my $hold ( @$holds ) {
858                 $hold->transit(
859                         $apputils->simplereq(
860                                 'open-ils.cstore',
861                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
862                                 { hold => $hold->id },
863                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
864                         )->[0]
865                 );
866         }
867 }
868
869
870
871
872 __PACKAGE__->register_method(
873         method => 'fetch_captured_holds',
874         api_name        => 'open-ils.circ.captured_holds.on_shelf.retrieve',
875         signature       => q/
876                 Returns a list ids of un-fulfilled holds for a given title id
877                 @param authtoken The login session key
878                 @param org The org id of the location in question
879         /
880 );
881 sub fetch_captured_holds {
882         my( $self, $conn, $auth, $org ) = @_;
883
884         my $e = new_editor(authtoken => $auth);
885         return $e->event unless $e->checkauth;
886         return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
887
888         $org ||= $e->requestor->ws_ou;
889
890         my $holds = $e->search_action_hold_request(
891                 { 
892                         capture_time            => { "!=" => undef },
893                         current_copy            => { "!=" => undef },
894                         fulfillment_time        => undef,
895                         pickup_lib                      => $org,
896                         cancel_time                     => undef,
897                 }
898         );
899
900         my @res;
901         for my $h (@$holds) {
902                 my $copy = $e->retrieve_asset_copy($h->current_copy)
903                         or return $e->event;
904                 push( @res, $h ) if 
905                         $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
906         }
907
908         flesh_hold_transits(\@res);
909         return \@res;
910 }
911
912
913
914
915
916 __PACKAGE__->register_method(
917         method  => "check_title_hold",
918         api_name        => "open-ils.circ.title_hold.is_possible",
919         notes           => q/
920                 Determines if a hold were to be placed by a given user,
921                 whether or not said hold would have any potential copies
922                 to fulfill it.
923                 @param authtoken The login session key
924                 @param params A hash of named params including:
925                         patronid  - the id of the hold recipient
926                         titleid (brn) - the id of the title to be held
927                         depth   - the hold range depth (defaults to 0)
928         /);
929
930 sub check_title_hold {
931         my( $self, $client, $authtoken, $params ) = @_;
932
933         my %params              = %$params;
934         my $titleid             = $params{titleid} ||"";
935         my $mrid                        = $params{mrid} ||"";
936         my $depth               = $params{depth} || 0;
937         my $pickup_lib  = $params{pickup_lib};
938         my $hold_type   = $params{hold_type} || 'T';
939
940         my $e = new_editor(authtoken=>$authtoken);
941         return $e->event unless $e->checkauth;
942         my $patron = $e->retrieve_actor_user($params{patronid})
943                 or return $e->event;
944
945         if( $e->requestor->id ne $patron->id ) {
946                 return $e->event unless 
947                         $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou);
948         }
949
950         return OpenILS::Event->new('PATRON_BARRED') 
951                 if $patron->barred and 
952                         ($patron->barred =~ /t/i or $patron->barred == 1);
953
954         my $rangelib    = $params{range_lib} || $patron->home_ou;
955
956         my $request_lib = $e->retrieve_actor_org_unit($e->requestor->ws_ou)
957                 or return $e->event;
958
959         if( $hold_type eq 'T' ) {
960                 return _check_title_hold_is_possible(
961                         $titleid, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
962         }
963
964         if( $hold_type eq 'M' ) {
965                 my $maps = $e->search_metabib_source_map({metarecord=>$mrid});
966                 my @recs = map { $_->source } @$maps;
967                 for my $rec (@recs) {
968                         return 1 if (_check_title_hold_is_possible(
969                                 $rec, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib));
970                 }
971         }
972 }
973
974
975
976 sub _check_title_hold_is_possible {
977         my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
978
979         my $limit       = 10;
980         my $offset      = 0;
981         my $title;
982
983         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
984
985         while( $title = $U->storagereq(
986                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
987                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
988
989                 last unless 
990                         ref($title) and 
991                         ref($title->call_numbers) and 
992                         @{$title->call_numbers};
993
994                 for my $cn (@{$title->call_numbers}) {
995         
996                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
997         
998                         for my $copy (@{$cn->copies}) {
999         
1000                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
1001         
1002                                 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
1003                                         {       patron                          => $patron, 
1004                                                 requestor                       => $requestor, 
1005                                                 copy                                    => $copy,
1006                                                 title                                   => $title, 
1007                                                 title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
1008                                                 pickup_lib                      => $pickup_lib,
1009                                                 request_lib                     => $request_lib 
1010                                         } 
1011                                 );
1012         
1013                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
1014                         }
1015                 }
1016
1017                 $offset += $limit;
1018         }
1019         return 0;
1020 }
1021
1022
1023
1024 sub find_nearest_permitted_hold {
1025
1026         my $class       = shift;
1027         my $session = shift;
1028         my $copy                = shift;
1029         my $user                = shift;
1030         my $evt         = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
1031
1032         # first see if this copy has already been selected to fulfill a hold
1033         my $hold  = $session->request(
1034                 "open-ils.storage.direct.action.hold_request.search_where",
1035                 { current_copy => $copy->id, cancel_time => undef, capture_time => undef } )->gather(1);
1036
1037         if( $hold ) {
1038                 $logger->info("hold found which can be fulfilled by copy ".$copy->id);
1039                 return $hold;
1040         }
1041
1042         # We know this hold is permitted, so just return it
1043         return $hold if $hold;
1044
1045         $logger->debug("searching for potential holds at org ". 
1046                 $user->ws_ou." and copy ".$copy->id);
1047
1048         my $holds = $session->request(
1049                 "open-ils.storage.action.hold_request.nearest_hold.atomic",
1050                 $user->ws_ou, $copy->id, 5 )->gather(1);
1051
1052         return (undef, $evt) unless @$holds;
1053
1054         # for each potential hold, we have to run the permit script
1055         # to make sure the hold is actually permitted.
1056
1057         for my $holdid (@$holds) {
1058                 next unless $holdid;
1059                 $logger->info("Checking if hold $holdid is permitted for user ".$user->id);
1060
1061                 my ($hold) = $U->fetch_hold($holdid);
1062                 next unless $hold;
1063                 my ($reqr) = $U->fetch_user($hold->requestor);
1064
1065                 return ($hold) if OpenILS::Utils::PermitHold::permit_copy_hold(
1066                         {
1067                                 patron_id                       => $hold->usr,
1068                                 requestor                       => $reqr->id,
1069                                 copy                                    => $copy,
1070                                 pickup_lib                      => $hold->pickup_lib,
1071                                 request_lib                     => $hold->request_lib 
1072                         } 
1073                 );
1074         }
1075
1076         return (undef, $evt);
1077 }
1078
1079
1080 1;