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);
255 __PACKAGE__->register_method(
256 method => "staff_age_to_lost",
257 api_name => "open-ils.circ.circulation.age_to_lost",
260 This fires a circ.staff_age_to_lost Action-Trigger event against all
261 overdue circulations in scope of the specified context library and
262 user profile, which effectively marks the associated items as Lost.
263 This is likely to be done at the end of a semester in an academic
266 @param args : circ_lib, user_profile
270 sub staff_age_to_lost {
271 my( $self, $conn, $auth, $args ) = @_;
273 my $orgs = $U->get_org_descendants($args->{'circ_lib'});
274 my $profiles = $U->fetch_permission_group_descendants($args->{'user_profile'});
276 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
278 my $method = 'open-ils.trigger.passive.event.autocreate.batch';
279 my $hook = 'circ.staff_age_to_lost';
280 my $context_org = 'circ_lib';
281 my $opt_granularity = undef;
283 "checkin_time" => undef,
284 "due_date" => { "<" => "now" },
286 { "stop_fines" => ["MAXFINES", "LONGOVERDUE"] }, # FIXME: CLAIMSRETURNED also?
287 { "stop_fines" => undef }
291 "select" => {"au" => ["id"]},
294 "profile" => $profiles,
295 "id" => { "=" => {"+circ" => "usr"} }
299 "select" => {"aou" => ["id"]},
303 {"id" => { "=" => {"+circ" => "circ_lib"} }},
310 my $req_timeout = 10800;
311 my $chunk_size = 100;
314 my $req = $ses->request($method, $hook, $context_org, $filter, $opt_granularity);
315 my @event_ids; my @chunked_ids;
316 while (my $resp = $req->recv(timeout => $req_timeout)) {
317 push(@event_ids, $resp->content);
318 push(@chunked_ids, $resp->content);
319 if (scalar(@chunked_ids) > $chunk_size) {
320 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
324 if (scalar(@chunked_ids) > 0) {
325 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
329 $logger->info("staff_age_to_lost: created ".scalar(@event_ids)." events for circ.staff_age_to_lost");
330 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>scalar(@event_ids)});
331 } elsif($req->complete) {
332 $logger->info("staff_age_to_lost: no events to create for circ.staff_age_to_lost");
333 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>0});
335 $logger->warn("staff_age_to_lost: timeout occurred during event creation for circ.staff_age_to_lost");
336 $conn->respond_complete({'total_progress'=>$progress-1,'error'=>'timeout'});
343 __PACKAGE__->register_method(
344 method => "new_set_circ_lost",
345 api_name => "open-ils.circ.circulation.set_lost",
347 Sets the copy and related open circulation to lost
349 @param args : barcode
354 # ---------------------------------------------------------------------
355 # Sets a circulation to lost. updates copy status to lost
356 # applies copy and/or prcoessing fees depending on org settings
357 # ---------------------------------------------------------------------
358 sub new_set_circ_lost {
359 my( $self, $conn, $auth, $args ) = @_;
361 my $e = new_editor(authtoken=>$auth, xact=>1);
362 return $e->die_event unless $e->checkauth;
364 my $copy = $e->search_asset_copy({barcode=>$$args{barcode}, deleted=>'f'})->[0]
365 or return $e->die_event;
367 my $evt = OpenILS::Application::Cat::AssetCommon->set_item_lost($e, $copy->id);
375 __PACKAGE__->register_method(
376 method => "set_circ_claims_returned",
377 api_name => "open-ils.circ.circulation.set_claims_returned",
379 desc => q/Sets the circ for a given item as claims returned
380 If a backdate is provided, overdue fines will be voided
381 back to the backdate/,
383 {desc => 'Authentication token', type => 'string'},
384 {desc => 'Arguments, including "barcode" and optional "backdate"', type => 'object'}
386 return => {desc => q/1 on success, failure event on error, and
387 PATRON_EXCEEDS_CLAIMS_RETURN_COUNT if the patron exceeds the
388 configured claims return maximum/}
392 __PACKAGE__->register_method(
393 method => "set_circ_claims_returned",
394 api_name => "open-ils.circ.circulation.set_claims_returned.override",
396 desc => q/This adds support for overrideing the configured max
397 claims returned amount.
398 @see open-ils.circ.circulation.set_claims_returned./,
402 sub set_circ_claims_returned {
403 my( $self, $conn, $auth, $args ) = @_;
405 my $e = new_editor(authtoken=>$auth, xact=>1);
406 return $e->die_event unless $e->checkauth;
408 my $barcode = $$args{barcode};
409 my $backdate = $$args{backdate};
411 my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0]
412 or return $e->die_event;
414 my $circ = $e->search_action_circulation(
415 {checkin_time => undef, target_copy => $copy->id})->[0]
416 or return $e->die_event;
418 $backdate = $circ->due_date if $$args{use_due_date};
420 $logger->info("marking circ for item $barcode as claims returned".
421 (($backdate) ? " with backdate $backdate" : ''));
423 my $patron = $e->retrieve_actor_user($circ->usr);
424 my $max_count = $U->ou_ancestor_setting_value(
425 $circ->circ_lib, 'circ.max_patron_claim_return_count', $e);
427 # If the patron has too instances of many claims returned,
428 # require an override to continue. A configured max of
429 # 0 means all attempts require an override
430 if(defined $max_count and $patron->claims_returned_count >= $max_count) {
432 if($self->api_name =~ /override/) {
434 # see if we're allowed to override
435 return $e->die_event unless
436 $e->allowed('SET_CIRC_CLAIMS_RETURNED.override', $circ->circ_lib);
440 # exit early and return the max claims return event
442 return OpenILS::Event->new(
443 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT',
445 patron_count => $patron->claims_returned_count,
446 max_count => $max_count
452 $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib)
453 or return $e->die_event;
455 $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
456 $circ->stop_fines_time('now') unless $circ->stop_fines_time;
459 $backdate = cleanse_ISO8601($backdate);
461 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
462 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($backdate);
463 $backdate = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
465 # clean it up once again; need a : in the timezone offset. E.g. -06:00 not -0600
466 $backdate = cleanse_ISO8601($backdate);
468 # make it look like the circ stopped at the cliams returned time
469 $circ->stop_fines_time($backdate);
470 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
474 $e->update_action_circulation($circ) or return $e->die_event;
476 # see if there is a configured post-claims-return copy status
477 if(my $stat = $U->ou_ancestor_setting_value($circ->circ_lib, 'circ.claim_return.copy_status')) {
478 $copy->status($stat);
479 $copy->edit_date('now');
480 $copy->editor($e->requestor->id);
481 $e->update_asset_copy($copy) or return $e->die_event;
489 __PACKAGE__->register_method(
490 method => "post_checkin_backdate_circ",
491 api_name => "open-ils.circ.post_checkin_backdate",
493 desc => q/Back-date an already checked in circulation/,
495 {desc => 'Authentication token', type => 'string'},
496 {desc => 'Circ ID', type => 'number'},
497 {desc => 'ISO8601 backdate', type => 'string'},
499 return => {desc => q/1 on success, failure event on error/}
503 __PACKAGE__->register_method(
504 method => "post_checkin_backdate_circ",
505 api_name => "open-ils.circ.post_checkin_backdate.batch",
508 desc => q/@see open-ils.circ.post_checkin_backdate. Batch mode/,
510 {desc => 'Authentication token', type => 'string'},
511 {desc => 'List of Circ ID', type => 'array'},
512 {desc => 'ISO8601 backdate', type => 'string'},
514 return => {desc => q/Set of: 1 on success, failure event on error/}
519 sub post_checkin_backdate_circ {
520 my( $self, $conn, $auth, $circ_id, $backdate ) = @_;
521 my $e = new_editor(authtoken=>$auth);
522 return $e->die_event unless $e->checkauth;
523 if($self->api_name =~ /batch/) {
524 foreach my $c (@$circ_id) {
525 $conn->respond(post_checkin_backdate_circ_impl($e, $c, $backdate));
528 $conn->respond_complete(post_checkin_backdate_circ_impl($e, $circ_id, $backdate));
536 sub post_checkin_backdate_circ_impl {
537 my($e, $circ_id, $backdate) = @_;
541 my $circ = $e->retrieve_action_circulation($circ_id)
542 or return $e->die_event;
544 # anyone with checkin perms can backdate (more restrictive?)
545 return $e->die_event unless $e->allowed('COPY_CHECKIN', $circ->circ_lib);
547 # don't allow back-dating an open circulation
548 return OpenILS::Event->new('BAD_PARAMS') unless
549 $backdate and $circ->checkin_time;
551 # update the checkin and stop_fines times to reflect the new backdate
552 $circ->stop_fines_time(cleanse_ISO8601($backdate));
553 $circ->checkin_time(cleanse_ISO8601($backdate));
554 $e->update_action_circulation($circ) or return $e->die_event;
556 # now void the overdues "erased" by the back-dating
557 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
560 # If the circ was closed before and the balance owned !=0, re-open the transaction
561 $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
570 __PACKAGE__->register_method (
571 method => 'set_circ_due_date',
572 api_name => 'open-ils.circ.circulation.due_date.update',
574 Updates the due_date on the given circ
576 @param circid The id of the circ to update
577 @param date The timestamp of the new due date
581 sub set_circ_due_date {
582 my( $self, $conn, $auth, $circ_id, $date ) = @_;
584 my $e = new_editor(xact=>1, authtoken=>$auth);
585 return $e->die_event unless $e->checkauth;
586 my $circ = $e->retrieve_action_circulation($circ_id)
587 or return $e->die_event;
589 return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
590 $date = cleanse_ISO8601($date);
592 if (!(interval_to_seconds($circ->duration) % 86400)) { # duration is divisible by days
593 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
594 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($date);
595 $date = cleanse_ISO8601( $new_date->ymd . 'T' . $original_date->strftime('%T%z') );
598 $circ->due_date($date);
599 $e->update_action_circulation($circ) or return $e->die_event;
606 __PACKAGE__->register_method(
607 method => "create_in_house_use",
608 api_name => 'open-ils.circ.in_house_use.create',
610 Creates an in-house use action.
611 @param $authtoken The login session key
612 @param params A hash of params including
613 'location' The org unit id where the in-house use occurs
614 'copyid' The copy in question
615 'count' The number of in-house uses to apply to this copy
616 @return An array of id's representing the id's of the newly created
617 in-house use objects or an event on an error
620 __PACKAGE__->register_method(
621 method => "create_in_house_use",
622 api_name => 'open-ils.circ.non_cat_in_house_use.create',
626 sub create_in_house_use {
627 my( $self, $client, $auth, $params ) = @_;
630 my $org = $params->{location};
631 my $copyid = $params->{copyid};
632 my $count = $params->{count} || 1;
633 my $nc_type = $params->{non_cat_type};
634 my $use_time = $params->{use_time} || 'now';
636 my $e = new_editor(xact=>1,authtoken=>$auth);
637 return $e->event unless $e->checkauth;
638 return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
640 my $non_cat = 1 if $self->api_name =~ /non_cat/;
644 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
646 $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
652 if( $use_time ne 'now' ) {
653 $use_time = cleanse_ISO8601($use_time);
654 $logger->debug("in_house_use setting use time to $use_time");
665 $ihu = Fieldmapper::action::non_cat_in_house_use->new;
666 $ihu->item_type($nc_type);
667 $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
668 $cmeth = "create_action_non_cat_in_house_use";
671 $ihu = Fieldmapper::action::in_house_use->new;
673 $method = 'open-ils.storage.direct.action.in_house_use.create';
674 $cmeth = "create_action_in_house_use";
677 $ihu->staff($e->requestor->id);
678 $ihu->org_unit($org);
679 $ihu->use_time($use_time);
681 $ihu = $e->$cmeth($ihu) or return $e->event;
682 push( @ids, $ihu->id );
693 __PACKAGE__->register_method(
694 method => "view_circs",
695 api_name => "open-ils.circ.copy_checkout_history.retrieve",
697 Retrieves the last X circs for a given copy
698 @param authtoken The login session key
699 @param copyid The copy to check
700 @param count How far to go back in the item history
701 @return An array of circ ids
704 # ----------------------------------------------------------------------
705 # Returns $count most recent circs. If count exceeds the configured
706 # max, use the configured max instead
707 # ----------------------------------------------------------------------
709 my( $self, $client, $authtoken, $copyid, $count ) = @_;
711 my $e = new_editor(authtoken => $authtoken);
712 return $e->event unless $e->checkauth;
714 my $copy = $e->retrieve_asset_copy([
717 flesh_fields => {acp => ['call_number']}
719 ]) or return $e->event;
721 return $e->event unless $e->allowed(
722 'VIEW_COPY_CHECKOUT_HISTORY',
723 ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ?
724 $copy->circ_lib : $copy->call_number->owning_lib);
726 my $max_history = $U->ou_ancestor_setting_value(
727 $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
729 if(defined $max_history) {
730 $count = $max_history unless defined $count and $count < $max_history;
732 $count = 4 unless defined $count;
735 return $e->search_action_circulation([
736 {target_copy => $copyid},
737 {limit => $count, order_by => { circ => "xact_start DESC" }}
742 __PACKAGE__->register_method(
743 method => "circ_count",
744 api_name => "open-ils.circ.circulation.count",
746 Returns the number of times the item has circulated
747 @param copyid The copy to check
751 my( $self, $client, $copyid, $range ) = @_;
752 my $e = OpenILS::Utils::Editor->new;
753 return $e->request('open-ils.storage.asset.copy.circ_count', $copyid, $range);
758 __PACKAGE__->register_method(
759 method => 'fetch_notes',
761 api_name => 'open-ils.circ.copy_note.retrieve.all',
763 Returns an array of copy note objects.
764 @param args A named hash of parameters including:
765 authtoken : Required if viewing non-public notes
766 itemid : The id of the item whose notes we want to retrieve
767 pub : True if all the caller wants are public notes
768 @return An array of note objects
771 __PACKAGE__->register_method(
772 method => 'fetch_notes',
773 api_name => 'open-ils.circ.call_number_note.retrieve.all',
774 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
776 __PACKAGE__->register_method(
777 method => 'fetch_notes',
778 api_name => 'open-ils.circ.title_note.retrieve.all',
779 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
782 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
784 my( $self, $connection, $args ) = @_;
786 my $id = $$args{itemid};
787 my $authtoken = $$args{authtoken};
790 if( $self->api_name =~ /copy/ ) {
792 return $U->cstorereq(
793 'open-ils.cstore.direct.asset.copy_note.search.atomic',
794 { owning_copy => $id, pub => 't' } );
796 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
798 return $U->cstorereq(
799 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
802 } elsif( $self->api_name =~ /call_number/ ) {
804 return $U->cstorereq(
805 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
806 { call_number => $id, pub => 't' } );
808 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
810 return $U->cstorereq(
811 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
814 } elsif( $self->api_name =~ /title/ ) {
816 return $U->cstorereq(
817 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
818 { record => $id, pub => 't' } );
820 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
822 return $U->cstorereq(
823 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
830 __PACKAGE__->register_method(
831 method => 'has_notes',
832 api_name => 'open-ils.circ.copy.has_notes');
833 __PACKAGE__->register_method(
834 method => 'has_notes',
835 api_name => 'open-ils.circ.call_number.has_notes');
836 __PACKAGE__->register_method(
837 method => 'has_notes',
838 api_name => 'open-ils.circ.title.has_notes');
842 my( $self, $conn, $authtoken, $id ) = @_;
843 my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
844 return $editor->event unless $editor->checkauth;
846 my $n = $editor->search_asset_copy_note(
847 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
849 $n = $editor->search_asset_call_number_note(
850 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
852 $n = $editor->search_biblio_record_note(
853 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
860 __PACKAGE__->register_method(
861 method => 'create_copy_note',
862 api_name => 'open-ils.circ.copy_note.create',
864 Creates a new copy note
865 @param authtoken The login session key
866 @param note The note object to create
867 @return The id of the new note object
870 sub create_copy_note {
871 my( $self, $connection, $authtoken, $note ) = @_;
873 my $e = new_editor(xact=>1, authtoken=>$authtoken);
874 return $e->event unless $e->checkauth;
875 my $copy = $e->retrieve_asset_copy(
879 flesh_fields => { 'acp' => ['call_number'] }
884 return $e->event unless
885 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
887 $note->create_date('now');
888 $note->creator($e->requestor->id);
889 $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
892 $e->create_asset_copy_note($note) or return $e->event;
898 __PACKAGE__->register_method(
899 method => 'delete_copy_note',
900 api_name => 'open-ils.circ.copy_note.delete',
902 Deletes an existing copy note
903 @param authtoken The login session key
904 @param noteid The id of the note to delete
905 @return 1 on success - Event otherwise.
907 sub delete_copy_note {
908 my( $self, $conn, $authtoken, $noteid ) = @_;
910 my $e = new_editor(xact=>1, authtoken=>$authtoken);
911 return $e->die_event unless $e->checkauth;
913 my $note = $e->retrieve_asset_copy_note([
917 'acpn' => [ 'owning_copy' ],
918 'acp' => [ 'call_number' ],
921 ]) or return $e->die_event;
923 if( $note->creator ne $e->requestor->id ) {
924 return $e->die_event unless
925 $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
928 $e->delete_asset_copy_note($note) or return $e->die_event;
934 __PACKAGE__->register_method(
935 method => 'age_hold_rules',
936 api_name => 'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
940 my( $self, $conn ) = @_;
941 return new_editor()->retrieve_all_config_rules_age_hold_protect();
946 __PACKAGE__->register_method(
947 method => 'copy_details_barcode',
949 api_name => 'open-ils.circ.copy_details.retrieve.barcode');
950 sub copy_details_barcode {
951 my( $self, $conn, $auth, $barcode ) = @_;
952 my $e = new_editor();
953 my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
954 return $e->event unless $cid;
955 return copy_details( $self, $conn, $auth, $cid );
959 __PACKAGE__->register_method(
960 method => 'copy_details',
961 api_name => 'open-ils.circ.copy_details.retrieve');
964 my( $self, $conn, $auth, $copy_id ) = @_;
965 my $e = new_editor(authtoken=>$auth);
966 return $e->event unless $e->checkauth;
968 my $flesh = { flesh => 1 };
970 my $copy = $e->retrieve_asset_copy(
976 acp => ['call_number','parts'],
977 acn => ['record','prefix','suffix','label_class']
980 ]) or return $e->event;
983 # De-flesh the copy for backwards compatibility
985 my $vol = $copy->call_number;
987 $copy->call_number($vol->id);
988 my $record = $vol->record;
990 $vol->record($record->id);
991 $mvr = $U->record_to_mvr($record);
996 my $hold = $e->search_action_hold_request(
998 current_copy => $copy_id,
999 capture_time => { "!=" => undef },
1000 fulfillment_time => undef,
1001 cancel_time => undef,
1005 OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
1007 my $transit = $e->search_action_transit_copy(
1008 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
1010 # find the latest circ, open or closed
1011 my $circ = $e->search_action_circulation(
1013 { target_copy => $copy_id },
1019 'checkin_workstation',
1022 'recurring_fine_rule'
1025 order_by => { circ => 'xact_start desc' },
1035 transit => $transit,
1045 __PACKAGE__->register_method(
1046 method => 'mark_item',
1047 api_name => 'open-ils.circ.mark_item_damaged',
1049 Changes the status of a copy to "damaged". Requires MARK_ITEM_DAMAGED permission.
1050 @param authtoken The login session key
1051 @param copy_id The ID of the copy to mark as damaged
1052 @return 1 on success - Event otherwise.
1055 __PACKAGE__->register_method(
1056 method => 'mark_item',
1057 api_name => 'open-ils.circ.mark_item_missing',
1059 Changes the status of a copy to "missing". Requires MARK_ITEM_MISSING permission.
1060 @param authtoken The login session key
1061 @param copy_id The ID of the copy to mark as missing
1062 @return 1 on success - Event otherwise.
1065 __PACKAGE__->register_method(
1066 method => 'mark_item',
1067 api_name => 'open-ils.circ.mark_item_bindery',
1069 Changes the status of a copy to "bindery". Requires MARK_ITEM_BINDERY permission.
1070 @param authtoken The login session key
1071 @param copy_id The ID of the copy to mark as bindery
1072 @return 1 on success - Event otherwise.
1075 __PACKAGE__->register_method(
1076 method => 'mark_item',
1077 api_name => 'open-ils.circ.mark_item_on_order',
1079 Changes the status of a copy to "on order". Requires MARK_ITEM_ON_ORDER permission.
1080 @param authtoken The login session key
1081 @param copy_id The ID of the copy to mark as on order
1082 @return 1 on success - Event otherwise.
1085 __PACKAGE__->register_method(
1086 method => 'mark_item',
1087 api_name => 'open-ils.circ.mark_item_ill',
1089 Changes the status of a copy to "inter-library loan". Requires MARK_ITEM_ILL permission.
1090 @param authtoken The login session key
1091 @param copy_id The ID of the copy to mark as inter-library loan
1092 @return 1 on success - Event otherwise.
1095 __PACKAGE__->register_method(
1096 method => 'mark_item',
1097 api_name => 'open-ils.circ.mark_item_cataloging',
1099 Changes the status of a copy to "cataloging". Requires MARK_ITEM_CATALOGING permission.
1100 @param authtoken The login session key
1101 @param copy_id The ID of the copy to mark as cataloging
1102 @return 1 on success - Event otherwise.
1105 __PACKAGE__->register_method(
1106 method => 'mark_item',
1107 api_name => 'open-ils.circ.mark_item_reserves',
1109 Changes the status of a copy to "reserves". Requires MARK_ITEM_RESERVES permission.
1110 @param authtoken The login session key
1111 @param copy_id The ID of the copy to mark as reserves
1112 @return 1 on success - Event otherwise.
1115 __PACKAGE__->register_method(
1116 method => 'mark_item',
1117 api_name => 'open-ils.circ.mark_item_discard',
1119 Changes the status of a copy to "discard". Requires MARK_ITEM_DISCARD permission.
1120 @param authtoken The login session key
1121 @param copy_id The ID of the copy to mark as discard
1122 @return 1 on success - Event otherwise.
1127 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1128 my $e = new_editor(authtoken=>$auth, xact =>1);
1129 return $e->die_event unless $e->checkauth;
1132 my $copy = $e->retrieve_asset_copy([
1134 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1135 or return $e->die_event;
1138 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1139 $copy->circ_lib : $copy->call_number->owning_lib;
1141 my $perm = 'MARK_ITEM_MISSING';
1142 my $stat = OILS_COPY_STATUS_MISSING;
1144 if( $self->api_name =~ /damaged/ ) {
1145 $perm = 'MARK_ITEM_DAMAGED';
1146 $stat = OILS_COPY_STATUS_DAMAGED;
1147 my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
1148 return $evt if $evt;
1150 } elsif ( $self->api_name =~ /bindery/ ) {
1151 $perm = 'MARK_ITEM_BINDERY';
1152 $stat = OILS_COPY_STATUS_BINDERY;
1153 } elsif ( $self->api_name =~ /on_order/ ) {
1154 $perm = 'MARK_ITEM_ON_ORDER';
1155 $stat = OILS_COPY_STATUS_ON_ORDER;
1156 } elsif ( $self->api_name =~ /ill/ ) {
1157 $perm = 'MARK_ITEM_ILL';
1158 $stat = OILS_COPY_STATUS_ILL;
1159 } elsif ( $self->api_name =~ /cataloging/ ) {
1160 $perm = 'MARK_ITEM_CATALOGING';
1161 $stat = OILS_COPY_STATUS_CATALOGING;
1162 } elsif ( $self->api_name =~ /reserves/ ) {
1163 $perm = 'MARK_ITEM_RESERVES';
1164 $stat = OILS_COPY_STATUS_RESERVES;
1165 } elsif ( $self->api_name =~ /discard/ ) {
1166 $perm = 'MARK_ITEM_DISCARD';
1167 $stat = OILS_COPY_STATUS_DISCARD;
1170 # caller may proceed if either perm is allowed
1171 return $e->die_event unless $e->allowed([$perm, 'UPDATE_COPY'], $owning_lib);
1173 $copy->status($stat);
1174 $copy->edit_date('now');
1175 $copy->editor($e->requestor->id);
1177 $e->update_asset_copy($copy) or return $e->die_event;
1179 my $holds = $e->search_action_hold_request(
1181 current_copy => $copy->id,
1182 fulfillment_time => undef,
1183 cancel_time => undef,
1189 if( $self->api_name =~ /damaged/ ) {
1190 # now that we've committed the changes, create related A/T events
1191 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1192 $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
1195 $logger->debug("resetting holds that target the marked copy");
1196 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1201 sub handle_mark_damaged {
1202 my($e, $copy, $owning_lib, $args) = @_;
1204 my $apply = $args->{apply_fines} || '';
1205 return undef if $apply eq 'noapply';
1207 my $new_amount = $args->{override_amount};
1208 my $new_btype = $args->{override_btype};
1209 my $new_note = $args->{override_note};
1211 # grab the last circulation
1212 my $circ = $e->search_action_circulation([
1213 { target_copy => $copy->id},
1215 order_by => {circ => "xact_start DESC"},
1217 flesh_fields => {circ => ['target_copy', 'usr'], au => ['card']}
1221 return undef unless $circ;
1223 my $charge_price = $U->ou_ancestor_setting_value(
1224 $owning_lib, 'circ.charge_on_damaged', $e);
1226 my $proc_fee = $U->ou_ancestor_setting_value(
1227 $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
1229 my $void_overdue = $U->ou_ancestor_setting_value(
1230 $owning_lib, 'circ.damaged.void_ovedue', $e) || 0;
1232 return undef unless $charge_price or $proc_fee;
1234 my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
1235 my $total = $copy_price + $proc_fee;
1239 if($new_amount and $new_btype) {
1241 # Allow staff to override the amount to charge for a damaged item
1242 # Consider the case where the item is only partially damaged
1243 # This value is meant to take the place of the item price and
1244 # optional processing fee.
1246 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1247 $e, $new_amount, $new_btype, 'Damaged Item Override', $circ->id, $new_note);
1248 return $evt if $evt;
1252 if($charge_price and $copy_price) {
1253 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1254 $e, $copy_price, 7, 'Damaged Item', $circ->id);
1255 return $evt if $evt;
1259 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1260 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
1261 return $evt if $evt;
1265 # the assumption is that you would not void the overdues unless you
1266 # were also charging for the item and/or applying a processing fee
1268 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
1269 return $evt if $evt;
1272 my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
1273 return $evt if $evt;
1275 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1276 $ses->request('open-ils.trigger.event.autocreate', 'checkout.damaged', $circ, $circ->circ_lib);
1281 return OpenILS::Event->new('DAMAGE_CHARGE',
1292 # ----------------------------------------------------------------------
1293 __PACKAGE__->register_method(
1294 method => 'mark_item_missing_pieces',
1295 api_name => 'open-ils.circ.mark_item_missing_pieces',
1297 Changes the status of a copy to "damaged" or to a custom status based on the
1298 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1300 @param authtoken The login session key
1301 @param copy_id The ID of the copy to mark as damaged
1302 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1306 sub mark_item_missing_pieces {
1307 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1308 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1309 my $e = new_editor(authtoken=>$auth, xact =>1);
1310 return $e->die_event unless $e->checkauth;
1313 my $copy = $e->retrieve_asset_copy([
1315 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1316 or return $e->die_event;
1319 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1320 $copy->circ_lib : $copy->call_number->owning_lib;
1322 return $e->die_event unless $e->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1324 #### grab the last circulation
1325 my $circ = $e->search_action_circulation([
1326 { target_copy => $copy->id},
1328 order_by => {circ => "xact_start DESC"}
1333 if (! $circ->checkin_time) { # if circ active, attempt renew
1334 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1335 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1336 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1337 $circ = $res->[0]->{payload}{'circ'};
1338 $circ->target_copy( $copy->id );
1339 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1341 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1346 'copy_id'=>$circ->target_copy,
1347 'patron_id'=>$circ->usr,
1348 'skip_deposit_fee'=>1,
1349 'skip_rental_fee'=>1
1352 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1354 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1355 $e, $copy, $e->requestor, 1 );
1357 if ($hold) { # needed for hold? then due now
1359 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1360 my $due_date = DateTime->now(time_zone => 'local');
1361 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1363 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1367 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params);
1368 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1369 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1370 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1371 $circ = $res->[0]->{payload}{'circ'};
1373 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1379 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1381 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1384 ### Update the item status
1386 my $custom_stat = $U->ou_ancestor_setting_value(
1387 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1388 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1390 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1391 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1393 $copy->status($stat);
1394 $copy->edit_date('now');
1395 $copy->editor($e->requestor->id);
1397 $e->update_asset_copy($copy) or return $e->die_event;
1399 my $holds = $e->search_action_hold_request(
1401 current_copy => $copy->id,
1402 fulfillment_time => undef,
1403 cancel_time => undef,
1407 $logger->debug("resetting holds that target the marked copy");
1408 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1412 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1413 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1415 return OpenILS::Event->new('SUCCESS',
1419 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1420 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1425 return $e->die_event;
1433 # ----------------------------------------------------------------------
1434 __PACKAGE__->register_method(
1435 method => 'magic_fetch',
1436 api_name => 'open-ils.agent.fetch'
1439 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1442 my( $self, $conn, $auth, $args ) = @_;
1443 my $e = new_editor( authtoken => $auth );
1444 return $e->event unless $e->checkauth;
1446 my $hint = $$args{hint};
1447 my $id = $$args{id};
1449 # Is the call allowed to fetch this type of object?
1450 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1452 # Find the class the implements the given hint
1453 my ($class) = grep {
1454 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1456 $class =~ s/Fieldmapper:://og;
1457 $class =~ s/::/_/og;
1458 my $method = "retrieve_$class";
1460 my $obj = $e->$method($id) or return $e->event;
1463 # ----------------------------------------------------------------------
1466 __PACKAGE__->register_method(
1467 method => "fleshed_circ_retrieve",
1469 api_name => "open-ils.circ.fleshed.retrieve",);
1471 sub fleshed_circ_retrieve {
1472 my( $self, $client, $id ) = @_;
1473 my $e = new_editor();
1474 my $circ = $e->retrieve_action_circulation(
1480 circ => [ qw/ target_copy / ],
1481 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number / ],
1482 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1483 acn => [ qw/ record / ],
1487 ) or return $e->event;
1489 my $copy = $circ->target_copy;
1490 my $vol = $copy->call_number;
1491 my $rec = $circ->target_copy->call_number->record;
1493 $vol->record($rec->id);
1494 $copy->call_number($vol->id);
1495 $circ->target_copy($copy->id);
1499 if( $rec->id == OILS_PRECAT_RECORD ) {
1503 $mvr = $U->record_to_mvr($rec);
1504 $rec->marc(''); # drop the bulky marc data
1518 __PACKAGE__->register_method(
1519 method => "test_batch_circ_events",
1520 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1523 # method for testing the behavior of a given event definition
1524 sub test_batch_circ_events {
1525 my($self, $conn, $auth, $event_def, $barcode) = @_;
1527 my $e = new_editor(authtoken => $auth);
1528 return $e->event unless $e->checkauth;
1529 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1531 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1532 or return $e->event;
1534 my $circ = $e->search_action_circulation(
1535 {target_copy => $copy->id, checkin_time => undef})->[0]
1536 or return $e->event;
1538 return undef unless $circ;
1540 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1544 __PACKAGE__->register_method(
1545 method => "fire_circ_events",
1546 api_name => "open-ils.circ.fire_circ_trigger_events",
1548 General event def runner for circ objects. If no event def ID
1549 is provided, the hook will be used to find the best event_def
1550 match based on the context org unit
1554 __PACKAGE__->register_method(
1555 method => "fire_circ_events",
1556 api_name => "open-ils.circ.fire_hold_trigger_events",
1558 General event def runner for hold objects. If no event def ID
1559 is provided, the hook will be used to find the best event_def
1560 match based on the context org unit
1564 __PACKAGE__->register_method(
1565 method => "fire_circ_events",
1566 api_name => "open-ils.circ.fire_user_trigger_events",
1568 General event def runner for user objects. If no event def ID
1569 is provided, the hook will be used to find the best event_def
1570 match based on the context org unit
1575 sub fire_circ_events {
1576 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1578 my $e = new_editor(authtoken => $auth, xact => 1);
1579 return $e->event unless $e->checkauth;
1583 if($self->api_name =~ /hold/) {
1584 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1585 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1586 } elsif($self->api_name =~ /user/) {
1587 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1588 $targets = $e->batch_retrieve_actor_user($target_ids);
1590 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1591 $targets = $e->batch_retrieve_action_circulation($target_ids);
1593 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1594 # simply making this method authoritative because of weirdness
1595 # with transaction handling in A/T code that causes rollback
1596 # failure down the line if handling many targets
1598 return undef unless @$targets;
1599 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1602 __PACKAGE__->register_method(
1603 method => "user_payments_list",
1604 api_name => "open-ils.circ.user_payments.filtered.batch",
1607 desc => q/Returns a fleshed, date-limited set of all payments a user
1608 has made. By default, ordered by payment date. Optionally
1609 ordered by other columns in the top-level "mp" object/,
1611 {desc => 'Authentication token', type => 'string'},
1612 {desc => 'User ID', type => 'number'},
1613 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1615 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1616 and the related fully-realized payment object (e.g money.cash_payment)/}
1620 sub user_payments_list {
1621 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1623 my $e = new_editor(authtoken => $auth);
1624 return $e->event unless $e->checkauth;
1626 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1627 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1629 $order_by ||= ['payment_ts'];
1631 # all payments by user, between start_date and end_date
1632 my $payments = $e->json_query({
1633 select => {mp => ['id']},
1637 fkey => 'xact', field => 'id'}
1641 '+mbt' => {usr => $user_id},
1642 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1644 order_by => {mp => $order_by}
1647 for my $payment_id (@$payments) {
1648 my $payment = $e->retrieve_money_payment([
1656 'credit_card_payment',
1671 $conn->respond($payment);
1678 __PACKAGE__->register_method(
1679 method => "retrieve_circ_chain",
1680 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1683 desc => q/Given a circulation, this returns all circulation objects
1684 that are part of the same chain of renewals./,
1686 {desc => 'Authentication token', type => 'string'},
1687 {desc => 'Circ ID', type => 'number'},
1689 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1693 __PACKAGE__->register_method(
1694 method => "retrieve_circ_chain",
1695 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1697 desc => q/Given a circulation, this returns a summary of the circulation objects
1698 that are part of the same chain of renewals./,
1700 {desc => 'Authentication token', type => 'string'},
1701 {desc => 'Circ ID', type => 'number'},
1703 return => {desc => q/Circulation Chain Summary/}
1707 sub retrieve_circ_chain {
1708 my($self, $conn, $auth, $circ_id) = @_;
1710 my $e = new_editor(authtoken => $auth);
1711 return $e->event unless $e->checkauth;
1712 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1714 if($self->api_name =~ /summary/) {
1715 return $U->create_circ_chain_summary($e, $circ_id);
1719 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1721 for my $circ_info (@$chain) {
1722 my $circ = Fieldmapper::action::circulation->new;
1723 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1724 $conn->respond($circ);
1731 __PACKAGE__->register_method(
1732 method => "retrieve_prev_circ_chain",
1733 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1736 desc => q/Given a circulation, this returns all circulation objects
1737 that are part of the previous chain of renewals./,
1739 {desc => 'Authentication token', type => 'string'},
1740 {desc => 'Circ ID', type => 'number'},
1742 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1746 __PACKAGE__->register_method(
1747 method => "retrieve_prev_circ_chain",
1748 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1750 desc => q/Given a circulation, this returns a summary of the circulation objects
1751 that are part of the previous chain of renewals./,
1753 {desc => 'Authentication token', type => 'string'},
1754 {desc => 'Circ ID', type => 'number'},
1756 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1760 sub retrieve_prev_circ_chain {
1761 my($self, $conn, $auth, $circ_id) = @_;
1763 my $e = new_editor(authtoken => $auth);
1764 return $e->event unless $e->checkauth;
1765 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1767 if($self->api_name =~ /summary/) {
1768 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1769 my $target_copy = $$first_circ{'target_copy'};
1770 my $usr = $$first_circ{'usr'};
1771 my $last_circ_from_prev_chain = $e->json_query({
1772 'select' => { 'circ' => ['id','usr'] },
1775 target_copy => $target_copy,
1776 xact_start => { '<' => $$first_circ{'xact_start'} }
1778 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1781 return undef unless $last_circ_from_prev_chain;
1782 return undef unless $$last_circ_from_prev_chain{'id'};
1783 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1784 return undef unless $sum;
1785 my $obj = Fieldmapper::action::circ_chain_summary->new;
1786 $obj->$_($sum->{$_}) for keys %$sum;
1787 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1791 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1792 my $target_copy = $$first_circ{'target_copy'};
1793 my $last_circ_from_prev_chain = $e->json_query({
1794 'select' => { 'circ' => ['id'] },
1797 target_copy => $target_copy,
1798 xact_start => { '<' => $$first_circ{'xact_start'} }
1800 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1803 return undef unless $last_circ_from_prev_chain;
1804 return undef unless $$last_circ_from_prev_chain{'id'};
1805 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1807 for my $circ_info (@$chain) {
1808 my $circ = Fieldmapper::action::circulation->new;
1809 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1810 $conn->respond($circ);
1818 __PACKAGE__->register_method(
1819 method => "get_copy_due_date",
1820 api_name => "open-ils.circ.copy.due_date.retrieve",
1823 Given a copy ID, returns the due date for the copy if it's
1824 currently circulating. Otherwise, returns null. Note, this is a public
1825 method requiring no authentication. Only the due date is exposed.
1828 {desc => 'Copy ID', type => 'number'}
1830 return => {desc => q/
1831 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1836 sub get_copy_due_date {
1837 my($self, $conn, $copy_id) = @_;
1838 my $e = new_editor();
1840 my $circ = $e->json_query({
1841 select => {circ => ['due_date']},
1844 target_copy => $copy_id,
1845 checkin_time => undef,
1847 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1848 {stop_fines => undef}
1852 })->[0] or return undef;
1854 return $circ->{due_date};
1861 # {"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}}