]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
forgot to commit the xact
[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 use OpenILS::Application::Circ::Transit;
31
32 my $apputils = "OpenILS::Application::AppUtils";
33 my $U = $apputils;
34
35
36
37 __PACKAGE__->register_method(
38         method  => "create_hold",
39         api_name        => "open-ils.circ.holds.create",
40         notes           => <<NOTE);
41 Create a new hold for an item.  From a permissions perspective, 
42 the login session is used as the 'requestor' of the hold.  
43 The hold recipient is determined by the 'usr' setting within
44 the hold object.
45
46 First we verify the requestion has holds request permissions.
47 Then we verify that the recipient is allowed to make the given hold.
48 If not, we see if the requestor has "override" capabilities.  If not,
49 a permission exception is returned.  If permissions allow, we cycle
50 through the set of holds objects and create.
51
52 If the recipient does not have permission to place multiple holds
53 on a single title and said operation is attempted, a permission
54 exception is returned
55 NOTE
56
57
58 __PACKAGE__->register_method(
59         method  => "create_hold",
60         api_name        => "open-ils.circ.holds.create.override",
61         signature       => q/
62                 If the recipient is not allowed to receive the requested hold,
63                 call this method to attempt the override
64                 @see open-ils.circ.holds.create
65         /
66 );
67
68 sub create_hold {
69         my( $self, $conn, $auth, @holds ) = @_;
70         my $e = new_editor(authtoken=>$auth, xact=>1);
71         return $e->event unless $e->checkauth;
72
73         my $override = 1 if $self->api_name =~ /override/;
74
75         my $holds = (ref($holds[0] eq 'ARRAY')) ? $holds[0] : [@holds];
76
77 #       my @copyholds;
78
79         for my $hold (@$holds) {
80
81                 next unless $hold;
82                 my @events;
83
84                 my $requestor = $e->requestor;
85                 my $recipient = $requestor;
86
87
88                 if( $requestor->id ne $hold->usr ) {
89                         # Make sure the requestor is allowed to place holds for 
90                         # the recipient if they are not the same people
91                         $recipient = $e->retrieve_actor_user($hold->usr) or return $e->event;
92                         $e->allowed('REQUEST_HOLDS', $recipient->home_ou) or return $e->event;
93                 }
94
95                 # Now make sure the recipient is allowed to receive the specified hold
96                 my $pevt;
97                 my $porg                = $recipient->home_ou;
98                 my $rid         = $e->requestor->id;
99                 my $t                   = $hold->hold_type;
100
101                 # See if a duplicate hold already exists
102                 my $sargs = {
103                         usr                     => $recipient->id, 
104                         hold_type       => $t, 
105                         fulfillment_time => undef, 
106                         target          => $hold->target,
107                         cancel_time     => undef,
108                 };
109
110                 $sargs->{holdable_formats} = $hold->holdable_formats if $t eq 'M';
111                         
112                 my $existing = $e->search_action_hold_request($sargs); 
113                 push( @events, OpenILS::Event->new('HOLD_EXISTS')) if @$existing;
114
115                 if( $t eq OILS_HOLD_TYPE_METARECORD ) 
116                         { $pevt = $e->event unless $e->checkperm($rid, $porg, 'MR_HOLDS'); }
117
118                 if( $t eq OILS_HOLD_TYPE_TITLE ) 
119                         { $pevt = $e->event unless $e->checkperm($rid, $porg, 'TITLE_HOLDS');  }
120
121                 if( $t eq OILS_HOLD_TYPE_VOLUME ) 
122                         { $pevt = $e->event unless $e->checkperm($rid, $porg, 'VOLUME_HOLDS'); }
123
124                 if( $t eq OILS_HOLD_TYPE_COPY ) 
125                         { $pevt = $e->event unless $e->checkperm($rid, $porg, 'COPY_HOLDS'); }
126
127                 return $pevt if $pevt;
128
129                 if( @events ) {
130                         if( $override ) {
131                                 for my $evt (@events) {
132                                         next unless $evt;
133                                         my $name = $evt->{textcode};
134                                         return $e->event unless $e->allowed("$name.override", $porg);
135                                 }
136                         } else {
137                                 return \@events;
138                         }
139                 }
140
141                 $hold->requestor($e->requestor->id); 
142                 $hold->request_lib($e->requestor->ws_ou);
143                 $hold->selection_ou($recipient->home_ou) unless $hold->selection_ou;
144                 $hold = $e->create_action_hold_request($hold) or return $e->event;
145         }
146
147         $e->commit;
148
149         $conn->respond_complete(1);
150
151     for(@holds) {
152         next if $_->frozen;
153             $U->storagereq(
154                     'open-ils.storage.action.hold_request.copy_targeter', 
155                     undef, $_->id );
156     }
157
158         return undef;
159 }
160
161 sub __create_hold {
162         my( $self, $client, $login_session, @holds) = @_;
163
164         if(!@holds){return 0;}
165         my( $user, $evt ) = $apputils->checkses($login_session);
166         return $evt if $evt;
167
168         my $holds;
169         if(ref($holds[0]) eq 'ARRAY') {
170                 $holds = $holds[0];
171         } else { $holds = [ @holds ]; }
172
173         $logger->debug("Iterating over holds requests...");
174
175         for my $hold (@$holds) {
176
177                 if(!$hold){next};
178                 my $type = $hold->hold_type;
179
180                 $logger->activity("User " . $user->id . 
181                         " creating new hold of type $type for user " . $hold->usr);
182
183                 my $recipient;
184                 if($user->id ne $hold->usr) {
185                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
186                         return $evt if $evt;
187
188                 } else {
189                         $recipient = $user;
190                 }
191
192
193                 my $perm = undef;
194
195                 # am I allowed to place holds for this user?
196                 if($hold->requestor ne $hold->usr) {
197                         $perm = _check_request_holds_perm($user->id, $user->home_ou);
198                         if($perm) { return $perm; }
199                 }
200
201                 # is this user allowed to have holds of this type?
202                 $perm = _check_holds_perm($type, $hold->requestor, $recipient->home_ou);
203         return $perm if $perm;
204
205                 #enforce the fact that the login is the one requesting the hold
206                 $hold->requestor($user->id); 
207                 $hold->selection_ou($recipient->home_ou) unless $hold->selection_ou;
208
209                 my $resp = $apputils->simplereq(
210                         'open-ils.storage',
211                         'open-ils.storage.direct.action.hold_request.create', $hold );
212
213                 if(!$resp) { 
214                         return OpenSRF::EX::ERROR ("Error creating hold"); 
215                 }
216         }
217
218         return 1;
219 }
220
221 # makes sure that a user has permission to place the type of requested hold
222 # returns the Perm exception if not allowed, returns undef if all is well
223 sub _check_holds_perm {
224         my($type, $user_id, $org_id) = @_;
225
226         my $evt;
227         if($type eq "M") {
228                 if($evt = $apputils->check_perms(
229                         $user_id, $org_id, "MR_HOLDS")) {
230                         return $evt;
231                 } 
232
233         } elsif ($type eq "T") {
234                 if($evt = $apputils->check_perms(
235                         $user_id, $org_id, "TITLE_HOLDS")) {
236                         return $evt;
237                 }
238
239         } elsif($type eq "V") {
240                 if($evt = $apputils->check_perms(
241                         $user_id, $org_id, "VOLUME_HOLDS")) {
242                         return $evt;
243                 }
244
245         } elsif($type eq "C") {
246                 if($evt = $apputils->check_perms(
247                         $user_id, $org_id, "COPY_HOLDS")) {
248                         return $evt;
249                 }
250         }
251
252         return undef;
253 }
254
255 # tests if the given user is allowed to place holds on another's behalf
256 sub _check_request_holds_perm {
257         my $user_id = shift;
258         my $org_id = shift;
259         if(my $evt = $apputils->check_perms(
260                 $user_id, $org_id, "REQUEST_HOLDS")) {
261                 return $evt;
262         }
263 }
264
265 __PACKAGE__->register_method(
266         method  => "retrieve_holds_by_id",
267         api_name        => "open-ils.circ.holds.retrieve_by_id",
268         notes           => <<NOTE);
269 Retrieve the hold, with hold transits attached, for the specified id The login session is the requestor and if the requestor is
270 different from the user, then the requestor must have VIEW_HOLD permissions.
271 NOTE
272
273
274 sub retrieve_holds_by_id {
275         my($self, $client, $auth, $hold_id) = @_;
276         my $e = new_editor(authtoken=>$auth);
277         $e->checkauth or return $e->event;
278         $e->allowed('VIEW_HOLD') or return $e->event;
279
280         my $holds = $e->search_action_hold_request(
281                 [
282                         { id =>  $hold_id , fulfillment_time => undef }, 
283                         { order_by => { ahr => "request_time" } }
284                 ]
285         );
286
287         flesh_hold_transits($holds);
288         flesh_hold_notices($holds, $e);
289         return $holds;
290 }
291
292
293 __PACKAGE__->register_method(
294         method  => "retrieve_holds",
295         api_name        => "open-ils.circ.holds.retrieve",
296         notes           => <<NOTE);
297 Retrieves all the holds, with hold transits attached, for the specified
298 user id.  The login session is the requestor and if the requestor is
299 different from the user, then the requestor must have VIEW_HOLD permissions.
300 NOTE
301
302 __PACKAGE__->register_method(
303         method  => "retrieve_holds",
304         api_name        => "open-ils.circ.holds.id_list.retrieve",
305         notes           => <<NOTE);
306 Retrieves all the hold ids 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 sub retrieve_holds {
312         my($self, $client, $login_session, $user_id) = @_;
313
314         my( $user, $target, $evt ) = $apputils->checkses_requestor(
315                 $login_session, $user_id, 'VIEW_HOLD' );
316         return $evt if $evt;
317
318         my $holds = $apputils->simplereq(
319                 'open-ils.cstore',
320                 "open-ils.cstore.direct.action.hold_request.search.atomic",
321                 { 
322                         usr =>  $user_id , 
323                         fulfillment_time => undef,
324                         cancel_time => undef,
325                 }, 
326                 { order_by => { ahr => "request_time" } }
327         );
328         
329         if( ! $self->api_name =~ /id_list/ ) {
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
342         if( $self->api_name =~ /id_list/ ) {
343                 return [ map { $_->id } @$holds ];
344         } else {
345                 return $holds;
346         }
347 }
348
349
350 __PACKAGE__->register_method(
351    method => 'user_hold_count',
352    api_name => 'open-ils.circ.hold.user.count');
353
354 sub user_hold_count {
355    my( $self, $conn, $auth, $userid ) = @_;
356    my $e = new_editor(authtoken=>$auth);
357    return $e->event unless $e->checkauth;
358    my $patron = $e->retrieve_actor_user($userid)
359       or return $e->event;
360    return $e->event unless $e->allowed('VIEW_HOLD', $patron->home_ou);
361    return $self->__user_hold_count($e, $userid);
362 }
363
364 sub __user_hold_count {
365    my( $self, $e, $userid ) = @_;
366    my $holds = $e->search_action_hold_request(
367       {  usr =>  $userid , 
368          fulfillment_time => undef,
369          cancel_time => undef,
370       }, 
371       {idlist => 1}
372    );
373
374    return scalar(@$holds);
375 }
376
377
378 __PACKAGE__->register_method(
379         method  => "retrieve_holds_by_pickup_lib",
380         api_name        => "open-ils.circ.holds.retrieve_by_pickup_lib",
381         notes           => <<NOTE);
382 Retrieves all the holds, with hold transits attached, for the specified
383 pickup_ou id. 
384 NOTE
385
386 __PACKAGE__->register_method(
387         method  => "retrieve_holds_by_pickup_lib",
388         api_name        => "open-ils.circ.holds.id_list.retrieve_by_pickup_lib",
389         notes           => <<NOTE);
390 Retrieves all the hold ids for the specified
391 pickup_ou id. 
392 NOTE
393
394 sub retrieve_holds_by_pickup_lib {
395         my($self, $client, $login_session, $ou_id) = @_;
396
397         #FIXME -- put an appropriate permission check here
398         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
399         #       $login_session, $user_id, 'VIEW_HOLD' );
400         #return $evt if $evt;
401
402         my $holds = $apputils->simplereq(
403                 'open-ils.cstore',
404                 "open-ils.cstore.direct.action.hold_request.search.atomic",
405                 { 
406                         pickup_lib =>  $ou_id , 
407                         fulfillment_time => undef,
408                         cancel_time => undef
409                 }, 
410                 { order_by => { ahr => "request_time" } });
411
412
413         if( ! $self->api_name =~ /id_list/ ) {
414                 flesh_hold_transits($holds);
415         }
416
417         if( $self->api_name =~ /id_list/ ) {
418                 return [ map { $_->id } @$holds ];
419         } else {
420                 return $holds;
421         }
422 }
423
424 __PACKAGE__->register_method(
425         method  => "cancel_hold",
426         api_name        => "open-ils.circ.hold.cancel",
427         notes           => <<"  NOTE");
428         Cancels the specified hold.  The login session
429         is the requestor and if the requestor is different from the usr field
430         on the hold, the requestor must have CANCEL_HOLDS permissions.
431         the hold may be either the hold object or the hold id
432         NOTE
433
434 sub cancel_hold {
435         my($self, $client, $auth, $holdid) = @_;
436
437         my $e = new_editor(authtoken=>$auth, xact=>1);
438         return $e->event unless $e->checkauth;
439
440         my $hold = $e->retrieve_action_hold_request($holdid)
441                 or return $e->event;
442
443         if( $e->requestor->id ne $hold->usr ) {
444                 return $e->event unless $e->allowed('CANCEL_HOLDS');
445         }
446
447         return 1 if $hold->cancel_time;
448
449         # If the hold is captured, reset the copy status
450         if( $hold->capture_time and $hold->current_copy ) {
451
452                 my $copy = $e->retrieve_asset_copy($hold->current_copy)
453                         or return $e->event;
454
455                 if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
456          $logger->info("canceling hold $holdid whose item is on the holds shelf");
457 #                       $logger->info("setting copy to status 'reshelving' on hold cancel");
458 #                       $copy->status(OILS_COPY_STATUS_RESHELVING);
459 #                       $copy->editor($e->requestor->id);
460 #                       $copy->edit_date('now');
461 #                       $e->update_asset_copy($copy) or return $e->event;
462
463                 } elsif( $copy->status == OILS_COPY_STATUS_IN_TRANSIT ) {
464
465                         my $hid = $hold->id;
466                         $logger->warn("! canceling hold [$hid] that is in transit");
467                         my $transid = $e->search_action_hold_transit_copy({hold=>$hold->id},{idlist=>1})->[0];
468
469                         if( $transid ) {
470                                 my $trans = $e->retrieve_action_transit_copy($transid);
471                                 # Leave the transit alive, but  set the copy status to 
472                                 # reshelving so it will be properly reshelved when it gets back home
473                                 if( $trans ) {
474                                         $trans->copy_status( OILS_COPY_STATUS_RESHELVING );
475                                         $e->update_action_transit_copy($trans) or return $e->die_event;
476                                 }
477                         }
478                 }
479         }
480
481         $hold->cancel_time('now');
482         $e->update_action_hold_request($hold)
483                 or return $e->event;
484
485         $self->delete_hold_copy_maps($e, $hold->id);
486
487         $e->commit;
488         return 1;
489 }
490
491 sub delete_hold_copy_maps {
492         my $class = shift;
493         my $editor = shift;
494         my $holdid = shift;
495
496         my $maps = $editor->search_action_hold_copy_map({hold=>$holdid});
497         for(@$maps) {
498                 $editor->delete_action_hold_copy_map($_) 
499                         or return $editor->event;
500         }
501         return undef;
502 }
503
504
505 __PACKAGE__->register_method(
506         method  => "update_hold",
507         api_name        => "open-ils.circ.hold.update",
508         notes           => <<"  NOTE");
509         Updates the specified hold.  The login session
510         is the requestor and if the requestor is different from the usr field
511         on the hold, the requestor must have UPDATE_HOLDS permissions.
512         NOTE
513
514 sub update_hold {
515         my($self, $client, $auth, $hold) = @_;
516
517     my $e = new_editor(authtoken=>$auth, xact=>1);
518     return $e->die_event unless $e->checkauth;
519
520     if($hold->usr ne $e->requestor->id) {
521         # if the hold is for a different user, make sure the 
522         # requestor has the appropriate permissions
523         my $usr = $e->retrieve_actor_user($hold->usr)
524             or return $e->die_event;
525         return $e->die_event unless $e->allowed('UPDATE_HOLD', $usr->home_ou);
526     }
527
528     my $evt = $self->update_hold_if_frozen($e, $hold);
529     return $evt if $evt;
530
531     $e->update_action_hold_request($hold)
532         or return $e->die_event;
533
534     $e->commit;
535     return $hold->id;
536 }
537
538
539 # if the hold is frozen, this method ensures that the hold is not "targeted", 
540 # that is, it clears the current_copy and prev_check_time to essentiallly 
541 # reset the hold
542 sub update_hold_if_frozen {
543     my($self, $e, $hold) = @_;
544     return undef if $hold->capture_time;
545     if($hold->frozen and ($hold->current_copy or $hold->prev_check_time)) {
546         $logger->info("clearing current_copy and check_time for frozen hold");
547         $hold->clear_current_copy;
548         $hold->clear_prev_check_time;
549         $e->update_action_hold_request($hold) or return $e->die_event;
550     }
551     return undef;
552 }
553
554
555 __PACKAGE__->register_method(
556         method  => "retrieve_hold_status",
557         api_name        => "open-ils.circ.hold.status.retrieve",
558         notes           => <<"  NOTE");
559         Calculates the current status of the hold.
560         the requestor must have VIEW_HOLD permissions if the hold is for a user
561         other than the requestor.
562         Returns -1  on error (for now)
563         Returns 1 for 'waiting for copy to become available'
564         Returns 2 for 'waiting for copy capture'
565         Returns 3 for 'in transit'
566         Returns 4 for 'arrived'
567         NOTE
568
569 sub retrieve_hold_status {
570         my($self, $client, $auth, $hold_id) = @_;
571
572         my $e = new_editor(authtoken => $auth);
573         return $e->event unless $e->checkauth;
574         my $hold = $e->retrieve_action_hold_request($hold_id)
575                 or return $e->event;
576
577         if( $e->requestor->id != $hold->usr ) {
578                 return $e->event unless $e->allowed('VIEW_HOLD');
579         }
580
581         return _hold_status($e, $hold);
582
583 }
584
585 sub _hold_status {
586         my($e, $hold) = @_;
587         return 1 unless $hold->current_copy;
588         return 2 unless $hold->capture_time;
589
590         my $copy = $hold->current_copy;
591         unless( ref $copy ) {
592                 $copy = $e->retrieve_asset_copy($hold->current_copy)
593                         or return $e->event;
594         }
595
596         return 3 if $copy->status == OILS_COPY_STATUS_IN_TRANSIT;
597         return 4 if $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
598
599         return -1;
600 }
601
602
603 #sub find_local_hold {
604 #       my( $class, $session, $copy, $user ) = @_;
605 #       return $class->find_nearest_permitted_hold($session, $copy, $user);
606 #}
607
608
609 sub fetch_open_hold_by_current_copy {
610         my $class = shift;
611         my $copyid = shift;
612         my $hold = $apputils->simplereq(
613                 'open-ils.cstore', 
614                 'open-ils.cstore.direct.action.hold_request.search.atomic',
615                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
616         return $hold->[0] if ref($hold);
617         return undef;
618 }
619
620 sub fetch_related_holds {
621         my $class = shift;
622         my $copyid = shift;
623         return $apputils->simplereq(
624                 'open-ils.cstore', 
625                 'open-ils.cstore.direct.action.hold_request.search.atomic',
626                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
627 }
628
629
630 __PACKAGE__->register_method (
631         method          => "hold_pull_list",
632         api_name                => "open-ils.circ.hold_pull_list.retrieve",
633         signature       => q/
634                 Returns a list of holds that need to be "pulled"
635                 by a given location
636         /
637 );
638
639 __PACKAGE__->register_method (
640         method          => "hold_pull_list",
641         api_name                => "open-ils.circ.hold_pull_list.id_list.retrieve",
642         signature       => q/
643                 Returns a list of hold ID's that need to be "pulled"
644                 by a given location
645         /
646 );
647
648
649 sub hold_pull_list {
650         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
651         my( $reqr, $evt ) = $U->checkses($authtoken);
652         return $evt if $evt;
653
654         my $org = $reqr->ws_ou || $reqr->home_ou;
655         # the perm locaiton shouldn't really matter here since holds
656         # will exist all over and VIEW_HOLDS should be universal
657         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
658         return $evt if $evt;
659
660         if( $self->api_name =~ /id_list/ ) {
661                 return $U->storagereq(
662                         'open-ils.storage.direct.action.hold_request.pull_list.id_list.current_copy_circ_lib.atomic',
663                         $org, $limit, $offset ); 
664         } else {
665                 return $U->storagereq(
666                         'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
667                         $org, $limit, $offset ); 
668         }
669 }
670
671 __PACKAGE__->register_method (
672         method          => 'fetch_hold_notify',
673         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
674         signature       => q/ 
675                 Returns a list of hold notification objects based on hold id.
676                 @param authtoken The loggin session key
677                 @param holdid The id of the hold whose notifications we want to retrieve
678                 @return An array of hold notification objects, event on error.
679         /
680 );
681
682 sub fetch_hold_notify {
683         my( $self, $conn, $authtoken, $holdid ) = @_;
684         my( $requestor, $evt ) = $U->checkses($authtoken);
685         return $evt if $evt;
686         my ($hold, $patron);
687         ($hold, $evt) = $U->fetch_hold($holdid);
688         return $evt if $evt;
689         ($patron, $evt) = $U->fetch_user($hold->usr);
690         return $evt if $evt;
691
692         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
693         return $evt if $evt;
694
695         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
696         return $U->cstorereq(
697                 'open-ils.cstore.direct.action.hold_notification.search.atomic', {hold => $holdid} );
698 }
699
700
701 __PACKAGE__->register_method (
702         method          => 'create_hold_notify',
703         api_name                => 'open-ils.circ.hold_notification.create',
704         signature       => q/
705                 Creates a new hold notification object
706                 @param authtoken The login session key
707                 @param notification The hold notification object to create
708                 @return ID of the new object on success, Event on error
709                 /
710 );
711 =head old
712 sub __create_hold_notify {
713         my( $self, $conn, $authtoken, $notification ) = @_;
714         my( $requestor, $evt ) = $U->checkses($authtoken);
715         return $evt if $evt;
716         my ($hold, $patron);
717         ($hold, $evt) = $U->fetch_hold($notification->hold);
718         return $evt if $evt;
719         ($patron, $evt) = $U->fetch_user($hold->usr);
720         return $evt if $evt;
721
722         # XXX perm depth probably doesn't matter here -- should always be consortium level
723         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
724         return $evt if $evt;
725
726         # Set the proper notifier 
727         $notification->notify_staff($requestor->id);
728         my $id = $U->storagereq(
729                 'open-ils.storage.direct.action.hold_notification.create', $notification );
730         return $U->DB_UPDATE_FAILED($notification) unless $id;
731         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
732         return $id;
733 }
734 =cut
735
736 sub create_hold_notify {
737    my( $self, $conn, $auth, $note ) = @_;
738    my $e = new_editor(authtoken=>$auth, xact=>1);
739    return $e->die_event unless $e->checkauth;
740
741    my $hold = $e->retrieve_action_hold_request($note->hold)
742       or return $e->die_event;
743    my $patron = $e->retrieve_actor_user($hold->usr) 
744       or return $e->die_event;
745
746    return $e->die_event unless 
747       $e->allowed('CREATE_HOLD_NOTIFICATION', $patron->home_ou);
748
749         $note->notify_staff($e->requestor->id);
750    $e->create_action_hold_notification($note) or return $e->die_event;
751    $e->commit;
752    return $note->id;
753 }
754
755
756 __PACKAGE__->register_method(
757         method  => 'reset_hold',
758         api_name        => 'open-ils.circ.hold.reset',
759         signature       => q/
760                 Un-captures and un-targets a hold, essentially returning
761                 it to the state it was in directly after it was placed,
762                 then attempts to re-target the hold
763                 @param authtoken The login session key
764                 @param holdid The id of the hold
765         /
766 );
767
768
769 sub reset_hold {
770         my( $self, $conn, $auth, $holdid ) = @_;
771         my $reqr;
772         my ($hold, $evt) = $U->fetch_hold($holdid);
773         return $evt if $evt;
774         ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD'); # XXX stronger permission
775         return $evt if $evt;
776         $evt = $self->_reset_hold($reqr, $hold);
777         return $evt if $evt;
778         return 1;
779 }
780
781 sub _reset_hold {
782         my ($self, $reqr, $hold) = @_;
783
784         my $e = new_editor(xact =>1, requestor => $reqr);
785
786         $logger->info("reseting hold ".$hold->id);
787
788         my $hid = $hold->id;
789
790         if( $hold->capture_time and $hold->current_copy ) {
791
792                 my $copy = $e->retrieve_asset_copy($hold->current_copy)
793                         or return $e->event;
794
795                 if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
796                         $logger->info("setting copy to status 'reshelving' on hold retarget");
797                         $copy->status(OILS_COPY_STATUS_RESHELVING);
798                         $copy->editor($e->requestor->id);
799                         $copy->edit_date('now');
800                         $e->update_asset_copy($copy) or return $e->event;
801
802                 } elsif( $copy->status == OILS_COPY_STATUS_IN_TRANSIT ) {
803
804                         # We don't want the copy to remain "in transit"
805                         $copy->status(OILS_COPY_STATUS_RESHELVING);
806                         $logger->warn("! reseting hold [$hid] that is in transit");
807                         my $transid = $e->search_action_hold_transit_copy({hold=>$hold->id},{idlist=>1})->[0];
808
809                         if( $transid ) {
810                                 my $trans = $e->retrieve_action_transit_copy($transid);
811                                 if( $trans ) {
812                                         $logger->info("Aborting transit [$transid] on hold [$hid] reset...");
813                                         my $evt = OpenILS::Application::Circ::Transit::__abort_transit($e, $trans, $copy, 1);
814                                         $logger->info("Transit abort completed with result $evt");
815                                         return $evt unless "$evt" eq 1;
816                                 }
817                         }
818                 }
819         }
820
821         $hold->clear_capture_time;
822         $hold->clear_current_copy;
823
824         $e->update_action_hold_request($hold) or return $e->event;
825         $e->commit;
826
827         $U->storagereq(
828                 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id );
829
830         return undef;
831 }
832
833
834 __PACKAGE__->register_method(
835         method => 'fetch_open_title_holds',
836         api_name        => 'open-ils.circ.open_holds.retrieve',
837         signature       => q/
838                 Returns a list ids of un-fulfilled holds for a given title id
839                 @param authtoken The login session key
840                 @param id the id of the item whose holds we want to retrieve
841                 @param type The hold type - M, T, V, C
842         /
843 );
844
845 sub fetch_open_title_holds {
846         my( $self, $conn, $auth, $id, $type, $org ) = @_;
847         my $e = new_editor( authtoken => $auth );
848         return $e->event unless $e->checkauth;
849
850         $type ||= "T";
851         $org ||= $e->requestor->ws_ou;
852
853 #       return $e->search_action_hold_request(
854 #               { target => $id, hold_type => $type, fulfillment_time => undef }, {idlist=>1});
855
856         # XXX make me return IDs in the future ^--
857         my $holds = $e->search_action_hold_request(
858                 { 
859                         target                          => $id, 
860                         cancel_time                     => undef, 
861                         hold_type                       => $type, 
862                         fulfillment_time        => undef 
863                 }
864         );
865
866         flesh_hold_transits($holds);
867         return $holds;
868 }
869
870
871 sub flesh_hold_transits {
872         my $holds = shift;
873         for my $hold ( @$holds ) {
874                 $hold->transit(
875                         $apputils->simplereq(
876                                 'open-ils.cstore',
877                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
878                                 { hold => $hold->id },
879                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
880                         )->[0]
881                 );
882         }
883 }
884
885 sub flesh_hold_notices {
886         my( $holds, $e ) = @_;
887         $e ||= new_editor();
888
889         for my $hold (@$holds) {
890                 my $notices = $e->search_action_hold_notification(
891                         [
892                                 { hold => $hold->id },
893                                 { order_by => { anh => 'notify_time desc' } },
894                         ],
895                         {idlist=>1}
896                 );
897
898                 $hold->notify_count(scalar(@$notices));
899                 if( @$notices ) {
900                         my $n = $e->retrieve_action_hold_notification($$notices[0])
901                                 or return $e->event;
902                         $hold->notify_time($n->notify_time);
903                 }
904         }
905 }
906
907
908
909
910 __PACKAGE__->register_method(
911         method => 'fetch_captured_holds',
912         api_name        => 'open-ils.circ.captured_holds.on_shelf.retrieve',
913         signature       => q/
914                 Returns a list of un-fulfilled holds for a given title id
915                 @param authtoken The login session key
916                 @param org The org id of the location in question
917         /
918 );
919
920 __PACKAGE__->register_method(
921         method => 'fetch_captured_holds',
922         api_name        => 'open-ils.circ.captured_holds.id_list.on_shelf.retrieve',
923         signature       => q/
924                 Returns a list ids of un-fulfilled holds for a given title id
925                 @param authtoken The login session key
926                 @param org The org id of the location in question
927         /
928 );
929
930 sub fetch_captured_holds {
931         my( $self, $conn, $auth, $org ) = @_;
932
933         my $e = new_editor(authtoken => $auth);
934         return $e->event unless $e->checkauth;
935         return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
936
937         $org ||= $e->requestor->ws_ou;
938
939         my $holds = $e->search_action_hold_request(
940                 { 
941                         capture_time            => { "!=" => undef },
942                         current_copy            => { "!=" => undef },
943                         fulfillment_time        => undef,
944                         pickup_lib                      => $org,
945                         cancel_time                     => undef,
946                 }
947         );
948
949         my @res;
950         for my $h (@$holds) {
951                 my $copy = $e->retrieve_asset_copy($h->current_copy)
952                         or return $e->event;
953                 push( @res, $h ) if 
954                         $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
955         }
956
957         if( ! $self->api_name =~ /id_list/ ) {
958                 flesh_hold_transits(\@res);
959                 flesh_hold_notices(\@res, $e);
960         }
961
962         if( $self->api_name =~ /id_list/ ) {
963                 return [ map { $_->id } @res ];
964         } else {
965                 return \@res;
966         }
967 }
968
969
970 __PACKAGE__->register_method(
971         method  => "check_title_hold",
972         api_name        => "open-ils.circ.title_hold.is_possible",
973         notes           => q/
974                 Determines if a hold were to be placed by a given user,
975                 whether or not said hold would have any potential copies
976                 to fulfill it.
977                 @param authtoken The login session key
978                 @param params A hash of named params including:
979                         patronid  - the id of the hold recipient
980                         titleid (brn) - the id of the title to be held
981                         depth   - the hold range depth (defaults to 0)
982         /);
983
984 sub check_title_hold {
985         my( $self, $client, $authtoken, $params ) = @_;
986
987         my %params              = %$params;
988         my $titleid             = $params{titleid} ||"";
989         my $volid               = $params{volume_id};
990         my $copyid              = $params{copy_id};
991         my $mrid                        = $params{mrid} ||"";
992         my $depth               = $params{depth} || 0;
993         my $pickup_lib  = $params{pickup_lib};
994         my $hold_type   = $params{hold_type} || 'T';
995
996         my $e = new_editor(authtoken=>$authtoken);
997         return $e->event unless $e->checkauth;
998         my $patron = $e->retrieve_actor_user($params{patronid})
999                 or return $e->event;
1000
1001         if( $e->requestor->id ne $patron->id ) {
1002                 return $e->event unless 
1003                         $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou);
1004         }
1005
1006         return OpenILS::Event->new('PATRON_BARRED') if $U->is_true($patron->barred);
1007
1008         my $rangelib    = $params{range_lib} || $patron->home_ou;
1009
1010         my $request_lib = $e->retrieve_actor_org_unit($e->requestor->ws_ou)
1011                 or return $e->event;
1012
1013         $logger->info("checking hold possibility with type $hold_type");
1014
1015         my $copy;
1016         my $volume;
1017         my $title;
1018
1019         if( $hold_type eq OILS_HOLD_TYPE_COPY ) {
1020
1021                 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
1022                 $volume = $e->retrieve_asset_call_number($copy->call_number)
1023                         or return $e->event;
1024                 $title = $e->retrieve_biblio_record_entry($volume->record)
1025                         or return $e->event;
1026                 return verify_copy_for_hold( 
1027                         $patron, $e->requestor, $title, $copy, $pickup_lib, $request_lib );
1028
1029         } elsif( $hold_type eq OILS_HOLD_TYPE_VOLUME ) {
1030
1031                 $volume = $e->retrieve_asset_call_number($volid)
1032                         or return $e->event;
1033                 $title = $e->retrieve_biblio_record_entry($volume->record)
1034                         or return $e->event;
1035
1036                 return _check_volume_hold_is_possible(
1037                         $volume, $title, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
1038
1039         } elsif( $hold_type eq OILS_HOLD_TYPE_TITLE ) {
1040
1041                 return _check_title_hold_is_possible(
1042                         $titleid, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
1043
1044         } elsif( $hold_type eq OILS_HOLD_TYPE_METARECORD ) {
1045
1046                 my $maps = $e->search_metabib_source_map({metarecord=>$mrid});
1047                 my @recs = map { $_->source } @$maps;
1048                 for my $rec (@recs) {
1049                         return 1 if (_check_title_hold_is_possible(
1050                                 $rec, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib));
1051                 }
1052                 return 0;       
1053         }
1054 }
1055
1056
1057
1058 sub ___check_title_hold_is_possible {
1059         my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
1060
1061         my $limit       = 10;
1062         my $offset      = 0;
1063         my $title;
1064
1065         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
1066
1067         while( $title = $U->storagereq(
1068                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
1069                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
1070
1071                 last unless 
1072                         ref($title) and 
1073                         ref($title->call_numbers) and 
1074                         @{$title->call_numbers};
1075
1076                 for my $cn (@{$title->call_numbers}) {
1077         
1078                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
1079         
1080                         for my $copy (@{$cn->copies}) {
1081                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
1082                                 return 1 if verify_copy_for_hold( 
1083                                         $patron, $requestor, $title, $copy, $pickup_lib, $request_lib );
1084                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
1085                         }
1086                 }
1087
1088                 $offset += $limit;
1089         }
1090         return 0;
1091 }
1092
1093 my %prox_cache;
1094
1095 sub _check_title_hold_is_possible {
1096         my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
1097    
1098    my $e = new_editor();
1099
1100     # this monster will grab the id and circ_lib of all of the "holdable" copies for the given record
1101     my $copies = $e->json_query(
1102         { 
1103             select => { acp => ['id', 'circ_lib'] },
1104             from => {
1105                 acp => {
1106                     acn => {
1107                         field => 'id',
1108                         fkey => 'call_number',
1109                         'join' => {
1110                             bre => {
1111                                 field => 'id',
1112                                 filter => { id => $titleid },
1113                                 fkey => 'record'
1114                             }
1115                         }
1116                     },
1117                     acpl => { field => 'id', filter => { holdable => 't'}, fkey => 'location' },
1118                     ccs => { field => 'id', filter => { holdable => 't'}, fkey => 'status' }
1119                 }
1120             }, 
1121             where => {
1122                 '+acp' => { circulate => 't', deleted => 'f', holdable => 't' }
1123             }
1124         }
1125     );
1126
1127    return $e->event unless defined $copies;
1128    $logger->info("title possible found ".scalar(@$copies)." potential copies");
1129    return 0 unless @$copies;
1130
1131    # -----------------------------------------------------------------------
1132    # sort the copies into buckets based on their circ_lib proximity to 
1133    # the patron's home_ou.  
1134    # -----------------------------------------------------------------------
1135
1136    my $home_org = $patron->home_ou;
1137    my $req_org = $request_lib->id;
1138
1139    my $home_prox = 
1140       ($prox_cache{$home_org}) ? 
1141          $prox_cache{$home_org} :
1142          $prox_cache{$home_org} = $e->search_actor_org_unit_proximity({from_org => $home_org});
1143
1144    my %buckets;
1145    my %hash = map { ($_->to_org => $_->prox) } @$home_prox;
1146    push( @{$buckets{ $hash{$_->{circ_lib}} } }, $_->{id} ) for @$copies;
1147
1148    my @keys = sort { $a <=> $b } keys %buckets;
1149
1150
1151    if( $home_org ne $req_org ) {
1152       # -----------------------------------------------------------------------
1153       # shove the copies close to the request_lib into the primary buckets 
1154       # directly before the farthest away copies.  That way, they are not 
1155       # given priority, but they are checked before the farthest copies.
1156       # -----------------------------------------------------------------------
1157       my $req_prox = 
1158          ($prox_cache{$req_org}) ? 
1159             $prox_cache{$req_org} :
1160             $prox_cache{$req_org} = $e->search_actor_org_unit_proximity({from_org => $req_org});
1161
1162       my %buckets2;
1163       my %hash2 = map { ($_->to_org => $_->prox) } @$req_prox;
1164       push( @{$buckets2{ $hash2{$_->{circ_lib}} } }, $_->{id} ) for @$copies;
1165
1166       my $highest_key = $keys[@keys - 1];  # the farthest prox in the exising buckets
1167       my $new_key = $highest_key - 0.5; # right before the farthest prox
1168       my @keys2 = sort { $a <=> $b } keys %buckets2;
1169       for my $key (@keys2) {
1170          last if $key >= $highest_key;
1171          push( @{$buckets{$new_key}}, $_ ) for @{$buckets2{$key}};
1172       }
1173    }
1174
1175    @keys = sort { $a <=> $b } keys %buckets;
1176
1177    my $title;
1178    my %seen;
1179    for my $key (@keys) {
1180       my @cps = @{$buckets{$key}};
1181
1182       $logger->info("looking at " . scalar(@{$buckets{$key}}). " copies in proximity bucket $key");
1183
1184       for my $copyid (@cps) {
1185
1186          next if $seen{$copyid};
1187          $seen{$copyid} = 1; # there could be dupes given the merged buckets
1188          my $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
1189          $logger->debug("looking at bucket_key=$key, copy $copyid : circ_lib = " . $copy->circ_lib);
1190
1191          unless($title) { # grab the title if we don't already have it
1192             my $vol = $e->retrieve_asset_call_number(
1193                [ $copy->call_number, { flesh => 1, flesh_fields => { acn => ['record'] } } ] );
1194             $title = $vol->record;
1195          }
1196    
1197          return 1 if verify_copy_for_hold( 
1198             $patron, $requestor, $title, $copy, $pickup_lib, $request_lib );
1199    
1200       }
1201    }
1202
1203    return 0;
1204 }
1205
1206
1207 sub _check_volume_hold_is_possible {
1208         my( $vol, $title, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
1209         my $copies = new_editor->search_asset_copy({call_number => $vol->id});
1210         $logger->info("checking possibility of volume hold for volume ".$vol->id);
1211         for my $copy ( @$copies ) {
1212                 return 1 if verify_copy_for_hold( 
1213                         $patron, $requestor, $title, $copy, $pickup_lib, $request_lib );
1214         }
1215         return 0;
1216 }
1217
1218
1219
1220 sub verify_copy_for_hold {
1221         my( $patron, $requestor, $title, $copy, $pickup_lib, $request_lib ) = @_;
1222         $logger->info("checking possibility of copy in hold request for copy ".$copy->id);
1223         return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
1224                 {       patron                          => $patron, 
1225                         requestor                       => $requestor, 
1226                         copy                            => $copy,
1227                         title                           => $title, 
1228                         title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
1229                         pickup_lib                      => $pickup_lib,
1230                         request_lib                     => $request_lib 
1231                 } 
1232         );
1233         return 0;
1234 }
1235
1236
1237
1238 sub find_nearest_permitted_hold {
1239
1240         my $class       = shift;
1241         my $editor      = shift; # CStoreEditor object
1242         my $copy                = shift; # copy to target
1243         my $user                = shift; # staff 
1244         my $check_only = shift; # do no updates, just see if the copy could fulfill a hold
1245         my $evt         = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
1246
1247         my $bc = $copy->barcode;
1248
1249         # find any existing holds that already target this copy
1250         my $old_holds = $editor->search_action_hold_request(
1251                 {       current_copy => $copy->id, 
1252                         cancel_time => undef, 
1253                         capture_time => undef 
1254                 } 
1255         );
1256
1257         # hold->type "R" means we need this copy
1258         for my $h (@$old_holds) { return ($h) if $h->hold_type eq 'R'; }
1259
1260
1261     my $hold_stall_interval = $U->ou_ancestor_setting_value($user->ws_ou, 'circ.hold_stalling.soft');
1262
1263         $logger->info("circulator: searching for best hold at org ".$user->ws_ou.
1264         " and copy $bc with a hold stalling interval of ". ($hold_stall_interval || "(none)"));
1265
1266         # search for what should be the best holds for this copy to fulfill
1267         my $best_holds = $U->storagereq(
1268                 "open-ils.storage.action.hold_request.nearest_hold.atomic",
1269                 $user->ws_ou, $copy->id, 10, $hold_stall_interval );
1270
1271         unless(@$best_holds) {
1272
1273                 if( my $hold = $$old_holds[0] ) {
1274                         $logger->info("circulator: using existing pre-targeted hold ".$hold->id." in hold search");
1275                         return ($hold);
1276                 }
1277
1278                 $logger->info("circulator: no suitable holds found for copy $bc");
1279                 return (undef, $evt);
1280         }
1281
1282
1283         my $best_hold;
1284
1285         # for each potential hold, we have to run the permit script
1286         # to make sure the hold is actually permitted.
1287         for my $holdid (@$best_holds) {
1288                 next unless $holdid;
1289                 $logger->info("circulator: checking if hold $holdid is permitted for copy $bc");
1290
1291                 my $hold = $editor->retrieve_action_hold_request($holdid) or next;
1292                 my $reqr = $editor->retrieve_actor_user($hold->requestor) or next;
1293                 my $rlib = $editor->retrieve_actor_org_unit($hold->request_lib) or next;
1294
1295                 # see if this hold is permitted
1296                 my $permitted = OpenILS::Utils::PermitHold::permit_copy_hold(
1297                         {       patron_id                       => $hold->usr,
1298                                 requestor                       => $reqr,
1299                                 copy                            => $copy,
1300                                 pickup_lib                      => $hold->pickup_lib,
1301                                 request_lib                     => $rlib,
1302                         } 
1303                 );
1304
1305                 if( $permitted ) {
1306                         $best_hold = $hold;
1307                         last;
1308                 }
1309         }
1310
1311
1312         unless( $best_hold ) { # no "good" permitted holds were found
1313                 if( my $hold = $$old_holds[0] ) { # can we return a pre-targeted hold?
1314                         $logger->info("circulator: using existing pre-targeted hold ".$hold->id." in hold search");
1315                         return ($hold);
1316                 }
1317
1318                 # we got nuthin
1319                 $logger->info("circulator: no suitable holds found for copy $bc");
1320                 return (undef, $evt);
1321         }
1322
1323         $logger->info("circulator: best hold ".$best_hold->id." found for copy $bc");
1324
1325         # indicate a permitted hold was found
1326         return $best_hold if $check_only;
1327
1328         # we've found a permitted hold.  we need to "grab" the copy 
1329         # to prevent re-targeted holds (next part) from re-grabbing the copy
1330         $best_hold->current_copy($copy->id);
1331         $editor->update_action_hold_request($best_hold) 
1332                 or return (undef, $editor->event);
1333
1334
1335     my $retarget = 0;
1336
1337         # re-target any other holds that already target this copy
1338         for my $old_hold (@$old_holds) {
1339                 next if $old_hold->id eq $best_hold->id; # don't re-target the hold we want
1340                 $logger->info("circulator: clearing current_copy and prev_check_time on hold ".
1341             $old_hold->id." after a better hold [".$best_hold->id."] was found");
1342         $old_hold->clear_current_copy;
1343         $old_hold->clear_prev_check_time;
1344         $editor->update_action_hold_request($old_hold) 
1345             or return (undef, $editor->event);
1346         $retarget = 1;
1347         }
1348
1349         return ($best_hold, undef, $retarget);
1350 }
1351
1352
1353
1354
1355
1356
1357 __PACKAGE__->register_method(
1358         method => 'all_rec_holds',
1359         api_name => 'open-ils.circ.holds.retrieve_all_from_title',
1360 );
1361
1362 sub all_rec_holds {
1363         my( $self, $conn, $auth, $title_id, $args ) = @_;
1364
1365         my $e = new_editor(authtoken=>$auth);
1366         $e->checkauth or return $e->event;
1367         $e->allowed('VIEW_HOLD') or return $e->event;
1368
1369         $args ||= { fulfillment_time => undef };
1370         $args->{cancel_time} = undef;
1371
1372         my $resp = { volume_holds => [], copy_holds => [] };
1373
1374         $resp->{title_holds} = $e->search_action_hold_request(
1375                 { 
1376                         hold_type => OILS_HOLD_TYPE_TITLE, 
1377                         target => $title_id, 
1378                         %$args 
1379                 }, {idlist=>1} );
1380
1381         my $vols = $e->search_asset_call_number(
1382                 { record => $title_id, deleted => 'f' }, {idlist=>1});
1383
1384         return $resp unless @$vols;
1385
1386         $resp->{volume_holds} = $e->search_action_hold_request(
1387                 { 
1388                         hold_type => OILS_HOLD_TYPE_VOLUME, 
1389                         target => $vols,
1390                         %$args }, 
1391                 {idlist=>1} );
1392
1393         my $copies = $e->search_asset_copy(
1394                 { call_number => $vols, deleted => 'f' }, {idlist=>1});
1395
1396         return $resp unless @$copies;
1397
1398         $resp->{copy_holds} = $e->search_action_hold_request(
1399                 { 
1400                         hold_type => OILS_HOLD_TYPE_COPY,
1401                         target => $copies,
1402                         %$args }, 
1403                 {idlist=>1} );
1404
1405         return $resp;
1406 }
1407
1408
1409
1410
1411
1412 __PACKAGE__->register_method(
1413         method => 'uber_hold',
1414         api_name => 'open-ils.circ.hold.details.retrieve'
1415 );
1416
1417 sub uber_hold {
1418         my($self, $client, $auth, $hold_id) = @_;
1419         my $e = new_editor(authtoken=>$auth);
1420         $e->checkauth or return $e->event;
1421         $e->allowed('VIEW_HOLD') or return $e->event;
1422
1423         my $resp = {};
1424
1425         my $hold = $e->retrieve_action_hold_request(
1426                 [
1427                         $hold_id,
1428                         {
1429                                 flesh => 1,
1430                                 flesh_fields => { ahr => [ 'current_copy', 'usr' ] }
1431                         }
1432                 ]
1433         ) or return $e->event;
1434
1435         my $user = $hold->usr;
1436         $hold->usr($user->id);
1437
1438         my $card = $e->retrieve_actor_card($user->card)
1439                 or return $e->event;
1440
1441         my( $mvr, $volume, $copy ) = find_hold_mvr($e, $hold);
1442
1443         flesh_hold_notices([$hold], $e);
1444         flesh_hold_transits([$hold]);
1445
1446         return {
1447                 hold            => $hold,
1448                 copy            => $copy,
1449                 volume  => $volume,
1450                 mvr             => $mvr,
1451                 status  => _hold_status($e, $hold),
1452                 patron_first => $user->first_given_name,
1453                 patron_last  => $user->family_name,
1454                 patron_barcode => $card->barcode,
1455         };
1456 }
1457
1458
1459
1460 # -----------------------------------------------------
1461 # Returns the MVR object that represents what the
1462 # hold is all about
1463 # -----------------------------------------------------
1464 sub find_hold_mvr {
1465         my( $e, $hold ) = @_;
1466
1467         my $tid;
1468         my $copy;
1469         my $volume;
1470
1471         if( $hold->hold_type eq OILS_HOLD_TYPE_METARECORD ) {
1472                 my $mr = $e->retrieve_metabib_metarecord($hold->target)
1473                         or return $e->event;
1474                 $tid = $mr->master_record;
1475
1476         } elsif( $hold->hold_type eq OILS_HOLD_TYPE_TITLE ) {
1477                 $tid = $hold->target;
1478
1479         } elsif( $hold->hold_type eq OILS_HOLD_TYPE_VOLUME ) {
1480                 $volume = $e->retrieve_asset_call_number($hold->target)
1481                         or return $e->event;
1482                 $tid = $volume->record;
1483
1484         } elsif( $hold->hold_type eq OILS_HOLD_TYPE_COPY ) {
1485                 $copy = $e->retrieve_asset_copy($hold->target)
1486                         or return $e->event;
1487                 $volume = $e->retrieve_asset_call_number($copy->call_number)
1488                         or return $e->event;
1489                 $tid = $volume->record;
1490         }
1491
1492         if(!$copy and ref $hold->current_copy ) {
1493                 $copy = $hold->current_copy;
1494                 $hold->current_copy($copy->id);
1495         }
1496
1497         if(!$volume and $copy) {
1498                 $volume = $e->retrieve_asset_call_number($copy->call_number);
1499         }
1500
1501         my $title = $e->retrieve_biblio_record_entry($tid);
1502         return ( $U->record_to_mvr($title), $volume, $copy );
1503 }
1504
1505
1506
1507
1508 1;