]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
more non-cat tweaks
[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 $k = "oils_permit_key_$key";
390         my $arr = $cache_handle->get_cache($k);
391         $cache_handle->delete_cache($k);
392         return 1 if( ref($arr) and @$arr[0] eq $cid and @$arr[1] eq $pid and @$arr[2] eq $rid );
393         return 0;
394 }
395
396
397 # ------------------------------------------------------------------------------
398
399 __PACKAGE__->register_method(
400         method  => "checkout",
401         api_name        => "open-ils.circ.checkout",
402         notes => q/
403                 Checks out an item
404                 @param authtoken The login session key
405                 @param params A named hash of params including:
406                         copy                    The copy object
407                         barcode         If no copy is provided, the copy is retrieved via barcode
408                         copyid          If no copy or barcode is provide, the copy id will be use
409                         patron          The patron's id
410                         noncat          True if this is a circulation for a non-cataloted item
411                         noncat_type     The non-cataloged type id
412                         noncat_circ_lib The location for the noncat circ.  
413                                 Default is the home org of the staff member
414                 @return The SUCCESS event on success, any other event depending on the error
415         /);
416
417 sub checkout {
418         my( $self, $client, $authtoken, $params ) = @_;
419
420         my ( $requestor, $patron, $ctx, $evt, $circ );
421         my $key = $params->{permit_key};
422
423         # check permisson of the requestor
424         ( $requestor, $patron, $evt ) = 
425                 $apputils->checkses_requestor( 
426                         $authtoken, $params->{patron}, 'COPY_CHECKOUT' );
427         return $evt if $evt;
428
429         if( $params->{noncat} ) {
430                 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY') 
431                         unless _check_permit_key( $key, -1, $patron->id, $requestor->id );
432
433                 ( $circ, $evt ) = _checkout_noncat( $requestor, $patron, %$params );
434                 return $evt if $evt;
435                 return OpenILS::Event->new('SUCCESS', 
436                         payload => { noncat_circ => $circ } );
437         }
438
439         my $session = $U->start_db_session();
440
441         # fetch and build the circulation environment
442         ( $ctx, $evt ) = create_circ_ctx( %$params, 
443                 patron                                                  => $patron, 
444                 requestor                                               => $requestor, 
445                 session                                                 => $session, 
446                 type                                                            => 'checkout',
447                 fetch_patron_circ_summary       => 1,
448                 fetch_copy_statuses                     => 1, 
449                 fetch_copy_locations                    => 1, 
450                 );
451         return $evt if $evt;
452
453         return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY') 
454                 unless _check_permit_key( $key, $ctx->{copy}->id, $patron->id, $requestor->id );
455
456         $ctx->{circ_lib} = (defined($params->{circ_lib})) ? 
457                 $params->{circ_lib} : $requestor->home_ou;
458
459         $evt = _run_checkout_scripts($ctx);
460         return $evt if $evt;
461
462         _build_checkout_circ_object($ctx);
463
464         $evt = _commit_checkout_circ_object($ctx);
465         return $evt if $evt;
466
467         _update_checkout_copy($ctx);
468
469         $evt = _handle_related_holds($ctx);
470         return $evt if $evt;
471
472         #$U->commit_db_session($session);
473         $session->disconnect;
474
475         return OpenILS::Event->new('SUCCESS', 
476                 payload         => { 
477                         copy            => $ctx->{copy},
478                         circ            => $ctx->{circ},
479                         record  => $U->record_to_mvr($ctx->{title}),
480                 } );
481 }
482
483
484 sub _run_checkout_scripts {
485         my $ctx = shift;
486         my $evt;
487         my $circ;
488
489         my $runner = $ctx->{runner};
490
491         $runner->insert('result.durationLevel');
492         $runner->insert('result.durationRule');
493         $runner->insert('result.recurringFinesRule');
494         $runner->insert('result.recurringFinesLevel');
495         $runner->insert('result.maxFine');
496
497         $runner->load($scripts{circ_duration});
498         $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
499         my $duration = $runner->retrieve('result.durationRule');
500         $logger->debug("Circ duration script yielded a duration rule of: $duration");
501
502         $runner->load($scripts{circ_recurring_fines});
503         $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
504         my $recurring = $runner->retrieve('result.recurringFinesRule');
505         $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
506
507         $runner->load($scripts{circ_max_fines});
508         $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
509         my $max_fine = $runner->retrieve('result.maxFine');
510         $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
511
512         ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
513         return $evt if $evt;
514         ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
515         return $evt if $evt;
516         ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
517         return $evt if $evt;
518
519         $ctx->{duration_level}                  = $runner->retrieve('result.durationLevel');
520         $ctx->{recurring_fines_level} = $runner->retrieve('result.recurringFinesLevel');
521         $ctx->{duration_rule}                   = $duration;
522         $ctx->{recurring_fines_rule}    = $recurring;
523         $ctx->{max_fine_rule}                   = $max_fine;
524
525         return undef;
526 }
527
528 sub _build_checkout_circ_object {
529         my $ctx = shift;
530
531         my $circ                        = new Fieldmapper::action::circulation;
532         my $duration    = $ctx->{duration_rule};
533         my $max                 = $ctx->{max_fine_rule};
534         my $recurring   = $ctx->{recurring_fines_rule};
535         my $copy                        = $ctx->{copy};
536         my $patron              = $ctx->{patron};
537         my $dur_level   = $ctx->{duration_level};
538         my $rec_level   = $ctx->{recurring_fines_level};
539
540         $circ->duration( $duration->shrt ) if ($dur_level == 1);
541         $circ->duration( $duration->normal ) if ($dur_level == 2);
542         $circ->duration( $duration->extended ) if ($dur_level == 3);
543
544         $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
545         $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
546         $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
547
548         $circ->duration_rule( $duration->name );
549         $circ->recuring_fine_rule( $recurring->name );
550         $circ->max_fine_rule( $max->name );
551         $circ->max_fine( $max->amount );
552
553         $circ->fine_interval($recurring->recurance_interval);
554         $circ->renewal_remaining( $duration->max_renewals );
555         $circ->target_copy( $copy->id );
556         $circ->usr( $patron->id );
557         $circ->circ_lib( $ctx->{circ_lib} );
558
559         if( $ctx->{renew} ) {
560                 $circ->opac_renewal(1); # XXX different for different types ?????
561                 $circ->clear_id;
562                 #$circ->renewal_remaining($numrenews - 1); # XXX
563                 $circ->circ_staff($ctx->{patron}->id);
564
565         } else {
566                 $circ->circ_staff( $ctx->{requestor}->id );
567         }
568
569         _set_circ_due_date($circ);
570         $ctx->{circ} = $circ;
571 }
572
573 sub _create_due_date {
574         my $duration = shift;
575
576         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 
577                 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
578
579         $year += 1900; $mon += 1;
580         my $due_date = sprintf(
581         '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
582         $year, $mon, $mday, $hour, $min, $sec);
583         return $due_date;
584 }
585
586 sub _set_circ_due_date {
587         my $circ = shift;
588         my $dd = _create_due_date($circ->duration);
589         $logger->debug("Checkout setting due date on circ to: $dd");
590         $circ->due_date($dd);
591 }
592
593 # Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
594 sub _update_checkout_copy {
595         my $ctx = shift;
596         my $copy = $ctx->{copy};
597
598         $copy->status( $copy->status->id );
599         $copy->editor( $ctx->{requestor}->id );
600         $copy->edit_date( 'now' );
601         $copy->location( $copy->location->id );
602         $copy->circ_lib( $copy->circ_lib->id );
603
604         $logger->debug("Updating editor info on copy in checkout: " . $copy->id );
605         $ctx->{session}->request( 
606                 'open-ils.storage.direct.asset.copy.update', $copy )->gather(1);
607 }
608
609 # commits the circ object to the db then fleshes the circ with rules objects
610 sub _commit_checkout_circ_object {
611
612         my $ctx = shift;
613         my $circ = $ctx->{circ};
614
615         my $r = $ctx->{session}->request(
616                 "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
617
618         return $U->DB_UPDATE_FAILED($circ) unless $r;
619
620         $logger->debug("Created a new circ object in checkout: $r");
621
622         $circ->id($r);
623         $circ->duration_rule($ctx->{duration_rule});
624         $circ->max_fine_rule($ctx->{max_fine_rule});
625         $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
626
627         return undef;
628 }
629
630
631 # sees if there are any holds that this copy 
632 sub _handle_related_holds {
633
634         my $ctx         = shift;
635         my $copy                = $ctx->{copy};
636         my $patron      = $ctx->{patron};
637         my $holds       = $holdcode->fetch_related_holds($copy->id);
638
639         if(ref($holds) && @$holds) {
640
641                 # for now, just sort by id to get what should be the oldest hold
642                 $holds = [ sort { $a->id <=> $b->id } @$holds ];
643                 $holds = [ grep { $_->usr eq $patron->id } @$holds ];
644
645                 if(@$holds) {
646                         my $hold = $holds->[0];
647
648                         $logger->debug("Related hold found in checkout: " . $hold->id );
649
650                         $hold->fulfillment_time('now');
651                         my $r = $ctx->{session}->request(
652                                 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
653                         return $U->DB_UPDATE_FAILED( $hold ) unless $r;
654                 }
655         }
656
657         return undef;
658 }
659
660
661 sub _checkout_noncat {
662         my ( $requestor, $patron, %params ) = @_;
663         my $circlib = $params{noncat_circ_lib} || $requestor->home_ou;
664         return OpenILS::Application::Circ::NonCat::create_non_cat_circ(
665                         $requestor->id, $patron->id, $circlib, $params{noncat_type} );
666 }
667
668
669 # ------------------------------------------------------------------------------
670
671 __PACKAGE__->register_method(
672         method  => "checkin",
673         api_name        => "open-ils.circ.checkin",
674         notes           => <<"  NOTES");
675         PARAMS( authtoken, barcode => bc )
676         Checks in based on barcode
677         Returns an event object whose payload contains the record, circ, and copy
678         If the item needs to be routed, the event is a ROUTE_COPY event
679         with an additional 'route_to' variable set on the event
680         NOTES
681
682 sub checkin {
683         my( $self, $client, $authtoken, $params ) = @_;
684 }
685
686 # ------------------------------------------------------------------------------
687
688 __PACKAGE__->register_method(
689         method  => "renew",
690         api_name        => "open-ils.circ.renew_",
691         notes           => <<"  NOTES");
692         PARAMS( authtoken, circ => circ_id );
693         open-ils.circ.renew(login_session, circ_object);
694         Renews the provided circulation.  login_session is the requestor of the
695         renewal and if the logged in user is not the same as circ->usr, then
696         the logged in user must have RENEW_CIRC permissions.
697         NOTES
698
699 sub renew {
700         my( $self, $client, $authtoken, $params ) = @_;
701 }
702
703         
704
705
706 1;