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' );
}
-# ------------------------------------------------------------------------------
-# 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
@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;