massive restructuring
authorerickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Fri, 28 Jul 2006 13:28:29 +0000 (13:28 +0000)
committererickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Fri, 28 Jul 2006 13:28:29 +0000 (13:28 +0000)
moved all calls where possible to inner-transaction cstore calls
return all overridable events whenever possible on renew

git-svn-id: svn://svn.open-ils.org/ILS/trunk@5148 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm

index 900449c..4143487 100644 (file)
@@ -1,53 +1,20 @@
 package OpenILS::Application::Circ::Circulate;
-use base 'OpenSRF::Application';
 use strict; use warnings;
+use base 'OpenSRF::Application';
 use OpenSRF::EX qw(:try);
-use Data::Dumper;
-use OpenSRF::Utils::Cache;
-use OpenSRF::AppSession;
-use Digest::MD5 qw(md5_hex);
-use OpenILS::Utils::ScriptRunner;
-use OpenILS::Application::AppUtils;
-use OpenILS::Application::Circ::Holds;
-use OpenILS::Application::Circ::Transit;
-use OpenILS::Utils::PermitHold;
+use OpenSRF::Utils::SettingsClient;
 use OpenSRF::Utils::Logger qw(:logger);
-use OpenILS::Utils::Editor qw/:funcs/;
-use DateTime;
-use DateTime::Format::ISO8601;
-use OpenSRF::Utils qw/:datetime/;
-use OpenILS::Application::Circ::ScriptBuilder;
-
-$Data::Dumper::Indent = 0;
-my $U                          = "OpenILS::Application::AppUtils";
-my $holdcode   = "OpenILS::Application::Circ::Holds";
-my $transcode  = "OpenILS::Application::Circ::Transit";
+#use OpenILS::Application::Circ::Circulator;
 
-my %scripts;                   # - circulation script filenames
-my $script_libs;               # - any additional script libraries
-#my %cache;                            # - db objects cache
-my $cache_handle;              # - memcache handle
+my %scripts;
+my $script_libs;
 
