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