]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
More uglifying of Bill's middle layer :)
[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 OpenILS::EX;
23 use OpenSRF::EX qw(:try);
24 use OpenILS::Perm;
25 use OpenILS::Event;
26 use OpenSRF::Utils::Logger qw(:logger);
27
28 my $apputils = "OpenILS::Application::AppUtils";
29 my $U = $apputils;
30
31
32
33 __PACKAGE__->register_method(
34         method  => "create_hold",
35         api_name        => "open-ils.circ.holds.create",
36         notes           => <<NOTE);
37 Create a new hold for an item.  From a permissions perspective, 
38 the login session is used as the 'requestor' of the hold.  
39 The hold recipient is determined by the 'usr' setting within
40 the hold object.
41
42 First we verify the requestion has holds request permissions.
43 Then we verify that the recipient is allowed to make the given hold.
44 If not, we see if the requestor has "override" capabilities.  If not,
45 a permission exception is returned.  If permissions allow, we cycle
46 through the set of holds objects and create.
47
48 If the recipient does not have permission to place multiple holds
49 on a single title and said operation is attempted, a permission
50 exception is returned
51 NOTE
52
53 sub create_hold {
54         my( $self, $client, $login_session, @holds) = @_;
55
56         if(!@holds){return 0;}
57         my( $user, $evt ) = $apputils->checkses($login_session);
58         return $evt if $evt;
59
60         my $holds;
61         if(ref($holds[0]) eq 'ARRAY') {
62                 $holds = $holds[0];
63         } else { $holds = [ @holds ]; }
64
65         $logger->debug("Iterating over holds requests...");
66
67         for my $hold (@$holds) {
68
69                 if(!$hold){next};
70                 my $type = $hold->hold_type;
71
72                 $logger->activity("User " . $user->id . 
73                         " creating new hold of type $type for user " . $hold->usr);
74
75                 my $recipient;
76                 if($user->id ne $hold->usr) {
77                         ( $recipient, $evt ) = $apputils->fetch_user($hold->usr);
78                         return $evt if $evt;
79
80                 } else {
81                         $recipient = $user;
82                 }
83
84
85                 my $perm = undef;
86
87                 # am I allowed to place holds for this user?
88                 if($hold->requestor ne $hold->usr) {
89                         $perm = _check_request_holds_perm($user->id, $user->home_ou);
90                         if($perm) { return $perm; }
91                 }
92
93                 # is this user allowed to have holds of this type?
94                 $perm = _check_holds_perm($type, $hold->usr, $recipient->home_ou);
95                 if($perm) { 
96                         #if there is a requestor, see if the requestor has override privelages
97                         if($hold->requestor ne $hold->usr) {
98                                 $perm = _check_request_holds_override($user->id, $user->home_ou);
99                                 if($perm) {return $perm;}
100
101                         } else {
102                                 return $perm; 
103                         }
104                 }
105
106                 #enforce the fact that the login is the one requesting the hold
107                 $hold->requestor($user->id); 
108
109                 my $resp = $apputils->simplereq(
110                         'open-ils.storage',
111                         'open-ils.storage.direct.action.hold_request.create', $hold );
112
113                 if(!$resp) { 
114                         return OpenSRF::EX::ERROR ("Error creating hold"); 
115                 }
116         }
117
118         return 1;
119 }
120
121 # makes sure that a user has permission to place the type of requested hold
122 # returns the Perm exception if not allowed, returns undef if all is well
123 sub _check_holds_perm {
124         my($type, $user_id, $org_id) = @_;
125
126         my $evt;
127         if($type eq "M") {
128                 if($evt = $apputils->check_perms(
129                         $user_id, $org_id, "MR_HOLDS")) {
130                         return $evt;
131                 } 
132
133         } elsif ($type eq "T") {
134                 if($evt = $apputils->check_perms(
135                         $user_id, $org_id, "TITLE_HOLDS")) {
136                         return $evt;
137                 }
138
139         } elsif($type eq "V") {
140                 if($evt = $apputils->check_perms(
141                         $user_id, $org_id, "VOLUME_HOLDS")) {
142                         return $evt;
143                 }
144
145         } elsif($type eq "C") {
146                 if($evt = $apputils->check_perms(
147                         $user_id, $org_id, "COPY_HOLDS")) {
148                         return $evt;
149                 }
150         }
151
152         return undef;
153 }
154
155 # tests if the given user is allowed to place holds on another's behalf
156 sub _check_request_holds_perm {
157         my $user_id = shift;
158         my $org_id = shift;
159         if(my $evt = $apputils->check_perms(
160                 $user_id, $org_id, "REQUEST_HOLDS")) {
161                 return $evt;
162         }
163 }
164
165 sub _check_request_holds_override {
166         my $user_id = shift;
167         my $org_id = shift;
168         if(my $evt = $apputils->check_perms(
169                 $user_id, $org_id, "REQUEST_HOLDS_OVERRIDE")) {
170                 return $evt;
171         }
172 }
173
174 __PACKAGE__->register_method(
175         method  => "retrieve_holds_by_id",
176         api_name        => "open-ils.circ.holds.retrieve_by_id",
177         notes           => <<NOTE);
178 Retrieve the hold, with hold transits attached, for the specified id
179 The login session is the requestor and if the requestor is
180 different from the user, then the requestor must have VIEW_HOLD permissions.
181 NOTE
182
183
184 sub retrieve_holds_by_id {
185         my($self, $client, $login_session, $hold_id) = @_;
186
187         #FIXME
188         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
189         #       $login_session, $user_id, 'VIEW_HOLD' );
190         #return $evt if $evt;
191
192         my $holds = $apputils->simplereq(
193                 'open-ils.storage',
194                 "open-ils.storage.direct.action.hold_request.search.atomic",
195                 "id" =>  $hold_id , fulfillment_time => undef, { order_by => "request_time" });
196         
197         for my $hold ( @$holds ) {
198                 $hold->transit(
199                         $apputils->simplereq(
200                                 'open-ils.storage',
201                                 "open-ils.storage.direct.action.hold_transit_copy.search.hold.atomic" => $hold->id,
202                                 { order_by => 'id desc', limit => 1 }
203                         )->[0]
204                 );
205         }
206
207         return $holds;
208 }
209
210
211 __PACKAGE__->register_method(
212         method  => "retrieve_holds",
213         api_name        => "open-ils.circ.holds.retrieve",
214         notes           => <<NOTE);
215 Retrieves all the holds, with hold transits attached, for the specified
216 user id.  The login session is the requestor and if the requestor is
217 different from the user, then the requestor must have VIEW_HOLD permissions.
218 NOTE
219
220
221 sub retrieve_holds {
222         my($self, $client, $login_session, $user_id) = @_;
223
224         my( $user, $target, $evt ) = $apputils->checkses_requestor(
225                 $login_session, $user_id, 'VIEW_HOLD' );
226         return $evt if $evt;
227
228         my $holds = $apputils->simplereq(
229                 'open-ils.storage',
230                 "open-ils.storage.direct.action.hold_request.search.atomic",
231                 "usr" =>  $user_id , fulfillment_time => undef, { order_by => "request_time" });
232         
233         for my $hold ( @$holds ) {
234                 $hold->transit(
235                         $apputils->simplereq(
236                                 'open-ils.storage',
237                                 "open-ils.storage.direct.action.hold_transit_copy.search.hold.atomic" => $hold->id,
238                                 { order_by => 'id desc', limit => 1 }
239                         )->[0]
240                 );
241         }
242
243         return $holds;
244 }
245
246 __PACKAGE__->register_method(
247         method  => "retrieve_holds_by_pickup_lib",
248         api_name        => "open-ils.circ.holds.retrieve_by_pickup_lib",
249         notes           => <<NOTE);
250 Retrieves all the holds, with hold transits attached, for the specified
251 pickup_ou id. 
252 NOTE
253
254
255 sub retrieve_holds_by_pickup_lib {
256         my($self, $client, $login_session, $ou_id) = @_;
257
258         #FIXME -- put an appropriate permission check here
259         #my( $user, $target, $evt ) = $apputils->checkses_requestor(
260         #       $login_session, $user_id, 'VIEW_HOLD' );
261         #return $evt if $evt;
262
263         my $holds = $apputils->simplereq(
264                 'open-ils.storage',
265                 "open-ils.storage.direct.action.hold_request.search.atomic",
266                 "pickup_lib" =>  $ou_id , fulfillment_time => undef, { order_by => "request_time" });
267         
268         for my $hold ( @$holds ) {
269                 $hold->transit(
270                         $apputils->simplereq(
271                                 'open-ils.storage',
272                                 "open-ils.storage.direct.action.hold_transit_copy.search.hold.atomic" => $hold->id,
273                                 { order_by => 'id desc', limit => 1 }
274                         )->[0]
275                 );
276         }
277
278         return $holds;
279 }
280
281
282 __PACKAGE__->register_method(
283         method  => "cancel_hold",
284         api_name        => "open-ils.circ.hold.cancel",
285         notes           => <<"  NOTE");
286         Cancels the specified hold.  The login session
287         is the requestor and if the requestor is different from the usr field
288         on the hold, the requestor must have CANCEL_HOLDS permissions.
289         the hold may be either the hold object or the hold id
290         NOTE
291
292 sub cancel_hold {
293         my($self, $client, $login_session, $holdid) = @_;
294         my $hold;       
295
296         my($user, $evt) = $U->checkses($login_session);
297         return $evt if $evt;
298
299         ( $hold, $evt ) = $U->fetch_hold($holdid);
300         return $evt if $evt;
301
302         if($user->id ne $hold->usr) { #am I allowed to cancel this user's hold?
303                 if($evt = $apputils->check_perms(
304                         $user->id, $user->home_ou, 'CANCEL_HOLDS')) {
305                         return $evt;
306                 }
307         }
308
309         $logger->activity( "User " . $user->id . 
310                 " canceling hold $holdid for user " . $hold->usr );
311
312         return $apputils->simplereq(
313                 'open-ils.storage',
314                 "open-ils.storage.direct.action.hold_request.delete", $hold );
315 }
316
317
318 __PACKAGE__->register_method(
319         method  => "update_hold",
320         api_name        => "open-ils.circ.hold.update",
321         notes           => <<"  NOTE");
322         Updates the specified hold.  The login session
323         is the requestor and if the requestor is different from the usr field
324         on the hold, the requestor must have UPDATE_HOLDS permissions.
325         NOTE
326
327 sub update_hold {
328         my($self, $client, $login_session, $hold) = @_;
329
330         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
331                 $login_session, $hold->usr, 'UPDATE_HOLD' );
332         return $evt if $evt;
333
334         $logger->activity('User ' . $requestor->id . 
335                 ' updating hold ' . $hold->id . ' for user ' . $target->id );
336
337         return $U->storagereq(
338                 "open-ils.storage.direct.action.hold_request.update", $hold );
339 }
340
341
342 __PACKAGE__->register_method(
343         method  => "retrieve_hold_status",
344         api_name        => "open-ils.circ.hold.status.retrieve",
345         notes           => <<"  NOTE");
346         Calculates the current status of the hold.
347         the requestor must have VIEW_HOLD permissions if the hold is for a user
348         other than the requestor.
349         Returns -1  on error (for now)
350         Returns 1 for 'waiting for copy to become available'
351         Returns 2 for 'waiting for copy capture'
352         Returns 3 for 'in transit'
353         Returns 4 for 'arrived'
354         NOTE
355
356 sub retrieve_hold_status {
357         my($self, $client, $login_session, $hold_id) = @_;
358
359
360         my( $requestor, $target, $hold, $copy, $transit, $evt );
361
362         ( $hold, $evt ) = $apputils->fetch_hold($hold_id);
363         return $evt if $evt;
364
365         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
366                 $login_session, $hold->usr, 'VIEW_HOLD' );
367         return $evt if $evt;
368
369         return 1 unless (defined($hold->current_copy));
370         
371         ( $copy, $evt ) = $apputils->fetch_copy($hold->current_copy);
372         return $evt if $evt;
373
374         return 4 if ($hold->capture_time and $copy->circ_lib eq $hold->pickup_lib);
375
376         ( $transit, $evt ) = $apputils->fetch_hold_transit_by_hold( $hold->id );
377         return 4 if(ref($transit) and defined($transit->dest_recv_time) ); 
378
379         return 3 if defined($hold->capture_time);
380
381         return 2;
382 }
383
384
385
386
387
388 __PACKAGE__->register_method(
389         method  => "capture_copy",
390         api_name        => "open-ils.circ.hold.capture_copy.barcode",
391         notes           => <<"  NOTE");
392         Captures a copy to fulfil a hold
393         Params is login session and copy barcode
394         Optional param is 'flesh'.  If set, we also return the
395         relevant copy and title
396         login mus have COPY_CHECKIN permissions (since this is essentially
397         copy checkin)
398         NOTE
399
400 # XXX deprecate me XXX
401
402 sub capture_copy {
403         my( $self, $client, $login_session, $params ) = @_;
404         my %params = %$params;
405         my $barcode = $params{barcode};
406
407
408         my( $user, $target, $copy, $hold, $evt );
409
410         ( $user, $evt ) = $apputils->checkses($login_session);
411         return $evt if $evt;
412
413         # am I allowed to checkin a copy?
414         $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN");
415         return $evt if $evt;
416
417         $logger->info("Capturing copy with barcode $barcode");
418
419         my $session = $apputils->start_db_session();
420
421         ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode);
422         return $evt if $evt;
423
424         $logger->debug("Capturing copy " . $copy->id);
425
426         ( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user);
427         return $evt if $evt;
428
429         warn "Found hold " . $hold->id . "\n";
430         $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode");
431
432         $hold->current_copy($copy->id);
433         $hold->capture_time("now"); 
434
435         #update the hold
436         my $stat = $session->request(
437                         "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
438         if(!$stat) { throw OpenSRF::EX::ERROR 
439                 ("Error updating hold request " . $copy->id); }
440
441         $copy->status(8); #status on holds shelf
442
443         # if the staff member capturing this item is not at the pickup lib
444         if( $user->home_ou ne $hold->pickup_lib ) {
445                 $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy );
446         }
447
448         $copy->editor($user->id);
449         $copy->edit_date("now");
450         $stat = $session->request(
451                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
452         if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); }
453
454         my $payload = { hold => $hold };
455         $payload->{copy} = $copy if $params{flesh_copy};
456
457         if($params{flesh_record}) {
458                 my $record;
459                 ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id );
460                 return $evt if $evt;
461                 $record = $apputils->record_to_mvr($record);
462                 $payload->{record} = $record;
463         }
464
465         $apputils->commit_db_session($session);
466
467         return OpenILS::Event->new('ROUTE_ITEM', 
468                 route_to => $hold->pickup_lib, payload => $payload );
469 }
470
471 sub _build_hold_transit {
472         my( $self, $login_session, $session, $hold, $user, $copy ) = @_;
473         my $trans = Fieldmapper::action::hold_transit_copy->new;
474
475         $trans->hold($hold->id);
476         $trans->source($user->home_ou);
477         $trans->dest($hold->pickup_lib);
478         $trans->source_send_time("now");
479         $trans->target_copy($copy->id);
480         $trans->copy_status($copy->status);
481
482         my $meth = $self->method_lookup("open-ils.circ.hold_transit.create");
483         my ($stat) = $meth->run( $login_session, $trans, $session );
484         if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); }
485         else { $copy->status(6); } #status in transit 
486 }
487
488
489 sub find_local_hold {
490         my( $class, $session, $copy, $user ) = @_;
491         return _find_local_hold_for_copy($session, $copy, $user);
492 }
493
494 sub _find_local_hold_for_copy {
495
496         my $session = shift;
497         my $copy = shift;
498         my $user = shift;
499         my $evt = OpenILS::Event->new('HOLD_NOT_FOUND');
500
501         # first see if this copy has already been selected to fulfill a hold
502         my $hold  = $session->request(
503                 "open-ils.storage.direct.action.hold_request.search_where",
504                 { current_copy => $copy->id, capture_time => undef } )->gather(1);
505
506         if($hold) {return $hold;}
507
508         $logger->debug("searching for local hold at org " . 
509                 $user->home_ou . " and copy " . $copy->id);
510
511         my $holdid = $session->request(
512                 "open-ils.storage.action.hold_request.nearest_hold",
513                 $user->home_ou, $copy->id )->gather(1);
514
515         return (undef, $evt) unless defined $holdid;
516
517         $logger->debug("Found hold id $holdid while ".
518                 "searching nearest hold to " .$user->home_ou);
519
520         return $apputils->fetch_hold($holdid);
521 }
522
523
524 __PACKAGE__->register_method(
525         method  => "create_hold_transit",
526         api_name        => "open-ils.circ.hold_transit.create",
527         notes           => <<"  NOTE");
528         Creates a new transit object
529         NOTE
530
531 sub create_hold_transit {
532         my( $self, $client, $login_session, $transit, $session ) = @_;
533
534         my( $user, $evt ) = $apputils->checkses($login_session);
535         return $evt if $evt;
536         $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT");
537         return $evt if $evt;
538
539         my $ses;
540         if($session) { $ses = $session; } 
541         else { $ses = OpenSRF::AppSession->create("open-ils.storage"); }
542
543         return $ses->request(
544                 "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1);
545 }
546
547
548 sub fetch_open_hold_by_current_copy {
549         my $class = shift;
550         my $copyid = shift;
551         my $hold = $apputils->simplereq(
552                 'open-ils.storage', 
553                 'open-ils.storage.direct.action.hold_request.search.atomic',
554                          current_copy =>  $copyid , fulfillment_time => undef );
555         return $hold->[0] if ref($hold);
556         return undef;
557 }
558
559 sub fetch_related_holds {
560         my $class = shift;
561         my $copyid = shift;
562         return $apputils->simplereq(
563                 'open-ils.storage', 
564                 'open-ils.storage.direct.action.hold_request.search.atomic',
565                          current_copy =>  $copyid , fulfillment_time => undef );
566 }
567
568
569 __PACKAGE__->register_method (
570         method          => "hold_pull_list",
571         api_name                => "open-ils.circ.hold_pull_list.retrieve",
572         signature       => q/
573                 Returns a list of hold ID's that need to be "pulled"
574                 by a given location
575         /
576 );
577
578 sub hold_pull_list {
579         my( $self, $conn, $authtoken, $limit, $offset ) = @_;
580         my( $reqr, $evt ) = $U->checkses($authtoken);
581         return $evt if $evt;
582
583         my $org = $reqr->ws_ou || $reqr->home_ou;
584         # the perm locaiton shouldn't really matter here since holds
585         # will exist all over and VIEW_HOLDS should be universal
586         $evt = $U->check_perms($reqr->id, $org, 'VIEW_HOLD');
587         return $evt if $evt;
588
589         return $U->storagereq(
590                 'open-ils.storage.direct.action.hold_request.pull_list.search.current_copy_circ_lib.atomic',
591                 $org, $limit, $offset ); # XXX change to workstation
592 }
593
594 __PACKAGE__->register_method (
595         method          => 'fetch_hold_notify',
596         api_name                => 'open-ils.circ.hold_notification.retrieve_by_hold',
597         signature       => q/ 
598                 Returns a list of hold notification objects based on hold id.
599                 @param authtoken The loggin session key
600                 @param holdid The id of the hold whose notifications we want to retrieve
601                 @return An array of hold notification objects, event on error.
602         /
603 );
604
605 sub fetch_hold_notify {
606         my( $self, $conn, $authtoken, $holdid ) = @_;
607         my( $requestor, $evt ) = $U->checkses($authtoken);
608         return $evt if $evt;
609         my ($hold, $patron);
610         ($hold, $evt) = $U->fetch_hold($holdid);
611         return $evt if $evt;
612         ($patron, $evt) = $U->fetch_user($hold->usr);
613         return $evt if $evt;
614
615         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'VIEW_HOLD_NOTIFICATION');
616         return $evt if $evt;
617
618         $logger->info("User ".$requestor->id." fetching hold notifications for hold $holdid");
619         return $U->storagereq(
620                 'open-ils.storage.direct.action.hold_notification.search.hold.atomic', $holdid );
621 }
622
623
624 __PACKAGE__->register_method (
625         method          => 'create_hold_notify',
626         api_name                => 'open-ils.circ.hold_notification.create',
627         signature       => q/
628                 Creates a new hold notification object
629                 @param authtoken The login session key
630                 @param notification The hold notification object to create
631                 @return ID of the new object on success, Event on error
632                 /
633 );
634 sub create_hold_notify {
635         my( $self, $conn, $authtoken, $notification ) = @_;
636         my( $requestor, $evt ) = $U->checkses($authtoken);
637         return $evt if $evt;
638         my ($hold, $patron);
639         ($hold, $evt) = $U->fetch_hold($notification->hold);
640         return $evt if $evt;
641         ($patron, $evt) = $U->fetch_user($hold->usr);
642         return $evt if $evt;
643
644         # XXX perm depth probably doesn't matter here -- should always be consortium level
645         $evt = $U->check_perms($requestor->id, $patron->home_ou, 'CREATE_HOLD_NOTIFICATION');
646         return $evt if $evt;
647
648         # Set the proper notifier 
649         $notification->notify_staff($requestor->id);
650         my $id = $U->storagereq(
651                 'open-ils.storage.direct.action.hold_notification.create', $notification );
652         return $U->DB_UPDATE_FAILED($notification) unless $id;
653         $logger->info("User ".$requestor->id." successfully created new hold notification $id");
654         return $id;
655 }
656
657
658 __PACKAGE__->register_method(
659         method  => 'reset_hold',
660         api_name        => 'open-ils.circ.hold.reset',
661         signature       => q/
662                 Un-captures and un-targets a hold, essentially returning
663                 it to the state it was in directly after it was placed,
664                 then attempts to re-target the hold
665                 @param authtoken The login session key
666                 @param holdid The id of the hold
667         /
668 );
669
670
671 sub reset_hold {
672         my( $self, $conn, $auth, $holdid ) = @_;
673         my $reqr;
674         my ($hold, $evt) = $U->fetch_hold($holdid);
675         return $evt if $evt;
676         ($reqr, $evt) = $U->checksesperm($auth, 'UPDATE_HOLD');
677         return $evt if $evt;
678         $evt = $self->_reset_hold($reqr, $hold);
679         return $evt if $evt;
680         return 1;
681 }
682
683 sub _reset_hold {
684         my ($self, $reqr, $hold, $session) = @_;
685
686         my $x;
687         if(!$session) {
688                 $x = 1;
689                 $session = $U->start_db_session();
690         }
691
692         $hold->clear_capture_time;
693         $hold->clear_current_copy;
694
695         return $U->DB_UPDATE_FAILED($hold) unless 
696                 $session->request(
697                         'open-ils.storage.direct.action.hold_request.update', $hold )->gather(1);
698
699         $session->request(
700                 'open-ils.storage.action.hold_request.copy_targeter', undef, $hold->id )->gather(1);
701
702         $U->commit_db_session($session) unless $x;
703         return undef;
704 }
705
706
707
708 1;