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