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