1 package OpenILS::Application::Circ;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 use OpenILS::Application::Circ::Circulate;
7 use OpenILS::Application::Circ::Survey;
8 use OpenILS::Application::Circ::StatCat;
9 use OpenILS::Application::Circ::Holds;
10 use OpenILS::Application::Circ::HoldNotify;
11 use OpenILS::Application::Circ::CreditCard;
12 use OpenILS::Application::Circ::Money;
13 use OpenILS::Application::Circ::NonCat;
14 use OpenILS::Application::Circ::CopyLocations;
15 use OpenILS::Application::Circ::CircCommon;
18 use DateTime::Format::ISO8601;
20 use OpenILS::Application::AppUtils;
22 use OpenSRF::Utils qw/:datetime/;
23 use OpenSRF::AppSession;
24 use OpenILS::Utils::ModsParser;
26 use OpenSRF::EX qw(:try);
27 use OpenSRF::Utils::Logger qw(:logger);
28 use OpenILS::Utils::Fieldmapper;
29 use OpenILS::Utils::Editor;
30 use OpenILS::Utils::CStoreEditor q/:funcs/;
31 use OpenILS::Const qw/:const/;
32 use OpenSRF::Utils::SettingsClient;
33 use OpenILS::Application::Cat::AssetCommon;
35 my $apputils = "OpenILS::Application::AppUtils";
38 my $holdcode = "OpenILS::Application::Circ::Holds";
40 # ------------------------------------------------------------------------
41 # Top level Circ package;
42 # ------------------------------------------------------------------------
46 OpenILS::Application::Circ::Circulate->initialize();
50 __PACKAGE__->register_method(
51 method => 'retrieve_circ',
52 api_name => 'open-ils.circ.retrieve',
54 Retrieve a circ object by id
55 @param authtoken Login session key
56 @pararm circid The id of the circ object
60 my( $s, $c, $a, $i ) = @_;
61 my $e = new_editor(authtoken => $a);
62 return $e->event unless $e->checkauth;
63 my $circ = $e->retrieve_action_circulation($i) or return $e->event;
64 if( $e->requestor->id ne $circ->usr ) {
65 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
71 __PACKAGE__->register_method(
72 method => 'fetch_circ_mods',
73 api_name => 'open-ils.circ.circ_modifier.retrieve.all');
75 my($self, $conn, $args) = @_;
76 my $mods = new_editor()->retrieve_all_config_circ_modifier;
77 return [ map {$_->code} @$mods ] unless $$args{full};
81 __PACKAGE__->register_method(
82 method => 'fetch_bill_types',
83 api_name => 'open-ils.circ.billing_type.retrieve.all');
84 sub fetch_bill_types {
85 my $conf = OpenSRF::Utils::SettingsClient->new;
86 return $conf->config_value(
87 'apps', 'open-ils.circ', 'app_settings', 'billing_types', 'type' );
91 __PACKAGE__->register_method(
92 method => 'ranged_billing_types',
93 api_name => 'open-ils.circ.billing_type.ranged.retrieve.all');
95 sub ranged_billing_types {
96 my($self, $conn, $auth, $org_id, $depth) = @_;
97 my $e = new_editor(authtoken => $auth);
98 return $e->event unless $e->checkauth;
99 return $e->event unless $e->allowed('VIEW_BILLING_TYPE', $org_id);
100 return $e->search_config_billing_type(
101 {owner => $U->get_org_full_path($org_id, $depth)});
106 # ------------------------------------------------------------------------
107 # Returns an array of {circ, record} hashes checked out by the user.
108 # ------------------------------------------------------------------------
109 __PACKAGE__->register_method(
110 method => "checkouts_by_user",
111 api_name => "open-ils.circ.actor.user.checked_out",
113 NOTES => <<" NOTES");
114 Returns a list of open circulations as a pile of objects. Each object
115 contains the relevant copy, circ, and record
118 sub checkouts_by_user {
119 my($self, $client, $auth, $user_id) = @_;
121 my $e = new_editor(authtoken=>$auth);
122 return $e->event unless $e->checkauth;
124 my $circ_ids = $e->search_action_circulation(
126 checkin_time => undef,
128 {stop_fines => undef},
129 {stop_fines => ['MAXFINES','LONGOVERDUE']}
135 for my $id (@$circ_ids) {
136 my $circ = $e->retrieve_action_circulation([
140 circ => ['target_copy'],
141 acp => ['call_number'],
147 # un-flesh for consistency
148 my $c = $circ->target_copy;
149 $circ->target_copy($c->id);
151 my $cn = $c->call_number;
152 $c->call_number($cn->id);
160 record => $U->record_to_mvr($t)
170 __PACKAGE__->register_method(
171 method => "checkouts_by_user_slim",
172 api_name => "open-ils.circ.actor.user.checked_out.slim",
173 NOTES => <<" NOTES");
174 Returns a list of open circulation objects
178 sub checkouts_by_user_slim {
179 my( $self, $client, $user_session, $user_id ) = @_;
181 my( $requestor, $target, $copy, $record, $evt );
183 ( $requestor, $target, $evt ) =
184 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
187 $logger->debug( 'User ' . $requestor->id .
188 " retrieving checked out items for user " . $target->id );
190 # XXX Make the call correct..
191 return $apputils->simplereq(
193 "open-ils.cstore.direct.action.open_circulation.search.atomic",
194 { usr => $target->id, checkin_time => undef } );
195 # { usr => $target->id } );
199 __PACKAGE__->register_method(
200 method => "checkouts_by_user_opac",
201 api_name => "open-ils.circ.actor.user.checked_out.opac",);
204 sub checkouts_by_user_opac {
205 my( $self, $client, $auth, $user_id ) = @_;
207 my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
208 return $e->event unless $e->checkauth;
209 $user_id ||= $e->requestor->id;
210 return $e->event unless
211 my $patron = $e->retrieve_actor_user($user_id);
214 my $search = {usr => $user_id, stop_fines => undef};
216 if( $user_id ne $e->requestor->id ) {
217 $data = $e->search_action_circulation(
218 $search, {checkperm=>1, permorg=>$patron->home_ou})
222 $data = $e->search_action_circulation($search);
229 __PACKAGE__->register_method(
230 method => "title_from_transaction",
231 api_name => "open-ils.circ.circ_transaction.find_title",
232 NOTES => <<" NOTES");
233 Returns a mods object for the title that is linked to from the
234 copy from the hold that created the given transaction
237 sub title_from_transaction {
238 my( $self, $client, $login_session, $transactionid ) = @_;
240 my( $user, $circ, $title, $evt );
242 ( $user, $evt ) = $apputils->checkses( $login_session );
245 ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
248 ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
251 return $apputils->record_to_mvr($title);
256 __PACKAGE__->register_method(
257 method => "new_set_circ_lost",
258 api_name => "open-ils.circ.circulation.set_lost",
260 Sets the copy and related open circulation to lost
262 @param args : barcode
267 # ---------------------------------------------------------------------
268 # Sets a circulation to lost. updates copy status to lost
269 # applies copy and/or prcoessing fees depending on org settings
270 # ---------------------------------------------------------------------
271 sub new_set_circ_lost {
272 my( $self, $conn, $auth, $args ) = @_;
274 my $e = new_editor(authtoken=>$auth, xact=>1);
275 return $e->die_event unless $e->checkauth;
277 my $copy = $e->search_asset_copy({barcode=>$$args{barcode}, deleted=>'f'})->[0]
278 or return $e->die_event;
280 my $evt = OpenILS::Application::Cat::AssetCommon->set_item_lost($e, $copy->id);
288 __PACKAGE__->register_method(
289 method => "set_circ_claims_returned",
290 api_name => "open-ils.circ.circulation.set_claims_returned",
292 desc => q/Sets the circ for a given item as claims returned
293 If a backdate is provided, overdue fines will be voided
294 back to the backdate/,
296 {desc => 'Authentication token', type => 'string'},
297 {desc => 'Arguments, including "barcode" and optional "backdate"', type => 'object'}
299 return => {desc => q/1 on success, failure event on error, and
300 PATRON_EXCEEDS_CLAIMS_RETURN_COUNT if the patron exceeds the
301 configured claims return maximum/}
305 __PACKAGE__->register_method(
306 method => "set_circ_claims_returned",
307 api_name => "open-ils.circ.circulation.set_claims_returned.override",
309 desc => q/This adds support for overrideing the configured max
310 claims returned amount.
311 @see open-ils.circ.circulation.set_claims_returned./,
315 sub set_circ_claims_returned {
316 my( $self, $conn, $auth, $args ) = @_;
318 my $e = new_editor(authtoken=>$auth, xact=>1);
319 return $e->die_event unless $e->checkauth;
321 my $barcode = $$args{barcode};
322 my $backdate = $$args{backdate};
324 my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0]
325 or return $e->die_event;
327 my $circ = $e->search_action_circulation(
328 {checkin_time => undef, target_copy => $copy->id})->[0]
329 or return $e->die_event;
331 $backdate = $circ->due_date if $$args{use_due_date};
333 $logger->info("marking circ for item $barcode as claims returned".
334 (($backdate) ? " with backdate $backdate" : ''));
336 my $patron = $e->retrieve_actor_user($circ->usr);
337 my $max_count = $U->ou_ancestor_setting_value(
338 $circ->circ_lib, 'circ.max_patron_claim_return_count', $e);
340 # If the patron has too instances of many claims returned,
341 # require an override to continue. A configured max of
342 # 0 means all attempts require an override
343 if(defined $max_count and $patron->claims_returned_count >= $max_count) {
345 if($self->api_name =~ /override/) {
347 # see if we're allowed to override
348 return $e->die_event unless
349 $e->allowed('SET_CIRC_CLAIMS_RETURNED.override', $circ->circ_lib);
353 # exit early and return the max claims return event
355 return OpenILS::Event->new(
356 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT',
358 patron_count => $patron->claims_returned_count,
359 max_count => $max_count
365 $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib)
366 or return $e->die_event;
368 $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
369 $circ->stop_fines_time('now') unless $circ->stop_fines_time;
372 $backdate = cleanse_ISO8601($backdate);
374 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
375 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($backdate);
376 $backdate = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
378 # clean it up once again; need a : in the timezone offset. E.g. -06:00 not -0600
379 $backdate = cleanse_ISO8601($backdate);
381 # make it look like the circ stopped at the cliams returned time
382 $circ->stop_fines_time($backdate);
383 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
387 $e->update_action_circulation($circ) or return $e->die_event;
389 # see if there is a configured post-claims-return copy status
390 if(my $stat = $U->ou_ancestor_setting_value($circ->circ_lib, 'circ.claim_return.copy_status')) {
391 $copy->status($stat);
392 $copy->edit_date('now');
393 $copy->editor($e->requestor->id);
394 $e->update_asset_copy($copy) or return $e->die_event;
402 __PACKAGE__->register_method(
403 method => "post_checkin_backdate_circ",
404 api_name => "open-ils.circ.post_checkin_backdate",
406 desc => q/Back-date an already checked in circulation/,
408 {desc => 'Authentication token', type => 'string'},
409 {desc => 'Circ ID', type => 'number'},
410 {desc => 'ISO8601 backdate', type => 'string'},
412 return => {desc => q/1 on success, failure event on error/}
416 __PACKAGE__->register_method(
417 method => "post_checkin_backdate_circ",
418 api_name => "open-ils.circ.post_checkin_backdate.batch",
421 desc => q/@see open-ils.circ.post_checkin_backdate. Batch mode/,
423 {desc => 'Authentication token', type => 'string'},
424 {desc => 'List of Circ ID', type => 'array'},
425 {desc => 'ISO8601 backdate', type => 'string'},
427 return => {desc => q/Set of: 1 on success, failure event on error/}
432 sub post_checkin_backdate_circ {
433 my( $self, $conn, $auth, $circ_id, $backdate ) = @_;
434 my $e = new_editor(authtoken=>$auth);
435 return $e->die_event unless $e->checkauth;
436 if($self->api_name =~ /batch/) {
437 foreach my $c (@$circ_id) {
438 $conn->respond(post_checkin_backdate_circ_impl($e, $c, $backdate));
441 $conn->respond_complete(post_checkin_backdate_circ_impl($e, $circ_id, $backdate));
449 sub post_checkin_backdate_circ_impl {
450 my($e, $circ_id, $backdate) = @_;
454 my $circ = $e->retrieve_action_circulation($circ_id)
455 or return $e->die_event;
457 # anyone with checkin perms can backdate (more restrictive?)
458 return $e->die_event unless $e->allowed('COPY_CHECKIN', $circ->circ_lib);
460 # don't allow back-dating an open circulation
461 return OpenILS::Event->new('BAD_PARAMS') unless
462 $backdate and $circ->checkin_time;
464 # update the checkin and stop_fines times to reflect the new backdate
465 $circ->stop_fines_time(cleanse_ISO8601($backdate));
466 $circ->checkin_time(cleanse_ISO8601($backdate));
467 $e->update_action_circulation($circ) or return $e->die_event;
469 # now void the overdues "erased" by the back-dating
470 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
473 # If the circ was closed before and the balance owned !=0, re-open the transaction
474 $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
483 __PACKAGE__->register_method (
484 method => 'set_circ_due_date',
485 api_name => 'open-ils.circ.circulation.due_date.update',
487 Updates the due_date on the given circ
489 @param circid The id of the circ to update
490 @param date The timestamp of the new due date
494 sub set_circ_due_date {
495 my( $self, $conn, $auth, $circ_id, $date ) = @_;
497 my $e = new_editor(xact=>1, authtoken=>$auth);
498 return $e->die_event unless $e->checkauth;
499 my $circ = $e->retrieve_action_circulation($circ_id)
500 or return $e->die_event;
502 return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
503 $date = cleanse_ISO8601($date);
505 if (!(interval_to_seconds($circ->duration) % 86400)) { # duration is divisible by days
506 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
507 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($date);
508 $date = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
509 if ($date =~ /^(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d-\d\d)(\d\d)$/) {
510 $date = "$1:$2"; # put a colon in the timestamp component for DateTime::Format::ISO8601->parse_datetime
514 $circ->due_date($date);
515 $e->update_action_circulation($circ) or return $e->die_event;
522 __PACKAGE__->register_method(
523 method => "create_in_house_use",
524 api_name => 'open-ils.circ.in_house_use.create',
526 Creates an in-house use action.
527 @param $authtoken The login session key
528 @param params A hash of params including
529 'location' The org unit id where the in-house use occurs
530 'copyid' The copy in question
531 'count' The number of in-house uses to apply to this copy
532 @return An array of id's representing the id's of the newly created
533 in-house use objects or an event on an error
536 __PACKAGE__->register_method(
537 method => "create_in_house_use",
538 api_name => 'open-ils.circ.non_cat_in_house_use.create',
542 sub create_in_house_use {
543 my( $self, $client, $auth, $params ) = @_;
546 my $org = $params->{location};
547 my $copyid = $params->{copyid};
548 my $count = $params->{count} || 1;
549 my $nc_type = $params->{non_cat_type};
550 my $use_time = $params->{use_time} || 'now';
552 my $e = new_editor(xact=>1,authtoken=>$auth);
553 return $e->event unless $e->checkauth;
554 return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
556 my $non_cat = 1 if $self->api_name =~ /non_cat/;
560 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
562 $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
568 if( $use_time ne 'now' ) {
569 $use_time = cleanse_ISO8601($use_time);
570 $logger->debug("in_house_use setting use time to $use_time");
581 $ihu = Fieldmapper::action::non_cat_in_house_use->new;
582 $ihu->item_type($nc_type);
583 $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
584 $cmeth = "create_action_non_cat_in_house_use";
587 $ihu = Fieldmapper::action::in_house_use->new;
589 $method = 'open-ils.storage.direct.action.in_house_use.create';
590 $cmeth = "create_action_in_house_use";
593 $ihu->staff($e->requestor->id);
594 $ihu->org_unit($org);
595 $ihu->use_time($use_time);
597 $ihu = $e->$cmeth($ihu) or return $e->event;
598 push( @ids, $ihu->id );
609 __PACKAGE__->register_method(
610 method => "view_circs",
611 api_name => "open-ils.circ.copy_checkout_history.retrieve",
613 Retrieves the last X circs for a given copy
614 @param authtoken The login session key
615 @param copyid The copy to check
616 @param count How far to go back in the item history
617 @return An array of circ ids
620 # ----------------------------------------------------------------------
621 # Returns $count most recent circs. If count exceeds the configured
622 # max, use the configured max instead
623 # ----------------------------------------------------------------------
625 my( $self, $client, $authtoken, $copyid, $count ) = @_;
627 my $e = new_editor(authtoken => $authtoken);
628 return $e->event unless $e->checkauth;
630 my $copy = $e->retrieve_asset_copy([
633 flesh_fields => {acp => ['call_number']}
635 ]) or return $e->event;
637 return $e->event unless $e->allowed(
638 'VIEW_COPY_CHECKOUT_HISTORY',
639 ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ?
640 $copy->circ_lib : $copy->call_number->owning_lib);
642 my $max_history = $U->ou_ancestor_setting_value(
643 $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
645 if(defined $max_history) {
646 $count = $max_history unless defined $count and $count < $max_history;
648 $count = 4 unless defined $count;
651 return $e->search_action_circulation([
652 {target_copy => $copyid},
653 {limit => $count, order_by => { circ => "xact_start DESC" }}
658 __PACKAGE__->register_method(
659 method => "circ_count",
660 api_name => "open-ils.circ.circulation.count",
662 Returns the number of times the item has circulated
663 @param copyid The copy to check
667 my( $self, $client, $copyid, $range ) = @_;
668 my $e = OpenILS::Utils::Editor->new;
669 return $e->request('open-ils.storage.asset.copy.circ_count', $copyid, $range);
674 __PACKAGE__->register_method(
675 method => 'fetch_notes',
677 api_name => 'open-ils.circ.copy_note.retrieve.all',
679 Returns an array of copy note objects.
680 @param args A named hash of parameters including:
681 authtoken : Required if viewing non-public notes
682 itemid : The id of the item whose notes we want to retrieve
683 pub : True if all the caller wants are public notes
684 @return An array of note objects
687 __PACKAGE__->register_method(
688 method => 'fetch_notes',
689 api_name => 'open-ils.circ.call_number_note.retrieve.all',
690 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
692 __PACKAGE__->register_method(
693 method => 'fetch_notes',
694 api_name => 'open-ils.circ.title_note.retrieve.all',
695 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
698 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
700 my( $self, $connection, $args ) = @_;
702 my $id = $$args{itemid};
703 my $authtoken = $$args{authtoken};
706 if( $self->api_name =~ /copy/ ) {
708 return $U->cstorereq(
709 'open-ils.cstore.direct.asset.copy_note.search.atomic',
710 { owning_copy => $id, pub => 't' } );
712 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
714 return $U->cstorereq(
715 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
718 } elsif( $self->api_name =~ /call_number/ ) {
720 return $U->cstorereq(
721 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
722 { call_number => $id, pub => 't' } );
724 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
726 return $U->cstorereq(
727 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
730 } elsif( $self->api_name =~ /title/ ) {
732 return $U->cstorereq(
733 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
734 { record => $id, pub => 't' } );
736 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
738 return $U->cstorereq(
739 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
746 __PACKAGE__->register_method(
747 method => 'has_notes',
748 api_name => 'open-ils.circ.copy.has_notes');
749 __PACKAGE__->register_method(
750 method => 'has_notes',
751 api_name => 'open-ils.circ.call_number.has_notes');
752 __PACKAGE__->register_method(
753 method => 'has_notes',
754 api_name => 'open-ils.circ.title.has_notes');
758 my( $self, $conn, $authtoken, $id ) = @_;
759 my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
760 return $editor->event unless $editor->checkauth;
762 my $n = $editor->search_asset_copy_note(
763 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
765 $n = $editor->search_asset_call_number_note(
766 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
768 $n = $editor->search_biblio_record_note(
769 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
776 __PACKAGE__->register_method(
777 method => 'create_copy_note',
778 api_name => 'open-ils.circ.copy_note.create',
780 Creates a new copy note
781 @param authtoken The login session key
782 @param note The note object to create
783 @return The id of the new note object
786 sub create_copy_note {
787 my( $self, $connection, $authtoken, $note ) = @_;
789 my $e = new_editor(xact=>1, authtoken=>$authtoken);
790 return $e->event unless $e->checkauth;
791 my $copy = $e->retrieve_asset_copy(
795 flesh_fields => { 'acp' => ['call_number'] }
800 return $e->event unless
801 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
803 $note->create_date('now');
804 $note->creator($e->requestor->id);
805 $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
808 $e->create_asset_copy_note($note) or return $e->event;
814 __PACKAGE__->register_method(
815 method => 'delete_copy_note',
816 api_name => 'open-ils.circ.copy_note.delete',
818 Deletes an existing copy note
819 @param authtoken The login session key
820 @param noteid The id of the note to delete
821 @return 1 on success - Event otherwise.
823 sub delete_copy_note {
824 my( $self, $conn, $authtoken, $noteid ) = @_;
826 my $e = new_editor(xact=>1, authtoken=>$authtoken);
827 return $e->die_event unless $e->checkauth;
829 my $note = $e->retrieve_asset_copy_note([
833 'acpn' => [ 'owning_copy' ],
834 'acp' => [ 'call_number' ],
837 ]) or return $e->die_event;
839 if( $note->creator ne $e->requestor->id ) {
840 return $e->die_event unless
841 $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
844 $e->delete_asset_copy_note($note) or return $e->die_event;
850 __PACKAGE__->register_method(
851 method => 'age_hold_rules',
852 api_name => 'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
856 my( $self, $conn ) = @_;
857 return new_editor()->retrieve_all_config_rules_age_hold_protect();
862 __PACKAGE__->register_method(
863 method => 'copy_details_barcode',
865 api_name => 'open-ils.circ.copy_details.retrieve.barcode');
866 sub copy_details_barcode {
867 my( $self, $conn, $auth, $barcode ) = @_;
868 my $e = new_editor();
869 my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
870 return $e->event unless $cid;
871 return copy_details( $self, $conn, $auth, $cid );
875 __PACKAGE__->register_method(
876 method => 'copy_details',
877 api_name => 'open-ils.circ.copy_details.retrieve');
880 my( $self, $conn, $auth, $copy_id ) = @_;
881 my $e = new_editor(authtoken=>$auth);
882 return $e->event unless $e->checkauth;
884 my $flesh = { flesh => 1 };
886 my $copy = $e->retrieve_asset_copy(
892 acp => ['call_number'],
896 ]) or return $e->event;
899 # De-flesh the copy for backwards compatibility
901 my $vol = $copy->call_number;
903 $copy->call_number($vol->id);
904 my $record = $vol->record;
906 $vol->record($record->id);
907 $mvr = $U->record_to_mvr($record);
912 my $hold = $e->search_action_hold_request(
914 current_copy => $copy_id,
915 capture_time => { "!=" => undef },
916 fulfillment_time => undef,
917 cancel_time => undef,
921 OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
923 my $transit = $e->search_action_transit_copy(
924 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
926 # find the latest circ, open or closed
927 my $circ = $e->search_action_circulation(
929 { target_copy => $copy_id },
935 'checkin_workstation',
938 'recurring_fine_rule'
941 order_by => { circ => 'xact_start desc' },
961 __PACKAGE__->register_method(
962 method => 'mark_item',
963 api_name => 'open-ils.circ.mark_item_damaged',
965 Changes the status of a copy to "damaged". Requires MARK_ITEM_DAMAGED permission.
966 @param authtoken The login session key
967 @param copy_id The ID of the copy to mark as damaged
968 @return 1 on success - Event otherwise.
971 __PACKAGE__->register_method(
972 method => 'mark_item',
973 api_name => 'open-ils.circ.mark_item_missing',
975 Changes the status of a copy to "missing". Requires MARK_ITEM_MISSING permission.
976 @param authtoken The login session key
977 @param copy_id The ID of the copy to mark as missing
978 @return 1 on success - Event otherwise.
981 __PACKAGE__->register_method(
982 method => 'mark_item',
983 api_name => 'open-ils.circ.mark_item_bindery',
985 Changes the status of a copy to "bindery". Requires MARK_ITEM_BINDERY permission.
986 @param authtoken The login session key
987 @param copy_id The ID of the copy to mark as bindery
988 @return 1 on success - Event otherwise.
991 __PACKAGE__->register_method(
992 method => 'mark_item',
993 api_name => 'open-ils.circ.mark_item_on_order',
995 Changes the status of a copy to "on order". Requires MARK_ITEM_ON_ORDER permission.
996 @param authtoken The login session key
997 @param copy_id The ID of the copy to mark as on order
998 @return 1 on success - Event otherwise.
1001 __PACKAGE__->register_method(
1002 method => 'mark_item',
1003 api_name => 'open-ils.circ.mark_item_ill',
1005 Changes the status of a copy to "inter-library loan". Requires MARK_ITEM_ILL permission.
1006 @param authtoken The login session key
1007 @param copy_id The ID of the copy to mark as inter-library loan
1008 @return 1 on success - Event otherwise.
1011 __PACKAGE__->register_method(
1012 method => 'mark_item',
1013 api_name => 'open-ils.circ.mark_item_cataloging',
1015 Changes the status of a copy to "cataloging". Requires MARK_ITEM_CATALOGING permission.
1016 @param authtoken The login session key
1017 @param copy_id The ID of the copy to mark as cataloging
1018 @return 1 on success - Event otherwise.
1021 __PACKAGE__->register_method(
1022 method => 'mark_item',
1023 api_name => 'open-ils.circ.mark_item_reserves',
1025 Changes the status of a copy to "reserves". Requires MARK_ITEM_RESERVES permission.
1026 @param authtoken The login session key
1027 @param copy_id The ID of the copy to mark as reserves
1028 @return 1 on success - Event otherwise.
1031 __PACKAGE__->register_method(
1032 method => 'mark_item',
1033 api_name => 'open-ils.circ.mark_item_discard',
1035 Changes the status of a copy to "discard". Requires MARK_ITEM_DISCARD permission.
1036 @param authtoken The login session key
1037 @param copy_id The ID of the copy to mark as discard
1038 @return 1 on success - Event otherwise.
1043 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1044 my $e = new_editor(authtoken=>$auth, xact =>1);
1045 return $e->die_event unless $e->checkauth;
1048 my $copy = $e->retrieve_asset_copy([
1050 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1051 or return $e->die_event;
1054 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1055 $copy->circ_lib : $copy->call_number->owning_lib;
1057 return $e->die_event unless $e->allowed('UPDATE_COPY', $owning_lib);
1060 my $perm = 'MARK_ITEM_MISSING';
1061 my $stat = OILS_COPY_STATUS_MISSING;
1063 if( $self->api_name =~ /damaged/ ) {
1064 $perm = 'MARK_ITEM_DAMAGED';
1065 $stat = OILS_COPY_STATUS_DAMAGED;
1066 my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
1067 return $evt if $evt;
1069 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1070 $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
1072 } elsif ( $self->api_name =~ /bindery/ ) {
1073 $perm = 'MARK_ITEM_BINDERY';
1074 $stat = OILS_COPY_STATUS_BINDERY;
1075 } elsif ( $self->api_name =~ /on_order/ ) {
1076 $perm = 'MARK_ITEM_ON_ORDER';
1077 $stat = OILS_COPY_STATUS_ON_ORDER;
1078 } elsif ( $self->api_name =~ /ill/ ) {
1079 $perm = 'MARK_ITEM_ILL';
1080 $stat = OILS_COPY_STATUS_ILL;
1081 } elsif ( $self->api_name =~ /cataloging/ ) {
1082 $perm = 'MARK_ITEM_CATALOGING';
1083 $stat = OILS_COPY_STATUS_CATALOGING;
1084 } elsif ( $self->api_name =~ /reserves/ ) {
1085 $perm = 'MARK_ITEM_RESERVES';
1086 $stat = OILS_COPY_STATUS_RESERVES;
1087 } elsif ( $self->api_name =~ /discard/ ) {
1088 $perm = 'MARK_ITEM_DISCARD';
1089 $stat = OILS_COPY_STATUS_DISCARD;
1093 $copy->status($stat);
1094 $copy->edit_date('now');
1095 $copy->editor($e->requestor->id);
1097 $e->update_asset_copy($copy) or return $e->die_event;
1099 my $holds = $e->search_action_hold_request(
1101 current_copy => $copy->id,
1102 fulfillment_time => undef,
1103 cancel_time => undef,
1109 $logger->debug("resetting holds that target the marked copy");
1110 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1115 sub handle_mark_damaged {
1116 my($e, $copy, $owning_lib, $args) = @_;
1118 my $apply = $args->{apply_fines} || '';
1119 return undef if $apply eq 'noapply';
1121 my $new_amount = $args->{override_amount};
1122 my $new_btype = $args->{override_btype};
1123 my $new_note = $args->{override_note};
1125 # grab the last circulation
1126 my $circ = $e->search_action_circulation([
1127 { target_copy => $copy->id},
1129 order_by => {circ => "xact_start DESC"},
1131 flesh_fields => {circ => ['target_copy', 'usr'], au => ['card']}
1135 return undef unless $circ;
1137 my $charge_price = $U->ou_ancestor_setting_value(
1138 $owning_lib, 'circ.charge_on_damaged', $e);
1140 my $proc_fee = $U->ou_ancestor_setting_value(
1141 $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
1143 my $void_overdue = $U->ou_ancestor_setting_value(
1144 $owning_lib, 'circ.damaged.void_ovedue', $e) || 0;
1146 return undef unless $charge_price or $proc_fee;
1148 my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
1149 my $total = $copy_price + $proc_fee;
1153 if($new_amount and $new_btype) {
1155 # Allow staff to override the amount to charge for a damaged item
1156 # Consider the case where the item is only partially damaged
1157 # This value is meant to take the place of the item price and
1158 # optional processing fee.
1160 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1161 $e, $new_amount, $new_btype, 'Damaged Item Override', $circ->id, $new_note);
1162 return $evt if $evt;
1166 if($charge_price and $copy_price) {
1167 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1168 $e, $copy_price, 7, 'Damaged Item', $circ->id);
1169 return $evt if $evt;
1173 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1174 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
1175 return $evt if $evt;
1179 # the assumption is that you would not void the overdues unless you
1180 # were also charging for the item and/or applying a processing fee
1182 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
1183 return $evt if $evt;
1186 my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
1187 return $evt if $evt;
1189 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1190 $ses->request('open-ils.trigger.event.autocreate', 'checkout.damaged', $circ, $circ->circ_lib);
1195 return OpenILS::Event->new('DAMAGE_CHARGE',
1206 # ----------------------------------------------------------------------
1207 __PACKAGE__->register_method(
1208 method => 'mark_item_missing_pieces',
1209 api_name => 'open-ils.circ.mark_item_missing_pieces',
1211 Changes the status of a copy to "damaged" or to a custom status based on the
1212 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1214 @param authtoken The login session key
1215 @param copy_id The ID of the copy to mark as damaged
1216 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1220 sub mark_item_missing_pieces {
1221 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1222 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1223 my $e = new_editor(authtoken=>$auth, xact =>1);
1224 return $e->die_event unless $e->checkauth;
1227 my $copy = $e->retrieve_asset_copy([
1229 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1230 or return $e->die_event;
1233 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1234 $copy->circ_lib : $copy->call_number->owning_lib;
1236 return $e->die_event unless $e->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1238 #### grab the last circulation
1239 my $circ = $e->search_action_circulation([
1240 { target_copy => $copy->id},
1242 order_by => {circ => "xact_start DESC"}
1247 if (! $circ->checkin_time) { # if circ active, attempt renew
1248 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1249 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1250 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1251 $circ = $res->[0]->{payload}{'circ'};
1252 $circ->target_copy( $copy->id );
1253 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1255 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1260 'copy_id'=>$circ->target_copy,
1261 'patron_id'=>$circ->usr,
1262 'skip_deposit_fee'=>1,
1263 'skip_rental_fee'=>1
1266 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1268 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1269 $e, $copy, $e->requestor, 1 );
1271 if ($hold) { # needed for hold? then due now
1273 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1274 my $due_date = DateTime->now(time_zone => 'local');
1275 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1277 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1281 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params);
1282 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1283 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1284 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1285 $circ = $res->[0]->{payload}{'circ'};
1287 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1293 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1295 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1298 ### Update the item status
1300 my $custom_stat = $U->ou_ancestor_setting_value(
1301 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1302 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1304 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1305 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1307 $copy->status($stat);
1308 $copy->edit_date('now');
1309 $copy->editor($e->requestor->id);
1311 $e->update_asset_copy($copy) or return $e->die_event;
1313 my $holds = $e->search_action_hold_request(
1315 current_copy => $copy->id,
1316 fulfillment_time => undef,
1317 cancel_time => undef,
1321 $logger->debug("resetting holds that target the marked copy");
1322 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1326 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1327 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1329 return OpenILS::Event->new('SUCCESS',
1333 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1334 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1339 return $e->die_event;
1347 # ----------------------------------------------------------------------
1348 __PACKAGE__->register_method(
1349 method => 'magic_fetch',
1350 api_name => 'open-ils.agent.fetch'
1353 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1356 my( $self, $conn, $auth, $args ) = @_;
1357 my $e = new_editor( authtoken => $auth );
1358 return $e->event unless $e->checkauth;
1360 my $hint = $$args{hint};
1361 my $id = $$args{id};
1363 # Is the call allowed to fetch this type of object?
1364 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1366 # Find the class the implements the given hint
1367 my ($class) = grep {
1368 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1370 $class =~ s/Fieldmapper:://og;
1371 $class =~ s/::/_/og;
1372 my $method = "retrieve_$class";
1374 my $obj = $e->$method($id) or return $e->event;
1377 # ----------------------------------------------------------------------
1380 __PACKAGE__->register_method(
1381 method => "fleshed_circ_retrieve",
1383 api_name => "open-ils.circ.fleshed.retrieve",);
1385 sub fleshed_circ_retrieve {
1386 my( $self, $client, $id ) = @_;
1387 my $e = new_editor();
1388 my $circ = $e->retrieve_action_circulation(
1394 circ => [ qw/ target_copy / ],
1395 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number / ],
1396 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1397 acn => [ qw/ record / ],
1401 ) or return $e->event;
1403 my $copy = $circ->target_copy;
1404 my $vol = $copy->call_number;
1405 my $rec = $circ->target_copy->call_number->record;
1407 $vol->record($rec->id);
1408 $copy->call_number($vol->id);
1409 $circ->target_copy($copy->id);
1413 if( $rec->id == OILS_PRECAT_RECORD ) {
1417 $mvr = $U->record_to_mvr($rec);
1418 $rec->marc(''); # drop the bulky marc data
1432 __PACKAGE__->register_method(
1433 method => "test_batch_circ_events",
1434 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1437 # method for testing the behavior of a given event definition
1438 sub test_batch_circ_events {
1439 my($self, $conn, $auth, $event_def, $barcode) = @_;
1441 my $e = new_editor(authtoken => $auth);
1442 return $e->event unless $e->checkauth;
1443 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1445 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1446 or return $e->event;
1448 my $circ = $e->search_action_circulation(
1449 {target_copy => $copy->id, checkin_time => undef})->[0]
1450 or return $e->event;
1452 return undef unless $circ;
1454 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1458 __PACKAGE__->register_method(
1459 method => "fire_circ_events",
1460 api_name => "open-ils.circ.fire_circ_trigger_events",
1462 General event def runner for circ objects. If no event def ID
1463 is provided, the hook will be used to find the best event_def
1464 match based on the context org unit
1468 __PACKAGE__->register_method(
1469 method => "fire_circ_events",
1470 api_name => "open-ils.circ.fire_hold_trigger_events",
1472 General event def runner for hold objects. If no event def ID
1473 is provided, the hook will be used to find the best event_def
1474 match based on the context org unit
1478 __PACKAGE__->register_method(
1479 method => "fire_circ_events",
1480 api_name => "open-ils.circ.fire_user_trigger_events",
1482 General event def runner for user objects. If no event def ID
1483 is provided, the hook will be used to find the best event_def
1484 match based on the context org unit
1489 sub fire_circ_events {
1490 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1492 my $e = new_editor(authtoken => $auth);
1493 return $e->event unless $e->checkauth;
1497 if($self->api_name =~ /hold/) {
1498 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1499 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1500 } elsif($self->api_name =~ /user/) {
1501 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1502 $targets = $e->batch_retrieve_actor_user($target_ids);
1504 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1505 $targets = $e->batch_retrieve_action_circulation($target_ids);
1508 return undef unless @$targets;
1509 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1512 __PACKAGE__->register_method(
1513 method => "user_payments_list",
1514 api_name => "open-ils.circ.user_payments.filtered.batch",
1517 desc => q/Returns a fleshed, date-limited set of all payments a user
1518 has made. By default, ordered by payment date. Optionally
1519 ordered by other columns in the top-level "mp" object/,
1521 {desc => 'Authentication token', type => 'string'},
1522 {desc => 'User ID', type => 'number'},
1523 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1525 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1526 and the related fully-realized payment object (e.g money.cash_payment)/}
1530 sub user_payments_list {
1531 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1533 my $e = new_editor(authtoken => $auth);
1534 return $e->event unless $e->checkauth;
1536 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1537 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1539 $order_by ||= ['payment_ts'];
1541 # all payments by user, between start_date and end_date
1542 my $payments = $e->json_query({
1543 select => {mp => ['id']},
1547 fkey => 'xact', field => 'id'}
1551 '+mbt' => {usr => $user_id},
1552 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1554 order_by => {mp => $order_by}
1557 for my $payment_id (@$payments) {
1558 my $payment = $e->retrieve_money_payment([
1566 'credit_card_payment',
1581 $conn->respond($payment);
1588 __PACKAGE__->register_method(
1589 method => "retrieve_circ_chain",
1590 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1593 desc => q/Given a circulation, this returns all circulation objects
1594 that are part of the same chain of renewals./,
1596 {desc => 'Authentication token', type => 'string'},
1597 {desc => 'Circ ID', type => 'number'},
1599 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1603 __PACKAGE__->register_method(
1604 method => "retrieve_circ_chain",
1605 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1607 desc => q/Given a circulation, this returns a summary of the circulation objects
1608 that are part of the same chain of renewals./,
1610 {desc => 'Authentication token', type => 'string'},
1611 {desc => 'Circ ID', type => 'number'},
1613 return => {desc => q/Circulation Chain Summary/}
1617 sub retrieve_circ_chain {
1618 my($self, $conn, $auth, $circ_id) = @_;
1620 my $e = new_editor(authtoken => $auth);
1621 return $e->event unless $e->checkauth;
1622 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1624 if($self->api_name =~ /summary/) {
1625 return $U->create_circ_chain_summary($e, $circ_id);
1629 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1631 for my $circ_info (@$chain) {
1632 my $circ = Fieldmapper::action::circulation->new;
1633 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1634 $conn->respond($circ);
1641 __PACKAGE__->register_method(
1642 method => "retrieve_prev_circ_chain",
1643 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1646 desc => q/Given a circulation, this returns all circulation objects
1647 that are part of the previous chain of renewals./,
1649 {desc => 'Authentication token', type => 'string'},
1650 {desc => 'Circ ID', type => 'number'},
1652 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1656 __PACKAGE__->register_method(
1657 method => "retrieve_prev_circ_chain",
1658 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1660 desc => q/Given a circulation, this returns a summary of the circulation objects
1661 that are part of the previous chain of renewals./,
1663 {desc => 'Authentication token', type => 'string'},
1664 {desc => 'Circ ID', type => 'number'},
1666 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1670 sub retrieve_prev_circ_chain {
1671 my($self, $conn, $auth, $circ_id) = @_;
1673 my $e = new_editor(authtoken => $auth);
1674 return $e->event unless $e->checkauth;
1675 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1677 if($self->api_name =~ /summary/) {
1678 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1679 my $target_copy = $$first_circ{'target_copy'};
1680 my $usr = $$first_circ{'usr'};
1681 my $last_circ_from_prev_chain = $e->json_query({
1682 'select' => { 'circ' => ['id','usr'] },
1685 target_copy => $target_copy,
1686 xact_start => { '<' => $$first_circ{'xact_start'} }
1688 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1691 return undef unless $last_circ_from_prev_chain;
1692 return undef unless $$last_circ_from_prev_chain{'id'};
1693 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1694 return undef unless $sum;
1695 my $obj = Fieldmapper::action::circ_chain_summary->new;
1696 $obj->$_($sum->{$_}) for keys %$sum;
1697 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1701 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1702 my $target_copy = $$first_circ{'target_copy'};
1703 my $last_circ_from_prev_chain = $e->json_query({
1704 'select' => { 'circ' => ['id'] },
1707 target_copy => $target_copy,
1708 xact_start => { '<' => $$first_circ{'xact_start'} }
1710 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1713 return undef unless $last_circ_from_prev_chain;
1714 return undef unless $$last_circ_from_prev_chain{'id'};
1715 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1717 for my $circ_info (@$chain) {
1718 my $circ = Fieldmapper::action::circulation->new;
1719 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1720 $conn->respond($circ);
1728 __PACKAGE__->register_method(
1729 method => "get_copy_due_date",
1730 api_name => "open-ils.circ.copy.due_date.retrieve",
1733 Given a copy ID, returns the due date for the copy if it's
1734 currently circulating. Otherwise, returns null. Note, this is a public
1735 method requiring no authentication. Only the due date is exposed.
1738 {desc => 'Copy ID', type => 'number'}
1740 return => {desc => q/
1741 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1746 sub get_copy_due_date {
1747 my($self, $conn, $copy_id) = @_;
1748 my $e = new_editor();
1750 my $circ = $e->json_query({
1751 select => {circ => ['due_date']},
1754 target_copy => $copy_id,
1755 checkin_time => undef,
1757 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1758 {stop_fines => undef}
1762 })->[0] or return undef;
1764 return $circ->{due_date};
1771 # {"select":{"acp":["id"],"circ":[{"aggregate":true,"transform":"count","alias":"count","column":"id"}]},"from":{"acp":{"circ":{"field":"target_copy","fkey":"id","type":"left"},"acn"{"field":"id","fkey":"call_number"}}},"where":{"+acn":{"record":200057}}