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','peer_record_maps'],
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 ### FIXME: Even better, we're going to use two transactions, the first to affect pertinent holds before checkout can
1311 my $e2 = new_editor(authtoken=>$auth, xact =>1);
1312 return $e2->die_event unless $e2->checkauth;
1315 my $copy = $e2->retrieve_asset_copy([
1317 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1318 or return $e2->die_event;
1321 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1322 $copy->circ_lib : $copy->call_number->owning_lib;
1324 return $e2->die_event unless $e2->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1326 #### grab the last circulation
1327 my $circ = $e2->search_action_circulation([
1328 { target_copy => $copy->id},
1330 order_by => {circ => "xact_start DESC"}
1335 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1337 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1340 my $holds = $e2->search_action_hold_request(
1342 current_copy => $copy->id,
1343 fulfillment_time => undef,
1344 cancel_time => undef,
1348 $logger->debug("resetting holds that target the marked copy");
1349 OpenILS::Application::Circ::Holds->_reset_hold($e2->requestor, $_) for @$holds;
1352 if (! $e2->commit) {
1353 return $e2->die_event;
1356 my $e = new_editor(authtoken=>$auth, xact =>1);
1357 return $e->die_event unless $e->checkauth;
1359 if (! $circ->checkin_time) { # if circ active, attempt renew
1360 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1361 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1362 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1363 $circ = $res->[0]->{payload}{'circ'};
1364 $circ->target_copy( $copy->id );
1365 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1367 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1372 'copy_id'=>$circ->target_copy,
1373 'patron_id'=>$circ->usr,
1374 'skip_deposit_fee'=>1,
1375 'skip_rental_fee'=>1
1378 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1380 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1381 $e, $copy, $e->requestor, 1 );
1383 if ($hold) { # needed for hold? then due now
1385 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1386 my $due_date = DateTime->now(time_zone => 'local');
1387 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1389 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1393 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params);
1394 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1395 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1396 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1397 $circ = $res->[0]->{payload}{'circ'};
1399 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1405 ### Update the item status
1407 my $custom_stat = $U->ou_ancestor_setting_value(
1408 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1409 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1411 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1412 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1414 $copy->status($stat);
1415 $copy->edit_date('now');
1416 $copy->editor($e->requestor->id);
1418 $e->update_asset_copy($copy) or return $e->die_event;
1422 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1423 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1425 return OpenILS::Event->new('SUCCESS',
1429 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1430 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1435 return $e->die_event;
1443 # ----------------------------------------------------------------------
1444 __PACKAGE__->register_method(
1445 method => 'magic_fetch',
1446 api_name => 'open-ils.agent.fetch'
1449 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1452 my( $self, $conn, $auth, $args ) = @_;
1453 my $e = new_editor( authtoken => $auth );
1454 return $e->event unless $e->checkauth;
1456 my $hint = $$args{hint};
1457 my $id = $$args{id};
1459 # Is the call allowed to fetch this type of object?
1460 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1462 # Find the class the implements the given hint
1463 my ($class) = grep {
1464 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1466 $class =~ s/Fieldmapper:://og;
1467 $class =~ s/::/_/og;
1468 my $method = "retrieve_$class";
1470 my $obj = $e->$method($id) or return $e->event;
1473 # ----------------------------------------------------------------------
1476 __PACKAGE__->register_method(
1477 method => "fleshed_circ_retrieve",
1479 api_name => "open-ils.circ.fleshed.retrieve",);
1481 sub fleshed_circ_retrieve {
1482 my( $self, $client, $id ) = @_;
1483 my $e = new_editor();
1484 my $circ = $e->retrieve_action_circulation(
1490 circ => [ qw/ target_copy / ],
1491 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number parts / ],
1492 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1493 acn => [ qw/ record / ],
1497 ) or return $e->event;
1499 my $copy = $circ->target_copy;
1500 my $vol = $copy->call_number;
1501 my $rec = $circ->target_copy->call_number->record;
1503 $vol->record($rec->id);
1504 $copy->call_number($vol->id);
1505 $circ->target_copy($copy->id);
1509 if( $rec->id == OILS_PRECAT_RECORD ) {
1513 $mvr = $U->record_to_mvr($rec);
1514 $rec->marc(''); # drop the bulky marc data
1528 __PACKAGE__->register_method(
1529 method => "test_batch_circ_events",
1530 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1533 # method for testing the behavior of a given event definition
1534 sub test_batch_circ_events {
1535 my($self, $conn, $auth, $event_def, $barcode) = @_;
1537 my $e = new_editor(authtoken => $auth);
1538 return $e->event unless $e->checkauth;
1539 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1541 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1542 or return $e->event;
1544 my $circ = $e->search_action_circulation(
1545 {target_copy => $copy->id, checkin_time => undef})->[0]
1546 or return $e->event;
1548 return undef unless $circ;
1550 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1554 __PACKAGE__->register_method(
1555 method => "fire_circ_events",
1556 api_name => "open-ils.circ.fire_circ_trigger_events",
1558 General event def runner for circ 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_hold_trigger_events",
1568 General event def runner for hold 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
1574 __PACKAGE__->register_method(
1575 method => "fire_circ_events",
1576 api_name => "open-ils.circ.fire_user_trigger_events",
1578 General event def runner for user objects. If no event def ID
1579 is provided, the hook will be used to find the best event_def
1580 match based on the context org unit
1585 sub fire_circ_events {
1586 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1588 my $e = new_editor(authtoken => $auth, xact => 1);
1589 return $e->event unless $e->checkauth;
1593 if($self->api_name =~ /hold/) {
1594 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1595 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1596 } elsif($self->api_name =~ /user/) {
1597 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1598 $targets = $e->batch_retrieve_actor_user($target_ids);
1600 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1601 $targets = $e->batch_retrieve_action_circulation($target_ids);
1603 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1604 # simply making this method authoritative because of weirdness
1605 # with transaction handling in A/T code that causes rollback
1606 # failure down the line if handling many targets
1608 return undef unless @$targets;
1609 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1612 __PACKAGE__->register_method(
1613 method => "user_payments_list",
1614 api_name => "open-ils.circ.user_payments.filtered.batch",
1617 desc => q/Returns a fleshed, date-limited set of all payments a user
1618 has made. By default, ordered by payment date. Optionally
1619 ordered by other columns in the top-level "mp" object/,
1621 {desc => 'Authentication token', type => 'string'},
1622 {desc => 'User ID', type => 'number'},
1623 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1625 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1626 and the related fully-realized payment object (e.g money.cash_payment)/}
1630 sub user_payments_list {
1631 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1633 my $e = new_editor(authtoken => $auth);
1634 return $e->event unless $e->checkauth;
1636 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1637 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1639 $order_by ||= ['payment_ts'];
1641 # all payments by user, between start_date and end_date
1642 my $payments = $e->json_query({
1643 select => {mp => ['id']},
1647 fkey => 'xact', field => 'id'}
1651 '+mbt' => {usr => $user_id},
1652 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1654 order_by => {mp => $order_by}
1657 for my $payment_id (@$payments) {
1658 my $payment = $e->retrieve_money_payment([
1666 'credit_card_payment',
1681 $conn->respond($payment);
1688 __PACKAGE__->register_method(
1689 method => "retrieve_circ_chain",
1690 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1693 desc => q/Given a circulation, this returns all circulation objects
1694 that are part of the same chain of renewals./,
1696 {desc => 'Authentication token', type => 'string'},
1697 {desc => 'Circ ID', type => 'number'},
1699 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1703 __PACKAGE__->register_method(
1704 method => "retrieve_circ_chain",
1705 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1707 desc => q/Given a circulation, this returns a summary of the circulation objects
1708 that are part of the same chain of renewals./,
1710 {desc => 'Authentication token', type => 'string'},
1711 {desc => 'Circ ID', type => 'number'},
1713 return => {desc => q/Circulation Chain Summary/}
1717 sub retrieve_circ_chain {
1718 my($self, $conn, $auth, $circ_id) = @_;
1720 my $e = new_editor(authtoken => $auth);
1721 return $e->event unless $e->checkauth;
1722 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1724 if($self->api_name =~ /summary/) {
1725 return $U->create_circ_chain_summary($e, $circ_id);
1729 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1731 for my $circ_info (@$chain) {
1732 my $circ = Fieldmapper::action::circulation->new;
1733 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1734 $conn->respond($circ);
1741 __PACKAGE__->register_method(
1742 method => "retrieve_prev_circ_chain",
1743 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1746 desc => q/Given a circulation, this returns all circulation objects
1747 that are part of the previous chain of renewals./,
1749 {desc => 'Authentication token', type => 'string'},
1750 {desc => 'Circ ID', type => 'number'},
1752 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1756 __PACKAGE__->register_method(
1757 method => "retrieve_prev_circ_chain",
1758 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1760 desc => q/Given a circulation, this returns a summary of the circulation objects
1761 that are part of the previous chain of renewals./,
1763 {desc => 'Authentication token', type => 'string'},
1764 {desc => 'Circ ID', type => 'number'},
1766 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1770 sub retrieve_prev_circ_chain {
1771 my($self, $conn, $auth, $circ_id) = @_;
1773 my $e = new_editor(authtoken => $auth);
1774 return $e->event unless $e->checkauth;
1775 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1777 if($self->api_name =~ /summary/) {
1778 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1779 my $target_copy = $$first_circ{'target_copy'};
1780 my $usr = $$first_circ{'usr'};
1781 my $last_circ_from_prev_chain = $e->json_query({
1782 'select' => { 'circ' => ['id','usr'] },
1785 target_copy => $target_copy,
1786 xact_start => { '<' => $$first_circ{'xact_start'} }
1788 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1791 return undef unless $last_circ_from_prev_chain;
1792 return undef unless $$last_circ_from_prev_chain{'id'};
1793 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1794 return undef unless $sum;
1795 my $obj = Fieldmapper::action::circ_chain_summary->new;
1796 $obj->$_($sum->{$_}) for keys %$sum;
1797 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1801 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1802 my $target_copy = $$first_circ{'target_copy'};
1803 my $last_circ_from_prev_chain = $e->json_query({
1804 'select' => { 'circ' => ['id'] },
1807 target_copy => $target_copy,
1808 xact_start => { '<' => $$first_circ{'xact_start'} }
1810 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1813 return undef unless $last_circ_from_prev_chain;
1814 return undef unless $$last_circ_from_prev_chain{'id'};
1815 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1817 for my $circ_info (@$chain) {
1818 my $circ = Fieldmapper::action::circulation->new;
1819 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1820 $conn->respond($circ);
1828 __PACKAGE__->register_method(
1829 method => "get_copy_due_date",
1830 api_name => "open-ils.circ.copy.due_date.retrieve",
1833 Given a copy ID, returns the due date for the copy if it's
1834 currently circulating. Otherwise, returns null. Note, this is a public
1835 method requiring no authentication. Only the due date is exposed.
1838 {desc => 'Copy ID', type => 'number'}
1840 return => {desc => q/
1841 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1846 sub get_copy_due_date {
1847 my($self, $conn, $copy_id) = @_;
1848 my $e = new_editor();
1850 my $circ = $e->json_query({
1851 select => {circ => ['due_date']},
1854 target_copy => $copy_id,
1855 checkin_time => undef,
1857 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1858 {stop_fines => undef}
1862 })->[0] or return undef;
1864 return $circ->{due_date};
1871 # {"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}}