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',
53 api_name => 'open-ils.circ.retrieve',
55 Retrieve a circ object by id
56 @param authtoken Login session key
57 @pararm circid The id of the circ object
61 my( $s, $c, $a, $i ) = @_;
62 my $e = new_editor(authtoken => $a);
63 return $e->event unless $e->checkauth;
64 my $circ = $e->retrieve_action_circulation($i) or return $e->event;
65 if( $e->requestor->id ne $circ->usr ) {
66 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
72 __PACKAGE__->register_method(
73 method => 'fetch_circ_mods',
74 api_name => 'open-ils.circ.circ_modifier.retrieve.all');
76 my($self, $conn, $args) = @_;
77 my $mods = new_editor()->retrieve_all_config_circ_modifier;
78 return [ map {$_->code} @$mods ] unless $$args{full};
82 __PACKAGE__->register_method(
83 method => 'fetch_bill_types',
84 api_name => 'open-ils.circ.billing_type.retrieve.all');
85 sub fetch_bill_types {
86 my $conf = OpenSRF::Utils::SettingsClient->new;
87 return $conf->config_value(
88 'apps', 'open-ils.circ', 'app_settings', 'billing_types', 'type' );
92 __PACKAGE__->register_method(
93 method => 'ranged_billing_types',
94 api_name => 'open-ils.circ.billing_type.ranged.retrieve.all');
96 sub ranged_billing_types {
97 my($self, $conn, $auth, $org_id, $depth) = @_;
98 my $e = new_editor(authtoken => $auth);
99 return $e->event unless $e->checkauth;
100 return $e->event unless $e->allowed('VIEW_BILLING_TYPE', $org_id);
101 return $e->search_config_billing_type(
102 {owner => $U->get_org_full_path($org_id, $depth)});
107 # ------------------------------------------------------------------------
108 # Returns an array of {circ, record} hashes checked out by the user.
109 # ------------------------------------------------------------------------
110 __PACKAGE__->register_method(
111 method => "checkouts_by_user",
112 api_name => "open-ils.circ.actor.user.checked_out",
114 NOTES => <<" NOTES");
115 Returns a list of open circulations as a pile of objects. Each object
116 contains the relevant copy, circ, and record
119 sub checkouts_by_user {
120 my($self, $client, $auth, $user_id) = @_;
122 my $e = new_editor(authtoken=>$auth);
123 return $e->event unless $e->checkauth;
125 my $circ_ids = $e->search_action_circulation(
127 checkin_time => undef,
129 {stop_fines => undef},
130 {stop_fines => ['MAXFINES','LONGOVERDUE']}
136 for my $id (@$circ_ids) {
137 my $circ = $e->retrieve_action_circulation([
141 circ => ['target_copy'],
142 acp => ['call_number'],
148 # un-flesh for consistency
149 my $c = $circ->target_copy;
150 $circ->target_copy($c->id);
152 my $cn = $c->call_number;
153 $c->call_number($cn->id);
161 record => $U->record_to_mvr($t)
171 __PACKAGE__->register_method(
172 method => "checkouts_by_user_slim",
173 api_name => "open-ils.circ.actor.user.checked_out.slim",
174 NOTES => <<" NOTES");
175 Returns a list of open circulation objects
179 sub checkouts_by_user_slim {
180 my( $self, $client, $user_session, $user_id ) = @_;
182 my( $requestor, $target, $copy, $record, $evt );
184 ( $requestor, $target, $evt ) =
185 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
188 $logger->debug( 'User ' . $requestor->id .
189 " retrieving checked out items for user " . $target->id );
191 # XXX Make the call correct..
192 return $apputils->simplereq(
194 "open-ils.cstore.direct.action.open_circulation.search.atomic",
195 { usr => $target->id, checkin_time => undef } );
196 # { usr => $target->id } );
200 __PACKAGE__->register_method(
201 method => "checkouts_by_user_opac",
202 api_name => "open-ils.circ.actor.user.checked_out.opac",);
205 sub checkouts_by_user_opac {
206 my( $self, $client, $auth, $user_id ) = @_;
208 my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
209 return $e->event unless $e->checkauth;
210 $user_id ||= $e->requestor->id;
211 return $e->event unless
212 my $patron = $e->retrieve_actor_user($user_id);
215 my $search = {usr => $user_id, stop_fines => undef};
217 if( $user_id ne $e->requestor->id ) {
218 $data = $e->search_action_circulation(
219 $search, {checkperm=>1, permorg=>$patron->home_ou})
223 $data = $e->search_action_circulation($search);
230 __PACKAGE__->register_method(
231 method => "title_from_transaction",
232 api_name => "open-ils.circ.circ_transaction.find_title",
233 NOTES => <<" NOTES");
234 Returns a mods object for the title that is linked to from the
235 copy from the hold that created the given transaction
238 sub title_from_transaction {
239 my( $self, $client, $login_session, $transactionid ) = @_;
241 my( $user, $circ, $title, $evt );
243 ( $user, $evt ) = $apputils->checkses( $login_session );
246 ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
249 ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
252 return $apputils->record_to_mvr($title);
257 __PACKAGE__->register_method(
258 method => "new_set_circ_lost",
259 api_name => "open-ils.circ.circulation.set_lost",
261 Sets the copy and related open circulation to lost
263 @param args : barcode
268 # ---------------------------------------------------------------------
269 # Sets a circulation to lost. updates copy status to lost
270 # applies copy and/or prcoessing fees depending on org settings
271 # ---------------------------------------------------------------------
272 sub new_set_circ_lost {
273 my( $self, $conn, $auth, $args ) = @_;
275 my $e = new_editor(authtoken=>$auth, xact=>1);
276 return $e->die_event unless $e->checkauth;
278 my $copy = $e->search_asset_copy({barcode=>$$args{barcode}, deleted=>'f'})->[0]
279 or return $e->die_event;
281 my $evt = OpenILS::Application::Cat::AssetCommon->set_item_lost($e, $copy->id);
289 __PACKAGE__->register_method(
290 method => "set_circ_claims_returned",
291 api_name => "open-ils.circ.circulation.set_claims_returned",
293 desc => q/Sets the circ for a given item as claims returned
294 If a backdate is provided, overdue fines will be voided
295 back to the backdate/,
297 {desc => 'Authentication token', type => 'string'},
298 {desc => 'Arguments, including "barcode" and optional "backdate"', type => 'object'}
300 return => {desc => q/1 on success, failure event on error, and
301 PATRON_EXCEEDS_CLAIMS_RETURN_COUNT if the patron exceeds the
302 configured claims return maximum/}
306 __PACKAGE__->register_method(
307 method => "set_circ_claims_returned",
308 api_name => "open-ils.circ.circulation.set_claims_returned.override",
310 desc => q/This adds support for overrideing the configured max
311 claims returned amount.
312 @see open-ils.circ.circulation.set_claims_returned./,
316 sub set_circ_claims_returned {
317 my( $self, $conn, $auth, $args ) = @_;
319 my $e = new_editor(authtoken=>$auth, xact=>1);
320 return $e->die_event unless $e->checkauth;
322 my $barcode = $$args{barcode};
323 my $backdate = $$args{backdate};
325 my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0]
326 or return $e->die_event;
328 my $circ = $e->search_action_circulation(
329 {checkin_time => undef, target_copy => $copy->id})->[0]
330 or return $e->die_event;
332 $backdate = $circ->due_date if $$args{use_due_date};
334 $logger->info("marking circ for item $barcode as claims returned".
335 (($backdate) ? " with backdate $backdate" : ''));
337 my $patron = $e->retrieve_actor_user($circ->usr);
338 my $max_count = $U->ou_ancestor_setting_value(
339 $circ->circ_lib, 'circ.max_patron_claim_return_count', $e);
341 # If the patron has too instances of many claims returned,
342 # require an override to continue. A configured max of
343 # 0 means all attempts require an override
344 if(defined $max_count and $patron->claims_returned_count >= $max_count) {
346 if($self->api_name =~ /override/) {
348 # see if we're allowed to override
349 return $e->die_event unless
350 $e->allowed('SET_CIRC_CLAIMS_RETURNED.override', $circ->circ_lib);
354 # exit early and return the max claims return event
356 return OpenILS::Event->new(
357 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT',
359 patron_count => $patron->claims_returned_count,
360 max_count => $max_count
366 $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib)
367 or return $e->die_event;
369 $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
370 $circ->stop_fines_time('now') unless $circ->stop_fines_time;
373 $backdate = cleanse_ISO8601($backdate);
375 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
376 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($backdate);
377 $backdate = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
379 # clean it up once again; need a : in the timezone offset. E.g. -06:00 not -0600
380 $backdate = cleanse_ISO8601($backdate);
382 # make it look like the circ stopped at the cliams returned time
383 $circ->stop_fines_time($backdate);
384 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
388 $e->update_action_circulation($circ) or return $e->die_event;
390 # see if there is a configured post-claims-return copy status
391 if(my $stat = $U->ou_ancestor_setting_value($circ->circ_lib, 'circ.claim_return.copy_status')) {
392 $copy->status($stat);
393 $copy->edit_date('now');
394 $copy->editor($e->requestor->id);
395 $e->update_asset_copy($copy) or return $e->die_event;
403 __PACKAGE__->register_method(
404 method => "post_checkin_backdate_circ",
405 api_name => "open-ils.circ.post_checkin_backdate",
407 desc => q/Back-date an already checked in circulation/,
409 {desc => 'Authentication token', type => 'string'},
410 {desc => 'Circ ID', type => 'number'},
411 {desc => 'ISO8601 backdate', type => 'string'},
413 return => {desc => q/1 on success, failure event on error/}
417 __PACKAGE__->register_method(
418 method => "post_checkin_backdate_circ",
419 api_name => "open-ils.circ.post_checkin_backdate.batch",
422 desc => q/@see open-ils.circ.post_checkin_backdate. Batch mode/,
424 {desc => 'Authentication token', type => 'string'},
425 {desc => 'List of Circ ID', type => 'array'},
426 {desc => 'ISO8601 backdate', type => 'string'},
428 return => {desc => q/Set of: 1 on success, failure event on error/}
433 sub post_checkin_backdate_circ {
434 my( $self, $conn, $auth, $circ_id, $backdate ) = @_;
435 my $e = new_editor(authtoken=>$auth);
436 return $e->die_event unless $e->checkauth;
437 if($self->api_name =~ /batch/) {
438 foreach my $c (@$circ_id) {
439 $conn->respond(post_checkin_backdate_circ_impl($e, $c, $backdate));
442 $conn->respond_complete(post_checkin_backdate_circ_impl($e, $circ_id, $backdate));
450 sub post_checkin_backdate_circ_impl {
451 my($e, $circ_id, $backdate) = @_;
455 my $circ = $e->retrieve_action_circulation($circ_id)
456 or return $e->die_event;
458 # anyone with checkin perms can backdate (more restrictive?)
459 return $e->die_event unless $e->allowed('COPY_CHECKIN', $circ->circ_lib);
461 # don't allow back-dating an open circulation
462 return OpenILS::Event->new('BAD_PARAMS') unless
463 $backdate and $circ->checkin_time;
465 # update the checkin and stop_fines times to reflect the new backdate
466 $circ->stop_fines_time(cleanse_ISO8601($backdate));
467 $circ->checkin_time(cleanse_ISO8601($backdate));
468 $e->update_action_circulation($circ) or return $e->die_event;
470 # now void the overdues "erased" by the back-dating
471 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
474 # If the circ was closed before and the balance owned !=0, re-open the transaction
475 $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
484 __PACKAGE__->register_method (
485 method => 'set_circ_due_date',
486 api_name => 'open-ils.circ.circulation.due_date.update',
488 Updates the due_date on the given circ
490 @param circid The id of the circ to update
491 @param date The timestamp of the new due date
495 sub set_circ_due_date {
496 my( $self, $conn, $auth, $circ_id, $date ) = @_;
498 my $e = new_editor(xact=>1, authtoken=>$auth);
499 return $e->die_event unless $e->checkauth;
500 my $circ = $e->retrieve_action_circulation($circ_id)
501 or return $e->die_event;
503 return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
504 $date = cleanse_ISO8601($date);
506 if (!(interval_to_seconds($circ->duration) % 86400)) { # duration is divisible by days
507 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
508 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($date);
509 $date = cleanse_ISO8601( $new_date->ymd . 'T' . $original_date->strftime('%T%z') );
512 $circ->due_date($date);
513 $e->update_action_circulation($circ) or return $e->die_event;
520 __PACKAGE__->register_method(
521 method => "create_in_house_use",
522 api_name => 'open-ils.circ.in_house_use.create',
524 Creates an in-house use action.
525 @param $authtoken The login session key
526 @param params A hash of params including
527 'location' The org unit id where the in-house use occurs
528 'copyid' The copy in question
529 'count' The number of in-house uses to apply to this copy
530 @return An array of id's representing the id's of the newly created
531 in-house use objects or an event on an error
534 __PACKAGE__->register_method(
535 method => "create_in_house_use",
536 api_name => 'open-ils.circ.non_cat_in_house_use.create',
540 sub create_in_house_use {
541 my( $self, $client, $auth, $params ) = @_;
544 my $org = $params->{location};
545 my $copyid = $params->{copyid};
546 my $count = $params->{count} || 1;
547 my $nc_type = $params->{non_cat_type};
548 my $use_time = $params->{use_time} || 'now';
550 my $e = new_editor(xact=>1,authtoken=>$auth);
551 return $e->event unless $e->checkauth;
552 return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
554 my $non_cat = 1 if $self->api_name =~ /non_cat/;
558 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
560 $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
566 if( $use_time ne 'now' ) {
567 $use_time = cleanse_ISO8601($use_time);
568 $logger->debug("in_house_use setting use time to $use_time");
579 $ihu = Fieldmapper::action::non_cat_in_house_use->new;
580 $ihu->item_type($nc_type);
581 $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
582 $cmeth = "create_action_non_cat_in_house_use";
585 $ihu = Fieldmapper::action::in_house_use->new;
587 $method = 'open-ils.storage.direct.action.in_house_use.create';
588 $cmeth = "create_action_in_house_use";
591 $ihu->staff($e->requestor->id);
592 $ihu->org_unit($org);
593 $ihu->use_time($use_time);
595 $ihu = $e->$cmeth($ihu) or return $e->event;
596 push( @ids, $ihu->id );
607 __PACKAGE__->register_method(
608 method => "view_circs",
609 api_name => "open-ils.circ.copy_checkout_history.retrieve",
611 Retrieves the last X circs for a given copy
612 @param authtoken The login session key
613 @param copyid The copy to check
614 @param count How far to go back in the item history
615 @return An array of circ ids
618 # ----------------------------------------------------------------------
619 # Returns $count most recent circs. If count exceeds the configured
620 # max, use the configured max instead
621 # ----------------------------------------------------------------------
623 my( $self, $client, $authtoken, $copyid, $count ) = @_;
625 my $e = new_editor(authtoken => $authtoken);
626 return $e->event unless $e->checkauth;
628 my $copy = $e->retrieve_asset_copy([
631 flesh_fields => {acp => ['call_number']}
633 ]) or return $e->event;
635 return $e->event unless $e->allowed(
636 'VIEW_COPY_CHECKOUT_HISTORY',
637 ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ?
638 $copy->circ_lib : $copy->call_number->owning_lib);
640 my $max_history = $U->ou_ancestor_setting_value(
641 $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
643 if(defined $max_history) {
644 $count = $max_history unless defined $count and $count < $max_history;
646 $count = 4 unless defined $count;
649 return $e->search_action_circulation([
650 {target_copy => $copyid},
651 {limit => $count, order_by => { circ => "xact_start DESC" }}
656 __PACKAGE__->register_method(
657 method => "circ_count",
658 api_name => "open-ils.circ.circulation.count",
660 Returns the number of times the item has circulated
661 @param copyid The copy to check
665 my( $self, $client, $copyid, $range ) = @_;
666 my $e = OpenILS::Utils::Editor->new;
667 return $e->request('open-ils.storage.asset.copy.circ_count', $copyid, $range);
672 __PACKAGE__->register_method(
673 method => 'fetch_notes',
675 api_name => 'open-ils.circ.copy_note.retrieve.all',
677 Returns an array of copy note objects.
678 @param args A named hash of parameters including:
679 authtoken : Required if viewing non-public notes
680 itemid : The id of the item whose notes we want to retrieve
681 pub : True if all the caller wants are public notes
682 @return An array of note objects
685 __PACKAGE__->register_method(
686 method => 'fetch_notes',
687 api_name => 'open-ils.circ.call_number_note.retrieve.all',
688 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
690 __PACKAGE__->register_method(
691 method => 'fetch_notes',
692 api_name => 'open-ils.circ.title_note.retrieve.all',
693 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
696 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
698 my( $self, $connection, $args ) = @_;
700 my $id = $$args{itemid};
701 my $authtoken = $$args{authtoken};
704 if( $self->api_name =~ /copy/ ) {
706 return $U->cstorereq(
707 'open-ils.cstore.direct.asset.copy_note.search.atomic',
708 { owning_copy => $id, pub => 't' } );
710 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
712 return $U->cstorereq(
713 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
716 } elsif( $self->api_name =~ /call_number/ ) {
718 return $U->cstorereq(
719 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
720 { call_number => $id, pub => 't' } );
722 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
724 return $U->cstorereq(
725 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
728 } elsif( $self->api_name =~ /title/ ) {
730 return $U->cstorereq(
731 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
732 { record => $id, pub => 't' } );
734 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
736 return $U->cstorereq(
737 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
744 __PACKAGE__->register_method(
745 method => 'has_notes',
746 api_name => 'open-ils.circ.copy.has_notes');
747 __PACKAGE__->register_method(
748 method => 'has_notes',
749 api_name => 'open-ils.circ.call_number.has_notes');
750 __PACKAGE__->register_method(
751 method => 'has_notes',
752 api_name => 'open-ils.circ.title.has_notes');
756 my( $self, $conn, $authtoken, $id ) = @_;
757 my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
758 return $editor->event unless $editor->checkauth;
760 my $n = $editor->search_asset_copy_note(
761 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
763 $n = $editor->search_asset_call_number_note(
764 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
766 $n = $editor->search_biblio_record_note(
767 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
774 __PACKAGE__->register_method(
775 method => 'create_copy_note',
776 api_name => 'open-ils.circ.copy_note.create',
778 Creates a new copy note
779 @param authtoken The login session key
780 @param note The note object to create
781 @return The id of the new note object
784 sub create_copy_note {
785 my( $self, $connection, $authtoken, $note ) = @_;
787 my $e = new_editor(xact=>1, authtoken=>$authtoken);
788 return $e->event unless $e->checkauth;
789 my $copy = $e->retrieve_asset_copy(
793 flesh_fields => { 'acp' => ['call_number'] }
798 return $e->event unless
799 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
801 $note->create_date('now');
802 $note->creator($e->requestor->id);
803 $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
806 $e->create_asset_copy_note($note) or return $e->event;
812 __PACKAGE__->register_method(
813 method => 'delete_copy_note',
814 api_name => 'open-ils.circ.copy_note.delete',
816 Deletes an existing copy note
817 @param authtoken The login session key
818 @param noteid The id of the note to delete
819 @return 1 on success - Event otherwise.
821 sub delete_copy_note {
822 my( $self, $conn, $authtoken, $noteid ) = @_;
824 my $e = new_editor(xact=>1, authtoken=>$authtoken);
825 return $e->die_event unless $e->checkauth;
827 my $note = $e->retrieve_asset_copy_note([
831 'acpn' => [ 'owning_copy' ],
832 'acp' => [ 'call_number' ],
835 ]) or return $e->die_event;
837 if( $note->creator ne $e->requestor->id ) {
838 return $e->die_event unless
839 $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
842 $e->delete_asset_copy_note($note) or return $e->die_event;
848 __PACKAGE__->register_method(
849 method => 'age_hold_rules',
850 api_name => 'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
854 my( $self, $conn ) = @_;
855 return new_editor()->retrieve_all_config_rules_age_hold_protect();
860 __PACKAGE__->register_method(
861 method => 'copy_details_barcode',
863 api_name => 'open-ils.circ.copy_details.retrieve.barcode');
864 sub copy_details_barcode {
865 my( $self, $conn, $auth, $barcode ) = @_;
866 my $e = new_editor();
867 my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
868 return $e->event unless $cid;
869 return copy_details( $self, $conn, $auth, $cid );
873 __PACKAGE__->register_method(
874 method => 'copy_details',
875 api_name => 'open-ils.circ.copy_details.retrieve');
878 my( $self, $conn, $auth, $copy_id ) = @_;
879 my $e = new_editor(authtoken=>$auth);
880 return $e->event unless $e->checkauth;
882 my $flesh = { flesh => 1 };
884 my $copy = $e->retrieve_asset_copy(
890 acp => ['call_number'],
894 ]) or return $e->event;
897 # De-flesh the copy for backwards compatibility
899 my $vol = $copy->call_number;
901 $copy->call_number($vol->id);
902 my $record = $vol->record;
904 $vol->record($record->id);
905 $mvr = $U->record_to_mvr($record);
910 my $hold = $e->search_action_hold_request(
912 current_copy => $copy_id,
913 capture_time => { "!=" => undef },
914 fulfillment_time => undef,
915 cancel_time => undef,
919 OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
921 my $transit = $e->search_action_transit_copy(
922 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
924 # find the latest circ, open or closed
925 my $circ = $e->search_action_circulation(
927 { target_copy => $copy_id },
933 'checkin_workstation',
936 'recurring_fine_rule'
939 order_by => { circ => 'xact_start desc' },
959 __PACKAGE__->register_method(
960 method => 'mark_item',
961 api_name => 'open-ils.circ.mark_item_damaged',
963 Changes the status of a copy to "damaged". Requires MARK_ITEM_DAMAGED permission.
964 @param authtoken The login session key
965 @param copy_id The ID of the copy to mark as damaged
966 @return 1 on success - Event otherwise.
969 __PACKAGE__->register_method(
970 method => 'mark_item',
971 api_name => 'open-ils.circ.mark_item_missing',
973 Changes the status of a copy to "missing". Requires MARK_ITEM_MISSING permission.
974 @param authtoken The login session key
975 @param copy_id The ID of the copy to mark as missing
976 @return 1 on success - Event otherwise.
979 __PACKAGE__->register_method(
980 method => 'mark_item',
981 api_name => 'open-ils.circ.mark_item_bindery',
983 Changes the status of a copy to "bindery". Requires MARK_ITEM_BINDERY permission.
984 @param authtoken The login session key
985 @param copy_id The ID of the copy to mark as bindery
986 @return 1 on success - Event otherwise.
989 __PACKAGE__->register_method(
990 method => 'mark_item',
991 api_name => 'open-ils.circ.mark_item_on_order',
993 Changes the status of a copy to "on order". Requires MARK_ITEM_ON_ORDER permission.
994 @param authtoken The login session key
995 @param copy_id The ID of the copy to mark as on order
996 @return 1 on success - Event otherwise.
999 __PACKAGE__->register_method(
1000 method => 'mark_item',
1001 api_name => 'open-ils.circ.mark_item_ill',
1003 Changes the status of a copy to "inter-library loan". Requires MARK_ITEM_ILL permission.
1004 @param authtoken The login session key
1005 @param copy_id The ID of the copy to mark as inter-library loan
1006 @return 1 on success - Event otherwise.
1009 __PACKAGE__->register_method(
1010 method => 'mark_item',
1011 api_name => 'open-ils.circ.mark_item_cataloging',
1013 Changes the status of a copy to "cataloging". Requires MARK_ITEM_CATALOGING permission.
1014 @param authtoken The login session key
1015 @param copy_id The ID of the copy to mark as cataloging
1016 @return 1 on success - Event otherwise.
1019 __PACKAGE__->register_method(
1020 method => 'mark_item',
1021 api_name => 'open-ils.circ.mark_item_reserves',
1023 Changes the status of a copy to "reserves". Requires MARK_ITEM_RESERVES permission.
1024 @param authtoken The login session key
1025 @param copy_id The ID of the copy to mark as reserves
1026 @return 1 on success - Event otherwise.
1029 __PACKAGE__->register_method(
1030 method => 'mark_item',
1031 api_name => 'open-ils.circ.mark_item_discard',
1033 Changes the status of a copy to "discard". Requires MARK_ITEM_DISCARD permission.
1034 @param authtoken The login session key
1035 @param copy_id The ID of the copy to mark as discard
1036 @return 1 on success - Event otherwise.
1041 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1042 my $e = new_editor(authtoken=>$auth, xact =>1);
1043 return $e->die_event unless $e->checkauth;
1046 my $copy = $e->retrieve_asset_copy([
1048 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1049 or return $e->die_event;
1052 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1053 $copy->circ_lib : $copy->call_number->owning_lib;
1055 return $e->die_event unless $e->allowed('UPDATE_COPY', $owning_lib);
1058 my $perm = 'MARK_ITEM_MISSING';
1059 my $stat = OILS_COPY_STATUS_MISSING;
1061 if( $self->api_name =~ /damaged/ ) {
1062 $perm = 'MARK_ITEM_DAMAGED';
1063 $stat = OILS_COPY_STATUS_DAMAGED;
1064 my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
1065 return $evt if $evt;
1067 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1068 $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
1070 } elsif ( $self->api_name =~ /bindery/ ) {
1071 $perm = 'MARK_ITEM_BINDERY';
1072 $stat = OILS_COPY_STATUS_BINDERY;
1073 } elsif ( $self->api_name =~ /on_order/ ) {
1074 $perm = 'MARK_ITEM_ON_ORDER';
1075 $stat = OILS_COPY_STATUS_ON_ORDER;
1076 } elsif ( $self->api_name =~ /ill/ ) {
1077 $perm = 'MARK_ITEM_ILL';
1078 $stat = OILS_COPY_STATUS_ILL;
1079 } elsif ( $self->api_name =~ /cataloging/ ) {
1080 $perm = 'MARK_ITEM_CATALOGING';
1081 $stat = OILS_COPY_STATUS_CATALOGING;
1082 } elsif ( $self->api_name =~ /reserves/ ) {
1083 $perm = 'MARK_ITEM_RESERVES';
1084 $stat = OILS_COPY_STATUS_RESERVES;
1085 } elsif ( $self->api_name =~ /discard/ ) {
1086 $perm = 'MARK_ITEM_DISCARD';
1087 $stat = OILS_COPY_STATUS_DISCARD;
1091 $copy->status($stat);
1092 $copy->edit_date('now');
1093 $copy->editor($e->requestor->id);
1095 $e->update_asset_copy($copy) or return $e->die_event;
1097 my $holds = $e->search_action_hold_request(
1099 current_copy => $copy->id,
1100 fulfillment_time => undef,
1101 cancel_time => undef,
1107 $logger->debug("resetting holds that target the marked copy");
1108 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1113 sub handle_mark_damaged {
1114 my($e, $copy, $owning_lib, $args) = @_;
1116 my $apply = $args->{apply_fines} || '';
1117 return undef if $apply eq 'noapply';
1119 my $new_amount = $args->{override_amount};
1120 my $new_btype = $args->{override_btype};
1121 my $new_note = $args->{override_note};
1123 # grab the last circulation
1124 my $circ = $e->search_action_circulation([
1125 { target_copy => $copy->id},
1127 order_by => {circ => "xact_start DESC"},
1129 flesh_fields => {circ => ['target_copy', 'usr'], au => ['card']}
1133 return undef unless $circ;
1135 my $charge_price = $U->ou_ancestor_setting_value(
1136 $owning_lib, 'circ.charge_on_damaged', $e);
1138 my $proc_fee = $U->ou_ancestor_setting_value(
1139 $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
1141 my $void_overdue = $U->ou_ancestor_setting_value(
1142 $owning_lib, 'circ.damaged.void_ovedue', $e) || 0;
1144 return undef unless $charge_price or $proc_fee;
1146 my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
1147 my $total = $copy_price + $proc_fee;
1151 if($new_amount and $new_btype) {
1153 # Allow staff to override the amount to charge for a damaged item
1154 # Consider the case where the item is only partially damaged
1155 # This value is meant to take the place of the item price and
1156 # optional processing fee.
1158 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1159 $e, $new_amount, $new_btype, 'Damaged Item Override', $circ->id, $new_note);
1160 return $evt if $evt;
1164 if($charge_price and $copy_price) {
1165 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1166 $e, $copy_price, 7, 'Damaged Item', $circ->id);
1167 return $evt if $evt;
1171 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1172 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
1173 return $evt if $evt;
1177 # the assumption is that you would not void the overdues unless you
1178 # were also charging for the item and/or applying a processing fee
1180 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
1181 return $evt if $evt;
1184 my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
1185 return $evt if $evt;
1187 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1188 $ses->request('open-ils.trigger.event.autocreate', 'checkout.damaged', $circ, $circ->circ_lib);
1193 return OpenILS::Event->new('DAMAGE_CHARGE',
1204 # ----------------------------------------------------------------------
1205 __PACKAGE__->register_method(
1206 method => 'mark_item_missing_pieces',
1207 api_name => 'open-ils.circ.mark_item_missing_pieces',
1209 Changes the status of a copy to "damaged" or to a custom status based on the
1210 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1212 @param authtoken The login session key
1213 @param copy_id The ID of the copy to mark as damaged
1214 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1218 sub mark_item_missing_pieces {
1219 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1220 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1221 my $e = new_editor(authtoken=>$auth, xact =>1);
1222 return $e->die_event unless $e->checkauth;
1225 my $copy = $e->retrieve_asset_copy([
1227 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1228 or return $e->die_event;
1231 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1232 $copy->circ_lib : $copy->call_number->owning_lib;
1234 return $e->die_event unless $e->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1236 #### grab the last circulation
1237 my $circ = $e->search_action_circulation([
1238 { target_copy => $copy->id},
1240 order_by => {circ => "xact_start DESC"}
1245 if (! $circ->checkin_time) { # if circ active, attempt renew
1246 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1247 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1248 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1249 $circ = $res->[0]->{payload}{'circ'};
1250 $circ->target_copy( $copy->id );
1251 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1253 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1258 'copy_id'=>$circ->target_copy,
1259 'patron_id'=>$circ->usr,
1260 'skip_deposit_fee'=>1,
1261 'skip_rental_fee'=>1
1264 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1266 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1267 $e, $copy, $e->requestor, 1 );
1269 if ($hold) { # needed for hold? then due now
1271 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1272 my $due_date = DateTime->now(time_zone => 'local');
1273 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1275 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1279 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params);
1280 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1281 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1282 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1283 $circ = $res->[0]->{payload}{'circ'};
1285 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1291 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1293 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1296 ### Update the item status
1298 my $custom_stat = $U->ou_ancestor_setting_value(
1299 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1300 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1302 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1303 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1305 $copy->status($stat);
1306 $copy->edit_date('now');
1307 $copy->editor($e->requestor->id);
1309 $e->update_asset_copy($copy) or return $e->die_event;
1311 my $holds = $e->search_action_hold_request(
1313 current_copy => $copy->id,
1314 fulfillment_time => undef,
1315 cancel_time => undef,
1319 $logger->debug("resetting holds that target the marked copy");
1320 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1324 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1325 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1327 return OpenILS::Event->new('SUCCESS',
1331 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1332 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1337 return $e->die_event;
1345 # ----------------------------------------------------------------------
1346 __PACKAGE__->register_method(
1347 method => 'magic_fetch',
1348 api_name => 'open-ils.agent.fetch'
1351 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1354 my( $self, $conn, $auth, $args ) = @_;
1355 my $e = new_editor( authtoken => $auth );
1356 return $e->event unless $e->checkauth;
1358 my $hint = $$args{hint};
1359 my $id = $$args{id};
1361 # Is the call allowed to fetch this type of object?
1362 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1364 # Find the class the implements the given hint
1365 my ($class) = grep {
1366 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1368 $class =~ s/Fieldmapper:://og;
1369 $class =~ s/::/_/og;
1370 my $method = "retrieve_$class";
1372 my $obj = $e->$method($id) or return $e->event;
1375 # ----------------------------------------------------------------------
1378 __PACKAGE__->register_method(
1379 method => "fleshed_circ_retrieve",
1381 api_name => "open-ils.circ.fleshed.retrieve",);
1383 sub fleshed_circ_retrieve {
1384 my( $self, $client, $id ) = @_;
1385 my $e = new_editor();
1386 my $circ = $e->retrieve_action_circulation(
1392 circ => [ qw/ target_copy / ],
1393 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number / ],
1394 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1395 acn => [ qw/ record / ],
1399 ) or return $e->event;
1401 my $copy = $circ->target_copy;
1402 my $vol = $copy->call_number;
1403 my $rec = $circ->target_copy->call_number->record;
1405 $vol->record($rec->id);
1406 $copy->call_number($vol->id);
1407 $circ->target_copy($copy->id);
1411 if( $rec->id == OILS_PRECAT_RECORD ) {
1415 $mvr = $U->record_to_mvr($rec);
1416 $rec->marc(''); # drop the bulky marc data
1430 __PACKAGE__->register_method(
1431 method => "test_batch_circ_events",
1432 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1435 # method for testing the behavior of a given event definition
1436 sub test_batch_circ_events {
1437 my($self, $conn, $auth, $event_def, $barcode) = @_;
1439 my $e = new_editor(authtoken => $auth);
1440 return $e->event unless $e->checkauth;
1441 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1443 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1444 or return $e->event;
1446 my $circ = $e->search_action_circulation(
1447 {target_copy => $copy->id, checkin_time => undef})->[0]
1448 or return $e->event;
1450 return undef unless $circ;
1452 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1456 __PACKAGE__->register_method(
1457 method => "fire_circ_events",
1458 api_name => "open-ils.circ.fire_circ_trigger_events",
1460 General event def runner for circ objects. If no event def ID
1461 is provided, the hook will be used to find the best event_def
1462 match based on the context org unit
1466 __PACKAGE__->register_method(
1467 method => "fire_circ_events",
1468 api_name => "open-ils.circ.fire_hold_trigger_events",
1470 General event def runner for hold objects. If no event def ID
1471 is provided, the hook will be used to find the best event_def
1472 match based on the context org unit
1476 __PACKAGE__->register_method(
1477 method => "fire_circ_events",
1478 api_name => "open-ils.circ.fire_user_trigger_events",
1480 General event def runner for user objects. If no event def ID
1481 is provided, the hook will be used to find the best event_def
1482 match based on the context org unit
1487 sub fire_circ_events {
1488 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1490 my $e = new_editor(authtoken => $auth, xact => 1);
1491 return $e->event unless $e->checkauth;
1495 if($self->api_name =~ /hold/) {
1496 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1497 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1498 } elsif($self->api_name =~ /user/) {
1499 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1500 $targets = $e->batch_retrieve_actor_user($target_ids);
1502 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1503 $targets = $e->batch_retrieve_action_circulation($target_ids);
1505 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1506 # simply making this method authoritative because of weirdness
1507 # with transaction handling in A/T code that causes rollback
1508 # failure down the line if handling many targets
1510 return undef unless @$targets;
1511 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1514 __PACKAGE__->register_method(
1515 method => "user_payments_list",
1516 api_name => "open-ils.circ.user_payments.filtered.batch",
1519 desc => q/Returns a fleshed, date-limited set of all payments a user
1520 has made. By default, ordered by payment date. Optionally
1521 ordered by other columns in the top-level "mp" object/,
1523 {desc => 'Authentication token', type => 'string'},
1524 {desc => 'User ID', type => 'number'},
1525 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1527 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1528 and the related fully-realized payment object (e.g money.cash_payment)/}
1532 sub user_payments_list {
1533 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1535 my $e = new_editor(authtoken => $auth);
1536 return $e->event unless $e->checkauth;
1538 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1539 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1541 $order_by ||= ['payment_ts'];
1543 # all payments by user, between start_date and end_date
1544 my $payments = $e->json_query({
1545 select => {mp => ['id']},
1549 fkey => 'xact', field => 'id'}
1553 '+mbt' => {usr => $user_id},
1554 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1556 order_by => {mp => $order_by}
1559 for my $payment_id (@$payments) {
1560 my $payment = $e->retrieve_money_payment([
1568 'credit_card_payment',
1583 $conn->respond($payment);
1590 __PACKAGE__->register_method(
1591 method => "retrieve_circ_chain",
1592 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1595 desc => q/Given a circulation, this returns all circulation objects
1596 that are part of the same chain of renewals./,
1598 {desc => 'Authentication token', type => 'string'},
1599 {desc => 'Circ ID', type => 'number'},
1601 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1605 __PACKAGE__->register_method(
1606 method => "retrieve_circ_chain",
1607 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1609 desc => q/Given a circulation, this returns a summary of the circulation objects
1610 that are part of the same chain of renewals./,
1612 {desc => 'Authentication token', type => 'string'},
1613 {desc => 'Circ ID', type => 'number'},
1615 return => {desc => q/Circulation Chain Summary/}
1619 sub retrieve_circ_chain {
1620 my($self, $conn, $auth, $circ_id) = @_;
1622 my $e = new_editor(authtoken => $auth);
1623 return $e->event unless $e->checkauth;
1624 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1626 if($self->api_name =~ /summary/) {
1627 return $U->create_circ_chain_summary($e, $circ_id);
1631 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1633 for my $circ_info (@$chain) {
1634 my $circ = Fieldmapper::action::circulation->new;
1635 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1636 $conn->respond($circ);
1643 __PACKAGE__->register_method(
1644 method => "retrieve_prev_circ_chain",
1645 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1648 desc => q/Given a circulation, this returns all circulation objects
1649 that are part of the previous chain of renewals./,
1651 {desc => 'Authentication token', type => 'string'},
1652 {desc => 'Circ ID', type => 'number'},
1654 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1658 __PACKAGE__->register_method(
1659 method => "retrieve_prev_circ_chain",
1660 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1662 desc => q/Given a circulation, this returns a summary of the circulation objects
1663 that are part of the previous chain of renewals./,
1665 {desc => 'Authentication token', type => 'string'},
1666 {desc => 'Circ ID', type => 'number'},
1668 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1672 sub retrieve_prev_circ_chain {
1673 my($self, $conn, $auth, $circ_id) = @_;
1675 my $e = new_editor(authtoken => $auth);
1676 return $e->event unless $e->checkauth;
1677 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1679 if($self->api_name =~ /summary/) {
1680 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1681 my $target_copy = $$first_circ{'target_copy'};
1682 my $usr = $$first_circ{'usr'};
1683 my $last_circ_from_prev_chain = $e->json_query({
1684 'select' => { 'circ' => ['id','usr'] },
1687 target_copy => $target_copy,
1688 xact_start => { '<' => $$first_circ{'xact_start'} }
1690 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1693 return undef unless $last_circ_from_prev_chain;
1694 return undef unless $$last_circ_from_prev_chain{'id'};
1695 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1696 return undef unless $sum;
1697 my $obj = Fieldmapper::action::circ_chain_summary->new;
1698 $obj->$_($sum->{$_}) for keys %$sum;
1699 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1703 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1704 my $target_copy = $$first_circ{'target_copy'};
1705 my $last_circ_from_prev_chain = $e->json_query({
1706 'select' => { 'circ' => ['id'] },
1709 target_copy => $target_copy,
1710 xact_start => { '<' => $$first_circ{'xact_start'} }
1712 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1715 return undef unless $last_circ_from_prev_chain;
1716 return undef unless $$last_circ_from_prev_chain{'id'};
1717 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1719 for my $circ_info (@$chain) {
1720 my $circ = Fieldmapper::action::circulation->new;
1721 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1722 $conn->respond($circ);
1730 __PACKAGE__->register_method(
1731 method => "get_copy_due_date",
1732 api_name => "open-ils.circ.copy.due_date.retrieve",
1735 Given a copy ID, returns the due date for the copy if it's
1736 currently circulating. Otherwise, returns null. Note, this is a public
1737 method requiring no authentication. Only the due date is exposed.
1740 {desc => 'Copy ID', type => 'number'}
1742 return => {desc => q/
1743 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1748 sub get_copy_due_date {
1749 my($self, $conn, $copy_id) = @_;
1750 my $e = new_editor();
1752 my $circ = $e->json_query({
1753 select => {circ => ['due_date']},
1756 target_copy => $copy_id,
1757 checkin_time => undef,
1759 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1760 {stop_fines => undef}
1764 })->[0] or return undef;
1766 return $circ->{due_date};
1773 # {"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}}