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