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