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'],
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 return $e->die_event unless $e->allowed('UPDATE_COPY', $owning_lib);
1144 my $perm = 'MARK_ITEM_MISSING';
1145 my $stat = OILS_COPY_STATUS_MISSING;
1147 if( $self->api_name =~ /damaged/ ) {
1148 $perm = 'MARK_ITEM_DAMAGED';
1149 $stat = OILS_COPY_STATUS_DAMAGED;
1150 my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
1151 return $evt if $evt;
1153 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1154 $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
1156 } elsif ( $self->api_name =~ /bindery/ ) {
1157 $perm = 'MARK_ITEM_BINDERY';
1158 $stat = OILS_COPY_STATUS_BINDERY;
1159 } elsif ( $self->api_name =~ /on_order/ ) {
1160 $perm = 'MARK_ITEM_ON_ORDER';
1161 $stat = OILS_COPY_STATUS_ON_ORDER;
1162 } elsif ( $self->api_name =~ /ill/ ) {
1163 $perm = 'MARK_ITEM_ILL';
1164 $stat = OILS_COPY_STATUS_ILL;
1165 } elsif ( $self->api_name =~ /cataloging/ ) {
1166 $perm = 'MARK_ITEM_CATALOGING';
1167 $stat = OILS_COPY_STATUS_CATALOGING;
1168 } elsif ( $self->api_name =~ /reserves/ ) {
1169 $perm = 'MARK_ITEM_RESERVES';
1170 $stat = OILS_COPY_STATUS_RESERVES;
1171 } elsif ( $self->api_name =~ /discard/ ) {
1172 $perm = 'MARK_ITEM_DISCARD';
1173 $stat = OILS_COPY_STATUS_DISCARD;
1177 $copy->status($stat);
1178 $copy->edit_date('now');
1179 $copy->editor($e->requestor->id);
1181 $e->update_asset_copy($copy) or return $e->die_event;
1183 my $holds = $e->search_action_hold_request(
1185 current_copy => $copy->id,
1186 fulfillment_time => undef,
1187 cancel_time => undef,
1193 $logger->debug("resetting holds that target the marked copy");
1194 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1199 sub handle_mark_damaged {
1200 my($e, $copy, $owning_lib, $args) = @_;
1202 my $apply = $args->{apply_fines} || '';
1203 return undef if $apply eq 'noapply';
1205 my $new_amount = $args->{override_amount};
1206 my $new_btype = $args->{override_btype};
1207 my $new_note = $args->{override_note};
1209 # grab the last circulation
1210 my $circ = $e->search_action_circulation([
1211 { target_copy => $copy->id},
1213 order_by => {circ => "xact_start DESC"},
1215 flesh_fields => {circ => ['target_copy', 'usr'], au => ['card']}
1219 return undef unless $circ;
1221 my $charge_price = $U->ou_ancestor_setting_value(
1222 $owning_lib, 'circ.charge_on_damaged', $e);
1224 my $proc_fee = $U->ou_ancestor_setting_value(
1225 $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
1227 my $void_overdue = $U->ou_ancestor_setting_value(
1228 $owning_lib, 'circ.damaged.void_ovedue', $e) || 0;
1230 return undef unless $charge_price or $proc_fee;
1232 my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
1233 my $total = $copy_price + $proc_fee;
1237 if($new_amount and $new_btype) {
1239 # Allow staff to override the amount to charge for a damaged item
1240 # Consider the case where the item is only partially damaged
1241 # This value is meant to take the place of the item price and
1242 # optional processing fee.
1244 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1245 $e, $new_amount, $new_btype, 'Damaged Item Override', $circ->id, $new_note);
1246 return $evt if $evt;
1250 if($charge_price and $copy_price) {
1251 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1252 $e, $copy_price, 7, 'Damaged Item', $circ->id);
1253 return $evt if $evt;
1257 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1258 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
1259 return $evt if $evt;
1263 # the assumption is that you would not void the overdues unless you
1264 # were also charging for the item and/or applying a processing fee
1266 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
1267 return $evt if $evt;
1270 my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
1271 return $evt if $evt;
1273 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1274 $ses->request('open-ils.trigger.event.autocreate', 'checkout.damaged', $circ, $circ->circ_lib);
1279 return OpenILS::Event->new('DAMAGE_CHARGE',
1290 # ----------------------------------------------------------------------
1291 __PACKAGE__->register_method(
1292 method => 'mark_item_missing_pieces',
1293 api_name => 'open-ils.circ.mark_item_missing_pieces',
1295 Changes the status of a copy to "damaged" or to a custom status based on the
1296 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1298 @param authtoken The login session key
1299 @param copy_id The ID of the copy to mark as damaged
1300 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1304 sub mark_item_missing_pieces {
1305 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1306 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1307 my $e = new_editor(authtoken=>$auth, xact =>1);
1308 return $e->die_event unless $e->checkauth;
1311 my $copy = $e->retrieve_asset_copy([
1313 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1314 or return $e->die_event;
1317 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1318 $copy->circ_lib : $copy->call_number->owning_lib;
1320 return $e->die_event unless $e->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1322 #### grab the last circulation
1323 my $circ = $e->search_action_circulation([
1324 { target_copy => $copy->id},
1326 order_by => {circ => "xact_start DESC"}
1331 if (! $circ->checkin_time) { # if circ active, attempt renew
1332 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1333 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1334 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1335 $circ = $res->[0]->{payload}{'circ'};
1336 $circ->target_copy( $copy->id );
1337 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1339 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1344 'copy_id'=>$circ->target_copy,
1345 'patron_id'=>$circ->usr,
1346 'skip_deposit_fee'=>1,
1347 'skip_rental_fee'=>1
1350 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1352 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1353 $e, $copy, $e->requestor, 1 );
1355 if ($hold) { # needed for hold? then due now
1357 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1358 my $due_date = DateTime->now(time_zone => 'local');
1359 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1361 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1365 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params);
1366 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1367 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1368 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1369 $circ = $res->[0]->{payload}{'circ'};
1371 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1377 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1379 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1382 ### Update the item status
1384 my $custom_stat = $U->ou_ancestor_setting_value(
1385 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1386 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1388 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1389 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1391 $copy->status($stat);
1392 $copy->edit_date('now');
1393 $copy->editor($e->requestor->id);
1395 $e->update_asset_copy($copy) or return $e->die_event;
1397 my $holds = $e->search_action_hold_request(
1399 current_copy => $copy->id,
1400 fulfillment_time => undef,
1401 cancel_time => undef,
1405 $logger->debug("resetting holds that target the marked copy");
1406 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1410 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1411 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1413 return OpenILS::Event->new('SUCCESS',
1417 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1418 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1423 return $e->die_event;
1431 # ----------------------------------------------------------------------
1432 __PACKAGE__->register_method(
1433 method => 'magic_fetch',
1434 api_name => 'open-ils.agent.fetch'
1437 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1440 my( $self, $conn, $auth, $args ) = @_;
1441 my $e = new_editor( authtoken => $auth );
1442 return $e->event unless $e->checkauth;
1444 my $hint = $$args{hint};
1445 my $id = $$args{id};
1447 # Is the call allowed to fetch this type of object?
1448 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1450 # Find the class the implements the given hint
1451 my ($class) = grep {
1452 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1454 $class =~ s/Fieldmapper:://og;
1455 $class =~ s/::/_/og;
1456 my $method = "retrieve_$class";
1458 my $obj = $e->$method($id) or return $e->event;
1461 # ----------------------------------------------------------------------
1464 __PACKAGE__->register_method(
1465 method => "fleshed_circ_retrieve",
1467 api_name => "open-ils.circ.fleshed.retrieve",);
1469 sub fleshed_circ_retrieve {
1470 my( $self, $client, $id ) = @_;
1471 my $e = new_editor();
1472 my $circ = $e->retrieve_action_circulation(
1478 circ => [ qw/ target_copy / ],
1479 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number / ],
1480 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1481 acn => [ qw/ record / ],
1485 ) or return $e->event;
1487 my $copy = $circ->target_copy;
1488 my $vol = $copy->call_number;
1489 my $rec = $circ->target_copy->call_number->record;
1491 $vol->record($rec->id);
1492 $copy->call_number($vol->id);
1493 $circ->target_copy($copy->id);
1497 if( $rec->id == OILS_PRECAT_RECORD ) {
1501 $mvr = $U->record_to_mvr($rec);
1502 $rec->marc(''); # drop the bulky marc data
1516 __PACKAGE__->register_method(
1517 method => "test_batch_circ_events",
1518 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1521 # method for testing the behavior of a given event definition
1522 sub test_batch_circ_events {
1523 my($self, $conn, $auth, $event_def, $barcode) = @_;
1525 my $e = new_editor(authtoken => $auth);
1526 return $e->event unless $e->checkauth;
1527 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1529 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1530 or return $e->event;
1532 my $circ = $e->search_action_circulation(
1533 {target_copy => $copy->id, checkin_time => undef})->[0]
1534 or return $e->event;
1536 return undef unless $circ;
1538 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1542 __PACKAGE__->register_method(
1543 method => "fire_circ_events",
1544 api_name => "open-ils.circ.fire_circ_trigger_events",
1546 General event def runner for circ objects. If no event def ID
1547 is provided, the hook will be used to find the best event_def
1548 match based on the context org unit
1552 __PACKAGE__->register_method(
1553 method => "fire_circ_events",
1554 api_name => "open-ils.circ.fire_hold_trigger_events",
1556 General event def runner for hold objects. If no event def ID
1557 is provided, the hook will be used to find the best event_def
1558 match based on the context org unit
1562 __PACKAGE__->register_method(
1563 method => "fire_circ_events",
1564 api_name => "open-ils.circ.fire_user_trigger_events",
1566 General event def runner for user objects. If no event def ID
1567 is provided, the hook will be used to find the best event_def
1568 match based on the context org unit
1573 sub fire_circ_events {
1574 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1576 my $e = new_editor(authtoken => $auth, xact => 1);
1577 return $e->event unless $e->checkauth;
1581 if($self->api_name =~ /hold/) {
1582 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1583 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1584 } elsif($self->api_name =~ /user/) {
1585 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1586 $targets = $e->batch_retrieve_actor_user($target_ids);
1588 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1589 $targets = $e->batch_retrieve_action_circulation($target_ids);
1591 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1592 # simply making this method authoritative because of weirdness
1593 # with transaction handling in A/T code that causes rollback
1594 # failure down the line if handling many targets
1596 return undef unless @$targets;
1597 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1600 __PACKAGE__->register_method(
1601 method => "user_payments_list",
1602 api_name => "open-ils.circ.user_payments.filtered.batch",
1605 desc => q/Returns a fleshed, date-limited set of all payments a user
1606 has made. By default, ordered by payment date. Optionally
1607 ordered by other columns in the top-level "mp" object/,
1609 {desc => 'Authentication token', type => 'string'},
1610 {desc => 'User ID', type => 'number'},
1611 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1613 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1614 and the related fully-realized payment object (e.g money.cash_payment)/}
1618 sub user_payments_list {
1619 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1621 my $e = new_editor(authtoken => $auth);
1622 return $e->event unless $e->checkauth;
1624 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1625 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1627 $order_by ||= ['payment_ts'];
1629 # all payments by user, between start_date and end_date
1630 my $payments = $e->json_query({
1631 select => {mp => ['id']},
1635 fkey => 'xact', field => 'id'}
1639 '+mbt' => {usr => $user_id},
1640 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1642 order_by => {mp => $order_by}
1645 for my $payment_id (@$payments) {
1646 my $payment = $e->retrieve_money_payment([
1654 'credit_card_payment',
1669 $conn->respond($payment);
1676 __PACKAGE__->register_method(
1677 method => "retrieve_circ_chain",
1678 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1681 desc => q/Given a circulation, this returns all circulation objects
1682 that are part of the same chain of renewals./,
1684 {desc => 'Authentication token', type => 'string'},
1685 {desc => 'Circ ID', type => 'number'},
1687 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1691 __PACKAGE__->register_method(
1692 method => "retrieve_circ_chain",
1693 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1695 desc => q/Given a circulation, this returns a summary of the circulation objects
1696 that are part of the same chain of renewals./,
1698 {desc => 'Authentication token', type => 'string'},
1699 {desc => 'Circ ID', type => 'number'},
1701 return => {desc => q/Circulation Chain Summary/}
1705 sub retrieve_circ_chain {
1706 my($self, $conn, $auth, $circ_id) = @_;
1708 my $e = new_editor(authtoken => $auth);
1709 return $e->event unless $e->checkauth;
1710 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1712 if($self->api_name =~ /summary/) {
1713 return $U->create_circ_chain_summary($e, $circ_id);
1717 my $chain = $e->json_query({from => ['action.circ_chain', $circ_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);
1729 __PACKAGE__->register_method(
1730 method => "retrieve_prev_circ_chain",
1731 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1734 desc => q/Given a circulation, this returns all circulation objects
1735 that are part of the previous chain of renewals./,
1737 {desc => 'Authentication token', type => 'string'},
1738 {desc => 'Circ ID', type => 'number'},
1740 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1744 __PACKAGE__->register_method(
1745 method => "retrieve_prev_circ_chain",
1746 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1748 desc => q/Given a circulation, this returns a summary of the circulation objects
1749 that are part of the previous chain of renewals./,
1751 {desc => 'Authentication token', type => 'string'},
1752 {desc => 'Circ ID', type => 'number'},
1754 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1758 sub retrieve_prev_circ_chain {
1759 my($self, $conn, $auth, $circ_id) = @_;
1761 my $e = new_editor(authtoken => $auth);
1762 return $e->event unless $e->checkauth;
1763 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1765 if($self->api_name =~ /summary/) {
1766 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1767 my $target_copy = $$first_circ{'target_copy'};
1768 my $usr = $$first_circ{'usr'};
1769 my $last_circ_from_prev_chain = $e->json_query({
1770 'select' => { 'circ' => ['id','usr'] },
1773 target_copy => $target_copy,
1774 xact_start => { '<' => $$first_circ{'xact_start'} }
1776 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1779 return undef unless $last_circ_from_prev_chain;
1780 return undef unless $$last_circ_from_prev_chain{'id'};
1781 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1782 return undef unless $sum;
1783 my $obj = Fieldmapper::action::circ_chain_summary->new;
1784 $obj->$_($sum->{$_}) for keys %$sum;
1785 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1789 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1790 my $target_copy = $$first_circ{'target_copy'};
1791 my $last_circ_from_prev_chain = $e->json_query({
1792 'select' => { 'circ' => ['id'] },
1795 target_copy => $target_copy,
1796 xact_start => { '<' => $$first_circ{'xact_start'} }
1798 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1801 return undef unless $last_circ_from_prev_chain;
1802 return undef unless $$last_circ_from_prev_chain{'id'};
1803 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1805 for my $circ_info (@$chain) {
1806 my $circ = Fieldmapper::action::circulation->new;
1807 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1808 $conn->respond($circ);
1816 __PACKAGE__->register_method(
1817 method => "get_copy_due_date",
1818 api_name => "open-ils.circ.copy.due_date.retrieve",
1821 Given a copy ID, returns the due date for the copy if it's
1822 currently circulating. Otherwise, returns null. Note, this is a public
1823 method requiring no authentication. Only the due date is exposed.
1826 {desc => 'Copy ID', type => 'number'}
1828 return => {desc => q/
1829 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1834 sub get_copy_due_date {
1835 my($self, $conn, $copy_id) = @_;
1836 my $e = new_editor();
1838 my $circ = $e->json_query({
1839 select => {circ => ['due_date']},
1842 target_copy => $copy_id,
1843 checkin_time => undef,
1845 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1846 {stop_fines => undef}
1850 })->[0] or return undef;
1852 return $circ->{due_date};
1859 # {"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}}