]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
Added permit key to circ permit calls and updated test code
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Circulate.pm
1 package OpenILS::Application::Circ::Circulate;
2 use base 'OpenSRF::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
5 use Data::Dumper;
6 use OpenSRF::Utils;
7 use OpenSRF::Utils::Cache;
8 use Digest::MD5 qw(md5_hex);
9 use OpenILS::Utils::ScriptRunner;
10 use OpenILS::Application::AppUtils;
11 use OpenILS::Application::Circ::Holds;
12 use OpenSRF::Utils::Logger qw(:logger);
13
14 $Data::Dumper::Indent = 0;
15 my $apputils = "OpenILS::Application::AppUtils";
16 my $U = $apputils;
17 my $holdcode = "OpenILS::Application::Circ::Holds";
18
19 my %scripts;                    # - circulation script filenames
20 my $script_libs;                # - any additional script libraries
21 my %cache;                              # - db objects cache
22 my %contexts;                   # - Script runner contexts
23 my $cache_handle;               # - memcache handle
24
25 # ------------------------------------------------------------------------------
26 # Load the circ script from the config
27 # ------------------------------------------------------------------------------
28 sub initialize {
29
30         my $self = shift;
31         $cache_handle = OpenSRF::Utils::Cache->new('global');
32         my $conf = OpenSRF::Utils::SettingsClient->new;
33         my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
34         my @pfx = ( @pfx2, "scripts" );
35
36         my $p           = $conf->config_value(  @pfx, 'circ_permit_patron' );
37         my $c           = $conf->config_value(  @pfx, 'circ_permit_copy' );
38         my $d           = $conf->config_value(  @pfx, 'circ_duration' );
39         my $f           = $conf->config_value(  @pfx, 'circ_recurring_fines' );
40         my $m           = $conf->config_value(  @pfx, 'circ_max_fines' );
41         my $pr  = $conf->config_value(  @pfx, 'circ_permit_renew' );
42         my $ph  = $conf->config_value(  @pfx, 'circ_permit_hold' );
43         my $lb  = $conf->config_value(  @pfx2, 'script_path' );
44
45         $logger->error( "Missing circ script(s)" ) 
46                 unless( $p and $c and $d and $f and $m and $pr and $ph );
47
48         $scripts{circ_permit_patron}    = $p;
49         $scripts{circ_permit_copy}              = $c;
50         $scripts{circ_duration}                 = $d;
51         $scripts{circ_recurring_fines}= $f;
52         $scripts{circ_max_fines}                = $m;
53         $scripts{circ_renew_permit}     = $pr;
54         $scripts{hold_permit}                   = $ph;
55
56         $lb = [ $lb ] unless ref($lb);
57         $script_libs = $lb;
58
59         $logger->debug("Loaded rules scripts for circ: " .
60                 "circ permit patron: $p, circ permit copy: $c, ".
61                 "circ duration :$d , circ recurring fines : $f, " .
62                 "circ max fines : $m, circ renew permit : $pr, permit hold: $ph");
63 }
64
65
66 # ------------------------------------------------------------------------------
67 # Loads the necessary circ objects and pushes them into the script environment
68 # Returns ( $data, $evt ).  if $evt is defined, then an
69 # unexpedted event occurred and should be dealt with / returned to the caller
70 # ------------------------------------------------------------------------------
71 sub create_circ_ctx {
72         my %params = @_;
73
74         my $evt;
75         my $ctx = \%params;
76
77         $evt = _ctx_add_patron_objects($ctx, %params);
78         return $evt if $evt;
79
80         if( ($params{copy} or $params{copyid} or $params{barcode}) and !$params{noncat} ) {
81                 $evt = _ctx_add_copy_objects($ctx, %params);
82                 return $evt if $evt;
83         }
84
85         _doctor_patron_object($ctx) if $ctx->{patron};
86         _doctor_copy_object($ctx) if $ctx->{copy};
87         _doctor_circ_objects($ctx);
88         _build_circ_script_runner($ctx);
89         _add_script_runner_methods( $ctx );
90
91         return $ctx;
92 }
93
94 sub _ctx_add_patron_objects {
95         my( $ctx, %params) = @_;
96
97         $ctx->{patron}  = $params{patron};
98
99         if(!defined($cache{patron_standings})) {
100                 $cache{patron_standings} = $apputils->fetch_patron_standings();
101                 $cache{group_tree} = $apputils->fetch_permission_group_tree();
102         }
103
104         $ctx->{patron_standings} = $cache{patron_standings};
105         $ctx->{group_tree} = $cache{group_tree};
106
107         $ctx->{patron_circ_summary} = 
108                 $apputils->fetch_patron_circ_summary($ctx->{patron}->id) 
109                 if $params{fetch_patron_circsummary};
110
111         return undef;
112 }
113
114
115 sub _ctx_add_copy_objects {
116         my($ctx, %params)  = @_;
117         my $evt;
118
119         $cache{copy_statuses} = $apputils->fetch_copy_statuses 
120                 if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
121
122         $cache{copy_locations} = $apputils->fetch_copy_locations 
123                 if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
124
125         $ctx->{copy_statuses} = $cache{copy_statuses};
126         $ctx->{copy_locations} = $cache{copy_locations};
127
128         my $copy = $params{copy} if $params{copy};
129
130         if(!$copy) {
131
132                 ( $copy, $evt ) = 
133                         $apputils->fetch_copy($params{copyid}) if $params{copyid};
134                 return $evt if $evt;
135
136                 if(!$copy) {
137                         ( $copy, $evt ) = 
138                                 $apputils->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
139                         return $evt if $evt;
140                 }
141         }
142
143         $ctx->{copy} = $copy;
144
145         ( $ctx->{title}, $evt ) = $apputils->fetch_record_by_copy( $ctx->{copy}->id );
146         return $evt if $evt;
147
148         return undef;
149 }
150
151
152 # ------------------------------------------------------------------------------
153 # Fleshes parts of the patron object
154 # ------------------------------------------------------------------------------
155 sub _doctor_copy_object {
156
157         my $ctx = shift;
158         my $copy = $ctx->{copy};
159
160         # set the copy status to a status name
161         $copy->status( _get_copy_status( 
162                 $copy, $ctx->{copy_statuses} ) ) if $copy;
163
164         # set the copy location to the location object
165         $copy->location( _get_copy_location( 
166                 $copy, $ctx->{copy_locations} ) ) if $copy;
167
168         $copy->circ_lib( $U->fetch_org_unit($copy->circ_lib) );
169 }
170
171
172 # ------------------------------------------------------------------------------
173 # Fleshes parts of the copy object
174 # ------------------------------------------------------------------------------
175 sub _doctor_patron_object {
176         my $ctx = shift;
177         my $patron = $ctx->{patron};
178
179         # push the standing object into the patron
180         if(ref($ctx->{patron_standings})) {
181                 for my $s (@{$ctx->{patron_standings}}) {
182                         $patron->standing($s) if ( $s->id eq $ctx->{patron}->standing );
183                 }
184         }
185
186         # set the patron ptofile to the profile name
187         $patron->profile( _get_patron_profile( 
188                 $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
189
190         # flesh the org unit
191         $patron->home_ou( 
192                 $apputils->fetch_org_unit( $patron->home_ou ) ) if $patron;
193
194 }
195
196 # recurse and find the patron profile name from the tree
197 # another option would be to grab the groups for the patron
198 # and cycle through those until the "profile" group has been found
199 sub _get_patron_profile { 
200         my( $patron, $group_tree ) = @_;
201         return $group_tree if ($group_tree->id eq $patron->profile);
202         return undef unless ($group_tree->children);
203
204         for my $child (@{$group_tree->children}) {
205                 my $ret = _get_patron_profile( $patron, $child );
206                 return $ret if $ret;
207         }
208         return undef;
209 }
210
211 sub _get_copy_status {
212         my( $copy, $cstatus ) = @_;
213         my $s = undef;
214         for my $status (@$cstatus) {
215                 $s = $status if( $status->id eq $copy->status ) 
216         }
217         $logger->debug("Retrieving copy status: " . $s->name) if $s;
218         return $s;
219 }
220
221 sub _get_copy_location {
222         my( $copy, $locations ) = @_;
223         my $l = undef;
224         for my $loc (@$locations) {
225                 $l = $loc if $loc->id eq $copy->location;
226         }
227         $logger->debug("Retrieving copy location: " . $l->name ) if $l;
228         return $l;
229 }
230
231
232 # ------------------------------------------------------------------------------
233 # Constructs and shoves data into the script environment
234 # ------------------------------------------------------------------------------
235 sub _build_circ_script_runner {
236         my $ctx = shift;
237
238         $logger->debug("Loading script environment for circulation");
239
240         my $runner;
241         if( $runner = $contexts{$ctx->{type}} ) {
242                 $runner->refresh_context;
243         } else {
244                 $runner = OpenILS::Utils::ScriptRunner->new unless $runner;
245                 $contexts{type} = $runner;
246         }
247
248         for(@$script_libs) {
249                 $logger->debug("Loading circ script lib path $_");
250                 $runner->add_path( $_ );
251         }
252
253         $runner->insert( 'environment.patron',          $ctx->{patron}, 1);
254         $runner->insert( 'environment.title',           $ctx->{title}, 1);
255         $runner->insert( 'environment.copy',            $ctx->{copy}, 1);
256
257         # circ script result
258         $runner->insert( 'result', {} );
259         $runner->insert( 'result.event', 'SUCCESS' );
260
261         $runner->insert('environment.isRenewal', 1) if $ctx->{renew};
262         $runner->insert('environment.isNonCat', 1) if $ctx->{noncat};
263         $runner->insert('environment.nonCatType', $ctx->{noncat_type}) if $ctx->{noncat};
264
265         if(ref($ctx->{patron_circ_summary})) {
266                 $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
267                 $runner->insert( 'environment.patronFines', $ctx->{patron_circ_summary}->[1], 1 );
268         }
269
270         $ctx->{runner} = $runner;
271         return $runner;
272 }
273
274
275 sub _add_script_runner_methods {
276         my $ctx = shift;
277         my $runner = $ctx->{runner};
278
279         if( $ctx->{copy} ) {
280                 
281                 # allows a script to fetch a hold that is currently targeting the
282                 # copy in question
283                 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
284                                 my $key = shift;
285                                 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
286                                 $hold = undef unless $hold;
287                                 $runner->insert( $key, $hold, 1 );
288                         }
289                 );
290         }
291 }
292
293 # ------------------------------------------------------------------------------
294
295 __PACKAGE__->register_method(
296         method  => "permit_circ",
297         api_name        => "open-ils.circ.checkout.permit",
298         notes           => q/
299                 Determines if the given checkout can occur
300                 @param authtoken The login session key
301                 @param params A trailing hash of named params including 
302                         barcode : The copy barcode, 
303                         patron : The patron the checkout is occurring for, 
304                         renew : true or false - whether or not this is a renewal
305                 @return The event that occurred during the permit check.  
306                         If all is well, the SUCCESS event is returned
307         /);
308
309 sub permit_circ {
310         my( $self, $client, $authtoken, $params ) = @_;
311
312         my ( $requestor, $patron, $ctx, $evt );
313
314         if(1) {
315                 $logger->debug("PERMIT: " . Dumper($params));
316         }
317
318         # check permisson of the requestor
319         ( $requestor, $patron, $evt ) = 
320                 $apputils->checkses_requestor( 
321                 $authtoken, $params->{patron}, 'VIEW_PERMIT_CHECKOUT' );
322         return $evt if $evt;
323
324         # fetch and build the circulation environment
325         ( $ctx, $evt ) = create_circ_ctx( %$params, 
326                 patron                                                  => $patron, 
327                 requestor                                               => $requestor, 
328                 type                                                            => 'permit',
329                 fetch_patron_circ_summary       => 1,
330                 fetch_copy_statuses                     => 1, 
331                 fetch_copy_locations                    => 1, 
332                 );
333         return $evt if $evt;
334
335         return _run_permit_scripts($ctx);
336 }
337
338
339 # Runs the patron and copy permit scripts
340 # if this is a non-cat circulation, the copy permit script 
341 # is not run
342 sub _run_permit_scripts {
343
344         my $ctx                 = shift;
345         my $runner              = $ctx->{runner};
346         my $patronid    = $ctx->{patron}->id;
347         my $barcode             = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
348
349         $runner->load($scripts{circ_permit_patron});
350         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
351         my $evtname = $runner->retrieve('result.event');
352         $logger->activity("circ_permit_patron for user $patronid returned event: $evtname");
353
354         return OpenILS::Event->new($evtname) if $evtname ne 'SUCCESS';
355
356         if ( $ctx->{noncat}  ) {
357                 my $key = _cache_permit_key(-1, $patronid, $ctx->{requestor}->id);
358                 return OpenILS::Event->new($evtname, payload => $key);
359         }
360
361         $runner->load($scripts{circ_permit_copy});
362         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
363         $evtname = $runner->retrieve('result.event');
364         $logger->activity("circ_permit_patron for user $patronid ".
365                 "and copy $barcode returned event: $evtname");
366
367         if( $evtname eq 'SUCCESS' ) {
368                 my $key = _cache_permit_key($ctx->{copy}->id, $patronid, $ctx->{requestor}->id);
369                 return OpenILS::Event->new($evtname, payload => $key);
370         }
371
372         return OpenILS::Event->new($evtname);
373
374 }
375
376 # takes copyid, patronid, and requestor id
377 sub _cache_permit_key {
378         my( $cid, $pid, $rid ) = @_;
379         my $key = md5_hex( time() . rand() . "$$ $cid $pid $rid" );
380         $logger->debug("Setting circ permit key [$key] for copy $cid, patron $pid, and staff $rid");
381         $cache_handle->put_cache( "oils_permit_key_$key", [ $cid, $pid, $rid ], 300 );
382         return $key;
383 }
384
385 # takes permit_key, copyid, patronid, and requestor id
386 sub _check_permit_key {
387         my( $key, $cid, $pid, $rid ) = @_;
388         $logger->debug("Fetching circ permit key $key");
389         my $arr = $cache_handle->get_cache("oils_permit_key_$key");
390         return 1 if( ref($arr) and @$arr[0] eq $cid and @$arr[1] eq $pid and @$arr[2] eq $rid );
391         return 0;
392 }
393
394
395 # ------------------------------------------------------------------------------
396
397 __PACKAGE__->register_method(
398         method  => "checkout",
399         api_name        => "open-ils.circ.checkout",
400         notes => q/
401                 Checks out an item
402                 @param authtoken The login session key
403                 @param params A named hash of params including:
404                         copy                    The copy object
405                         barcode         If no copy is provided, the copy is retrieved via barcode
406                         copyid          If no copy or barcode is provide, the copy id will be use
407                         patron          The patron's id
408                         noncat          True if this is a circulation for a non-cataloted item
409                         noncat_type     The non-cataloged type id
410                         noncat_circ_lib The location for the noncat circ.  
411                                 Default is the home org of the staff member
412                 @return The SUCCESS event on success, any other event depending on the error
413         /);
414
415 sub checkout {
416         my( $self, $client, $authtoken, $params ) = @_;
417
418         my ( $requestor, $patron, $ctx, $evt, $circ );
419         my $key = $params->{permit_key};
420
421         # check permisson of the requestor
422         ( $requestor, $patron, $evt ) = 
423                 $apputils->checkses_requestor( 
424                         $authtoken, $params->{patron}, 'COPY_CHECKOUT' );
425         return $evt if $evt;
426
427         if( $params->{noncat} ) {
428                 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY') 
429                         unless _check_permit_key( $key, -1, $patron->id, $requestor->id );
430                 return _checkout_noncat( $requestor, $patron, %$params )
431         }
432
433         my $session = $U->start_db_session();
434
435         # fetch and build the circulation environment
436         ( $ctx, $evt ) = create_circ_ctx( %$params, 
437                 patron                                                  => $patron, 
438                 requestor                                               => $requestor, 
439                 session                                                 => $session, 
440                 type                                                            => 'checkout',
441                 fetch_patron_circ_summary       => 1,
442                 fetch_copy_statuses                     => 1, 
443                 fetch_copy_locations                    => 1, 
444                 );
445         return $evt if $evt;
446
447         return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY') 
448                 unless _check_permit_key( $key, $ctx->{copy}->id, $patron->id, $requestor->id );
449
450         $ctx->{circ_lib} = (defined($params->{circ_lib})) ? 
451                 $params->{circ_lib} : $requestor->home_ou;
452
453         $evt = _run_checkout_scripts($ctx);
454         return $evt if $evt;
455
456         _build_checkout_circ_object($ctx);
457
458         $evt = _commit_checkout_circ_object($ctx);
459         return $evt if $evt;
460
461         _update_checkout_copy($ctx);
462
463         $evt = _handle_related_holds($ctx);
464         return $evt if $evt;
465
466         #$U->commit_db_session($session);
467         $session->disconnect;
468
469         return OpenILS::Event->new('SUCCESS', 
470                 payload         => { 
471                         copy            => $ctx->{copy},
472                         circ            => $ctx->{circ},
473                         record  => $U->record_to_mvr($ctx->{title}),
474                 } );
475 }
476
477
478 sub _run_checkout_scripts {
479         my $ctx = shift;
480         my $evt;
481         my $circ;
482
483         my $runner = $ctx->{runner};
484
485         $runner->insert('result.durationLevel');
486         $runner->insert('result.durationRule');
487         $runner->insert('result.recurringFinesRule');
488         $runner->insert('result.recurringFinesLevel');
489         $runner->insert('result.maxFine');
490
491         $runner->load($scripts{circ_duration});
492         $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
493         my $duration = $runner->retrieve('result.durationRule');
494         $logger->debug("Circ duration script yielded a duration rule of: $duration");
495
496         $runner->load($scripts{circ_recurring_fines});
497         $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
498         my $recurring = $runner->retrieve('result.recurringFinesRule');
499         $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
500
501         $runner->load($scripts{circ_max_fines});
502         $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
503         my $max_fine = $runner->retrieve('result.maxFine');
504         $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
505
506         ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
507         return $evt if $evt;
508         ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
509         return $evt if $evt;
510         ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
511         return $evt if $evt;
512
513         $ctx->{duration_level}                  = $runner->retrieve('result.durationLevel');
514         $ctx->{recurring_fines_level} = $runner->retrieve('result.recurringFinesLevel');
515         $ctx->{duration_rule}                   = $duration;
516         $ctx->{recurring_fines_rule}    = $recurring;
517         $ctx->{max_fine_rule}                   = $max_fine;
518
519         return undef;
520 }
521
522 sub _build_checkout_circ_object {
523         my $ctx = shift;
524
525         my $circ                        = new Fieldmapper::action::circulation;
526         my $duration    = $ctx->{duration_rule};
527         my $max                 = $ctx->{max_fine_rule};
528         my $recurring   = $ctx->{recurring_fines_rule};
529         my $copy                        = $ctx->{copy};
530         my $patron              = $ctx->{patron};
531         my $dur_level   = $ctx->{duration_level};
532         my $rec_level   = $ctx->{recurring_fines_level};
533
534         $circ->duration( $duration->shrt ) if ($dur_level == 1);
535         $circ->duration( $duration->normal ) if ($dur_level == 2);
536         $circ->duration( $duration->extended ) if ($dur_level == 3);
537
538         $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
539         $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
540         $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
541
542         $circ->duration_rule( $duration->name );
543         $circ->recuring_fine_rule( $recurring->name );
544         $circ->max_fine_rule( $max->name );
545         $circ->max_fine( $max->amount );
546
547         $circ->fine_interval($recurring->recurance_interval);
548         $circ->renewal_remaining( $duration->max_renewals );
549         $circ->target_copy( $copy->id );
550         $circ->usr( $patron->id );
551         $circ->circ_lib( $ctx->{circ_lib} );
552
553         if( $ctx->{renew} ) {
554                 $circ->opac_renewal(1); # XXX different for different types ?????
555                 $circ->clear_id;
556                 #$circ->renewal_remaining($numrenews - 1); # XXX
557                 $circ->circ_staff($ctx->{patron}->id);
558
559         } else {
560                 $circ->circ_staff( $ctx->{requestor}->id );
561         }
562
563         _set_circ_due_date($circ);
564         $ctx->{circ} = $circ;
565 }
566
567 sub _set_circ_due_date {
568         my $circ = shift;
569
570         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 
571                 gmtime(OpenSRF::Utils->interval_to_seconds($circ->duration) + int(time()));
572
573         $year += 1900; $mon += 1;
574         my $due_date = sprintf(
575         '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
576         $year, $mon, $mday, $hour, $min, $sec);
577
578         $logger->debug("Checkout setting due date on circ to: $due_date");
579         $circ->due_date($due_date);
580 }
581
582 # Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
583 sub _update_checkout_copy {
584         my $ctx = shift;
585         my $copy = $ctx->{copy};
586
587         $copy->status( $copy->status->id );
588         $copy->editor( $ctx->{requestor}->id );
589         $copy->edit_date( 'now' );
590         $copy->location( $copy->location->id );
591         $copy->circ_lib( $copy->circ_lib->id );
592
593         $logger->debug("Updating editor info on copy in checkout: " . $copy->id );
594         $ctx->{session}->request( 
595                 'open-ils.storage.direct.asset.copy.update', $copy )->gather(1);
596 }
597
598 # commits the circ object to the db then fleshes the circ with rules objects
599 sub _commit_checkout_circ_object {
600
601         my $ctx = shift;
602         my $circ = $ctx->{circ};
603
604         my $r = $ctx->{session}->request(
605                 "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
606
607         return $U->DB_UPDATE_FAILED($circ) unless $r;
608
609         $logger->debug("Created a new circ object in checkout: $r");
610
611         $circ->id($r);
612         $circ->duration_rule($ctx->{duration_rule});
613         $circ->max_fine_rule($ctx->{max_fine_rule});
614         $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
615
616         return undef;
617 }
618
619
620 # sees if there are any holds that this copy 
621 sub _handle_related_holds {
622
623         my $ctx         = shift;
624         my $copy                = $ctx->{copy};
625         my $patron      = $ctx->{patron};
626         my $holds       = $holdcode->fetch_related_holds($copy->id);
627
628         if(ref($holds) && @$holds) {
629
630                 # for now, just sort by id to get what should be the oldest hold
631                 $holds = [ sort { $a->id <=> $b->id } @$holds ];
632                 $holds = [ grep { $_->usr eq $patron->id } @$holds ];
633
634                 if(@$holds) {
635                         my $hold = $holds->[0];
636
637                         $logger->debug("Related hold found in checkout: " . $hold->id );
638
639                         $hold->fulfillment_time('now');
640                         my $r = $ctx->{session}->request(
641                                 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
642                         return $U->DB_UPDATE_FAILED( $hold ) unless $r;
643                 }
644         }
645
646         return undef;
647 }
648
649
650 sub _checkout_noncat {
651         my ( $requestor, $patron, %params ) = @_;
652         my $circlib = $params{noncat_circ_lib} || $requestor->home_ou;
653         my( $circ, $evt ) = 
654                 OpenILS::Application::Circ::NonCat::create_non_cat_circ(
655                         $requestor->id, $patron->id, $circlib, $params{noncat_type} );
656         return $evt if $evt;
657         return OpenILS::Event->new('SUCCESS');
658 }
659
660
661 # ------------------------------------------------------------------------------
662
663 __PACKAGE__->register_method(
664         method  => "checkin",
665         api_name        => "open-ils.circ.checkin",
666         notes           => <<"  NOTES");
667         PARAMS( authtoken, barcode => bc )
668         Checks in based on barcode
669         Returns an event object whose payload contains the record, circ, and copy
670         If the item needs to be routed, the event is a ROUTE_COPY event
671         with an additional 'route_to' variable set on the event
672         NOTES
673
674 sub checkin {
675         my( $self, $client, $authtoken, $params ) = @_;
676 }
677
678 # ------------------------------------------------------------------------------
679
680 __PACKAGE__->register_method(
681         method  => "renew",
682         api_name        => "open-ils.circ.renew_",
683         notes           => <<"  NOTES");
684         PARAMS( authtoken, circ => circ_id );
685         open-ils.circ.renew(login_session, circ_object);
686         Renews the provided circulation.  login_session is the requestor of the
687         renewal and if the logged in user is not the same as circ->usr, then
688         the logged in user must have RENEW_CIRC permissions.
689         NOTES
690
691 sub renew {
692         my( $self, $client, $authtoken, $params ) = @_;
693 }
694
695         
696
697
698 1;