]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
removed some unused code
[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, $login_session, $hold_id) = @_;
481
482
483         my( $requestor, $target, $hold, $copy, $transit, $evt );
484
485         ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
486         return $evt if $evt;
487
488         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
489                 $login_session, $hold->usr, 'VIEW_HOLD' );
490         return $evt if $evt;
491
492         return 1 unless (defined($hold->current_copy));
493         
494         ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
495         return $evt if $evt;
496
497         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
498
499         ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
500         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
501
502         return 3 if defined($hold->capture_time);
503
504         return 2;
505 }
506
507
508
509
510
511 =head DEPRECATED
512 __PACKAGE__->register_method(
513         method  => "capture_copy",
514         api_name        => "open-ils.circ.hold.capture_copy.barcode",
515         notes           => <<"  NOTE");
516         Captures a copy to fulfil a hold
517         Params is login session and copy barcode
518         Optional param is 'flesh'.  If set, we also return the
519         relevant copy and title
520         login mus have COPY_CHECKIN permissions (since this is essentially
521         copy checkin)
522         NOTE
523
524 # XXX deprecate me XXX
525
526 sub capture_copy {
527         my( $self, $client, $login_session, $params ) = @_;
528         my %params = %$params;
529         my $barcode = $params{barcode};
530
531
532         my( $user, $target, $copy, $hold, $evt );
533
534         ( $user, $evt ) = $apputils->checkses($login_session);
535         return $evt if $evt;
536
537         # am I allowed to checkin a copy?
538         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
539         return $evt if $evt;
540
541         $logger->info("Capturing copy with barcode $barcode");
542
543         my $session = $apputils->start_db_session();
544
545         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
546         return $evt if $evt;
547
548         $logger->debug("Capturing copy " . $copy->id);
549
550         #( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
551         ( $hold, $evt ) = $self->find_nearest_permitted_hold($session, $copy, $user);
552         return $evt if $evt;
553
554         warn "Found hold " . $hold->id . "\n";
555         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
556
557         $hold->current_copy($copy->id);
558         $hold->capture_time("now"); 
559
560         #update the hold
561         my $stat = $session->request(
562                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
563         if(!$stat) { throw OpenSRF::EX::ERROR 
564                 ("Error updating hold request " . $copy->id); }
565
566         $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); #status on holds shelf
567
568         # if the staff member capturing this item is not at the pickup lib
569         if( $user->home_ou ne $hold->pickup_lib ) {
570                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
571         }
572
573         $copy->editor($user->id);
574         $copy->edit_date("now");
575         $stat = $session->request(
576                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
577         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
578
579         my $payload = { hold => $hold };
580         $payload->{copy} = $copy if $params{flesh_copy};
581
582         if($params{flesh_record}) {
583                 my $record;
584                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
585                 return $evt if $evt;
586                 $record = $apputils->record_to_mvr($record);
587                 $payload->{record} = $record;
588         }
589
590         $apputils->commit_db_session($session);
591
592         return OpenILS::Event->new('ROUTE_ITEM', 
593                 route_to => $hold->pickup_lib, payload => $payload );
594 }
595
596 sub _build_hold_transit {
597         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
598         my $trans = Fieldmapper::action::hold_transit_copy->new;
599
600         $trans->hold($hold->id);
601         $trans->source($user->home_ou);
602         $trans->dest($hold->pickup_lib);
603         $trans->source_send_time("now");
604         $trans->target_copy($copy->id);
605         $trans->copy_status($copy->status);
606
607         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
608         my ($stat) = $meth->run( $login_session, $trans, $session );
609         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
610         else { $copy->status(6); } #status in transit 
611 }
612
613
614
615 __PACKAGE__->register_method(
616         method  => "create_hold_transit",
617         api_name        => "open-ils.circ.hold_transit.create",
618         notes           => <<"  NOTE");
619         Creates a new transit object
620         NOTE
621
622 sub create_hold_transit {
623         my( $self, $client, $login_session, $transit, $session ) = @_;
624
625         my( $user, $evt ) = $apputils->checkses($login_session);
626         return $evt if $evt;
627         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
628         return $evt if $evt;
629
630         my $ses;
631         if($session) { $ses = $session; } 
632         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
633
634         return $ses->request(
635                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
636 }
637
638 =cut
639
640
641 sub find_local_hold {
642         my( $class, $session, $copy, $user ) = @_;
643         return $class->find_nearest_permitted_hold($session, $copy, $user);
644 }
645
646
647
648
649
650
651 sub fetch_open_hold_by_current_copy {
652         my $class = shift;
653         my $copyid = shift;
654         my $hold = $apputils->simplereq(
655                 'open-ils.cstore', 
656                 'open-ils.cstore.direct.action.hold_request.search.atomic',
657                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
658         return $hold->[0] if ref($hold);
659         return undef;
660 }
661
662 sub fetch_related_holds {
663         my $class = shift;
664         my $copyid = shift;
665         return $apputils->simplereq(
666                 'open-ils.cstore', 
667                 'open-ils.cstore.direct.action.hold_request.search.atomic',
668                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
669 }
670
671
672 __PACKAGE__->register_method (
673         method          => "hold_pull_list",
674         api_name                => "open-ils.circ.hold_pull_list.retrieve",
675         signature       => q/
676                 Returns a list of hold ID's that need to be "pulled"
677                 by a given location
678         /
679 );
680
681 sub hold_pull_list {
682         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
683         my( $reqr, $evt ) = $U->checkses($authtoken);
684         return $evt if $evt;
685
686         my $org = $reqr->ws_ou || $reqr->home_ou;
687         # the perm locaiton shouldn't really matter here since holds
688         # will exist all over and VIEW_HOLDS should be universal
689         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
690         return $evt if $evt;
691
692         return $U->storagereq(
693                 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
694                 $org, $limit, $offset ); 
695 }
696
697 __PACKAGE__->register_method (
698         method          => 'fetch_hold_notify',
699         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
700         signature       => q/ 
701                 Returns a list of hold notification objects based on hold id.
702                 @param authtoken The loggin session key
703                 @param holdid The id of the hold whose notifications we want to retrieve
704                 @return An array of hold notification objects, event on error.
705         /
706 );
707
708 sub fetch_hold_notify {
709         my( $self, $conn, $authtoken, $holdid ) = @_;
710         my( $requestor, $evt ) = $U->checkses($authtoken);
711         return $evt if $evt;
712         my ($hold, $patron);
713         ($hold, $evt) = $U->fetch_hold($holdid);
714         return $evt if $evt;
715         ($patron, $evt) = $U->fetch_user($hold->usr);
716         return $evt if $evt;
717
718         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
719         return $evt if $evt;
720
721         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
722         return $U->cstorereq(
723                 'open-ils.cstore.direct.action.hold_notification.search.atomic', {hold => $holdid} );
724 }
725
726
727 __PACKAGE__->register_method (
728         method          => 'create_hold_notify',
729         api_name                => 'open-ils.circ.hold_notification.create',
730         signature       => q/
731                 Creates a new hold notification object
732                 @param authtoken The login session key
733                 @param notification The hold notification object to create
734                 @return ID of the new object on success, Event on error
735                 /
736 );
737 sub create_hold_notify {
738         my( $self, $conn, $authtoken, $notification ) = @_;
739         my( $requestor, $evt ) = $U->checkses($authtoken);
740         return $evt if $evt;
741         my ($hold, $patron);
742         ($hold, $evt) = $U->fetch_hold($notification->hold);
743         return $evt if $evt;
744         ($patron, $evt) = $U->fetch_user($hold->usr);
745         return $evt if $evt;
746
747         # XXX perm depth probably doesn't matter here -- should always be consortium level
748         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
749         return $evt if $evt;
750
751         # Set the proper notifier 
752         $notification->notify_staff($requestor->id);
753         my $id = $U->storagereq(
754                 'open-ils.storage.direct.action.hold_notification.create', $notification );
755         return $U->DB_UPDATE_FAILED($notification) unless $id;
756         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
757         return $id;
758 }
759
760
761 __PACKAGE__->register_method(
762         method  => 'reset_hold',
763         api_name        => 'open-ils.circ.hold.reset',
764         signature       => q/
765                 Un-captures and un-targets a hold, essentially returning
766                 it to the state it was in directly after it was placed,
767                 then attempts to re-target the hold
768                 @param authtoken The login session key
769                 @param holdid The id of the hold
770         /
771 );
772
773
774 sub reset_hold {
775         my( $self, $conn, $auth, $holdid ) = @_;
776         my $reqr;
777         my ($hold, $evt) = $U->fetch_hold($holdid);
778         return $evt if $evt;
779         ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD'); # XXX stronger permission
780         return $evt if $evt;
781         $evt = $self->_reset_hold($reqr, $hold);
782         return $evt if $evt;
783         return 1;
784 }
785
786 sub _reset_hold {
787         my ($self, $reqr, $hold, $session) = @_;
788
789         my $x;
790         if(!$session) {
791                 $x = 1;
792                 $session = $U->start_db_session();
793         }
794
795         $hold->clear_capture_time;
796         $hold->clear_current_copy;
797
798         return $U->DB_UPDATE_FAILED($hold) unless 
799                 $session->request(
800                         'open-ils.storage.direct.action.hold_request.update', $hold )->gather(1);
801
802         $session->request(
803                 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id )->gather(1);
804
805         $U->commit_db_session($session) unless $x;
806         return undef;
807 }
808
809
810 __PACKAGE__->register_method(
811         method => 'fetch_open_title_holds',
812         api_name        => 'open-ils.circ.open_holds.retrieve',
813         signature       => q/
814                 Returns a list ids of un-fulfilled holds for a given title id
815                 @param authtoken The login session key
816                 @param id the id of the item whose holds we want to retrieve
817                 @param type The hold type - M, T, V, C
818         /
819 );
820
821 sub fetch_open_title_holds {
822         my( $self, $conn, $auth, $id, $type, $org ) = @_;
823         my $e = new_editor( authtoken => $auth );
824         return $e->event unless $e->checkauth;
825
826         $type ||= "T";
827         $org ||= $e->requestor->ws_ou;
828
829 #       return $e->search_action_hold_request(
830 #               { target => $id, hold_type => $type, fulfillment_time => undef }, {idlist=>1});
831
832         # XXX make me return IDs in the future ^--
833         my $holds = $e->search_action_hold_request(
834                 { 
835                         target                          => $id, 
836                         cancel_time                     => undef, 
837                         hold_type                       => $type, 
838                         fulfillment_time        => undef 
839                 }
840         );
841
842         flesh_hold_transits($holds);
843         return $holds;
844 }
845
846
847 sub flesh_hold_transits {
848         my $holds = shift;
849         for my $hold ( @$holds ) {
850                 $hold->transit(
851                         $apputils->simplereq(
852                                 'open-ils.cstore',
853                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
854                                 { hold => $hold->id },
855                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
856                         )->[0]
857                 );
858         }
859 }
860
861
862
863
864 __PACKAGE__->register_method(
865         method => 'fetch_captured_holds',
866         api_name        => 'open-ils.circ.captured_holds.on_shelf.retrieve',
867         signature       => q/
868                 Returns a list ids of un-fulfilled holds for a given title id
869                 @param authtoken The login session key
870                 @param org The org id of the location in question
871         /
872 );
873 sub fetch_captured_holds {
874         my( $self, $conn, $auth, $org ) = @_;
875
876         my $e = new_editor(authtoken => $auth);
877         return $e->event unless $e->checkauth;
878         return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
879
880         $org ||= $e->requestor->ws_ou;
881
882         my $holds = $e->search_action_hold_request(
883                 { 
884                         capture_time            => { "!=" => undef },
885                         current_copy            => { "!=" => undef },
886                         fulfillment_time        => undef,
887                         pickup_lib                      => $org,
888                         cancel_time                     => undef,
889                 }
890         );
891
892         my @res;
893         for my $h (@$holds) {
894                 my $copy = $e->retrieve_asset_copy($h->current_copy)
895                         or return $e->event;
896                 push( @res, $h ) if 
897                         $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
898         }
899
900         flesh_hold_transits(\@res);
901         return \@res;
902 }
903
904
905
906
907
908 __PACKAGE__->register_method(
909         method  => "check_title_hold",
910         api_name        => "open-ils.circ.title_hold.is_possible",
911         notes           => q/
912                 Determines if a hold were to be placed by a given user,
913                 whether or not said hold would have any potential copies
914                 to fulfill it.
915                 @param authtoken The login session key
916                 @param params A hash of named params including:
917                         patronid  - the id of the hold recipient
918                         titleid (brn) - the id of the title to be held
919                         depth   - the hold range depth (defaults to 0)
920         /);
921
922 sub check_title_hold {
923         my( $self, $client, $authtoken, $params ) = @_;
924
925         my %params              = %$params;
926         my $titleid             = $params{titleid} ||"";
927         my $mrid                        = $params{mrid} ||"";
928         my $depth               = $params{depth} || 0;
929         my $pickup_lib  = $params{pickup_lib};
930         my $hold_type   = $params{hold_type} || 'T';
931
932         my $e = new_editor(authtoken=>$authtoken);
933         return $e->event unless $e->checkauth;
934         my $patron = $e->retrieve_actor_user($params{patronid})
935                 or return $e->event;
936
937         if( $e->requestor->id ne $patron->id ) {
938                 return $e->event unless 
939                         $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou);
940         }
941
942         return OpenILS::Event->new('PATRON_BARRED') 
943                 if $patron->barred and 
944                         ($patron->barred =~ /t/i or $patron->barred == 1);
945
946         my $rangelib    = $params{range_lib} || $patron->home_ou;
947
948         my $request_lib = $e->retrieve_actor_org_unit($e->requestor->ws_ou)
949                 or return $e->event;
950
951         if( $hold_type eq 'T' ) {
952                 return _check_title_hold_is_possible(
953                         $titleid, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
954         }
955
956         if( $hold_type eq 'M' ) {
957                 my $maps = $e->search_metabib_source_map({metarecord=>$mrid});
958                 my @recs = map { $_->source } @$maps;
959                 for my $rec (@recs) {
960                         return 1 if (_check_title_hold_is_possible(
961                                 $rec, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib));
962                 }
963         }
964 }
965
966
967
968 sub _check_title_hold_is_possible {
969         my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
970
971         my $limit       = 10;
972         my $offset      = 0;
973         my $title;
974
975         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
976
977         while( $title = $U->storagereq(
978                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
979                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
980
981                 last unless 
982                         ref($title) and 
983                         ref($title->call_numbers) and 
984                         @{$title->call_numbers};
985
986                 for my $cn (@{$title->call_numbers}) {
987         
988                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
989         
990                         for my $copy (@{$cn->copies}) {
991         
992                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
993         
994                                 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
995                                         {       patron                          => $patron, 
996                                                 requestor                       => $requestor, 
997                                                 copy                                    => $copy,
998                                                 title                                   => $title, 
999                                                 title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
1000                                                 pickup_lib                      => $pickup_lib,
1001                                                 request_lib                     => $request_lib 
1002                                         } 
1003                                 );
1004         
1005                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
1006                         }
1007                 }
1008
1009                 $offset += $limit;
1010         }
1011         return 0;
1012 }
1013
1014
1015
1016 sub find_nearest_permitted_hold {
1017
1018         my $class       = shift;
1019         my $session = shift;
1020         my $copy                = shift;
1021         my $user                = shift;
1022         my $evt         = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
1023
1024         # first see if this copy has already been selected to fulfill a hold
1025         my $hold  = $session->request(
1026                 "open-ils.storage.direct.action.hold_request.search_where",
1027                 { current_copy => $copy->id, cancel_time => undef, capture_time => undef } )->gather(1);
1028
1029         if( $hold ) {
1030                 $logger->info("hold found which can be fulfilled by copy ".$copy->id);
1031                 return $hold;
1032         }
1033
1034         # We know this hold is permitted, so just return it
1035         return $hold if $hold;
1036
1037         $logger->debug("searching for potential holds at org ". 
1038                 $user->ws_ou." and copy ".$copy->id);
1039
1040         my $holds = $session->request(
1041                 "open-ils.storage.action.hold_request.nearest_hold.atomic",
1042                 $user->ws_ou, $copy->id, 5 )->gather(1);
1043
1044         return (undef, $evt) unless @$holds;
1045
1046         # for each potential hold, we have to run the permit script
1047         # to make sure the hold is actually permitted.
1048
1049         for my $holdid (@$holds) {
1050                 next unless $holdid;
1051                 $logger->info("Checking if hold $holdid is permitted for user ".$user->id);
1052
1053                 my ($hold) = $U->fetch_hold($holdid);
1054                 next unless $hold;
1055                 my ($reqr) = $U->fetch_user($hold->requestor);
1056
1057                 return ($hold) if OpenILS::Utils::PermitHold::permit_copy_hold(
1058                         {
1059                                 patron_id                       => $hold->usr,
1060                                 requestor                       => $reqr->id,
1061                                 copy                                    => $copy,
1062                                 pickup_lib                      => $hold->pickup_lib,
1063                                 request_lib                     => $hold->request_lib 
1064                         } 
1065                 );
1066         }
1067
1068         return (undef, $evt);
1069 }
1070
1071
1072 1;