LP 1779467: Fix Error When Marking Item on Hold as Discard/Weed
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Circ.pm
index e07f173..0dfebd3 100644 (file)
@@ -8,6 +8,7 @@ use OpenILS::Application::Circ::Survey;
 use OpenILS::Application::Circ::StatCat;
 use OpenILS::Application::Circ::Holds;
 use OpenILS::Application::Circ::HoldNotify;
+use OpenILS::Application::Circ::CircNotify;
 use OpenILS::Application::Circ::CreditCard;
 use OpenILS::Application::Circ::Money;
 use OpenILS::Application::Circ::NonCat;
@@ -19,14 +20,13 @@ use DateTime::Format::ISO8601;
 
 use OpenILS::Application::AppUtils;
 
-use OpenSRF::Utils qw/:datetime/;
+use OpenILS::Utils::DateTime qw/:datetime/;
 use OpenSRF::AppSession;
 use OpenILS::Utils::ModsParser;
 use OpenILS::Event;
 use OpenSRF::EX qw(:try);
 use OpenSRF::Utils::Logger qw(:logger);
 use OpenILS::Utils::Fieldmapper;
-use OpenILS::Utils::Editor;
 use OpenILS::Utils::CStoreEditor q/:funcs/;
 use OpenILS::Const qw/:const/;
 use OpenSRF::Utils::SettingsClient;
@@ -55,14 +55,20 @@ __PACKAGE__->register_method(
         Retrieve a circ object by id
         @param authtoken Login session key
         @pararm circid The id of the circ object
+        @param all_circ Returns an action.all_circulation_slim object instead
+            of an action.circulation object to pick up aged circs.
     /
 );
