]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
setting status to reshelving of hold was cancelled and the copy was in transit
[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 = $e->retrieve_asset_copy($hold->current_copy)
558                 or return $e->event;
559
560         return 3 if $copy->status == OILS_COPY_STATUS_IN_TRANSIT;
561         return 4 if $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
562
563         return -1;
564 }
565
566
567
568
569
570 =head DEPRECATED
571 __PACKAGE__->register_method(
572         method  => "capture_copy",
573         api_name        => "open-ils.circ.hold.capture_copy.barcode",
574         notes           => <<"  NOTE");
575         Captures a copy to fulfil a hold
576         Params is login session and copy barcode
577         Optional param is 'flesh'.  If set, we also return the
578         relevant copy and title
579         login mus have COPY_CHECKIN permissions (since this is essentially
580         copy checkin)
581         NOTE
582
583 # XXX deprecate me XXX
584
585 sub capture_copy {
586         my( $self, $client, $login_session, $params ) = @_;
587         my %params = %$params;
588         my $barcode = $params{barcode};
589
590
591         my( $user, $target, $copy, $hold, $evt );
592
593         ( $user, $evt ) = $apputils->checkses($login_session);
594         return $evt if $evt;
595
596         # am I allowed to checkin a copy?
597         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
598         return $evt if $evt;
599
600         $logger->info("Capturing copy with barcode $barcode");
601
602         my $session = $apputils->start_db_session();
603
604         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
605         return $evt if $evt;
606
607         $logger->debug("Capturing copy " . $copy->id);
608
609         #( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
610         ( $hold, $evt ) = $self->find_nearest_permitted_hold($session, $copy, $user);
611         return $evt if $evt;
612
613         warn "Found hold " . $hold->id . "\n";
614         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
615
616         $hold->current_copy($copy->id);
617         $hold->capture_time("now"); 
618
619         #update the hold
620         my $stat = $session->request(
621                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
622         if(!$stat) { throw OpenSRF::EX::ERROR 
623                 ("Error updating hold request " . $copy->id); }
624
625         $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); #status on holds shelf
626
627         # if the staff member capturing this item is not at the pickup lib
628         if( $user->home_ou ne $hold->pickup_lib ) {
629                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
630         }
631
632         $copy->editor($user->id);
633         $copy->edit_date("now");
634         $stat = $session->request(
635                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
636         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
637
638         my $payload = { hold => $hold };
639         $payload->{copy} = $copy if $params{flesh_copy};
640
641         if($params{flesh_record}) {
642                 my $record;
643                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
644                 return $evt if $evt;
645                 $record = $apputils->record_to_mvr($record);
646                 $payload->{record} = $record;
647         }
648
649         $apputils->commit_db_session($session);
650
651         return OpenILS::Event->new('ROUTE_ITEM', 
652                 route_to => $hold->pickup_lib, payload => $payload );
653 }
654
655 sub _build_hold_transit {
656         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
657         my $trans = Fieldmapper::action::hold_transit_copy->new;
658
659         $trans->hold($hold->id);
660         $trans->source($user->home_ou);
661         $trans->dest($hold->pickup_lib);
662         $trans->source_send_time("now");
663         $trans->target_copy($copy->id);
664         $trans->copy_status($copy->status);
665
666         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
667         my ($stat) = $meth->run( $login_session, $trans, $session );
668         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
669         else { $copy->status(6); } #status in transit 
670 }
671
672
673
674 __PACKAGE__->register_method(
675         method  => "create_hold_transit",
676         api_name        => "open-ils.circ.hold_transit.create",
677         notes           => <<"  NOTE");
678         Creates a new transit object
679         NOTE
680
681 sub create_hold_transit {
682         my( $self, $client, $login_session, $transit, $session ) = @_;
683
684         my( $user, $evt ) = $apputils->checkses($login_session);
685         return $evt if $evt;
686         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
687         return $evt if $evt;
688
689         my $ses;
690         if($session) { $ses = $session; } 
691         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
692
693         return $ses->request(
694                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
695 }
696
697 =cut
698
699
700 sub find_local_hold {
701         my( $class, $session, $copy, $user ) = @_;
702         return $class->find_nearest_permitted_hold($session, $copy, $user);
703 }
704
705
706
707
708
709
710 sub fetch_open_hold_by_current_copy {
711         my $class = shift;
712         my $copyid = shift;
713         my $hold = $apputils->simplereq(
714                 'open-ils.cstore', 
715                 'open-ils.cstore.direct.action.hold_request.search.atomic',
716                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
717         return $hold->[0] if ref($hold);
718         return undef;
719 }
720
721 sub fetch_related_holds {
722         my $class = shift;
723         my $copyid = shift;
724         return $apputils->simplereq(
725                 'open-ils.cstore', 
726                 'open-ils.cstore.direct.action.hold_request.search.atomic',
727                 { current_copy =>  $copyid , cancel_time => undef, fulfillment_time => undef });
728 }
729
730
731 __PACKAGE__->register_method (
732         method          => "hold_pull_list",
733         api_name                => "open-ils.circ.hold_pull_list.retrieve",
734         signature       => q/
735                 Returns a list of holds that need to be "pulled"
736                 by a given location
737         /
738 );
739
740 __PACKAGE__->register_method (
741         method          => "hold_pull_list",
742         api_name                => "open-ils.circ.hold_pull_list.id_list.retrieve",
743         signature       => q/
744                 Returns a list of hold ID's that need to be "pulled"
745                 by a given location
746         /
747 );
748
749
750 sub hold_pull_list {
751         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
752         my( $reqr, $evt ) = $U->checkses($authtoken);
753         return $evt if $evt;
754
755         my $org = $reqr->ws_ou || $reqr->home_ou;
756         # the perm locaiton shouldn't really matter here since holds
757         # will exist all over and VIEW_HOLDS should be universal
758         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
759         return $evt if $evt;
760
761         if( $self->api_name =~ /id_list/ ) {
762                 return $U->storagereq(
763                         'open-ils.storage.direct.action.hold_request.pull_list.id_list.current_copy_circ_lib.atomic',
764                         $org, $limit, $offset ); 
765         } else {
766                 return $U->storagereq(
767                         'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
768                         $org, $limit, $offset ); 
769         }
770 }
771
772 __PACKAGE__->register_method (
773         method          => 'fetch_hold_notify',
774         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
775         signature       => q/ 
776                 Returns a list of hold notification objects based on hold id.
777                 @param authtoken The loggin session key
778                 @param holdid The id of the hold whose notifications we want to retrieve
779                 @return An array of hold notification objects, event on error.
780         /
781 );
782
783 sub fetch_hold_notify {
784         my( $self, $conn, $authtoken, $holdid ) = @_;
785         my( $requestor, $evt ) = $U->checkses($authtoken);
786         return $evt if $evt;
787         my ($hold, $patron);
788         ($hold, $evt) = $U->fetch_hold($holdid);
789         return $evt if $evt;
790         ($patron, $evt) = $U->fetch_user($hold->usr);
791         return $evt if $evt;
792
793         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
794         return $evt if $evt;
795
796         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
797         return $U->cstorereq(
798                 'open-ils.cstore.direct.action.hold_notification.search.atomic', {hold => $holdid} );
799 }
800
801
802 __PACKAGE__->register_method (
803         method          => 'create_hold_notify',
804         api_name                => 'open-ils.circ.hold_notification.create',
805         signature       => q/
806                 Creates a new hold notification object
807                 @param authtoken The login session key
808                 @param notification The hold notification object to create
809                 @return ID of the new object on success, Event on error
810                 /
811 );
812 sub create_hold_notify {
813         my( $self, $conn, $authtoken, $notification ) = @_;
814         my( $requestor, $evt ) = $U->checkses($authtoken);
815         return $evt if $evt;
816         my ($hold, $patron);
817         ($hold, $evt) = $U->fetch_hold($notification->hold);
818         return $evt if $evt;
819         ($patron, $evt) = $U->fetch_user($hold->usr);
820         return $evt if $evt;
821
822         # XXX perm depth probably doesn't matter here -- should always be consortium level
823         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
824         return $evt if $evt;
825
826         # Set the proper notifier 
827         $notification->notify_staff($requestor->id);
828         my $id = $U->storagereq(
829                 'open-ils.storage.direct.action.hold_notification.create', $notification );
830         return $U->DB_UPDATE_FAILED($notification) unless $id;
831         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
832         return $id;
833 }
834
835
836 __PACKAGE__->register_method(
837         method  => 'reset_hold',
838         api_name        => 'open-ils.circ.hold.reset',
839         signature       => q/
840                 Un-captures and un-targets a hold, essentially returning
841                 it to the state it was in directly after it was placed,
842                 then attempts to re-target the hold
843                 @param authtoken The login session key
844                 @param holdid The id of the hold
845         /
846 );
847
848
849 sub reset_hold {
850         my( $self, $conn, $auth, $holdid ) = @_;
851         my $reqr;
852         my ($hold, $evt) = $U->fetch_hold($holdid);
853         return $evt if $evt;
854         ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD'); # XXX stronger permission
855         return $evt if $evt;
856         $evt = $self->_reset_hold($reqr, $hold);
857         return $evt if $evt;
858         return 1;
859 }
860
861 sub _reset_hold {
862         my ($self, $reqr, $hold) = @_;
863
864         my $e = new_editor(xact =>1, requestor => $reqr);
865
866         $logger->info("reseting hold ".$hold->id);
867
868         my $hid = $hold->id;
869
870         if( $hold->capture_time and $hold->current_copy ) {
871
872                 my $copy = $e->retrieve_asset_copy($hold->current_copy)
873                         or return $e->event;
874
875                 if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
876                         $logger->info("setting copy to status 'reshelving' on hold retarget");
877                         $copy->status(OILS_COPY_STATUS_RESHELVING);
878                         $copy->editor($e->requestor->id);
879                         $copy->edit_date('now');
880                         $e->update_asset_copy($copy) or return $e->event;
881
882                 } elsif( $copy->status == OILS_COPY_STATUS_IN_TRANSIT ) {
883
884                         # We don't want the copy to remain "in transit"
885                         $copy->status(OILS_COPY_STATUS_RESHELVING);
886                         $logger->warn("! reseting hold [$hid] that is in transit");
887                         my $transid = $e->search_action_hold_transit_copy({hold=>$hold->id},{idlist=>1})->[0];
888
889                         if( $transid ) {
890                                 my $trans = $e->retrieve_action_transit_copy($transid);
891                                 if( $trans ) {
892                                         $logger->info("Aborting transit [$transid] on hold [$hid] reset...");
893                                         my $evt = OpenILS::Application::Circ::Transit::__abort_transit($e, $trans, $copy, 1);
894                                         $logger->info("Transit abort completed with result $evt");
895                                         return $evt unless "$evt" eq 1;
896                                 }
897                         }
898                 }
899         }
900
901         $hold->clear_capture_time;
902         $hold->clear_current_copy;
903
904         $e->update_action_hold_request($hold) or return $e->event;
905         $e->commit;
906
907         $U->storagereq(
908                 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id );
909
910         return undef;
911 }
912
913
914 __PACKAGE__->register_method(
915         method => 'fetch_open_title_holds',
916         api_name        => 'open-ils.circ.open_holds.retrieve',
917         signature       => q/
918                 Returns a list ids of un-fulfilled holds for a given title id
919                 @param authtoken The login session key
920                 @param id the id of the item whose holds we want to retrieve
921                 @param type The hold type - M, T, V, C
922         /
923 );
924
925 sub fetch_open_title_holds {
926         my( $self, $conn, $auth, $id, $type, $org ) = @_;
927         my $e = new_editor( authtoken => $auth );
928         return $e->event unless $e->checkauth;
929
930         $type ||= "T";
931         $org ||= $e->requestor->ws_ou;
932
933 #       return $e->search_action_hold_request(
934 #               { target => $id, hold_type => $type, fulfillment_time => undef }, {idlist=>1});
935
936         # XXX make me return IDs in the future ^--
937         my $holds = $e->search_action_hold_request(
938                 { 
939                         target                          => $id, 
940                         cancel_time                     => undef, 
941                         hold_type                       => $type, 
942                         fulfillment_time        => undef 
943                 }
944         );
945
946         flesh_hold_transits($holds);
947         return $holds;
948 }
949
950
951 sub flesh_hold_transits {
952         my $holds = shift;
953         for my $hold ( @$holds ) {
954                 $hold->transit(
955                         $apputils->simplereq(
956                                 'open-ils.cstore',
957                                 "open-ils.cstore.direct.action.hold_transit_copy.search.atomic",
958                                 { hold => $hold->id },
959                                 { order_by => { ahtc => 'id desc' }, limit => 1 }
960                         )->[0]
961                 );
962         }
963 }
964
965 sub flesh_hold_notices {
966         my( $holds, $e ) = @_;
967         $e ||= new_editor();
968
969         for my $hold (@$holds) {
970                 my $notices = $e->search_action_hold_notification(
971                         [
972                                 { hold => $hold->id },
973                                 { order_by => { anh => { 'notify_time desc' } } },
974                         ],
975                         {idlist=>1}
976                 );
977
978                 $hold->notify_count(scalar(@$notices));
979                 if( @$notices ) {
980                         my $n = $e->retrieve_action_hold_notification($$notices[0])
981                                 or return $e->event;
982                         $hold->notify_time($n->notify_time);
983                 }
984         }
985 }
986
987
988
989
990 __PACKAGE__->register_method(
991         method => 'fetch_captured_holds',
992         api_name        => 'open-ils.circ.captured_holds.on_shelf.retrieve',
993         signature       => q/
994                 Returns a list of un-fulfilled holds for a given title id
995                 @param authtoken The login session key
996                 @param org The org id of the location in question
997         /
998 );
999
1000 __PACKAGE__->register_method(
1001         method => 'fetch_captured_holds',
1002         api_name        => 'open-ils.circ.captured_holds.id_list.on_shelf.retrieve',
1003         signature       => q/
1004                 Returns a list ids of un-fulfilled holds for a given title id
1005                 @param authtoken The login session key
1006                 @param org The org id of the location in question
1007         /
1008 );
1009
1010 sub fetch_captured_holds {
1011         my( $self, $conn, $auth, $org ) = @_;
1012
1013         my $e = new_editor(authtoken => $auth);
1014         return $e->event unless $e->checkauth;
1015         return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
1016
1017         $org ||= $e->requestor->ws_ou;
1018
1019         my $holds = $e->search_action_hold_request(
1020                 { 
1021                         capture_time            => { "!=" => undef },
1022                         current_copy            => { "!=" => undef },
1023                         fulfillment_time        => undef,
1024                         pickup_lib                      => $org,
1025                         cancel_time                     => undef,
1026                 }
1027         );
1028
1029         my @res;
1030         for my $h (@$holds) {
1031                 my $copy = $e->retrieve_asset_copy($h->current_copy)
1032                         or return $e->event;
1033                 push( @res, $h ) if 
1034                         $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF;
1035         }
1036
1037         if( ! $self->api_name =~ /id_list/ ) {
1038                 flesh_hold_transits(\@res);
1039                 flesh_hold_notices(\@res, $e);
1040         }
1041
1042         if( $self->api_name =~ /id_list/ ) {
1043                 return [ map { $_->id } @res ];
1044         } else {
1045                 return \@res;
1046         }
1047 }
1048
1049
1050 __PACKAGE__->register_method(
1051         method  => "check_title_hold",
1052         api_name        => "open-ils.circ.title_hold.is_possible",
1053         notes           => q/
1054                 Determines if a hold were to be placed by a given user,
1055                 whether or not said hold would have any potential copies
1056                 to fulfill it.
1057                 @param authtoken The login session key
1058                 @param params A hash of named params including:
1059                         patronid  - the id of the hold recipient
1060                         titleid (brn) - the id of the title to be held
1061                         depth   - the hold range depth (defaults to 0)
1062         /);
1063
1064 sub check_title_hold {
1065         my( $self, $client, $authtoken, $params ) = @_;
1066
1067         my %params              = %$params;
1068         my $titleid             = $params{titleid} ||"";
1069         my $volid               = $params{volume_id};
1070         my $copyid              = $params{copy_id};
1071         my $mrid                        = $params{mrid} ||"";
1072         my $depth               = $params{depth} || 0;
1073         my $pickup_lib  = $params{pickup_lib};
1074         my $hold_type   = $params{hold_type} || 'T';
1075
1076         my $e = new_editor(authtoken=>$authtoken);
1077         return $e->event unless $e->checkauth;
1078         my $patron = $e->retrieve_actor_user($params{patronid})
1079                 or return $e->event;
1080
1081         if( $e->requestor->id ne $patron->id ) {
1082                 return $e->event unless 
1083                         $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou);
1084         }
1085
1086         return OpenILS::Event->new('PATRON_BARRED') 
1087                 if $patron->barred and 
1088                         ($patron->barred =~ /t/i or $patron->barred == 1);
1089
1090         my $rangelib    = $params{range_lib} || $patron->home_ou;
1091
1092         my $request_lib = $e->retrieve_actor_org_unit($e->requestor->ws_ou)
1093                 or return $e->event;
1094
1095         $logger->info("checking hold possibility with type $hold_type");
1096
1097         my $copy;
1098         my $volume;
1099         my $title;
1100
1101         if( $hold_type eq OILS_HOLD_TYPE_COPY ) {
1102
1103                 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
1104                 $volume = $e->retrieve_asset_call_number($copy->call_number)
1105                         or return $e->event;
1106                 $title = $e->retrieve_biblio_record_entry($volume->record)
1107                         or return $e->event;
1108                 return verify_copy_for_hold( 
1109                         $patron, $e->requestor, $title, $copy, $pickup_lib, $request_lib );
1110
1111         } elsif( $hold_type eq OILS_HOLD_TYPE_VOLUME ) {
1112
1113                 $volume = $e->retrieve_asset_call_number($volid)
1114                         or return $e->event;
1115                 $title = $e->retrieve_biblio_record_entry($volume->record)
1116                         or return $e->event;
1117
1118                 return _check_volume_hold_is_possible(
1119                         $volume, $title, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
1120
1121         } elsif( $hold_type eq OILS_HOLD_TYPE_TITLE ) {
1122
1123                 return _check_title_hold_is_possible(
1124                         $titleid, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib);
1125
1126         } elsif( $hold_type eq OILS_HOLD_TYPE_METARECORD ) {
1127
1128                 my $maps = $e->search_metabib_source_map({metarecord=>$mrid});
1129                 my @recs = map { $_->source } @$maps;
1130                 for my $rec (@recs) {
1131                         return 1 if (_check_title_hold_is_possible(
1132                                 $rec, $rangelib, $depth, $request_lib, $patron, $e->requestor, $pickup_lib));
1133                 }
1134                 return 0;       
1135         }
1136 }
1137
1138
1139
1140 sub _check_title_hold_is_possible {
1141         my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
1142
1143         my $limit       = 10;
1144         my $offset      = 0;
1145         my $title;
1146
1147         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
1148
1149         while( $title = $U->storagereq(
1150                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
1151                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
1152
1153                 last unless 
1154                         ref($title) and 
1155                         ref($title->call_numbers) and 
1156                         @{$title->call_numbers};
1157
1158                 for my $cn (@{$title->call_numbers}) {
1159         
1160                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
1161         
1162                         for my $copy (@{$cn->copies}) {
1163                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
1164                                 return 1 if verify_copy_for_hold( 
1165                                         $patron, $requestor, $title, $copy, $pickup_lib, $request_lib );
1166                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
1167                         }
1168                 }
1169
1170                 $offset += $limit;
1171         }
1172         return 0;
1173 }
1174
1175 sub _check_volume_hold_is_possible {
1176         my( $vol, $title, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_;
1177         my $copies = new_editor->search_asset_copy({call_number => $vol->id});
1178         $logger->info("checking possibility of volume hold for volume ".$vol->id);
1179         for my $copy ( @$copies ) {
1180                 return 1 if verify_copy_for_hold( 
1181                         $patron, $requestor, $title, $copy, $pickup_lib, $request_lib );
1182         }
1183         return 0;
1184 }
1185
1186
1187
1188 sub verify_copy_for_hold {
1189         my( $patron, $requestor, $title, $copy, $pickup_lib, $request_lib ) = @_;
1190         $logger->info("checking possibility of copy in hold request for copy ".$copy->id);
1191         return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
1192                 {       patron                          => $patron, 
1193                         requestor                       => $requestor, 
1194                         copy                                    => $copy,
1195                         title                                   => $title, 
1196                         title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
1197                         pickup_lib                      => $pickup_lib,
1198                         request_lib                     => $request_lib 
1199                 } 
1200         );
1201         return 0;
1202 }
1203
1204
1205
1206 sub find_nearest_permitted_hold {
1207
1208         my $class       = shift;
1209         my $session = shift;
1210         my $copy                = shift;
1211         my $user                = shift;
1212         my $evt         = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND');
1213
1214         # first see if this copy has already been selected to fulfill a hold
1215         my $hold  = $session->request(
1216                 "open-ils.storage.direct.action.hold_request.search_where",
1217                 { current_copy => $copy->id, cancel_time => undef, capture_time => undef } )->gather(1);
1218
1219         if( $hold ) {
1220                 $logger->info("hold found which can be fulfilled by copy ".$copy->id);
1221                 return $hold;
1222         }
1223
1224         # We know this hold is permitted, so just return it
1225         return $hold if $hold;
1226
1227         $logger->debug("searching for potential holds at org ". 
1228                 $user->ws_ou." and copy ".$copy->id);
1229
1230         my $holds = $session->request(
1231                 "open-ils.storage.action.hold_request.nearest_hold.atomic",
1232                 $user->ws_ou, $copy->id, 5 )->gather(1);
1233
1234         return (undef, $evt) unless @$holds;
1235
1236         # for each potential hold, we have to run the permit script
1237         # to make sure the hold is actually permitted.
1238
1239         for my $holdid (@$holds) {
1240                 next unless $holdid;
1241                 $logger->info("Checking if hold $holdid is permitted for user ".$user->id);
1242
1243                 my ($hold) = $U->fetch_hold($holdid);
1244                 next unless $hold;
1245                 my ($reqr) = $U->fetch_user($hold->requestor);
1246
1247                 return ($hold) if OpenILS::Utils::PermitHold::permit_copy_hold(
1248                         {
1249                                 patron_id                       => $hold->usr,
1250                                 requestor                       => $reqr->id,
1251                                 copy                                    => $copy,
1252                                 pickup_lib                      => $hold->pickup_lib,
1253                                 request_lib                     => $hold->request_lib 
1254                         } 
1255                 );
1256         }
1257
1258         return (undef, $evt);
1259 }
1260
1261
1262
1263
1264
1265
1266 __PACKAGE__->register_method(
1267         method => 'all_rec_holds',
1268         api_name => 'open-ils.circ.holds.retrieve_all_from_title',
1269 );
1270
1271 sub all_rec_holds {
1272         my( $self, $conn, $auth, $title_id, $args ) = @_;
1273
1274         my $e = new_editor(authtoken=>$auth);
1275         $e->checkauth or return $e->event;
1276         $e->allowed('VIEW_HOLD') or return $e->event;
1277
1278         $args ||= { fulfillment_time => undef };
1279         $args->{cancel_time} = undef;
1280
1281         my $resp = { volume_holds => [], copy_holds => [] };
1282
1283         $resp->{title_holds} = $e->search_action_hold_request(
1284                 { 
1285                         hold_type => OILS_HOLD_TYPE_TITLE, 
1286                         target => $title_id, 
1287                         %$args 
1288                 }, {idlist=>1} );
1289
1290         my $vols = $e->search_asset_call_number(
1291                 { record => $title_id, deleted => 'f' }, {idlist=>1});
1292
1293         return $resp unless @$vols;
1294
1295         $resp->{volume_holds} = $e->search_action_hold_request(
1296                 { 
1297                         hold_type => OILS_HOLD_TYPE_VOLUME, 
1298                         target => $vols,
1299                         %$args }, 
1300                 {idlist=>1} );
1301
1302         my $copies = $e->search_asset_copy(
1303                 { call_number => $vols, deleted => 'f' }, {idlist=>1});
1304
1305         return $resp unless @$copies;
1306
1307         $resp->{copy_holds} = $e->search_action_hold_request(
1308                 { 
1309                         hold_type => OILS_HOLD_TYPE_COPY,
1310                         target => $copies,
1311                         %$args }, 
1312                 {idlist=>1} );
1313
1314         return $resp;
1315 }
1316
1317
1318
1319
1320
1321 __PACKAGE__->register_method(
1322         method => 'uber_hold',
1323         api_name => 'open-ils.circ.hold.details.retrieve'
1324 );
1325
1326 sub uber_hold {
1327         my($self, $client, $auth, $hold_id) = @_;
1328         my $e = new_editor(authtoken=>$auth);
1329         $e->checkauth or return $e->event;
1330         $e->allowed('VIEW_HOLD') or return $e->event;
1331
1332         my $resp = {};
1333
1334         my $hold = $e->retrieve_action_hold_request(
1335                 [
1336                         $hold_id,
1337                         {
1338                                 flesh => 1,
1339                                 flesh_fields => { ahr => [ 'current_copy', 'usr' ] }
1340                         }
1341                 ]
1342         ) or return $e->event;
1343
1344         my $user = $hold->usr;
1345         $hold->usr($user->id);
1346
1347         my $card = $e->retrieve_actor_card($user->card)
1348                 or return $e->event;
1349
1350         my( $mvr, $volume, $copy ) = find_hold_mvr($e, $hold);
1351
1352         flesh_hold_notices([$hold], $e);
1353         flesh_hold_transits([$hold]);
1354
1355         return {
1356                 hold            => $hold,
1357                 copy            => $copy,
1358                 volume  => $volume,
1359                 mvr             => $mvr,
1360                 status  => _hold_status($e, $hold),
1361                 patron_first => $user->first_given_name,
1362                 patron_last  => $user->family_name,
1363                 patron_barcode => $card->barcode,
1364         };
1365 }
1366
1367
1368
1369 # -----------------------------------------------------
1370 # Returns the MVR object that represents what the
1371 # hold is all about
1372 # -----------------------------------------------------
1373 sub find_hold_mvr {
1374         my( $e, $hold ) = @_;
1375
1376         my $tid;
1377         my $copy;
1378         my $volume;
1379
1380         if( $hold->hold_type eq OILS_HOLD_TYPE_METARECORD ) {
1381                 my $mr = $e->retrieve_metabib_metarecord($hold->target)
1382                         or return $e->event;
1383                 $tid = $mr->master_record;
1384
1385         } elsif( $hold->hold_type eq OILS_HOLD_TYPE_TITLE ) {
1386                 $tid = $hold->target;
1387
1388         } elsif( $hold->hold_type eq OILS_HOLD_TYPE_VOLUME ) {
1389                 $volume = $e->retrieve_asset_call_number($hold->target)
1390                         or return $e->event;
1391                 $tid = $volume->record;
1392
1393         } elsif( $hold->hold_type eq OILS_HOLD_TYPE_COPY ) {
1394                 $copy = $e->retrieve_asset_copy($hold->target)
1395                         or return $e->event;
1396                 $volume = $e->retrieve_asset_call_number($copy->call_number)
1397                         or return $e->event;
1398                 $tid = $volume->record;
1399         }
1400
1401         if(!$copy and ref $hold->current_copy ) {
1402                 $copy = $hold->current_copy;
1403                 $hold->current_copy($copy->id);
1404         }
1405
1406         if(!$volume and $copy) {
1407                 $volume = $e->retrieve_asset_call_number($copy->call_number);
1408         }
1409
1410         my $title = $e->retrieve_biblio_record_entry($tid);
1411         return ( $U->record_to_mvr($title), $volume, $copy );
1412 }
1413
1414
1415
1416
1417 1;