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