+
 sub retrieve_circ {
-    my( $s, $c, $a, $i ) = @_;
+    my( $s, $c, $a, $i, $all_circ ) = @_;
     my $e = new_editor(authtoken => $a);
     return $e->event unless $e->checkauth;
-    my $circ = $e->retrieve_action_circulation($i) or return $e->event;
-    if( $e->requestor->id ne $circ->usr ) {
+    my $method = $all_circ ?
+        'retrieve_action_all_circulation_slim' :
+        'retrieve_action_circulation';
+    my $circ = $e->$method($i) or return $e->event;
+    if( $e->requestor->id ne ($circ->usr || '') ) {
         return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
     }
     return $circ;
@@ -195,7 +201,7 @@ __PACKAGE__->register_method(
 sub checkouts_by_user_opac {
     my( $self, $client, $auth, $user_id ) = @_;
 
-    my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
+    my $e = new_editor( authtoken => $auth );
     return $e->event unless $e->checkauth;
     $user_id ||= $e->requestor->id;
     return $e->event unless 
@@ -259,6 +265,9 @@ __PACKAGE__->register_method(
 
 sub staff_age_to_lost {
     my( $self, $conn, $auth, $args ) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('SET_CIRC_LOST', $args->{'circ_lib'});
 
     my $orgs = $U->get_org_descendants($args->{'circ_lib'});
     my $profiles = $U->fetch_permission_group_descendants($args->{'user_profile'});
@@ -361,6 +370,37 @@ sub new_set_circ_lost {
     return 1;
 }
 
+__PACKAGE__->register_method(
+    method    => "update_latest_inventory",
+    api_name  => "open-ils.circ.circulation.update_latest_inventory");
+
+sub update_latest_inventory {
+    my( $self, $conn, $auth, $args ) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+
+    my $copies = $$args{copy_list};
+    foreach my $copyid (@$copies) {
+        my $copy = $e->retrieve_asset_copy($copyid);
+        my $alci = $e->search_asset_latest_inventory({copy => $copyid})->[0];
+
+        if($alci) {
+            $alci->inventory_date('now');
+            $alci->inventory_workstation($e->requestor->wsid);
+            $e->update_asset_latest_inventory($alci) or return $e->die_event;
+        } else {
+            my $alci = Fieldmapper::asset::latest_inventory->new;
+            $alci->inventory_date('now');
+            $alci->inventory_workstation($e->requestor->wsid);
+            $alci->copy($copy->id);
+            $e->create_asset_latest_inventory($alci) or return $e->die_event;
+        }
+
+        $copy->latest_inventory($alci);
+    }
+    $e->commit;
+    return 1;
+}
 
 __PACKAGE__->register_method(
     method  => "set_circ_claims_returned",
@@ -448,18 +488,18 @@ sub set_circ_claims_returned {
     $circ->stop_fines_time('now') unless $circ->stop_fines_time;
 
     if( $backdate ) {
-        $backdate = cleanse_ISO8601($backdate);
+        $backdate = clean_ISO8601($backdate);
 
-        my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
+        my $original_date = DateTime::Format::ISO8601->new->parse_datetime(clean_ISO8601($circ->due_date));
         my $new_date = DateTime::Format::ISO8601->new->parse_datetime($backdate);
         $backdate = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
 
         # clean it up once again; need a : in the timezone offset. E.g. -06:00 not -0600
-        $backdate = cleanse_ISO8601($backdate);
+        $backdate = clean_ISO8601($backdate);
 
         # make it look like the circ stopped at the cliams returned time
         $circ->stop_fines_time($backdate);
-        my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
+        my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate, note => 'System: OVERDUE REVERSED FOR CLAIMS-RETURNED', force_zero => 1});
         return $evt if $evt;
     }
 
@@ -473,6 +513,67 @@ sub set_circ_claims_returned {
         $e->update_asset_copy($copy) or return $e->die_event;
     }
 
+    # Check if the copy circ lib wants lost fees voided on claims
+    # returned.
+    if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_lost_on_claimsreturned', $e))) {
+        my $result = OpenILS::Application::Circ::CircCommon->void_lost(
+            $e,
+            $circ,
+            3
+        );
+        if ($result) {
+            $e->rollback;
+            return $result;
+        }
+    }
+
+    # Check if the copy circ lib wants lost processing fees voided on
+    # claims returned.
+    if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_lost_proc_fee_on_claimsreturned', $e))) {
+        my $result = OpenILS::Application::Circ::CircCommon->void_lost(
+            $e,
+            $circ,
+            4
+        );
+        if ($result) {
+            $e->rollback;
+            return $result;
+        }
+    }
+
+    # Check if the copy circ lib wants longoverdue fees voided on claims
+    # returned.
+    if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_longoverdue_on_claimsreturned', $e))) {
+        my $result = OpenILS::Application::Circ::CircCommon->void_lost(
+            $e,
+            $circ,
+            10
+        );
+        if ($result) {
+            $e->rollback;
+            return $result;
+        }
+    }
+
+    # Check if the copy circ lib wants longoverdue processing fees voided on
+    # claims returned.
+    if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_longoverdue_proc_fee_on_claimsreturned', $e))) {
+        my $result = OpenILS::Application::Circ::CircCommon->void_lost(
+            $e,
+            $circ,
+            11
+        );
+        if ($result) {
+            $e->rollback;
+            return $result;
+        }
+    }
+
+    # Now that all data has been munged, do a no-op update of 
+    # the patron to force a change of the last_xact_id value.
+    $e->update_actor_user($e->retrieve_actor_user($circ->usr))
+        or return $e->die_event;
+
     $e->commit;
     return 1;
 }
@@ -541,12 +642,12 @@ sub post_checkin_backdate_circ_impl {
         $backdate and $circ->checkin_time;
 
     # update the checkin and stop_fines times to reflect the new backdate
-    $circ->stop_fines_time(cleanse_ISO8601($backdate));
-    $circ->checkin_time(cleanse_ISO8601($backdate));
+    $circ->stop_fines_time(clean_ISO8601($backdate));
+    $circ->checkin_time(clean_ISO8601($backdate));
     $e->update_action_circulation($circ) or return $e->die_event;
 
     # now void the overdues "erased" by the back-dating
-    my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
+    my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate});
     return $evt if $evt;
 
     # If the circ was closed before and the balance owned !=0, re-open the transaction