-sub PRECAT_FINE_LEVEL { return 2; }
-sub PRECAT_LOAN_DURATION { return 2; }
-
-#my %RECORD_FROM_COPY_CACHE;
-
-
-# for security, this is a process-defined and not
-# a client-defined variable
-my $__isrenewal        = 0;
-
-# ------------------------------------------------------------------------------
-# Load the circ script from the config
-# ------------------------------------------------------------------------------
 sub initialize {
 
        my $self = shift;
-       $cache_handle = OpenSRF::Utils::Cache->new('global');
        my $conf = OpenSRF::Utils::SettingsClient->new;
        my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
-       my @pfx = ( @pfx2, "scripts" );
+       my @pfx = ( @pfx2, "scripts" );
 
        my $p           = $conf->config_value(  @pfx, 'circ_permit_patron' );
        my $c           = $conf->config_value(  @pfx, 'circ_permit_copy' );
@@ -82,345 +49,8 @@ sub initialize {
 }
 
 
-# ------------------------------------------------------------------------------
-# Loads the necessary circ objects and pushes them into the script environment
-# Returns ( $data, $evt ).  if $evt is defined, then an
-# unexpedted event occurred and should be dealt with / returned to the caller
-# ------------------------------------------------------------------------------
-sub create_circ_ctx {
-       my %params = @_;
-       $U->logmark;
-
-       my $evt;
-       my $ctx = \%params;
-
-       $ctx->{copy_id}         = $ctx->{copyid};
-       $ctx->{patron_id}               = $ctx->{patronid};
-       $ctx->{copy_barcode} = $ctx->{barcode};
-       $ctx->{fetch_patron_circ_info} = 1;
-
-       $ctx->{runner} = OpenILS::Application::Circ::ScriptBuilder->build($ctx);
-       my @evts = @{$ctx->{_events}} if $ctx->{_events};
-
-       $logger->debug("script builder events: : @evts") if @evts;
-
-       if(!$params{noncat}) {
-               if( @evts and grep { $_->{textcode} eq 'ASSET_COPY_NOT_FOUND' } @evts) {
-                       $ctx->{precat} = 1;
-               } else {
-                       $ctx->{precat} = 1 if ( $ctx->{copy}->call_number == -1 ); # special case copy
-               }
-       }
-
-       warn "PRECAT = TRUE\n" if $ctx->{precat};
-
-       _build_circ_script_runner($ctx);
-       return ($ctx);
-
-#      # XXX XXX
-#
-#      $evt = _ctx_add_patron_objects($ctx, %params);
-#      return (undef,$evt) if $evt;
-#
-#      if(!$params{noncat}) {
-#              if( $evt = _ctx_add_copy_objects($ctx, %params) ) {
-#                      $ctx->{precat} = 1 if($evt->{textcode} eq 'ASSET_COPY_NOT_FOUND')
-#              } else {
-#                      $ctx->{precat} = 1 if ( $ctx->{copy}->call_number == -1 ); # special case copy
-#              }
-#      }
-#
-#      _doctor_patron_object($ctx) if $ctx->{patron};
-#      _doctor_copy_object($ctx) if $ctx->{copy};
-#
-#      if(!$ctx->{no_runner}) {
-#              _build_circ_script_runner($ctx);
-#              _add_script_runner_methods($ctx);
-#      }
-#
-#      return $ctx;
-
-}
-
-#sub _ctx_add_patron_objects {
-#      my( $ctx, %params) = @_;
-#      $U->logmark;
-#
-#      $cache{group_tree} = $U->fetch_permission_group_tree() unless $cache{group_tree};
-#      $ctx->{group_tree} = $cache{group_tree};
-#
-#      $ctx->{patron_circ_summary} = 
-#              $U->fetch_patron_circ_summary($ctx->{patron}->id) 
-#              if $params{fetch_patron_circsummary};
-#
-#      return undef;
-#}
-#
-#
-sub _find_copy_by_attr {
-       my %params = @_;
-       $U->logmark;
-       my $evt;
-
-       my $copy = $params{copy} || undef;
-
-       if(!$copy) {
-
-               ( $copy, $evt ) = 
-                       $U->fetch_copy($params{copyid}) if $params{copyid};
-               return (undef,$evt) if $evt;
-
-               if(!$copy) {
-                       ( $copy, $evt ) = 
-                               $U->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
-                       return (undef,$evt) if $evt;
-               }
-       }
-       return ( $copy, $evt );
-}
-
-#
-#sub _ctx_add_copy_objects {
-#      my($ctx, %params)  = @_;
-#      $U->logmark;
-#      my $evt;
-#      my $copy;
-#
-#      $cache{copy_statuses} = $U->fetch_copy_statuses 
-#              if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
-#
-#      $cache{copy_locations} = $U->fetch_copy_locations 
-#              if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
-#
-#      $ctx->{copy_statuses} = $cache{copy_statuses};
-#      $ctx->{copy_locations} = $cache{copy_locations};
-#
-#      ($copy, $evt) = _find_copy_by_attr(%params);
-#      return $evt if $evt;
-#
-#      if( $copy and !$ctx->{title} ) {
-#
-#              my $r = $RECORD_FROM_COPY_CACHE{$copy->id};
-#              ($r, $evt) = $U->fetch_record_by_copy( $copy->id ) unless $r;
-#              return $evt if $evt;
-#              $RECORD_FROM_COPY_CACHE{$copy->id} = $r;
-#
-#              $ctx->{title} = $r;
-#              $ctx->{copy} = $copy;
-#
-#              ($ctx->{volume}) = $U->fetch_callnumber($copy->call_number);
-#              $ctx->{recordDescriptor} = $U->cstorereq(
-#                      'open-ils.cstore.direct.metabib.record_descriptor.search', 
-#                      { record => $ctx->{title}->id });
-#
-#
-#      }
-#
-#      return undef;
-#}
-#
-#
-## ------------------------------------------------------------------------------
-## Fleshes parts of the patron object
-## ------------------------------------------------------------------------------
-#sub _doctor_copy_object {
-#      my $ctx = shift;
-#      $U->logmark;
-#      my $copy = $ctx->{copy} || return undef;
-#
-#      $logger->debug("Doctoring copy object...");
-#
-#      # set the copy status to a status name
-#      $copy->status( _get_copy_status( $copy, $ctx->{copy_statuses} ) );
-#
-#      # set the copy location to the location object
-#      $copy->location( _get_copy_location( $copy, $ctx->{copy_locations} ) );
-#
-#      $copy->circ_lib( $U->fetch_org_unit($copy->circ_lib) );
-#
-#}
-#
-#
-## ------------------------------------------------------------------------------
-## Fleshes parts of the patron object
-## ------------------------------------------------------------------------------
-#sub _doctor_patron_object {
-#      my $ctx = shift;
-#      $U->logmark;
-#      my $patron = $ctx->{patron} || return undef;
-#
-#      # set the patron ptofile to the profile name
-#      $patron->profile( _get_patron_profile( 
-#              $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
-#
-#      # flesh the org unit
-#      $patron->home_ou( 
-#              $U->fetch_org_unit( $patron->home_ou ) ) if $patron;
-#
-#}
-#
-## recurse and find the patron profile name from the tree
-## another option would be to grab the groups for the patron
-## and cycle through those until the "profile" group has been found
-#sub _get_patron_profile { 
-#      my( $patron, $group_tree ) = @_;
-#      return $group_tree if ($group_tree->id eq $patron->profile);
-#      return undef unless ($group_tree->children);
-#
-#      for my $child (@{$group_tree->children}) {
-#              my $ret = _get_patron_profile( $patron, $child );
-#              return $ret if $ret;
-#      }
-#      return undef;
-#}
-#
-#sub _get_copy_status {
-#      my( $copy, $cstatus ) = @_;
-#      $U->logmark;
-#      my $s = undef;
-#      for my $status (@$cstatus) {
-#              $s = $status if( $status->id eq $copy->status ) 
-#      }
-#      $logger->debug("Retrieving copy status: " . $s->name) if $s;
-#      return $s;
-#}
-#
-#sub _get_copy_location {
-#      my( $copy, $locations ) = @_;
-#      $U->logmark;
-#      my $l = undef;
-#      for my $loc (@$locations) {
-#              $l = $loc if $loc->id eq $copy->location;
-#      }
-#      $logger->debug("Retrieving copy location: " . $l->name ) if $l;
-#      return $l;
-#}
-#
-
-# ------------------------------------------------------------------------------
-# Constructs and shoves data into the script environment
-# ------------------------------------------------------------------------------
-sub _build_circ_script_runner {
-       my $ctx = shift;
-       $U->logmark;
-
-       $logger->debug("Loading script environment for circulation");
-
-
-       my $runner = $ctx->{runner};
-
-       if($__isrenewal) {
-               $runner->insert('environment.isRenewal', 1);
-       } else {
-               $runner->insert('environment.isRenewal', undef);
-       }
-
-       if($ctx->{ishold} ) { 
-               $runner->insert('environment.isHold', 1); 
-       } else{ 
-               $runner->insert('environment.isHold', undef) 
-       }
-
-       if( $ctx->{noncat} ) {
-               $runner->insert('environment.isNonCat', 1);
-               $runner->insert('environment.nonCatType', $ctx->{noncat_type});
-       } else {
-               $runner->insert('environment.isNonCat', undef);
-       }
-
-       for(@$script_libs) {
-               $logger->debug("Loading circ script lib path $_");
-               $runner->add_path( $_ );
-       }
-
-
-       return $runner;
-
-
-#      # XXX XXX
-#
-#
-#
-#
-#      for(@$script_libs) {
-#              $logger->debug("Loading circ script lib path $_");
-#              $runner->add_path( $_ );
-#      }
-#
-#      # Note: inserting the number 0 into the script turns into the
-#      # string "0", and thus evaluates to true in JS land
-#      # inserting undef will insert "", which evaluates to false
-#
-#      $runner->insert( 'environment.patron',  $ctx->{patron}, 1);
-#      $runner->insert( 'environment.record',  $ctx->{title}, 1);
-#      $runner->insert( 'environment.copy',    $ctx->{copy}, 1);
-#      $runner->insert( 'environment.volume',  $ctx->{volume}, 1);
-#      $runner->insert( 'environment.recordDescriptor', $ctx->{recordDescriptor}, 1);
-#      $runner->insert( 'environment.requestor', $ctx->{requestor}, 1);
-#
-#      # circ script result
-#      $runner->insert( 'result', {} );
-#      #$runner->insert( 'result.event', 'SUCCESS' );
-#      $runner->insert( 'result.events', [] );
-#
-#      if($__isrenewal) {
-#              $runner->insert('environment.isRenewal', 1);
-#      } else {
-#              $runner->insert('environment.isRenewal', undef);
-#      }
-#
-#      if($ctx->{ishold} ) { 
-#              $runner->insert('environment.isHold', 1); 
-#      } else{ 
-#              $runner->insert('environment.isHold', undef) 
-#      }
-#
-#      if( $ctx->{noncat} ) {
-#              $runner->insert('environment.isNonCat', 1);
-#              $runner->insert('environment.nonCatType', $ctx->{noncat_type});
-#      } else {
-#              $runner->insert('environment.isNonCat', undef);
-#      }
-#
-#      if(ref($ctx->{patron_circ_summary})) {
-#              $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
-#      }
-#
-#      $ctx->{runner} = $runner;
-#      return $runner;
-
-
-
-}
-
-
-
-#
-#
-#sub _add_script_runner_methods {
-#      my $ctx = shift;
-#      $U->logmark;
-#      my $runner = $ctx->{runner};
-#
-#      if( $ctx->{copy} ) {
-#              
-#              # allows a script to fetch a hold that is currently targeting the
-#              # copy in question
-#              $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
-#                              my $key = shift;
-#                              my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
-#                              $hold = undef unless $hold;
-#                              $runner->insert( $key, $hold, 1 );
-#                      }
-#              );
-#      }
-#}
-#
-# ------------------------------------------------------------------------------
-
-
 __PACKAGE__->register_method(
-       method  => "permit_circ",
+       method  => "run_method",
        api_name        => "open-ils.circ.checkout.permit",
        notes           => q/
                Determines if the given checkout can occur
@@ -432,1492 +62,1507 @@ __PACKAGE__->register_method(
                @return The event that occurred during the permit check.  
        /);
 
+
 __PACKAGE__->register_method (
-       method          => 'permit_circ',
+       method          => 'run_method',
        api_name                => 'open-ils.circ.checkout.permit.override',
        signature       => q/@see open-ils.circ.checkout.permit/,
 );
 
-sub permit_circ {
-       my( $self, $client, $authtoken, $params ) = @_;
-       $U->logmark;
 
-       my $override = $params->{override} = 1 if $self->api_name =~ /override/o;
+__PACKAGE__->register_method(
+       method  => "run_method",
+       api_name        => "open-ils.circ.checkout",
+       notes => q/
+               Checks out an item
+               @param authtoken The login session key
+               @param params A named hash of params including:
+                       copy                    The copy object
+                       barcode         If no copy is provided, the copy is retrieved via barcode
+                       copyid          If no copy or barcode is provide, the copy id will be use
+                       patron          The patron's id
+                       noncat          True if this is a circulation for a non-cataloted item
+                       noncat_type     The non-cataloged type id
+                       noncat_circ_lib The location for the noncat circ.  
+                       precat          The item has yet to be cataloged
+                       dummy_title The temporary title of the pre-cataloded item
+                       dummy_author The temporary authr of the pre-cataloded item
+                               Default is the home org of the staff member
+               @return The SUCCESS event on success, any other event depending on the error
+       /);
 
-       my ( $requestor, $patron, $ctx, $evt, $circ );
+__PACKAGE__->register_method(
+       method  => "run_method",
+       api_name        => "open-ils.circ.checkin",
+       argc            => 2,
+       signature       => q/
+               Generic super-method for handling all copies
+               @param authtoken The login session key
+               @param params Hash of named parameters including:
+                       barcode - The copy barcode
+                       force           - If true, copies in bad statuses will be checked in and give good statuses
+                       ...
+       /
+);
 
-       # check permisson of the requestor
-       ( $requestor, $patron, $evt ) = 
-               $U->checkses_requestor( 
-               $authtoken, $params->{patron}, 'VIEW_PERMIT_CHECKOUT' );
-       return $evt if $evt;
+__PACKAGE__->register_method(
+       method  => "run_method",
+       api_name        => "open-ils.circ.checkin.override",
+       signature       => q/@see open-ils.circ.checkin/
+);
 
+__PACKAGE__->register_method(
+       method  => "run_method",
+       api_name        => "open-ils.circ.renew.override",
+       signature       => q/@see open-ils.circ.renew/,
+);
 
-       # fetch and build the circulation environment
-       if( !( $ctx = $params->{_ctx}) ) {
 
-               ( $ctx, $evt ) = create_circ_ctx( %$params, 
-                       patron                                                  => $patron, 
-                       requestor                                               => $requestor, 
-                       type                                                            => 'circ',
-                       #fetch_patron_circ_summary      => 1,
-                       fetch_copy_statuses                     => 1, 
-                       fetch_copy_locations                    => 1, 
-                       );
-               return $evt if $evt;
-       }
+__PACKAGE__->register_method(
+       method  => "run_method",
+       api_name        => "open-ils.circ.renew",
+       notes           => <<"  NOTES");
+       PARAMS( authtoken, circ => circ_id );
+       open-ils.circ.renew(login_session, circ_object);
+       Renews the provided circulation.  login_session is the requestor of the
+       renewal and if the logged in user is not the same as circ->usr, then
+       the logged in user must have RENEW_CIRC permissions.
+       NOTES
 
 
-       my $copy = $ctx->{copy};
-       if($copy) {
-               my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
-               return OpenILS::Event->new('COPY_IN_TRANSIT') 
-                       if $stat == $U->copy_status_from_name('in transit')->id;
-       }
+sub run_method {
+       my( $self, $conn, $auth, $args ) = @_;
+       translate_legacy_args($args);
+       my $api = $self->api_name;
 
-       $ctx->{authtoken} = $authtoken;
+       my $circulator = 
+               OpenILS::Application::Circ::Circulator->new($auth, %$args);
 
-       $evt = undef;
-       if( $ctx->{copy} and ($evt = _handle_claims_returned($ctx)) ) {
-               return $evt unless $U->event_equals($evt, 'SUCCESS');
-       }
+       return circ_events($circulator) if $circulator->bail_out;
 
-       if($evt) { 
-               $evt = undef;
+       # --------------------------------------------------------------------------
+       # Go ahead and load the script runner to make sure we have all 
+       # of the objects we need
+       # --------------------------------------------------------------------------
+       $circulator->is_renewal(1) if $api =~ /renew/;
+       $circulator->mk_script_runner;
+       return circ_events($circulator) if $circulator->bail_out;
 
-       } else { 
+       $circulator->circ_permit_patron($scripts{circ_permit_patron});
+       $circulator->circ_permit_copy($scripts{circ_permit_copy});              
+       $circulator->circ_duration($scripts{circ_duration});                     
+       $circulator->circ_permit_renew($scripts{circ_permit_renew});
+       
+       $circulator->override(1) if $api =~ /override/o;
 
-               # no claims returned circ was found, check if there is any open circ
-               if( !$ctx->{ishold} and !$__isrenewal and $ctx->{copy} ) {
-                       ($circ, $evt) = $U->fetch_open_circulation($ctx->{copy}->id);
-                       return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
-               }
-       }
+       if( $api =~ /checkout\.permit/ ) {
+               $circulator->do_permit();
 
+       } elsif( $api =~ /checkout/ ) {
+               $circulator->do_checkout();
 
-       $ctx->{permit_key} = _cache_permit_key();
-       my $events = _run_permit_scripts($ctx);
+       } elsif( $api =~ /checkin/ ) {
+               $circulator->do_checkin();
 
-       if( $override ) {
-               $evt = override_events($requestor, $requestor->ws_ou, 
-                       $events, $authtoken, $client);
-               return $evt if $evt;
-               return OpenILS::Event->new(
-                       'ITEM_NOT_CATALOGED', payload => $ctx->{permit_key}) if $ctx->{precat};
-               return OpenILS::Event->new('SUCCESS', payload => $ctx->{permit_key} );
+       } elsif( $api =~ /renew/ ) {
+               $circulator->is_renewal(1);
+               $circulator->do_renew();
        }
 
-       return $events;
-}
+       if( $circulator->bail_out ) {
 
-sub override_events {
+               my @ee;
+               # make sure no success event accidentally slip in
+               $circulator->events(
+                       [ grep { $_->{textcode} ne 'SUCCESS' } @{$circulator->events} ]);
+               my @e = @{$circulator->events};
+               push( @ee, $_->{textcode} ) for @e;
+               $logger->info("circulator: bailing out with events: @ee");
+               $circulator->editor->xact_rollback;
 
-       my( $requestor, $org, $events, $authtoken, $conn ) = @_;
-       $events = [ $events ] unless ref($events) eq 'ARRAY';
-       my @failed;
-
-       for my $e (@$events) {
-               my $tc = $e->{textcode};
-               next if $tc eq 'SUCCESS';
-               my $ov = "$tc.override";
-               $logger->info("attempting to override event $ov");
-               my $evt = $U->check_perms( $requestor->id, $org, $ov );
-               return $evt if $evt;
+       } else {
+               $circulator->editor->commit;
        }
 
-       return undef;
+       $circulator->script_runner->cleanup;
+       
+       return circ_events($circulator);
 }
 
+sub circ_events {
+       my $circ = shift;
+       my @e = @{$circ->events};
+       return (@e == 1) ? $e[0] : \@e;
+}
 
 
 
-# Runs the patron and copy permit scripts
-# if this is a non-cat circulation, the copy permit script 
-# is not run
-sub _run_permit_scripts {
+sub translate_legacy_args {
+       my $args = shift;
 
-       my $ctx                 = shift;
-       my $runner              = $ctx->{runner};
-       my $patronid    = $ctx->{patron}->id;
-       my $barcode             = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
-       my $key                 = $ctx->{permit_key};
+       if( $$args{barcode} ) {
+               $$args{copy_barcode} = $$args{barcode};
+               delete $$args{barcode};
+       }
 
+       if( $$args{copyid} ) {
+               $$args{copy_id} = $$args{copyid};
+               delete $$args{copyid};
+       }
 
-       # ---------------------------------------------------------------------
-       # Find all of the fatal penalties currently set on the user
-       # ---------------------------------------------------------------------
-       my $penalties = $U->update_patron_penalties( 
-               authtoken => $ctx->{authtoken}, 
-               patron    => $ctx->{patron} 
-       );
+       if( $$args{patronid} ) {
+               $$args{patron_id} = $$args{patronid};
+               delete $$args{patronid};
+       }
 
-       $penalties = $penalties->{fatal_penalties};
-       $logger->info("circ patron penalties user $patronid: @$penalties");
+       if( $$args{patron} and !ref($$args{patron}) ) {
+               $$args{patron_id} = $$args{patron};
+               delete $$args{patron};
+       }
 
 
-       # ---------------------------------------------------------------------
-       # Now run the patron permit script 
-       # ---------------------------------------------------------------------
-       $logger->debug("Running circ script: " . $scripts{circ_permit_patron});
+       if( $$args{noncat} ) {
+               $$args{is_noncat} = $$args{noncat};
+               delete $$args{noncat};
+       }
 
-       $runner->load($scripts{circ_permit_patron});
-       my $result = $runner->run or 
-               throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
+       if( $$args{precat} ) {
+               $$args{is_precat} = $$args{precat};
+               delete $$args{precat};
+       }
+}
 
-       my $patron_events = $result->{events};
-       $ctx->{circ_permit_patron_events} = $patron_events;
-       $logger->activity("circ_permit_patron for returned @$patron_events") if @$patron_events;
 
-       my @evts_so_far = (@$penalties, @$patron_events);
-       my @allevents; 
-       push( @allevents, OpenILS::Event->new($_)) for @evts_so_far;
 
+# --------------------------------------------------------------------------
+# This package actually manages all of the circulation logic
+# --------------------------------------------------------------------------
+package OpenILS::Application::Circ::Circulator;
+use strict; use warnings;
+use vars q/$AUTOLOAD/;
+use DateTime;
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Cache;
+use Digest::MD5 qw(md5_hex);
+use DateTime::Format::ISO8601;
+use OpenILS::Utils::PermitHold;
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Application::Circ::Holds;
+use OpenILS::Application::Circ::Transit;
+use OpenSRF::Utils::Logger qw(:logger);
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Application::Circ::ScriptBuilder;
 
-       return \@allevents if @allevents;
-
-       if($ctx->{precat}) {
-               warn "Item is precat in checkout permit\n";
-               $logger->debug("Exiting circ permit early because copy is pre-cataloged");
-               #push( @allevents, OpenILS::Event->new('ITEM_NOT_CATALOGED', payload => $key));
-               return OpenILS::Event->new('ITEM_NOT_CATALOGED', payload => $key);
-       }
+sub PRECAT_FINE_LEVEL { return 2; }
+sub PRECAT_LOAN_DURATION { return 2; }
+my $U                          = "OpenILS::Application::AppUtils";
+my $holdcode   = "OpenILS::Application::Circ::Holds";
+my $transcode  = "OpenILS::Application::Circ::Transit";
 
-       if( $ctx->{noncat} ) {
-               $logger->debug("Exiting circ permit early because item is a non-cataloged item");
-               return OpenILS::Event->new('SUCCESS', payload => $key);
+sub DESTROY { }
+
+
+# --------------------------------------------------------------------------
+# Add a pile of automagic getter/setter methods
+# --------------------------------------------------------------------------
+my @AUTOLOAD_FIELDS = qw/
+       backdate
+       copy
+       copy_id
+       copy_barcode
+       patron
+       patron_id
+       patron_barcode
+       script_runner
+       volume
+       title
+       is_renewal
+       is_noncat
+       is_precat
+       noncat_type
+       editor
+       events
+       cache_handle
+       override
+       circ_permit_patron
+       circ_permit_copy
+       circ_duration
+       circ_recurring_fines
+       circ_max_fines
+       circ_permit_renew
+       circ
+       transit
+       hold
+       permit_key
+       noncat_circ_lib
+       noncat_count
+       checkout_time
+       dummy_title
+       dummy_author
+       circ_lib
+       barcode
+   duration_level
+   recurring_fines_level
+   duration_rule
+   recurring_fines_rule
+   max_fine_rule
+       renewal_remaining
+       due_date
+       fulfilled_holds
+       transit
+       checkin_changed
+       force
+       old_circ
+       permit_override
+/;
+
+
+sub AUTOLOAD {
+       my $self = shift;
+       my $type = ref($self) or die "$self is not an object";
+       my $data = shift;
+       my $name = $AUTOLOAD;
+       $name =~ s/.*://o;   
+
+       unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) {
+               $logger->error("$type: invalid autoload field: $name");
+               die "$type: invalid autoload field: $name\n" 
        }
 
-
-       if($ctx->{ishold}) {
-               $logger->debug("Exiting circ permit early because request is for hold patron permit");
-               return OpenILS::Event->new('SUCCESS');
+       {
+               no strict 'refs';
+               *{"${type}::${name}"} = sub {
+                       my $s = shift;
+                       my $v = shift;
+                       $s->{$name} = $v if defined $v;
+                       return $s->{$name};
+               }
        }
+       return $self->$name($data);
+}
 
 
+sub new {
+       my( $class, $auth, %args ) = @_;
+       $class = ref($class) || $class;
+       my $self = bless( {}, $class );
 
-       # ---------------------------------------------------------------------
-       # Capture all of the copy permit events
-       # ---------------------------------------------------------------------
-       $runner->load($scripts{circ_permit_copy});
-       $result = $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
-       my $copy_events = $result->{events};
-
-       $ctx->{circ_permit_copy_events} = $copy_events;
-       $logger->activity("circ_permit_copy for copy ".
-               "$barcode returned events: @$copy_events") if @$copy_events;
-
+       $self->events([]);
+       $self->editor( 
+               new_editor(xact => 1, authtoken => $auth) );
 
+       unless( $self->editor->checkauth ) {
+               $self->bail_on_events($self->editor->event);
+               return $self;
+       }
 
+       $self->cache_handle(OpenSRF::Utils::Cache->new('global'));
 
-       # ---------------------------------------------------------------------
-       # Now collect all of the events together
-       # ---------------------------------------------------------------------
+       $self->$_($args{$_}) for keys %args;
 
-       push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
+       $self->circ_lib(
+               ($self->circ_lib) ? $self->circ_lib : $self->editor->requestor->ws_ou);
 
-       my $ae = _check_copy_alert($ctx->{copy});
-       push( @allevents, $ae ) if $ae;
-
-       return OpenILS::Event->new('SUCCESS', payload => $key) unless (@allevents);
+       return $self;
+}
 
-       # uniquify 
-       my %hash = map { ($_->{ilsevent} => $_) } @allevents;
-       @allevents = values %hash;
 
-       for (@allevents) {
-               $_->{payload} = $ctx->{copy}->status->id
-                       if ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
+# --------------------------------------------------------------------------
+# True if we should discontinue processing
+# --------------------------------------------------------------------------
+sub bail_out {
+       my( $self, $bool ) = @_;
+       if( defined $bool ) {
+               $logger->info("circulator: BAILING OUT") if $bool;
+               $self->{bail_out} = $bool;
        }
-
-       return \@allevents;
+       return $self->{bail_out};
 }
 
-sub _check_copy_alert {
-       my $copy = shift;
-       return OpenILS::Event->new('COPY_ALERT_MESSAGE', 
-               payload => $copy->alert_message) if $copy->alert_message;
-       return undef;
+
+sub push_events {
+       my( $self, @evts ) = @_;
+       for my $e (@evts) {
+               next unless $e;
+               $logger->info("circulator: pushing event ".$e->{textcode});
+               push( @{$self->events}, $e ) unless
+                       grep { $_->{textcode} eq $e->{textcode} } @{$self->events};
+       }
 }
 
-# takes copyid, patronid, and requestor id
-sub _cache_permit_key {
+sub mk_permit_key {
+       my $self = shift;
        my $key = md5_hex( time() . rand() . "$$" );
-       $logger->debug("Setting circ permit key to $key");
-       $cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
-       return $key;
+       $self->cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
+       return $self->permit_key($key);
 }
 
-sub _check_permit_key {
-       my $key = shift;
-       $logger->debug("Fetching circ permit key $key");
+sub check_permit_key {
+       my $self = shift;
+       my $key = $self->permit_key;
+       return 0 unless $key;
        my $k = "oils_permit_key_$key";
-       my $one = $cache_handle->get_cache($k);
-       $cache_handle->delete_cache($k);
+       my $one = $self->cache_handle->get_cache($k);
+       $self->cache_handle->delete_cache($k);
        return ($one) ? 1 : 0;
 }
 
 
-# ------------------------------------------------------------------------------
-
-__PACKAGE__->register_method(
-       method  => "checkout",
-       api_name        => "open-ils.circ.checkout",
-       notes => q/
-               Checks out an item
-               @param authtoken The login session key
-               @param params A named hash of params including:
-                       copy                    The copy object
-                       barcode         If no copy is provided, the copy is retrieved via barcode
-                       copyid          If no copy or barcode is provide, the copy id will be use
-                       patron          The patron's id
-                       noncat          True if this is a circulation for a non-cataloted item
-                       noncat_type     The non-cataloged type id
-                       noncat_circ_lib The location for the noncat circ.  
-                       precat          The item has yet to be cataloged
-                       dummy_title The temporary title of the pre-cataloded item
-                       dummy_author The temporary authr of the pre-cataloded item
-                               Default is the home org of the staff member
-               @return The SUCCESS event on success, any other event depending on the error
-       /);
-
-sub checkout {
-       my( $self, $client, $authtoken, $params ) = @_;
-       $U->logmark;
+# --------------------------------------------------------------------------
+# This builds the script runner environment and fetches most of the
+# objects we need
+# --------------------------------------------------------------------------
+sub mk_script_runner {
+       my $self = shift;
+       my $args = {};
 
-       my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
-       my $key = $params->{permit_key};
+       my @fields = 
+               qw/copy copy_barcode copy_id patron 
+                       patron_id patron_barcode volume title editor/;
 
-       # if this is a renewal, then the requestor does not have to
-       # have checkout privelages
-       ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
-       ( $requestor, $evt ) = $U->checksesperm( $authtoken, 'COPY_CHECKOUT' ) unless $__isrenewal;
-       return $evt if $evt;
+       # Translate our objects into the ScriptBuilder args hash
+       $$args{$_} = $self->$_() for @fields;
+       $$args{fetch_patron_by_circ_copy} = 1;
+       $$args{fetch_patron_circ_info} = 1;
 
-       if( $params->{patron} ) {
-               ( $patron, $evt ) = $U->fetch_user($params->{patron});
-               return $evt if $evt;
-       } else {
-               ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
-               return $evt if $evt;
-       }
+       # This fetches most of the objects we need
+       $self->script_runner(
+               OpenILS::Application::Circ::ScriptBuilder->build($args));
 
-       # set the circ lib to the home org of the requestor if not specified
-       my $circlib = (defined($params->{circ_lib})) ? 
-               $params->{circ_lib} : $requestor->ws_ou;
+       # Now we translate the ScriptBuilder objects back into self
+       $self->$_($$args{$_}) for @fields;
 
+       my @evts = @{$args->{_events}} if $args->{_events};
 
-       # Make sure the caller has a valid permit key or is 
-       # overriding the permit can
-       if( $params->{permit_override} ) {
-               $evt = $U->check_perms(
-                       $requestor->id, $requestor->ws_ou, 'CIRC_PERMIT_OVERRIDE');
-               return $evt if $evt;
+       $logger->debug("script builder returned events: : @evts") if @evts;
 
-       } else {
-               return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY') 
-                       unless _check_permit_key($key);
-       }
 
-       # if this is a non-cataloged item, check it out and return
-       return _checkout_noncat( 
-               $key, $requestor, $patron, %$params ) if $params->{noncat};
-
-       # if this item has yet to be cataloged, make sure a dummy copy exists
-       ( $params->{copy}, $evt ) = _make_precat_copy(
-               $requestor, $circlib, $params ) if $params->{precat};
-       return $evt if $evt;
-
-
-       # fetch and build the circulation environment
-       if( !( $ctx = $params->{_ctx}) ) {
-               ( $ctx, $evt ) = create_circ_ctx( %$params, 
-                       patron                                                  => $patron, 
-                       requestor                                               => $requestor, 
-                       session                                                 => $U->start_db_session(),
-                       type                                                            => 'circ',
-                       fetch_copy_statuses                     => 1, 
-                       fetch_copy_locations                    => 1, 
-                       );
-               return $evt if $evt;
-       }
-       $ctx->{session} = $U->start_db_session() unless $ctx->{session};
+       if(@evts) {
+               # Anything besides ASSET_COPY_NOT_FOUND will stop processing
+               if(!$self->is_noncat and 
+                       @evts == 1 and 
+                       $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') {
+                               $self->is_precat(1);
 
-       # if the call doesn't know it's not cataloged..
-       if(!$params->{precat}) {
-               if( $ctx->{copy}->call_number eq '-1' ) {
-                       return OpenILS::Event->new('ITEM_NOT_CATALOGED');
+               } else {
+                       my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts;
+                       return $self->bail_on_events(@e);
                }
        }
 
+       $self->is_precat(1) if $self->copy and $self->copy->call_number == -1;
 
-       $copy = $ctx->{copy};
-       if($copy) {
-               my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
-               return OpenILS::Event->new('COPY_IN_TRANSIT') 
-                       if $stat == $U->copy_status_from_name('in transit')->id;
-       }
-
-       # this happens in permit.. but we need to check here for 'offline' requests
-       ($circ) = $U->fetch_open_circulation($ctx->{copy}->id);
-       return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
-
-       my $cid = ($params->{precat}) ? -1 : $ctx->{copy}->id;
-
-
-       $ctx->{circ_lib} = $circlib;
-
-       $evt = _run_checkout_scripts($ctx);
-       return $evt if $evt;
-
-
-       _build_checkout_circ_object($ctx);
+       # Set some circ-specific flags in the script environment
+       my $evt = "environment";
+       $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef);
 
-       $evt = _apply_modified_due_date($ctx);
-       return $evt if $evt;
-
-       $evt = _commit_checkout_circ_object($ctx);
-       return $evt if $evt;
-
-       $evt = _update_checkout_copy($ctx);
-       return $evt if $evt;
-
-       my $holds;
-       ($holds, $evt) = _handle_related_holds($ctx);
-       return $evt if $evt;
-
-
-       $logger->debug("Checkout committing objects with session thread trace: ".$ctx->{session}->session_id);
-       $U->commit_db_session($ctx->{session});
-       my $record = $U->record_to_mvr($ctx->{title}) unless $ctx->{precat};
-
-       $logger->activity("user ".$requestor->id." successfully checked out item ".
-               $ctx->{copy}->barcode." to user ".$ctx->{patron}->id );
+       if( $self->is_noncat ) {
+      $self->script_runner->insert("$evt.isNonCat", 1);
+      $self->script_runner->insert("$evt.nonCatType", $self->noncat_type);
+       }
 
+       $self->script_runner->add_path( $_ ) for @$script_libs;
 
-       # ------------------------------------------------------------------------------
-       # Update the patron penalty info in the DB
-       # ------------------------------------------------------------------------------
-       $U->update_patron_penalties( 
-               authtoken => $authtoken, 
-               patron    => $ctx->{patron} ,
-               background      => 1,
-       );
-
-       return OpenILS::Event->new('SUCCESS', 
-               payload => { 
-                       copy                                    => $U->unflesh_copy($ctx->{copy}),
-                       circ                                    => $ctx->{circ},
-                       record                          => $record,
-                       holds_fulfilled => $holds,
-               } 
-       )
+       return 1;
 }
 
 
-sub _make_precat_copy {
-       my ( $requestor, $circlib, $params ) =  @_;
-       $U->logmark;
-       my( $copy, undef ) = _find_copy_by_attr(%$params);
-
-       if($copy) {
-               $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
 
-               $copy->editor($requestor->id);
-               $copy->edit_date('now');
-               $copy->dummy_title($params->{dummy_title});
-               $copy->dummy_author($params->{dummy_author});
 
-               my $stat = $U->storagereq(
-                       'open-ils.storage.direct.asset.copy.update', $copy );
+# --------------------------------------------------------------------------
+# Does the circ permit work
+# --------------------------------------------------------------------------
+sub do_permit {
+       my $self = shift;
 
-               return (undef, $U->DB_UPDATE_FAILED($copy)) unless $stat;
-               return ($copy);
+       unless( $self->editor->requestor->id == $self->patron->id ) {
+               return $self->bail_on_events($self->editor->event)
+                       unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') );
        }
 
-       $logger->debug("Creating a new precataloged copy in checkout with barcode " . $params->{barcode});
-
-       my $evt = OpenILS::Event->new(
-               'BAD_PARAMS', desc => "Dummy title or author not provided" ) 
-               unless ( $params->{dummy_title} and $params->{dummy_author} );
-       return (undef, $evt) if $evt;
-
-       $copy = Fieldmapper::asset::copy->new;
-       $copy->circ_lib($circlib);
-       $copy->creator($requestor->id);
-       $copy->editor($requestor->id);
-       $copy->barcode($params->{barcode});
-       $copy->call_number(-1); #special CN for precat materials
-       $copy->loan_duration(&PRECAT_LOAN_DURATION); 
-       $copy->fine_level(&PRECAT_FINE_LEVEL);
-
-       $copy->dummy_title($params->{dummy_title});
-       $copy->dummy_author($params->{dummy_author});
-
-       my $id = $U->storagereq(
-               'open-ils.storage.direct.asset.copy.create', $copy );
-       return (undef, $U->DB_UPDATE_FAILED($copy)) unless $copy;
+       $self->do_copy_checks();
+       return if $self->bail_out;
+       $self->run_patron_permit_scripts();
+       $self->run_copy_permit_scripts() 
+               unless $self->is_precat or $self->is_noncat;
+       $self->override_events() unless $self->is_renewal;
+       return if $self->bail_out;
+
+       if( $self->is_precat ) {
+               $self->push_events(
+                       OpenILS::Event->new(
+                               'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key));
+               return $self->bail_out(1) unless $self->is_renewal;
+       }
 
-       $logger->debug("Pre-cataloged copy successfully created");
-       return ($U->fetch_copy($id));
+       $self->push_events(
+      OpenILS::Event->new(
+                       'SUCCESS', 
+                       payload => $self->mk_permit_key));
 }
 
 
-sub _run_checkout_scripts {
-       my $ctx = shift;
-       $U->logmark;
-       my $evt;
-       my $circ;
+sub do_copy_checks {
+       my $self = shift;
+       my $copy = $self->copy;
+       return unless $copy;
 
-       my $runner = $ctx->{runner};
-
-#      $runner->insert('result.durationLevel');
-#      $runner->insert('result.durationRule');
-#      $runner->insert('result.recurringFinesRule');
-#      $runner->insert('result.recurringFinesLevel');
-#      $runner->insert('result.maxFine');
-
-       $runner->load($scripts{circ_duration});
-
-       my $result              = $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
-       my $duration    = $result->{durationRule};
-       my $dur_level   = $result->{durationLevel};
-       my $recurring   = $result->{recurringFinesRule};
-       my $rec_fines_level = $result->{recurringFinesLevel};
-       my $max_fine    = $result->{maxFine};
-
-#      $runner->load($scripts{circ_recurring_fines});
-#      $result = $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
-#      my $recurring = $result->{recurringFinesRule};
-#      my $rec_fines_level = $result->{recurringFinesLevel};
-#      $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
-
-#      $runner->load($scripts{circ_max_fines});
-#      $result = $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
-#      my $max_fine = $result->{maxFine};
-#      $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
-
-       ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
-       return $evt if $evt;
-       ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
-       return $evt if $evt;
-       ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
-       return $evt if $evt;
-
-       $ctx->{duration_level}                  = $dur_level;
-       $ctx->{recurring_fines_level} = $rec_fines_level;
-       $ctx->{duration_rule}                   = $duration;
-       $ctx->{recurring_fines_rule}    = $recurring;
-       $ctx->{max_fine_rule}                   = $max_fine;
+       my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
 
-       return undef;
-}
+       # We cannot check out a copy if it is in-transit
+       if( $stat == $U->copy_status_from_name('in transit')->id ) {
+               return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT'));
+       }
 
-sub _build_checkout_circ_object {
-       my $ctx = shift;
-       $U->logmark;
+       $self->handle_claims_returned();
+       return if $self->bail_out;
 
-       my $circ                        = new Fieldmapper::action::circulation;
-       my $duration    = $ctx->{duration_rule};
-       my $max                 = $ctx->{max_fine_rule};
-       my $recurring   = $ctx->{recurring_fines_rule};
-       my $copy                        = $ctx->{copy};
-       my $patron              = $ctx->{patron};
-       my $dur_level   = $ctx->{duration_level};
-       my $rec_level   = $ctx->{recurring_fines_level};
-
-       $circ->duration( $duration->shrt ) if ($dur_level == 1);
-       $circ->duration( $duration->normal ) if ($dur_level == 2);
-       $circ->duration( $duration->extended ) if ($dur_level == 3);
-
-       $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
-       $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
-       $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
-
-       $circ->duration_rule( $duration->name );
-       $circ->recuring_fine_rule( $recurring->name );
-       $circ->max_fine_rule( $max->name );
-       $circ->max_fine( $max->amount );
-
-       $circ->fine_interval($recurring->recurance_interval);
-       $circ->renewal_remaining( $duration->max_renewals );
-       $circ->target_copy( $copy->id );
-       $circ->usr( $patron->id );
-       $circ->circ_lib( $ctx->{circ_lib} );
-
-       if( $__isrenewal ) {
-               $logger->debug("Circ is a renewal.  Setting renewal_remaining to " . $ctx->{renewal_remaining} );
-               $circ->opac_renewal(1); 
-               $circ->renewal_remaining($ctx->{renewal_remaining});
-               $circ->circ_staff($ctx->{requestor}->id);
-       } 
-
-
-       # if the user provided an overiding checkout time, 
-       # (e.g. the checkout really happened several hours ago), then
-       # we apply that here.  Does this need a perm??
-       if( my $ds = _create_date_stamp($ctx->{checkout_time}) ) {
-               $logger->debug("circ setting checkout_time to $ds");
-               $circ->xact_start($ds);
-       }
+       # no claims returned circ was found, check if there is any open circ
+       unless( $self->is_renewal ) {
+               my $circs = $self->editor->search_action_circulation(
+                       { target_copy => $copy->id, stop_fines_time => undef }
+               );
 
-       # if a patron is renewing, 'requestor' will be the patron
-       $circ->circ_staff($ctx->{requestor}->id ); 
-       _set_circ_due_date($circ);
-       $ctx->{circ} = $circ;
+               return $self->bail_on_events(
+                       OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs;
+       }
 }
 
-sub _apply_modified_due_date {
-       my $ctx = shift;
-       my $circ = $ctx->{circ};
 
-       $U->logmark;
+# ---------------------------------------------------------------------
+# This pushes any patron-related events into the list but does not
+# set bail_out for any events
+# ---------------------------------------------------------------------
+sub run_patron_permit_scripts {
+       my $self                = shift;
+       my $runner              = $self->script_runner;
+       my $patronid    = $self->patron->id;
 
-       if( $ctx->{due_date} ) {
+       # ---------------------------------------------------------------------
+       # Find all of the fatal penalties currently set on the user
+       # ---------------------------------------------------------------------
+       my $penalties = $U->update_patron_penalties( 
+               authtoken => $self->editor->authtoken,
+               patron    => $self->patron,
+       );
 
-               my $evt = $U->check_perms(
-                       $ctx->{requestor}->id, $ctx->{circ_lib}, 'CIRC_OVERRIDE_DUE_DATE');
-               return $evt if $evt;
+       $penalties = $penalties->{fatal_penalties};
 
-               my $ds = _create_date_stamp($ctx->{due_date});
-               $logger->debug("circ modifying  due_date to $ds");
-               $circ->due_date($ds);
+       # ---------------------------------------------------------------------
+       # Now run the patron permit script 
+       # ---------------------------------------------------------------------
+       $runner->load($self->circ_permit_patron);
+       my $result = $runner->run or 
+               throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
 
-       } else {
+       my $patron_events = $result->{events};
+       my @allevents; 
+       push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events);
 
-               # if the due_date lands on a day when the location is closed
-               my $copy = $ctx->{copy};
-               return unless $copy;
+       $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents;
 
-               $logger->info("circ searching for closed date overlap on lib ".
-                       $copy->circ_lib->id ." with an item due date of ".$circ->due_date );
+       $self->push_events(@allevents);
+}
 
-               my $dateinfo = $ctx->{session}->request(
-                       'open-ils.storage.actor.org_unit.closed_date.overlap',
-                       $copy->circ_lib->id, $circ->due_date )->gather(1);
 
+sub run_copy_permit_scripts {
+       my $self = shift;
+       my $copy = $self->copy || return;
+       my $runner = $self->script_runner;
+       
+   # ---------------------------------------------------------------------
+   # Capture all of the copy permit events
+   # ---------------------------------------------------------------------
+   $runner->load($self->circ_permit_copy);
+   my $result = $runner->run or 
+               throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
+   my $copy_events = $result->{events};
+
+   # ---------------------------------------------------------------------
+   # Now collect all of the events together
+   # ---------------------------------------------------------------------
+       my @allevents;
+   push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
 
-               if($dateinfo) {
-                       $logger->info("$dateinfo : circ due data / close date overlap found : due_date=".
-                               $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
+       # See if this copy has an alert message
+       my $ae = $self->check_copy_alert();
+       push( @allevents, $ae ) if $ae;
 
-                               # XXX make the behavior more dynamic
-                               # for now, we just push the due date to after the close date
-                               $circ->due_date($dateinfo->{end});
-               }
+   # uniquify the events
+   my %hash = map { ($_->{ilsevent} => $_) } @allevents;
+   @allevents = values %hash;
 
-       }
-       return undef;
-}
 
-sub _create_date_stamp {
-       my $datestring = shift;
-       return undef unless $datestring;
-       $datestring = clense_ISO8601($datestring);
-       $logger->debug("circ created date stamp => $datestring");
-       return $datestring;
-}
+       # If the script says the copy is not available, put the status
+       # in as the payload for that event
+       my $stat = ref($copy->status) ? $copy->status->id : $copy->status;
+   for (@allevents) {
+      $_->{payload} = $stat if 
+                       ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
+   }
 
-sub _create_due_date {
-       my $duration = shift;
-       $U->logmark;
-       my ($sec,$min,$hour,$mday,$mon,$year) = 
-               gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
-       $year += 1900; $mon += 1;
-       my $due_date = sprintf(
-       '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
-       $year, $mon, $mday, $hour, $min, $sec);
-       return $due_date;
-}
+       $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents;
 
-sub _set_circ_due_date {
-       my $circ = shift;
-       $U->logmark;
-       my $dd = _create_due_date($circ->duration);
-       $logger->debug("Checkout setting due date on circ to: $dd");
-       $circ->due_date($dd);
+       $self->push_events(@allevents);
 }
 
-# Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
-sub _update_checkout_copy {
-       my $ctx = shift;
-       $U->logmark;
-       my $copy = $ctx->{copy};
-
-       my $s = $U->copy_status_from_name('checked out');
-       $copy->status( $s->id ) if $s;
-
-       my $evt = $U->update_copy( session => $ctx->{session}, 
-               copy => $copy, editor => $ctx->{requestor}->id );
-       return (undef,$evt) if $evt;
 
+sub check_copy_alert {
+       my $self = shift;
+       return OpenILS::Event->new(
+               'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message)
+               if $self->copy and $self->copy->alert_message;
        return undef;
 }
 
-# commits the circ object to the db then fleshes the circ with rules objects
-sub _commit_checkout_circ_object {
-
-       my $ctx = shift;
-       my $circ = $ctx->{circ};
-       $U->logmark;
-
-       $circ->clear_id;
-       my $r = $ctx->{session}->request(
-               "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
 
-       return $U->DB_UPDATE_FAILED($circ) unless $r;
 
-       $logger->debug("Created a new circ object in checkout: $r");
+# --------------------------------------------------------------------------
+# If the call is overriding and has permissions to override every collected
+# event, the are cleared.  Any event that the caller does not have
+# permission to override, will be left in the event list and bail_out will
+# be set
+# XXX We need code in here to cancel any holds/transits on copies 
+# that are being force-checked out
+# --------------------------------------------------------------------------
+sub override_events {
+       my $self = shift;
+       my @events = @{$self->events};
+       return unless @events;
 
-       $circ->id($r);
-       $circ->duration_rule($ctx->{duration_rule});
-       $circ->max_fine_rule($ctx->{max_fine_rule});
-       $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
+       if(!$self->override) {
+               return $self->bail_out(1) 
+                       if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' );
+       }       
 
-       return undef;
+       $self->events([]);
+       
+   for my $e (@events) {
+      my $tc = $e->{textcode};
+      next if $tc eq 'SUCCESS';
+      my $ov = "$tc.override";
+      $logger->info("circulator: attempting to override event: $ov");
+
+               return $self->bail_on_events($self->editor->event)
+                       unless( $self->editor->allowed($ov)     );
+   }
 }
+       
 
+# --------------------------------------------------------------------------
+# If there is an open claimsreturn circ on the requested copy, close the 
+# circ if overriding, otherwise bail out
+# --------------------------------------------------------------------------
+sub handle_claims_returned {
+       my $self = shift;
+       my $copy = $self->copy;
 
-# sees if there are any holds that this copy 
-sub _handle_related_holds {
-
-       my $ctx         = shift;
-       my $copy                = $ctx->{copy};
-       my $patron      = $ctx->{patron};
-       my $holds       = $holdcode->fetch_related_holds($copy->id);
-       $U->logmark;
-       my @fulfilled;
-
-       # XXX We should only fulfill one hold here...
-       # XXX If a hold was transited to the user who is checking out
-       # the item, we need to make sure that hold is what's grabbed
-       if(ref($holds) && @$holds) {
-
-               # for now, just sort by id to get what should be the oldest hold
-               $holds = [ sort { $a->id <=> $b->id } @$holds ];
-               my @myholds = grep { $_->usr eq $patron->id } @$holds;
-               my @altholds    = grep { $_->usr ne $patron->id } @$holds;
-
-               if(@myholds) {
-                       my $hold = $myholds[0];
-
-                       $logger->debug("Related hold found in checkout: " . $hold->id );
-
-                       $hold->current_copy($copy->id); # just make sure it's set
-                       # if the hold was never officially captured, capture it.
-                       $hold->capture_time('now') unless $hold->capture_time;
-                       $hold->fulfillment_time('now');
-                       my $r = $ctx->{session}->request(
-                               "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
-                       return (undef,$U->DB_UPDATE_FAILED( $hold )) unless $r;
-                       push( @fulfilled, $hold->id );
+       my $CR = $self->editor->search_action_circulation(
+               {       
+                       target_copy             => $copy->id,
+                       stop_fines              => 'CLAIMSRETURNED',
+                       checkin_time    => undef,
                }
+       );
 
-               # If there are any holds placed for other users that point to this copy,
-               # then we need to un-target those holds so the targeter can pick a new copy
-               for(@altholds) {
-
-                       $logger->info("Un-targeting hold ".$_->id.
-                               " because copy ".$copy->id." is getting checked out");
+       return unless ($CR = $CR->[0]); 
 
-                       $_->clear_current_copy;
-                       my $r = $ctx->{session}->request(
-                               "open-ils.storage.direct.action.hold_request.update", $_ )->gather(1);
-                       return (undef,$U->DB_UPDATE_FAILED( $_ )) unless $r;
-               }
-       }
+       my $evt;
 
-       return (\@fulfilled, undef);
-}
+       # - If the caller has set the override flag, we will check the item in
+       if($self->override) {
 
-sub _checkout_noncat {
-       my ( $key, $requestor, $patron, %params ) = @_;
-       my( $circ, $circlib, $evt );
-       $U->logmark;
+               $CR->checkin_time('now');       
+               $CR->checkin_lib($self->editor->requestor->ws_ou);
+               $CR->checkin_staff($self->editor->requestor->id);
 
-       $circlib = $params{noncat_circ_lib} || $requestor->ws_ou;
+               $evt = $self->editor->event 
+                       unless $self->editor->update_action_circulation($CR);
 
-       my $count = $params{noncat_count} || 1;
-       my $cotime = _create_date_stamp($params{checkout_time}) || "";
-       $logger->info("circ creating $count noncat circs with checkout time $cotime");
-       for(1..$count) {
-               ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
-                       $requestor->id, $patron->id, $circlib, $params{noncat_type}, $cotime );
-               return $evt if $evt;
+       } else {
+               $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
        }
 
-       return OpenILS::Event->new( 
-               'SUCCESS', payload => { noncat_circ => $circ } );
+       $self->bail_on_events($evt) if $evt;
+       return;
 }
 
 
-__PACKAGE__->register_method(
-       method  => "generic_receive",
-       api_name        => "open-ils.circ.checkin",
-       argc            => 2,
-       signature       => q/
-               Generic super-method for handling all copies
-               @param authtoken The login session key
-               @param params Hash of named parameters including:
-                       barcode - The copy barcode
-                       force           - If true, copies in bad statuses will be checked in and give good statuses
-                       ...
-       /
-);
-
-__PACKAGE__->register_method(
-       method  => "generic_receive",
-       api_name        => "open-ils.circ.checkin.override",
-       signature       => q/@see open-ils.circ.checkin/
-);
+# --------------------------------------------------------------------------
+# This performs the checkout
+# --------------------------------------------------------------------------
+sub do_checkout {
+       my $self = shift;
 
-sub generic_receive {
-       my( $self, $conn, $authtoken, $params ) = @_;
-       my( $ctx, $requestor, $evt );
-
-       ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
-       ( $requestor, $evt ) = $U->checksesperm( 
-               $authtoken, 'COPY_CHECKIN' ) unless $__isrenewal;
-       return $evt if $evt;
-
-
-       my ($patron) = _find_patron_from_params($params);
-       $ctx->{patron} = $patron if $patron;
-
-       # load up the circ objects
-       if( !( $ctx = $params->{_ctx}) ) {
-               ( $ctx, $evt ) = create_circ_ctx( %$params, 
-                       requestor                                               => $requestor, 
-                       session                                                 => $U->start_db_session(),
-                       type                                                            => 'circ',
-                       fetch_copy_statuses                     => 1, 
-                       fetch_copy_locations                    => 1, 
-                       no_runner                                               => 1,  
-                       );
-               return $evt if $evt;
+       # make sure perms are good if this isn't a renewal
+       unless( $self->is_renewal ) {
+               return $self->bail_on_events($self->editor->event)
+                       unless( $self->editor->allowed('COPY_CHECKOUT') );
        }
-       $ctx->{override} = 1 if $self->api_name =~ /override/o;
-       $ctx->{session} = $U->start_db_session() unless $ctx->{session};
-       $ctx->{authtoken} = $authtoken;
-       my $session = $ctx->{session};
 
-       my $copy = $ctx->{copy};
-       $U->unflesh_copy($copy);
-       return OpenILS::Event->new('ASSET_COPY_NOT_FOUND') unless $copy;
+       # verify the permit key
+       unless( $self->check_permit_key ) {
+               if( $self->permit_override ) {
+                       return $self->bail_on_events($self->editor->event)
+                               unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE');
+               } else {
+                       return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY'))
+               }       
+       }
 
-       $logger->info("Checkin copy called by user ".
-               $requestor->id." for copy ".$copy->id);
+       # if this is a non-cataloged circ, build the circ and finish
+       if( $self->is_noncat ) {
+               $self->checkout_noncat;
+               $self->push_events(
+                       OpenILS::Event->new('SUCCESS', 
+                       payload => { noncat_circ => $self->circ }));
+               return;
+       }
 
+       if( $self->is_precat ) {
+               $self->script_runner->insert("environment.isPrecat", 1, 1);
+               $self->make_precat_copy;
+               return if $self->bail_out;
 
-       my $val = $self->checkin_do_receive($conn, $ctx);
+       } elsif( $self->copy->call_number == -1 ) {
+               return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
+       }
 
-       # ------------------------------------------------------------------------------
-       # Update the patron penalty info in the DB
-       # ------------------------------------------------------------------------------
-       $U->update_patron_penalties( 
-               authtoken => $authtoken, 
-               patron    => $ctx->{patron},
-               background => 1,
+       $self->do_copy_checks;
+       return if $self->bail_out;
+
+       $self->run_checkout_scripts();
+       return if $self->bail_out;
+
+       $self->build_checkout_circ_object();
+       return if $self->bail_out;
+
+       $self->apply_modified_due_date();
+       return if $self->bail_out;
+
+       return $self->bail_on_events($self->editor->event)
+               unless $self->editor->create_action_circulation($self->circ);
+
+       $self->copy->status($U->copy_status_from_name('checked out'));
+       $self->update_copy;
+       return if $self->bail_out;
+
+       $self->handle_checkout_holds();
+       return if $self->bail_out;
+
+   # ------------------------------------------------------------------------------
+   # Update the patron penalty info in the DB
+   # ------------------------------------------------------------------------------
+   $U->update_patron_penalties(
+      authtoken => $self->editor->authtoken,
+      patron    => $self->patron,
+      background  => 1,
+   );
+
+       my $record = $U->record_to_mvr($self->title) unless $self->is_precat;
+       $self->push_events(
+               OpenILS::Event->new('SUCCESS',
+                       payload  => {
+                               copy              => $U->unflesh_copy($self->copy),
+                               circ              => $self->circ,
+                               record            => $record,
+                               holds_fulfilled   => $self->fulfilled_holds,
+                       }
+               )
        );
-
-       return $val;
 }
 
-sub checkin_do_receive {
+sub update_copy {
+       my $self = shift;
+       my $copy = $self->copy;
 
-       my( $self, $connection, $ctx ) = @_;
+       my $stat = $copy->status if ref $copy->status;
+       my $loc = $copy->location if ref $copy->location;
+       my $circ_lib = $copy->circ_lib if ref $copy->circ_lib;
 
-       my $evt;
-       my $copy                        = $ctx->{copy};
-       my $session             = $ctx->{session};
-       my $requestor   = $ctx->{requestor};
-       my $change              = 0; # did we actually do anything?
-       my $circ;
+       $copy->status($stat->id) if $stat;
+       $copy->location($loc->id) if $loc;
+       $copy->circ_lib($circ_lib->id) if $circ_lib;
 
-       my @eventlist;
+       return $self->bail_on_events($self->editor->event)
+               unless $self->editor->update_asset_copy($self->copy);
 
-       # does the copy have an attached alert message?
-       my $ae = _check_copy_alert($copy);
-       push(@eventlist, $ae) if $ae;
+       $copy->status($stat) if $stat;
+       $copy->location($loc) if $loc;
+       $copy->circ_lib($circ_lib) if $circ_lib;
+}
 
-       # is the copy is an a status we can't automatically resolve?
-       $evt = _checkin_check_copy_status($ctx);
-       push( @eventlist, $evt ) if $evt;
 
+sub bail_on_events {
+       my( $self, @evts ) = @_;
+       $self->push_events(@evts);
+       $self->bail_out(1);
+}
 
-       # - see if the copy has an open circ attached
-       #($ctx->{circ}, $evt)   = $U->fetch_open_circulation($copy->id);
-       ($ctx->{circ}, $evt)    = $U->fetch_all_open_circulation($copy->id); # - get ones with stop fines as well
-       return $evt if ($evt and $__isrenewal); # renewals require a circulation
-       $evt = undef;
-       $circ = $ctx->{circ};
+sub handle_checkout_holds {
+   my $self    = shift;
 
-       # if the circ is marked as 'claims returned', add the event to the list
-       push( @eventlist, OpenILS::Event->new('CIRC_CLAIMS_RETURNED') ) 
-               if ($circ and $circ->stop_fines and $circ->stop_fines eq 'CLAIMSRETURNED');
-
-       # override or die
-       if(@eventlist) {
-               if($ctx->{override}) {
-                       $evt = override_events($requestor, $requestor->ws_ou, \@eventlist );
-                       return $evt if $evt;
-               } else {
-                       return \@eventlist;
-               }
-       }
+   my $copy    = $self->copy;
+   my $patron  = $self->patron;
+       my $holds       = $self->editor->search_action_hold_request(
+               { current_copy =>  $copy->id , fulfillment_time => undef });
 
-       ($ctx->{transit})       = $U->fetch_open_transit_by_copy($copy->id);
+   my @fulfilled;
 
-       if( $ctx->{circ} ) {
+   # XXX We should only fulfill one hold here...
+   # XXX If a hold was transited to the user who is checking out
+   # the item, we need to make sure that hold is what's grabbed
+   if(@$holds) {
 
-               # There is an open circ on this item, close it out.
-               $change = 1;
-               $evt            = _checkin_handle_circ($ctx);
-               return $evt if $evt;
+      # for now, just sort by id to get what should be the oldest hold
+      $holds = [ sort { $a->id <=> $b->id } @$holds ];
+      my @myholds = grep { $_->usr eq $patron->id } @$holds;
+      my @altholds   = grep { $_->usr ne $patron->id } @$holds;
 
-       } elsif( $ctx->{transit} ) {
+      if(@myholds) {
+         my $hold = $myholds[0];
 
-               # is this item currently in transit?
-               $change                 = 1;
-               $evt                            = $transcode->transit_receive( $copy, $requestor, $session );
-               my $holdtrans   = $evt->{holdtransit};
-               ($ctx->{hold})  = $U->fetch_hold($holdtrans->hold) if $holdtrans;
+         $logger->debug("Related hold found in checkout: " . $hold->id );
 
-               if( ! $U->event_equals($evt, 'SUCCESS') ) {
+         $hold->current_copy($copy->id); # just make sure it's set
+         # if the hold was never officially captured, capture it.
+         $hold->capture_time('now') unless $hold->capture_time;
+         $hold->fulfillment_time('now');
+                       return $self->bail_on_events($self->editor->event)
+                               unless $self->editor->update_action_hold_request($hold);
 
-                       # either an error occurred or a ROUTE_ITEM was generated and the 
-                       # item must be forwarded on to its destination.
-                       return _checkin_flesh_event($ctx, $evt);
+         push( @fulfilled, $hold->id );
+      }
 
-               } else {
+      # If there are any holds placed for other users that point to this copy,
+      # then we need to un-target those holds so the targeter can pick a new copy
+      for(@altholds) {
 
-                       # Transit has been closed, now let's see if the copy's original
-                       # status is something the staff should be warned of
-                       my $e = _checkin_check_copy_status($ctx);
-                       $evt = $e if $e;
+         $logger->info("Un-targeting hold ".$_->id.
+            " because copy ".$copy->id." is getting checked out");
 
-                       if($holdtrans) {
+         $_->clear_current_copy;
+                       return $self->bail_on_event($self->editor->event)
+                               unless $self->editor->update_action_hold_request($_);
+      }
+   }
 
-                               # copy was received as a hold transit.  Copy is at target lib
-                               # and hold transit is complete.  We're done here...
-                               $U->commit_db_session($session);
-                               return _checkin_flesh_event($ctx, $evt);
-                       }
-                       $evt = undef;
-               }
-       }
+       $self->fulfilled_holds(\@fulfilled);
+}
 
-       # ------------------------------------------------------------------------------
-       # Circulations and transits are now closed where necessary.  Now go on to see if
-       # this copy can fulfill a hold or needs to be routed to a different location
-       # ------------------------------------------------------------------------------
 
 
-       # If it's a renewal, we're done
-       if($__isrenewal) {
-               $U->commit_db_session($session);
-               return OpenILS::Event->new('SUCCESS');
-       }
+sub run_checkout_scripts {
+       my $self = shift;
 
-       # Now, let's see if this copy is needed for a hold
-       my ($hold) = $holdcode->find_local_hold( $session, $copy, $requestor ); 
+       my $evt;
+   my $runner = $self->script_runner;
+   $runner->load($self->circ_duration);
+
+   my $result = $runner->run or 
+               throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
+
+   my $duration   = $result->{durationRule};
+   my $dur_level  = $result->{durationLevel};
+   my $recurring  = $result->{recurringFinesRule};
+   my $max_fine   = $result->{maxFine};
+   my $rec_fines_level = $result->{recurringFinesLevel};
+
+   ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
+       return $self->bail_on_events($evt) if $evt;
+   ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
+       return $self->bail_on_events($evt) if $evt;
+   ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
+       return $self->bail_on_events($evt) if $evt;
+
+   $self->duration_level($dur_level);
+   $self->recurring_fines_level($rec_fines_level);
+   $self->duration_rule($duration);
+   $self->recurring_fines_rule($recurring);
+   $self->max_fine_rule($max_fine);
+}
 
-       if($hold) {
 
-               $ctx->{hold}    = $hold;
-               $change                 = 1;
-               
-               # Capture the hold with this copy
-               return $evt if ($evt = _checkin_capture_hold($ctx));
+sub build_checkout_circ_object {
+       my $self = shift;
 
-               if( $hold->pickup_lib == $requestor->ws_ou ) {
+   my $circ       = Fieldmapper::action::circulation->new;
+   my $duration   = $self->duration_rule;
+   my $max        = $self->max_fine_rule;
+   my $recurring  = $self->recurring_fines_rule;
+   my $copy       = $self->copy;
+   my $patron     = $self->patron;
+   my $dur_level  = $self->duration_level;
+   my $rec_level  = $self->recurring_fines_level;
+
+   $circ->duration( $duration->shrt ) if ($dur_level == 1);
+   $circ->duration( $duration->normal ) if ($dur_level == 2);
+   $circ->duration( $duration->extended ) if ($dur_level == 3);
+
+   $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
+   $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
+   $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
+
+   $circ->duration_rule( $duration->name );
+   $circ->recuring_fine_rule( $recurring->name );
+   $circ->max_fine_rule( $max->name );
+   $circ->max_fine( $max->amount );
+
+   $circ->fine_interval($recurring->recurance_interval);
+   $circ->renewal_remaining( $duration->max_renewals );
+   $circ->target_copy( $copy->id );
+   $circ->usr( $patron->id );
+   $circ->circ_lib( $self->circ_lib );
+
+   if( $self->is_renewal ) {
+      $circ->opac_renewal(1);
+      $circ->renewal_remaining($self->renewal_remaining);
+      $circ->circ_staff($self->editor->requestor->id);
+   }
+
+
+   # if the user provided an overiding checkout time,
+   # (e.g. the checkout really happened several hours ago), then
+   # we apply that here.  Does this need a perm??
+       $circ->xact_start(clense_ISO8601($self->checkout_time))
+               if $self->checkout_time;
+
+   # if a patron is renewing, 'requestor' will be the patron
+   $circ->circ_staff($self->editor->requestor->id);
+       $circ->due_date( $self->create_due_date($circ->duration) );
+
+       $self->circ($circ);
+}
 
-                       # This hold was captured in the correct location
-                       $evt = OpenILS::Event->new('SUCCESS');
 
-               } else {
+sub apply_modified_due_date {
+       my $self = shift;
+       my $circ = $self->circ;
+       my $copy = $self->copy;
 
-                       # Hold needs to be picked up elsewhere.  Build a hold 
-                       # transit and route the item.
-                       return $evt if ($evt =_checkin_build_hold_transit($ctx));
-                       $evt = OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib);
-               }
+   if( $self->due_date ) {
 
-       } else { # not needed for a hold
+               return $self->bail_on_events($self->editor->event)
+                       unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
 
-               if( $copy->circ_lib == $requestor->ws_ou ) {
+      $circ->due_date(clense_ISO8601($self->due_date));
 
-                       # Copy is in the right place.
-                       $evt = OpenILS::Event->new('SUCCESS');
+   } else {
 
-                       # if the item happens to be a pre-cataloged item, send it
-                       # to cataloging and return the event
-                       my( $e, $c, $err ) = _checkin_handle_precat($ctx);
-                       return $err if $err;
-                       $change         = 1 if $c;
-                       $evt                    = $e if $e;
+      # if the due_date lands on a day when the location is closed
+      return unless $copy;
 
-               } else {
+               my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
 
-                       # Copy wants to go home. Transit it there.
-                       return $evt if ( $evt = _checkin_build_generic_copy_transit($ctx) );
-                       $evt                    = OpenILS::Event->new('ROUTE_ITEM', org => $copy->circ_lib);
-                       $change         = 1;
-               }
-       }
+      $logger->info("circ searching for closed date overlap on lib $org".
+                       " with an item due date of ".$circ->due_date );
 
+      my $dateinfo = $U->storagereq(
+         'open-ils.storage.actor.org_unit.closed_date.overlap', 
+                       $org, $circ->due_date );
 
-       # ------------------------------------------------------------------
-       # if the copy is not in a state that should persist,
-       # set the copy to reshelving if it's not already there
-       # ------------------------------------------------------------------
-       my ($c, $e) = _reshelve_copy($ctx);
-       return $e if $e;
-       $change = $c unless $change;
+      if($dateinfo) {
+         $logger->info("$dateinfo : circ due data / close date overlap found : due_date=".
+            $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
 
-       if(!$change) {
+            # XXX make the behavior more dynamic
+            # for now, we just push the due date to after the close date
+            $circ->due_date($dateinfo->{end});
+      }
+   }
+}
 
-               $evt = OpenILS::Event->new('NO_CHANGE');
-               ($ctx->{hold}) = $U->fetch_open_hold_by_copy($copy->id) 
 
-               # what is this?
-               if( $copy->status == $U->copy_status_from_name('on holds shelf')->id );
 
-       } else {
+sub create_due_date {
+       my( $self, $duration ) = @_;
+   my ($sec,$min,$hour,$mday,$mon,$year) =
+      gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
+   $year += 1900; $mon += 1;
+   my $due_date = sprintf(
+      '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
+      $year, $mon, $mday, $hour, $min, $sec);
+   return $due_date;
+}
 
-               $U->commit_db_session($session);
-       }
 
-       $logger->activity("checkin by user ".$requestor->id." on item ".
-               $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
 
-       return _checkin_flesh_event($ctx, $evt);
+sub make_precat_copy {
+       my $self = shift;
+       my $copy = $self->copy;
+
+   if($copy) {
+      $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
+
+      $copy->editor($self->editor->requestor->id);
+      $copy->edit_date('now');
+      $copy->dummy_title($self->dummy_title);
+      $copy->dummy_author($self->dummy_author);
+
+               $self->update_copy();
+               return;
+   }
+
+   $logger->info("circulator: Creating a new precataloged ".
+               "copy in checkout with barcode " . $self->copy_barcode);
+
+   $copy = Fieldmapper::asset::copy->new;
+   $copy->circ_lib($self->circ_lib);
+   $copy->creator($self->editor->requestor->id);
+   $copy->editor($self->editor->requestor->id);
+   $copy->barcode($self->copy_barcode);
+   $copy->call_number(-1); #special CN for precat materials
+   $copy->loan_duration(&PRECAT_LOAN_DURATION);
+   $copy->fine_level(&PRECAT_FINE_LEVEL);
+
+   $copy->dummy_title($self->dummy_title || "");
+   $copy->dummy_author($self->dummy_author || "");
+
+       unless( $self->copy($self->editor->create_asset_copy($copy)) ) {
+               $self->bail_out(1);
+               $self->push_events($self->editor->event);
+               return;
+       }       
+
+       # this is a little bit of a hack, but we need to 
+       # get the copy into the script runner
+       $self->script_runner->insert("environment.copy", $copy, 1);
 }
 
-sub _reshelve_copy {
 
-       my $ctx         = shift;
-       my $copy                = $ctx->{copy};
-       my $reqr                = $ctx->{requestor};
-       my $session     = $ctx->{session};
-       my $force       = $ctx->{force};
+sub checkout_noncat {
+       my $self = shift;
 
-       my $stat = ref($copy->status) ? $copy->status->id : $copy->status;
+       my $circ;
+       my $evt;
 
-       if($force || (
-               $stat != $U->copy_status_from_name('on holds shelf')->id and 
-               $stat != $U->copy_status_from_name('available')->id and 
-               $stat != $U->copy_status_from_name('cataloging')->id and 
-               $stat != $U->copy_status_from_name('in transit')->id and 
-               $stat != $U->copy_status_from_name('reshelving')->id) ) {
+   my $lib             = $self->noncat_circ_lib || $self->editor->requestor->ws_ou;
+   my $count   = $self->noncat_count || 1;
+   my $cotime  = clense_ISO8601($self->checkout_time) || "";
 
-               $copy->status( $U->copy_status_from_name('reshelving')->id );
+   $logger->info("circ creating $count noncat circs with checkout time $cotime");
 
-               my $evt = $U->update_copy( 
-                       copy            => $copy,
-                       editor  => $reqr->id,
-                       session => $session,
-                       );
+   for(1..$count) {
 
-               return( 1, $evt );
-       }
-       return undef;
-}
+      ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
+         $self->editor->requestor->id, 
+                       $self->patron->id, 
+                       $lib, 
+                       $self->noncat_type, 
+                       $cotime,
+                       $self->editor );
 
+               if( $evt ) {
+                       $self->push_events($evt);
+                       $self->bail_out(1);
+                       return; 
+               }
+               $self->circ($circ);
+   }
+}
 
 
+sub do_checkin {
+       my $self = shift;
 
-# returns undef if there are no 'open' claims-returned circs attached
-# to the given copy.  if there is an open claims-returned circ, 
-# then we check for override mode.  if in override, mark the claims-returned
-# circ as checked in.  if not, return event.
-sub _handle_claims_returned {
-       my $ctx = shift;
-       my $copy = $ctx->{copy};
+       unless( $self->is_renewal ) {
+               return $self->bail_on_events($self->editor->event)
+                       unless $self->editor->allowed('COPY_CHECKIN');
+       }
 
-       my $CR  = _fetch_open_claims_returned($copy->id);
-       return undef unless $CR;
+       $self->push_events($self->check_copy_alert());
+       $self->push_events($self->check_checkin_copy_status());
 
-       # - If the caller has set the override flag, we will check the item in
-       if($ctx->{override}) {
+       $self->circ(
+               $self->editor->search_action_circulation(
+               { target_copy => $self->copy->id, stop_fines => undef } )->[0]);
 
-               $CR->checkin_time('now');       
-               $CR->checkin_lib($ctx->{requestor}->ws_ou);
-               $CR->checkin_staff($ctx->{requestor}->id);
+       # renewals require a circulation
+       return $self->bail_on_events($self->editor->event)
+               if( $self->is_renewal and !$self->circ );
 
-               my $stat = $U->storagereq(
-                       'open-ils.storage.direct.action.circulation.update', $CR);
-               return $U->DB_UPDATE_FAILED($CR) unless $stat;
-               return OpenILS::Event->new('SUCCESS');
+       # if the circ is marked as 'claims returned', add the event to the list
+       $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED'))
+               if ($self->circ and $self->circ->stop_fines 
+                               and $self->circ->stop_fines eq 'CLAIMSRETURNED');
 
-       } else {
-               # - if not in override mode, return the CR event
-               return OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
+       # handle the overridable events 
+       $self->override_events unless $self->is_renewal;
+       
+       if( $self->copy ) {
+               $self->transit(
+                       $self->editor->search_action_transit_copy(
+                       { target_copy => $self->copy->id, dest_recv_time => undef })->[0]);     
        }
-}
 
+       if( $self->circ ) {
+               $self->checkin_handle_circ;
+               return if $self->bail_out;
+               $self->checkin_changed(1);
 
-sub _fetch_open_claims_returned {
-       my $copyid = shift;
-       my $trans = $U->cstorereq(
-               'open-ils.cstore.direct.action.circulation.search',
-               {       
-                       target_copy             => $copyid, 
-                       stop_fines              => 'CLAIMSRETURNED',
-                       checkin_time    => undef,
+       } elsif( $self->transit ) {
+               my $hold_transit = $self->process_received_transit;
+               $self->checkin_changed(1);
+
+               if( $self->bail_out ) { 
+                       $self->checkin_flesh_events;
+                       return;
+               }
+               
+               if( my $e = $self->check_checkin_copy_status() ) {
+                       # If the original copy status is special, alert the caller
+                       return $self->bail_on_events($e);       
                }
-       );
-       return $$trans[0] if $trans && $$trans[0];
-       return undef;
-}
 
+               if( $hold_transit ) {
+                       $self->checkin_flesh_events;
+                       return;
+               } 
+       }
 
-# returns (ITEM_NOT_CATALOGED, change_occurred, $error_event) where necessary
-sub _checkin_handle_precat {
+       if( $self->is_renewal ) {
+               $self->push_events(OpenILS::Event->new('SUCCESS'));
+               return;
+       }
 
-       my $ctx         = shift;
-       my $copy                = $ctx->{copy};
-       my $evt         = undef;
-       my $errevt      = undef;
-       my $change      = 0;
+   # ------------------------------------------------------------------------------
+   # Circulations and transits are now closed where necessary.  Now go on to see if
+   # this copy can fulfill a hold or needs to be routed to a different location
+   # ------------------------------------------------------------------------------
 
-       my $catstat = $U->copy_status_from_name('cataloging');
+       if( $self->attempt_checkin_hold_capture() ) {
+               return if $self->bail_out;
 
-       if( $ctx->{precat} ) {
+   } else { # not needed for a hold
 
-               $evt = OpenILS::Event->new('ITEM_NOT_CATALOGED');
+               my $circ_lib = (ref $self->copy->circ_lib) ? 
+                               $self->copy->circ_lib->id : $self->copy->circ_lib;
 
-               if( $copy->status != $catstat->id ) {
-                       $copy->status($catstat->id);
+               $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou);
 
-                       return (undef, 0, $errevt) if (
-                               $errevt = $U->update_copy(
-                                       copy            => $copy, 
-                                       editor  => $ctx->{requestor}->id, 
-                                       session => $ctx->{session} ));
-                       $change = 1;
+      if( $circ_lib == $self->editor->requestor->ws_ou ) {
 
-               }
-       }
+                       $self->checkin_handle_precat();
+                       return if $self->bail_out;
 
-       return ($evt, $change, undef);
-}
+      } else {
 
+                       $self->checkin_build_copy_transit();
+                       return if $self->bail_out;
+                       $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib));
+      }
+   }
 
-# returns the appropriate event for the given copy status
-# if the copy is not in a 'special' status, undef is returned
-sub _checkin_check_copy_status {
-       my $ctx = shift;
-       my $copy = $ctx->{copy};
-       my $reqr        = $ctx->{requestor};
-       my $ses = $ctx->{session};
 
-       my $islost              = 0;
-       my $ismissing   = 0;
-       my $evt                 = undef;
+       $self->reshelve_copy;
+       return if $self->bail_out;
 
-       my $status = ref($copy->status) ? $copy->status->id : $copy->status;
+       unless($self->checkin_changed) {
 
-       return undef 
-               if(     $status == $U->copy_status_from_name('available')->id           ||
-                               $status == $U->copy_status_from_name('checked out')->id ||
-                               $status == $U->copy_status_from_name('in process')->id  ||
-                               $status == $U->copy_status_from_name('in transit')->id  ||
-                               $status == $U->copy_status_from_name('reshelving')->id );
+               $self->push_events(OpenILS::Event->new('NO_CHANGE'));
+               my $stat = (ref $self->copy->status) ? $self->copy->status->id : $self->copy->status;
 
-       return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy ) 
-               if( $status == $U->copy_status_from_name('lost')->id );
+       $self->hold($U->fetch_open_hold_by_copy($self->copy->id))
+         if( $stat == $U->copy_status_from_name('on holds shelf')->id );
+               $self->bail_out(1); # no need to commit anything
 
-       return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy ) 
-               if( $status == $U->copy_status_from_name('missing')->id );
+       } else {
+               $self->push_events(OpenILS::Event->new('SUCCESS')) 
+                       unless @{$self->events};
+       }
 
-       return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
+       $self->checkin_flesh_events;
+       return;
+}
 
+sub reshelve_copy {
+   my $self    = shift;
+   my $copy    = $self->copy;
+   my $force   = $self->force;
 
+   my $stat = ref($copy->status) ? $copy->status->id : $copy->status;
 
+   if($force || (
+      $stat != $U->copy_status_from_name('on holds shelf')->id and
+      $stat != $U->copy_status_from_name('available')->id and
+      $stat != $U->copy_status_from_name('cataloging')->id and
+      $stat != $U->copy_status_from_name('in transit')->id and
+      $stat != $U->copy_status_from_name('reshelving')->id) ) {
 
+       $copy->status( $U->copy_status_from_name('reshelving') );
+                       $self->update_copy;
+                       $self->checkin_changed(1);
+       }
 }
 
-# Just gets the copy back home.  Returns undef on success, event on error
-sub _checkin_build_generic_copy_transit {
 
-       my $ctx                 = shift;
-       my $requestor   = $ctx->{requestor};
-       my $copy                        = $ctx->{copy};
-       my $transit             = Fieldmapper::action::transit_copy->new;
-       my $session             = $ctx->{session};
+sub checkin_handle_precat {
+       my $self        = shift;
+   my $copy    = $self->copy;
+   my $catstat = $U->copy_status_from_name('cataloging');
 
-       $logger->activity("User ". $requestor->id ." creating a ".
-               " new copy transit for copy ".$copy->id." to org ".$copy->circ_lib);
+   if( $self->is_precat and ($copy->status != $catstat->id) ) {
+      $copy->status($catstat);
+               $self->update_copy();
+               $self->checkin_changed(1);
+               $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED'));
+   }
+}
 
-       $transit->source($requestor->ws_ou);
-       $transit->dest($copy->circ_lib);
-       $transit->target_copy($copy->id);
-       $transit->source_send_time('now');
-       $transit->copy_status($copy->status);
-       
-       $logger->debug("Creating new copy_transit in DB");
 
-       my $s = $session->request(
-               "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
-       return $U->DB_UPDATE_FAILED($transit) unless $s;
+sub checkin_build_copy_transit {
+       my $self                        = shift;
+   my $copy       = $self->copy;
+   my $transit    = Fieldmapper::action::transit_copy->new;
 
-       $logger->info("Checkin copy successfully created new transit: $s");
+   $transit->source($self->editor->requestor->ws_ou);
+   $transit->dest( (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib );
+   $transit->target_copy($copy->id);
+   $transit->source_send_time('now');
+   $transit->copy_status( (ref $copy->status) ? $copy->status->id : $copy->status );
 
-       $copy->status($U->copy_status_from_name('in transit')->id );
+       return $self->bail_on_events($self->editor->event)
+               unless $self->editor->create_action_transit_copy($transit);
 
-       return $U->update_copy( copy => $copy, 
-                       editor => $requestor->id, session => $session );
-       
+   $copy->status($U->copy_status_from_name('in transit'));
+       $self->update_copy;
+       $self->checkin_changed(1);
 }
 
 
-# returns event on error, undef on success
-sub _checkin_build_hold_transit {
-       my $ctx = shift;
-
-       my $copy = $ctx->{copy};
-       my $hold = $ctx->{hold};
-       my $trans = Fieldmapper::action::hold_transit_copy->new;
+sub attempt_checkin_hold_capture {
+       my $self = shift;
+       my $copy = $self->copy;
 
-       $trans->hold($hold->id);
-       $trans->source($ctx->{requestor}->ws_ou);
-       $trans->dest($hold->pickup_lib);
-       $trans->source_send_time("now");
-       $trans->target_copy($copy->id);
-       $trans->copy_status($copy->status);
+       # See if this copy can fulfill any holds
+       my ($hold) = $holdcode->find_nearest_permitted_hold(
+               OpenSRF::AppSession->create('open-ils.storage'), 
+               $copy, $self->editor->requestor );
 
-       my $id = $ctx->{session}->request(
-               "open-ils.storage.direct.action.hold_transit_copy.create", $trans )->gather(1);
-       return $U->DB_UPDATE_FAILED($trans) unless $id;
+       if(!$hold) {
+               $logger->debug("circulator: no potential permitted".
+                       "holds found for copy ".$copy->barcode);
+               return undef;
+       }
 
-       $logger->info("Checkin copy successfully created hold transit: $id");
+       $logger->info("circulator: found permitted hold ".
+               $hold->id . " for copy, capturing...");
 
-       $copy->status($U->copy_status_from_name('in transit')->id );
-       return $U->update_copy( copy => $copy, 
-                       editor => $ctx->{requestor}->id, session => $ctx->{session} );
-}
+       $hold->current_copy($copy->id);
+       $hold->capture_time('now');
 
-# Returns event on error, undef on success
-sub _checkin_capture_hold {
-       my $ctx = shift;
-       my $copy = $ctx->{copy};
-       my $hold = $ctx->{hold}
+       # prevent some DB errors
+       $hold->clear_fulfillment_time;
+       $hold->clear_fulfillment_staff;
+       $hold->clear_fulfillment_lib;
+       $hold->clear_expire_time
 
-       $logger->debug("Checkin copy capturing hold ".$hold->id);
+       $self->bail_on_events($self->editor->event)
+               unless $self->editor->update_action_hold_request($hold);
+       $self->hold($hold);
+       $self->checkin_changed(1);
 
-       $hold->current_copy($copy->id);
-       $hold->capture_time('now'); 
+       return 1 if $self->bail_out;
 
-       my $stat = $ctx->{session}->request(
-               "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
-       return $U->DB_UPDATE_FAILED($hold) unless $stat;
+       if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) {
 
-       $copy->status( $U->copy_status_from_name('on holds shelf')->id );
+               # This hold was captured in the correct location
+       $copy->status( $U->copy_status_from_name('on holds shelf') );
+               $self->push_events(OpenILS::Event->new('SUCCESS'));
+       
+       } else {
+       
+               # Hold needs to be picked up elsewhere.  Build a hold
+               # transit and route the item.
+               $self->checkin_build_hold_transit();
+       $copy->status($U->copy_status_from_name('in transit') );
+               return 1 if $self->bail_out;
+               $self->push_events(
+                       OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib));
+       }
 
-       return $U->update_copy( copy => $copy, 
-                       editor => $ctx->{requestor}->id, session => $ctx->{session} );
+       # make sure we save the copy status
+       $self->update_copy;
+       return 1;
 }
 
-# fleshes an event with the relevant objects from the context
-sub _checkin_flesh_event {
-       my $ctx = shift;
-       my $evt = shift;
 
-       my $payload                             = {};
-       $payload->{copy}                = $U->unflesh_copy($ctx->{copy});
-       $payload->{record}      = $U->record_to_mvr($ctx->{title}) if($ctx->{title} and !$ctx->{precat});
-       $payload->{circ}                = $ctx->{circ} if $ctx->{circ};
-       $payload->{transit}     = $ctx->{transit} if $ctx->{transit};
-       $payload->{hold}                = $ctx->{hold} if $ctx->{hold};
+sub checkin_build_hold_transit {
+       my $self = shift;
+
+   my $copy = $self->copy;
+   my $hold = $self->hold;
+   my $trans = Fieldmapper::action::hold_transit_copy->new;
+
+       my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
+   $trans->hold($hold->id);
+   $trans->source($self->editor->requestor->ws_ou);
+   $trans->dest($hold->pickup_lib);
+   $trans->source_send_time("now");
+   $trans->target_copy($copy->id);
+   $trans->copy_status($stat);
 
-       $evt->{payload} = $payload;
-       return $evt;
+       return $self->bail_on_events($self->editor->event)
+               unless $self->editor->create_action_hold_transit_copy($trans);
 }
 
 
-# Closes out the circulation, puts the copy into reshelving.
-# Voids any bills attached to this circ after the backdate time 
-# if a backdate is provided
-sub _checkin_handle_circ { 
 
-       my $ctx = shift;
+sub process_received_transit {
+       my $self = shift;
+       my $copy = $self->copy;
+   my $copyid = $self->copy->id;
 
-       my $circ = $ctx->{circ};
-       my $copy = $ctx->{copy};
-       my $requestor   = $ctx->{requestor};
-       my $session             = $ctx->{session};
-       my $evt;
-       my $obt;
+   my $status_name = $U->copy_status_to_name($copy->status);
+   $logger->debug("circulator: attempting transit receive on ".
+               "copy $copyid. Copy status is $status_name");
 
-       $logger->info("Handling circulation [".$circ->id."] found in checkin...");
+       my $transit = $self->transit;
 
-       # backdate the circ if necessary
-       if(my $backdate = $ctx->{backdate}) {
-               return $evt if ($evt = 
-                       _checkin_handle_backdate($backdate, $circ, $requestor, $session, 1));
-       }
+   if( $transit->dest != $self->editor->requestor->ws_ou ) {
+      $logger->activity("Fowarding transit on copy which is destined ".
+         "for a different location. copy=$copyid,current ".
+         "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest);
 
+               $self->bail_on_events(
+                       OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest ));
+   }
 
-       if(!$circ->stop_fines) {
-               $circ->stop_fines('CHECKIN');
-               $circ->stop_fines('RENEW') if $__isrenewal;
-               $circ->stop_fines_time('now');
-       }
+   # The transit is received, set the receive time
+   $transit->dest_recv_time('now');
+       $self->bail_on_events($self->editor->event)
+               unless $self->editor->update_action_transit_copy($transit);
 
-       # see if there are any fines owed on this circ.  if not, close it
-       ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
-       return $evt if $evt;
-       $circ->xact_finish('now') if( $obt->balance_owed == 0 );
+       my $hold_transit = $self->editor->search_action_hold_transit_copy(
+               { hold => $transit->id }
+       );
 
-       # Set the checkin vars since we have the item
-       $circ->checkin_time('now');
-       $circ->checkin_staff($requestor->id);
-       $circ->checkin_lib($requestor->ws_ou);
+   $logger->info("Recovering original copy status in transit: ".$transit->copy_status);
+   $copy->status( $transit->copy_status );
+       $self->update_copy();
+       return if $self->bail_out;
 
-       $evt = _set_copy_reshelving($copy, $requestor->id, $ctx->{session}); 
-       return $evt if $evt;
+       my $ishold = ($hold_transit) ? 1 : 0;
 
-       $ctx->{session}->request(
-               'open-ils.storage.direct.action.circulation.update', $circ )->gather(1);
+       $self->push_events( 
+               OpenILS::Event->new(
+               'SUCCESS', 
+               ishold => $ishold,
+      payload => { transit => $transit, holdtransit => $hold_transit } ));
 
-       return undef;
+       return $hold_transit;
 }
 
-sub _set_copy_reshelving {
-       my( $copy, $reqr, $session ) = @_;
 
-       $logger->info("Setting copy ".$copy->id." to reshelving");
-       $copy->status($U->copy_status_from_name('reshelving')->id);
+sub checkin_handle_circ {
+   my $self = shift;
+       $U->logmark;
 
-       my $evt = $U->update_copy( 
-               session => $session, 
-               copy            => $copy, 
-               editor  => $reqr
-               );
-       return $evt if $evt;
+   my $circ = $self->circ;
+   my $copy = $self->copy;
+   my $evt;
+   my $obt;
+
+   # backdate the circ if necessary
+   if($self->backdate) {
+               $self->handle_backdate;
+               return if $self->bail_out;
+   }
+
+   if(!$circ->stop_fines) {
+      $circ->stop_fines('CHECKIN');
+      $circ->stop_fines('RENEW') if $self->is_renewal;
+      $circ->stop_fines_time('now');
+   }
+
+   # see if there are any fines owed on this circ.  if not, close it
+       $obt = $self->editor->retrieve_money_open_billable_transaction_summary($circ->id);
+   $circ->xact_finish('now') if( $obt->balance_owed == 0 );
+
+   # Set the checkin vars since we have the item
+   $circ->checkin_time('now');
+   $circ->checkin_staff($self->editor->requestor->id);
+   $circ->checkin_lib($self->editor->requestor->ws_ou);
+
+       $self->copy->status($U->copy_status_from_name('reshelving'));
+       $self->update_copy;
+
+       return $self->bail_on_events($self->editor->event)
+               unless $self->editor->update_action_circulation($circ);
 }
 
-# returns event on error, undef on success
-# This voids all bills attached to the given circulation that occurred
-# after the backdate 
-# THIS DOES NOT CLOSE THE CIRC if there are no more fines on the item
-sub _checkin_handle_backdate {
-       my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
 
-       $logger->activity("User ".$requestor->id.
-               " backdating circ [".$circ->target_copy."] to date: $backdate");
+sub checkin_handle_backdate {
+       my $self = shift;
 
-       my $bills = $session->request( # XXX Verify this call is correct
-               "open-ils.storage.direct.money.billing.search_where.atomic",
-               billing_ts => { ">=" => $backdate }, "xact" => $circ->id )->gather(1);
+       my $bills = $self->editor->search_money_billing(
+               { billing_ts => { ">=" => $self->backdate }, "xact" => $self->circ->id }
+       );
 
-       if($bills) {
-               for my $bill (@$bills) {
+       for my $bill (@$bills) {        
+               if( !$bill->voided or $bill->voided =~ /f/i ) {
                        $bill->voided('t');
                        my $n = $bill->note || "";
-                       $bill->note($n . "\nSYSTEM VOIDED FOR BACKDATE");
-                       my $s = $session->request(
-                               "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
-                       return $U->DB_UPDATE_FAILED($bill) unless $s;
+                       $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
+
+                       $self->bail_on_events($self->editor->event)
+                               unless $self->editor->update_money_billing($bill);
                }
        }
+}
 
-       # if the caller elects to attempt to close the circulation
-       # transaction, then it will be closed if there are not further
-       # charges on the transaction
-       #if( $closecirc ) {
-               #my ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
-          #return $evt if $evt;
-               #$circ->xact_finish($backdate) if $obt->balance_owed <= 0;
-       #}
 
-       return undef;
+
+# XXX Legacy version for Circ.pm support
+sub _checkin_handle_backdate {
+   my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
+
+   my $bills = $session->request(
+      "open-ils.storage.direct.money.billing.search_where.atomic",
+      billing_ts => { ">=" => $backdate }, "xact" => $circ->id )->gather(1);
+
+   if($bills) {
+      for my $bill (@$bills) {
+         $bill->voided('t');
+         my $n = $bill->note || "";
+         $bill->note($n . "\nSystem: VOIDED FOR BACKDATE");
+         my $s = $session->request(
+            "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
+         return $U->DB_UPDATE_FAILED($bill) unless $s;
+      }
+   }
 }
 
 
-sub _find_patron_from_params {
-       my $params = shift;
 
-       my $patron;
-       my $copy;
-       my $circ;
-       my $evt;
 
-       if(my $barcode = $params->{barcode}) {
-               $logger->debug("circ finding user from params with barcode $barcode");
-               ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
-               return (undef, undef, $evt) if $evt;
-               ($circ, $evt) = $U->fetch_open_circulation($copy->id);
-               return (undef, undef, $evt) if $evt;
-               ($patron, $evt) = $U->fetch_user($circ->usr);
-               return (undef, undef, $evt) if $evt;
-       }
-       return ($patron, $copy);
+
+
+sub find_patron_from_copy {
+       my $self = shift;
+       my $circs = $self->editor->search_action_circulation(
+               { target_copy => $self->copy->id, stop_fines_time => undef });
+       my $circ = $circs->[0];
+       return unless $circ;
+       my $u = $self->editor->retrieve_actor_user($circ->usr)
+               or return $self->bail_on_events($self->editor->event);
+       $self->patron($u);
 }
 
+sub check_checkin_copy_status {
+       my $self = shift;
+   my $copy = $self->copy;
 
-# ------------------------------------------------------------------------------
+   my $islost     = 0;
+   my $ismissing  = 0;
+   my $evt        = undef;
 
-__PACKAGE__->register_method(
-       method  => "renew",
-       api_name        => "open-ils.circ.renew.override",
-       signature       => q/@see open-ils.circ.renew/,
-);
+   my $status = ref($copy->status) ? $copy->status->id : $copy->status;
 
+   return undef
+      if(   $status == $U->copy_status_from_name('available')->id    ||
+            $status == $U->copy_status_from_name('checked out')->id  ||
+            $status == $U->copy_status_from_name('in process')->id   ||
+            $status == $U->copy_status_from_name('in transit')->id   ||
+            $status == $U->copy_status_from_name('reshelving')->id );
 
-__PACKAGE__->register_method(
-       method  => "renew",
-       api_name        => "open-ils.circ.renew",
-       notes           => <<"  NOTES");
-       PARAMS( authtoken, circ => circ_id );
-       open-ils.circ.renew(login_session, circ_object);
-       Renews the provided circulation.  login_session is the requestor of the
-       renewal and if the logged in user is not the same as circ->usr, then
-       the logged in user must have RENEW_CIRC permissions.
-       NOTES
+   return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )
+      if( $status == $U->copy_status_from_name('lost')->id );
 
-sub renew {
-       my( $self, $client, $authtoken, $params ) = @_;
-       $U->logmark;
+   return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy )
+      if( $status == $U->copy_status_from_name('missing')->id );
+
+   return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
+}
 
-       my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
-       $__isrenewal = 1;
 
-       $params->{override} = 1 if $self->api_name =~ /override/o;
 
-       # fetch the patron object one way or another
-       if( $params->{patron} ) {
-               ( $patron, $evt ) = $U->fetch_user($params->{patron});
-               if($evt) { $__isrenewal = 0; return $evt; }
+# --------------------------------------------------------------------------
+# On checkin, we need to return as many relevant objects as we can
+# --------------------------------------------------------------------------
+sub checkin_flesh_events {
+       my $self = shift;
 
-       } elsif( $params->{patron_barcode} ) {
-               ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
-               if($evt) { $__isrenewal = 0; return $evt; }
+       for my $evt (@{$self->events}) {
 
-       } else {
-               ($patron, $copy, $evt) = _find_patron_from_params($params);
-               if($evt) { $__isrenewal = 0; return $evt; }
-               $params->{copy} = $copy;
+               my $payload          = {};
+               $payload->{copy}     = $U->unflesh_copy($self->copy);
+               $payload->{record}   = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat);
+               $payload->{circ}     = $self->circ;
+               $payload->{transit}  = $self->transit;
+               $payload->{hold}     = $self->hold;
+               
+               $evt->{payload} = $payload;
        }
+}
 
-       # verify our login session
-       ($requestor, $evt) = $U->checkses($authtoken);
-       if($evt) { $__isrenewal = 0; return $evt; }
 
-       # make sure we have permission to perform a renewal
-       if( $requestor->id ne $patron->id ) {
-               $evt = $U->check_perms($requestor->id, $requestor->ws_ou, 'RENEW_CIRC');
-               if($evt) { $__isrenewal = 0; return $evt; }
-       }
+sub do_renew {
+       my $self = shift;
+       $self->is_renewal(1);
 
+       #$self->find_patron_from_copy unless $self->patron;
 
-       # fetch and build the circulation environment
-       ( $ctx, $evt ) = create_circ_ctx( %$params, 
-               patron                                                  => $patron, 
-               requestor                                               => $requestor, 
-               patron                                                  => $patron, 
-               type                                                            => 'circ',
-               fetch_copy_statuses                     => 1, 
-               fetch_copy_locations                    => 1, 
-               );
-       if($evt) { $__isrenewal = 0; return $evt; }
-       $params->{_ctx} = $ctx;
-
-       # make sure they have some renewals left and make sure the circulation exists
-       ($circ, $evt) = _check_renewal_remaining($ctx);
-       if($evt) { $__isrenewal = 0; return $evt; }
-       $ctx->{old_circ} = $circ;
-       my $renewals = $circ->renewal_remaining - 1;
-
-       # run the renew permit script
-       $evt = _run_renew_scripts($ctx);
-       if($evt) { $__isrenewal = 0; return $evt; }
-
-       # checkin the cop
-       #$ctx->{patron} = $ctx->{patron}->id;
-       $evt = $self->generic_receive($client, $authtoken, $ctx );
-               #{ barcode => $params->{barcode}, patron => $params->{patron}} );
-
-       if( !$U->event_equals($evt, 'SUCCESS') ) {
-               $__isrenewal = 0; return $evt; 
-       }
+       unless( $self->is_renewal ) {
+               return $self->bail_on_events($self->editor->events)
+                       unless $self->editor->allowed('RENEW_CIRC');
+       }       
 
-       # re-fetch the context since objects have changed in the checkin
-       # XXX Do we really need to do this - what changes that we don't control??
-       ( $ctx, $evt ) = create_circ_ctx( %$params, 
-               patron                                                  => $patron, 
-               requestor                                               => $requestor, 
-               patron                                                  => $patron, 
-               type                                                            => 'circ',
-               fetch_copy_statuses                     => 1, 
-               fetch_copy_locations                    => 1, 
-               );
-       if($evt) { $__isrenewal = 0; return $evt; }
-       $params->{_ctx} = $ctx;
-       $ctx->{renewal_remaining} = $renewals;
+       # Make sure there is an open circ to renew
+       my $circ = $self->editor->search_action_circulation(
+                       { target_copy => $self->copy->id, stop_fines_time => undef } )->[0];
 
-       # run the circ permit scripts
-       if( $ctx->{permit_override} ) {
-               $evt = $U->check_perms(
-                       $requestor->id, $ctx->{copy}->circ_lib->id, 'CIRC_PERMIT_OVERRIDE');
-               if($evt) { $__isrenewal = 0; return $evt; }
+       return $self->bail_on_events($self->editor->event) unless $circ;
 
-       } else {
-               $evt = $self->permit_circ( $client, $authtoken, $params );
-               if( $U->event_equals($evt, 'ITEM_NOT_CATALOGED')) {
-                       $params->{precat} = 1;
+       $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED'))
+               if $circ->renewal_remaining < 1;
 
-               } else {
-                       if(!$U->event_equals($evt, 'SUCCESS')) {
-                               if($evt) { $__isrenewal = 0; return $evt; }
-                       }
-               }
-               $params->{permit_key} = $evt->{payload};
-       }
+       # -----------------------------------------------------------------
 
+       $self->renewal_remaining( $circ->renewal_remaining - 1 );
+       $self->renewal_remaining(0) if $self->renewal_remaining < 0;
+       $self->old_circ($circ);
 
-       # checkout the item again
-       $params->{patron} = $ctx->{patron}->id;
-       $evt = $self->checkout($client, $authtoken, $params );
+       $self->run_renew_permit;
 
-       $logger->activity("user ".$requestor->id." renewl of item ".
-               $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
+       # Check the item in
+       $self->do_checkin();
+       return if $self->bail_out;
 
-       $__isrenewal = 0;
-       return $evt;
-}
+       unless( $self->permit_override ) {
+               $self->do_permit();
+               return if $self->bail_out;
+               my $e = $self->events->[0];
+               if( $e and $U->event_equals($e, 'ITEM_NOT_CATALOGED') ) {
+                       $self->is_precat(1);
+               }
+       }       
 
-sub _check_renewal_remaining {
-       my $ctx = shift;
-       $U->logmark;
-       my( $circ, $evt ) = $U->fetch_open_circulation($ctx->{copy}->id);
-       return (undef, $evt) if $evt;
-       $evt = OpenILS::Event->new(
-               'MAX_RENEWALS_REACHED') if $circ->renewal_remaining < 1;
-       return ($circ, $evt);
+       $self->override_events;
+       return if $self->bail_out;
+
+       $self->events([]);
+       $self->do_checkout();
 }
 
-sub _run_renew_scripts {
-       my $ctx = shift;
-       my $runner = $ctx->{runner};
-       $U->logmark;
 
-       $runner->load($scripts{circ_permit_renew});
-       my $result = $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
-       my $events = $result->{events};
 
-       $logger->activity("circ_permit_renew for user ".
-               $ctx->{patron}->id." returned events: @$events") if @$events;
+sub run_renew_permit {
+       my $self = shift;
+   my $runner = $self->script_runner;
 
-       my @allevents;
-       push( @allevents, OpenILS::Event->new($_)) for @$events;
-       return \@allevents if  @allevents;
+   $runner->load($self->circ_permit_renew);
+   my $result = $runner->run or 
+               throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
+   my $events = $result->{events};
 
-       return undef;
+   $logger->activity("circ_permit_renew for user ".
+      $self->patron->id." returned events: @$events") if @$events;
+
+       $self->push_events(OpenILS::Event->new($_)) for @$events;
 }
 
-       
 
 
-1;