@@ -579,12 +680,17 @@ sub set_circ_due_date {
         or return $e->die_event;
 
     return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
-    $date = cleanse_ISO8601($date);
+    $date = clean_ISO8601($date);
 
     if (!(interval_to_seconds($circ->duration) % 86400)) { # duration is divisible by days
-        my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
+        my $original_date = DateTime::Format::ISO8601->new->parse_datetime(clean_ISO8601($circ->due_date));
         my $new_date = DateTime::Format::ISO8601->new->parse_datetime($date);
-        $date = cleanse_ISO8601( $new_date->ymd . 'T' . $original_date->strftime('%T%z') );
+
+        # since the new date may be coming in as UTC, convert it
+        # to the same time zone as the original due date so that
+        # ->ymd is more likely to yield the expected results
+        $new_date->set_time_zone($original_date->time_zone());
+        $date = clean_ISO8601( $new_date->ymd . 'T' . $original_date->strftime('%T%z') );
     }
 
     $circ->due_date($date);
@@ -642,7 +748,7 @@ sub create_in_house_use {
     }
 
     if( $use_time ne 'now' ) {
-        $use_time = cleanse_ISO8601($use_time);
+        $use_time = clean_ISO8601($use_time);
         $logger->debug("in_house_use setting use time to $use_time");
     }
 
@@ -724,9 +830,9 @@ sub view_circs {
         $count = 4 unless defined $count;
     }
 
-    return $e->search_action_circulation([
+    return $e->search_action_all_circulation_slim([
         {target_copy => $copyid}, 
-        {limit => $count, order_by => { circ => "xact_start DESC" }} 
+        {limit => $count, order_by => { aacs => "xact_start DESC" }} 
     ]);
 }
 
@@ -740,11 +846,27 @@ __PACKAGE__->register_method(
     /);
 
 sub circ_count {
-    my( $self, $client, $copyid, $range ) = @_; 
-    my $e = OpenILS::Utils::Editor->new;
-    return $e->request('open-ils.storage.asset.copy.circ_count', $copyid, $range);
-}
+    my( $self, $client, $copyid ) = @_; 
+
+    my $count = new_editor()->json_query({
+        select => {
+            circbyyr => [{
+                column => 'count',
+                transform => 'sum',
+                aggregate => 1
+            }]
+        },
+        from => 'circbyyr',
+        where => {'+circbyyr' => {copy => $copyid}}
+    })->[0]->{count};
 
+    return {
+        total => {
+            when => 'total',
+            count => $count
+        }
+    };
+}
 
 
 __PACKAGE__->register_method(
@@ -832,7 +954,7 @@ __PACKAGE__->register_method(
 
 sub has_notes {
     my( $self, $conn, $authtoken, $id ) = @_;
-    my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
+    my $editor = new_editor(authtoken => $authtoken);
     return $editor->event unless $editor->checkauth;
 
     my $n = $editor->search_asset_copy_note(
@@ -922,6 +1044,74 @@ sub delete_copy_note {
     return 1;
 }
 
+__PACKAGE__->register_method(
+    method      => 'fetch_copy_tags',
+    authoritative   => 1,
+    api_name        => 'open-ils.circ.copy_tags.retrieve',
+    signature   => q/
+        Returns an array of publicly-visible copy tag objects.  
+        @param args A named hash of parameters including:
+            copy_id     : The id of the item whose notes we want to retrieve
+            tag_type    : Type of copy tags to retrieve, e.g., 'bookplate' (optional)
+            scope       : top of org subtree whose copy tags we want to see
+            depth       : how far down to look for copy tags (optional)
+        @return An array of copy tag objects
+    /);
+__PACKAGE__->register_method(
+    method      => 'fetch_copy_tags',
+    authoritative   => 1,
+    api_name        => 'open-ils.circ.copy_tags.retrieve.staff',
+    signature   => q/
+        Returns an array of all copy tag objects.  
+        @param args A named hash of parameters including:
+            authtoken   : Required to view non-public notes
+            copy_id     : The id of the item whose notes we want to retrieve (optional)
+            tag_type    : Type of copy tags to retrieve, e.g., 'bookplate'
+            scope       : top of org subtree whose copy tags we want to see
+            depth       : how far down to look for copy tags (optional)
+        @return An array of copy tag objects
+    /);
+
+sub fetch_copy_tags {
+    my ($self, $conn, $args) = @_;
+
+    my $org = $args->{scope};
+    my $depth = $args->{depth};
+
+    my $filter = {};
+    my $e;
+    if ($self->api_name =~ /\.staff/) {
+        my $authtoken = $args->{authtoken};
+        return new OpenILS::Event("BAD_PARAMS", "desc" => "authtoken required") unless defined $authtoken;    
+        $e = new_editor(authtoken => $args->{authtoken});
+        return $e->event unless $e->checkauth;
+        return $e->event unless $e->allowed('STAFF_LOGIN', $org);
+    } else {
+        $e = new_editor();
+        $filter->{pub} = 't';
+    }
+    $filter->{tag_type} = $args->{tag_type} if exists($args->{tag_type});
+    $filter->{'+acptcm'} = {
+        copy => $args->{copy_id}
+    };
+
+    # filter by owner of copy tag and depth
+    $filter->{owner} = {
+        in => {
+            select => {aou => [{
+                column => 'id',
+                transform => 'actor.org_unit_descendants',
+                result_field => 'id',
+                (defined($depth) ? ( params => [$depth] ) : ()),
+            }]},
+            from => 'aou',
+            where => {id => $org}
+        }
+    };
+
+    return $e->search_asset_copy_tag([$filter, { join => { acptcm => {} } }]);
+}
+
 
 __PACKAGE__->register_method(
     method => 'age_hold_rules',
@@ -997,37 +1187,35 @@ sub copy_details {
     OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
 
     my $transit = $e->search_action_transit_copy(
-        { target_copy => $copy_id, dest_recv_time => undef } )->[0];
-
-    # find the latest circ, open or closed
-    my $circ = $e->search_action_circulation(
-        [
-            { target_copy => $copy_id },
-            { 
-                flesh => 1,
-                flesh_fields => {
-                    circ => [
-                        'workstation',
-                        'checkin_workstation', 
-                        'duration_rule', 
-                        'max_fine_rule', 
-                        'recurring_fine_rule'
-                    ]
-                },
-                order_by => { circ => 'xact_start desc' }, 
-                limit => 1 
-            }
-        ]
-    )->[0];
-
+        { target_copy => $copy_id, dest_recv_time => undef, cancel_time => undef } )->[0];
+
+    # find the most recent circulation for the requested copy,
+    # be it active, completed, or aged.
+    my $circ = $e->search_action_all_circulation_slim([
+        { target_copy => $copy_id },
+        {
+            flesh => 1,
+            flesh_fields => {
+                aacs => [
+                    'workstation',
+                    'checkin_workstation',
+                    'duration_rule',
+                    'max_fine_rule',
+                    'recurring_fine_rule'
+                ],
+            },
+            order_by => { aacs => 'xact_start desc' },
+            limit => 1
+        }
+    ])->[0];
 
     return {
-        copy        => $copy,
-        hold        => $hold,
+        copy    => $copy,
+        hold    => $hold,
         transit => $transit,
-        circ        => $circ,
+        circ    => $circ,
         volume  => $vol,
-        mvr     => $mvr,
+        mvr     => $mvr
     };
 }
 
@@ -1117,28 +1305,26 @@ __PACKAGE__->register_method(
 
 sub mark_item {
     my( $self, $conn, $auth, $copy_id, $args ) = @_;
-    my $e = new_editor(authtoken=>$auth, xact =>1);
-    return $e->die_event unless $e->checkauth;
     $args ||= {};
 
+    my $e = new_editor(authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
     my $copy = $e->retrieve_asset_copy([
         $copy_id,
-        {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
+        {flesh => 1, flesh_fields => {'acp' => ['call_number','status']}}])
             or return $e->die_event;
 
-    my $owning_lib = 
+    my $owning_lib =
         ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ? 
             $copy->circ_lib : $copy->call_number->owning_lib;
 
+    my $evt; # For later.
     my $perm = 'MARK_ITEM_MISSING';
     my $stat = OILS_COPY_STATUS_MISSING;
 
     if( $self->api_name =~ /damaged/ ) {
         $perm = 'MARK_ITEM_DAMAGED';
         $stat = OILS_COPY_STATUS_DAMAGED;
-        my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
-        return $evt if $evt;
-
     } elsif ( $self->api_name =~ /bindery/ ) {
         $perm = 'MARK_ITEM_BINDERY';
         $stat = OILS_COPY_STATUS_BINDERY;
@@ -1162,19 +1348,72 @@ sub mark_item {
     # caller may proceed if either perm is allowed
     return $e->die_event unless $e->allowed([$perm, 'UPDATE_COPY'], $owning_lib);
 
-    $copy->status($stat);
-    $copy->edit_date('now');
-    $copy->editor($e->requestor->id);
-
-    $e->update_asset_copy($copy) or return $e->die_event;
+    # Copy status checks.
+    if ($copy->status->id() == OILS_COPY_STATUS_CHECKED_OUT) {
+        # Items must be checked in before any attempt is made to change its status.
+        if ($args->{handle_checkin}) {
+            $evt = try_checkin($auth, $copy_id);
+        } else {
+            $evt = OpenILS::Event->new('ITEM_TO_MARK_CHECKED_OUT');
+        }
+    } elsif ($copy->status->id() == OILS_COPY_STATUS_IN_TRANSIT) {
+        # Items in transit need to have the transit aborted before being marked.
+        if ($args->{handle_transit}) {
+            $evt = try_abort_transit($auth, $copy_id);
+        } else {
+            $evt = OpenILS::Event->new('ITEM_TO_MARK_IN_TRANSIT');
+        }
+    } elsif ($U->is_true($copy->status->restrict_copy_delete()) && $self->api_name =~ /discard/) {
+        # Items with restrict_copy_delete status require the
+        # COPY_DELETE_WARNING.override permission to be marked for
+        # discard.
+        if ($args->{handle_copy_delete_warning}) {
+            $evt = $e->event unless $e->allowed(['COPY_DELETE_WARNING.override'], $owning_lib);
+        } else {
+            $evt = OpenILS::Event->new('COPY_DELETE_WARNING');
+        }
+    }
+    return $evt if $evt;
 
-    my $holds = $e->search_action_hold_request(
-        { 
+    # Retrieving holds for later use.
+    my $holds = $e->search_action_hold_request([
+        {
             current_copy => $copy->id,
             fulfillment_time => undef,
             cancel_time => undef,
+        },
+        {flesh=>1, flesh_fields=>{ahr=>['eligible_copies']}}
+    ]);
+
+    # Throw event if attempting to  mark discard the only copy to fill a hold.
+    if ($self->api_name =~ /discard/) {
+        if (!$args->{handle_last_hold_copy}) {
+            for my $hold (@$holds) {
+                my $eligible = $hold->eligible_copies();
+                if (!defined($eligible) || scalar(@{$eligible}) < 2) {
+                    $evt = OpenILS::Event->new('ITEM_TO_MARK_LAST_HOLD_COPY');
+                    last;
+                }
+            }
         }
-    );
+    }
+    return $evt if $evt;
+
+    # Things below here require a transaction and there is nothing left to interfere with it.
+    $e->xact_begin;
+
+    # Handle extra mark damaged charges, etc.
+    if ($self->api_name =~ /damaged/) {
+        $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
+        return $evt if $evt;
+    }
+
+    # Mark the copy.
+    $copy->status($stat);
+    $copy->edit_date('now');
+    $copy->editor($e->requestor->id);
+
+    $e->update_asset_copy($copy) or return $e->die_event;
 
     $e->commit;
 
@@ -1190,6 +1429,44 @@ sub mark_item {
     return 1;
 }
 
+sub try_checkin {
+    my($auth, $copy_id) = @_;
+
+    my $checkin = $U->simplereq(
+        'open-ils.circ',
+        'open-ils.circ.checkin.override',
+        $auth, {
+            copy_id => $copy_id,
+            noop => 1
+        }
+    );
+    if(ref $checkin ne 'ARRAY') { $checkin = [$checkin]; }
+
+    my $evt_code = $checkin->[0]->{textcode};
+    $logger->info("try_checkin() received event: $evt_code");
+
+    if($evt_code eq 'SUCCESS' || $evt_code eq 'NO_CHANGE') {
+        $logger->info('try_checkin() successful checkin');
+        return undef;
+    } else {
+        $logger->warn('try_checkin() un-successful checkin');
+        return $checkin;
+    }
+}
+
+sub try_abort_transit {
+    my ($auth, $copy_id) = @_;
+
+    my $abort = $U->simplereq(
+        'open-ils.circ',
+        'open-ils.circ.transit.abort',
+        $auth, {copyid => $copy_id}
+    );
+    # Above returns 1 or an event.
+    return $abort if (ref $abort);
+    return undef;
+}
+
 sub handle_mark_damaged {
     my($e, $copy, $owning_lib, $args) = @_;
 
@@ -1257,7 +1534,7 @@ sub handle_mark_damaged {
         # the assumption is that you would not void the overdues unless you 
         # were also charging for the item and/or applying a processing fee
         if($void_overdue) {
-            my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
+            my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {note => 'System: OVERDUE REVERSED FOR DAMAGE CHARGE'});
             return $evt if $evt;
         }
 
@@ -1270,8 +1547,6 @@ sub handle_mark_damaged {
         my $evt2 = OpenILS::Utils::Penalty->calculate_penalties($e, $circ->usr->id, $e->requestor->ws_ou);
         return $evt2 if $evt2;
 
-        return undef;
-
     } else {
         return OpenILS::Event->new('DAMAGE_CHARGE', 
             payload => {
@@ -1379,7 +1654,7 @@ sub mark_item_missing_pieces {
 
                 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
                 my $due_date = DateTime->now(time_zone => 'local');
-                $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
+                $co_params->{'due_date'} = clean_ISO8601( $due_date->strftime('%FT%T%z') );
             } else {
                 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
             }
@@ -1721,10 +1996,10 @@ sub retrieve_circ_chain {
 
     } else {
 
-        my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
+        my $chain = $e->json_query({from => ['action.all_circ_chain', $circ_id]});
 
         for my $circ_info (@$chain) {
-            my $circ = Fieldmapper::action::circulation->new;
+            my $circ = Fieldmapper::action::all_circulation_slim->new;
             $circ->$_($circ_info->{$_}) for keys %$circ_info;
             $conn->respond($circ);
         }
@@ -1769,51 +2044,51 @@ sub retrieve_prev_circ_chain {
     return $e->event unless $e->checkauth;
     return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
 
-    if($self->api_name =~ /summary/) {
-        my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
-        my $target_copy = $$first_circ{'target_copy'};
-        my $usr = $$first_circ{'usr'};
-        my $last_circ_from_prev_chain = $e->json_query({
-            'select' => { 'circ' => ['id','usr'] },
-            'from' => 'circ', 
-            'where' => {
-                target_copy => $target_copy,
-                xact_start => { '<' => $$first_circ{'xact_start'} }
+    my $first_circ = 
+        $e->json_query({from => ['action.all_circ_chain', $circ_id]})->[0];
+
+    my $prev_circ = $e->search_action_all_circulation_slim([
+        {   target_copy => $first_circ->{target_copy},
+            xact_start => {'<' => $first_circ->{xact_start}}
+        }, {   
+            flesh => 1,
+            flesh_fields => {
+                aacs => [
+                    'active_circ',
+                    'aged_circ'
+                ]
             },
-            'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
-            'limit' => 1
-        })->[0];
-        return undef unless $last_circ_from_prev_chain;
-        return undef unless $$last_circ_from_prev_chain{'id'};
-        my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
-        return undef unless $sum;
-        my $obj = Fieldmapper::action::circ_chain_summary->new;
-        $obj->$_($sum->{$_}) for keys %$sum;
-        return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
+            order_by => { aacs => 'xact_start desc' },
+            limit => 1 
+        }
+    ])->[0];
 
-    } else {
+    return undef unless $prev_circ;
 
-        my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
-        my $target_copy = $$first_circ{'target_copy'};
-        my $last_circ_from_prev_chain = $e->json_query({
-            'select' => { 'circ' => ['id'] },
-            'from' => 'circ', 
-            'where' => {
-                target_copy => $target_copy,
-                xact_start => { '<' => $$first_circ{'xact_start'} }
-            },
-            'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
-            'limit' => 1
+    my $chain_usr = $prev_circ->usr; # note: may be undef
+
+    if ($self->api_name =~ /summary/) {
+        my $sum = $e->json_query({
+            from => [
+                'action.summarize_all_circ_chain', 
+                $prev_circ->id
+            ]
         })->[0];
-        return undef unless $last_circ_from_prev_chain;
-        return undef unless $$last_circ_from_prev_chain{'id'};
-        my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
 
-        for my $circ_info (@$chain) {
-            my $circ = Fieldmapper::action::circulation->new;
-            $circ->$_($circ_info->{$_}) for keys %$circ_info;
-            $conn->respond($circ);
-        }
+        my $summary = Fieldmapper::action::circ_chain_summary->new;
+        $summary->$_($sum->{$_}) for keys %$sum;
+
+        return {summary => $summary, usr => $chain_usr};
+    }
+
+
+    my $chain = $e->json_query(
+        {from => ['action.all_circ_chain', $prev_circ->id]});
+
+    for my $circ_info (@$chain) {
+        my $circ = Fieldmapper::action::all_circulation_slim->new;
+        $circ->$_($circ_info->{$_}) for keys %$circ_info;
+        $conn->respond($circ);
     }
 
     return undef;