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);
1278 my $evt2 = OpenILS::Utils::Penalty->calculate_penalties($e, $circ->usr->id, $e->requestor->ws_ou);
1279 return $evt2 if $evt2;
1284 return OpenILS::Event->new('DAMAGE_CHARGE',
1295 # ----------------------------------------------------------------------
1296 __PACKAGE__->register_method(
1297 method => 'mark_item_missing_pieces',
1298 api_name => 'open-ils.circ.mark_item_missing_pieces',
1300 Changes the status of a copy to "damaged" or to a custom status based on the
1301 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1303 @param authtoken The login session key
1304 @param copy_id The ID of the copy to mark as damaged
1305 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1309 sub mark_item_missing_pieces {
1310 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1311 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1312 ### FIXME: Even better, we're going to use two transactions, the first to affect pertinent holds before checkout can
1314 my $e2 = new_editor(authtoken=>$auth, xact =>1);
1315 return $e2->die_event unless $e2->checkauth;
1318 my $copy = $e2->retrieve_asset_copy([
1320 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1321 or return $e2->die_event;
1324 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1325 $copy->circ_lib : $copy->call_number->owning_lib;
1327 return $e2->die_event unless $e2->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1329 #### grab the last circulation
1330 my $circ = $e2->search_action_circulation([
1331 { target_copy => $copy->id},
1333 order_by => {circ => "xact_start DESC"}
1338 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1340 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1343 my $holds = $e2->search_action_hold_request(
1345 current_copy => $copy->id,
1346 fulfillment_time => undef,
1347 cancel_time => undef,
1351 $logger->debug("resetting holds that target the marked copy");
1352 OpenILS::Application::Circ::Holds->_reset_hold($e2->requestor, $_) for @$holds;
1355 if (! $e2->commit) {
1356 return $e2->die_event;
1359 my $e = new_editor(authtoken=>$auth, xact =>1);
1360 return $e->die_event unless $e->checkauth;
1362 if (! $circ->checkin_time) { # if circ active, attempt renew
1363 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1364 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1365 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1366 $circ = $res->[0]->{payload}{'circ'};
1367 $circ->target_copy( $copy->id );
1368 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1370 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1375 'copy_id'=>$circ->target_copy,
1376 'patron_id'=>$circ->usr,
1377 'skip_deposit_fee'=>1,
1378 'skip_rental_fee'=>1
1381 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1383 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1384 $e, $copy, $e->requestor, 1 );
1386 if ($hold) { # needed for hold? then due now
1388 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1389 my $due_date = DateTime->now(time_zone => 'local');
1390 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1392 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1396 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params);
1397 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1398 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1399 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1400 $circ = $res->[0]->{payload}{'circ'};
1402 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1408 ### Update the item status
1410 my $custom_stat = $U->ou_ancestor_setting_value(
1411 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1412 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1414 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1415 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1417 $copy->status($stat);
1418 $copy->edit_date('now');
1419 $copy->editor($e->requestor->id);
1421 $e->update_asset_copy($copy) or return $e->die_event;
1425 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1426 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1428 return OpenILS::Event->new('SUCCESS',
1432 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1433 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1438 return $e->die_event;
1446 # ----------------------------------------------------------------------
1447 __PACKAGE__->register_method(
1448 method => 'magic_fetch',
1449 api_name => 'open-ils.agent.fetch'
1452 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1455 my( $self, $conn, $auth, $args ) = @_;
1456 my $e = new_editor( authtoken => $auth );
1457 return $e->event unless $e->checkauth;
1459 my $hint = $$args{hint};
1460 my $id = $$args{id};
1462 # Is the call allowed to fetch this type of object?
1463 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1465 # Find the class the implements the given hint
1466 my ($class) = grep {
1467 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1469 $class =~ s/Fieldmapper:://og;
1470 $class =~ s/::/_/og;
1471 my $method = "retrieve_$class";
1473 my $obj = $e->$method($id) or return $e->event;
1476 # ----------------------------------------------------------------------
1479 __PACKAGE__->register_method(
1480 method => "fleshed_circ_retrieve",
1482 api_name => "open-ils.circ.fleshed.retrieve",);
1484 sub fleshed_circ_retrieve {
1485 my( $self, $client, $id ) = @_;
1486 my $e = new_editor();
1487 my $circ = $e->retrieve_action_circulation(
1493 circ => [ qw/ target_copy / ],
1494 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number parts / ],
1495 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1496 acn => [ qw/ record / ],
1500 ) or return $e->event;
1502 my $copy = $circ->target_copy;
1503 my $vol = $copy->call_number;
1504 my $rec = $circ->target_copy->call_number->record;
1506 $vol->record($rec->id);
1507 $copy->call_number($vol->id);
1508 $circ->target_copy($copy->id);
1512 if( $rec->id == OILS_PRECAT_RECORD ) {
1516 $mvr = $U->record_to_mvr($rec);
1517 $rec->marc(''); # drop the bulky marc data
1531 __PACKAGE__->register_method(
1532 method => "test_batch_circ_events",
1533 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1536 # method for testing the behavior of a given event definition
1537 sub test_batch_circ_events {
1538 my($self, $conn, $auth, $event_def, $barcode) = @_;
1540 my $e = new_editor(authtoken => $auth);
1541 return $e->event unless $e->checkauth;
1542 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1544 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1545 or return $e->event;
1547 my $circ = $e->search_action_circulation(
1548 {target_copy => $copy->id, checkin_time => undef})->[0]
1549 or return $e->event;
1551 return undef unless $circ;
1553 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1557 __PACKAGE__->register_method(
1558 method => "fire_circ_events",
1559 api_name => "open-ils.circ.fire_circ_trigger_events",
1561 General event def runner for circ objects. If no event def ID
1562 is provided, the hook will be used to find the best event_def
1563 match based on the context org unit
1567 __PACKAGE__->register_method(
1568 method => "fire_circ_events",
1569 api_name => "open-ils.circ.fire_hold_trigger_events",
1571 General event def runner for hold objects. If no event def ID
1572 is provided, the hook will be used to find the best event_def
1573 match based on the context org unit
1577 __PACKAGE__->register_method(
1578 method => "fire_circ_events",
1579 api_name => "open-ils.circ.fire_user_trigger_events",
1581 General event def runner for user objects. If no event def ID
1582 is provided, the hook will be used to find the best event_def
1583 match based on the context org unit
1588 sub fire_circ_events {
1589 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1591 my $e = new_editor(authtoken => $auth, xact => 1);
1592 return $e->event unless $e->checkauth;
1596 if($self->api_name =~ /hold/) {
1597 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1598 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1599 } elsif($self->api_name =~ /user/) {
1600 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1601 $targets = $e->batch_retrieve_actor_user($target_ids);
1603 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1604 $targets = $e->batch_retrieve_action_circulation($target_ids);
1606 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1607 # simply making this method authoritative because of weirdness
1608 # with transaction handling in A/T code that causes rollback
1609 # failure down the line if handling many targets
1611 return undef unless @$targets;
1612 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1615 __PACKAGE__->register_method(
1616 method => "user_payments_list",
1617 api_name => "open-ils.circ.user_payments.filtered.batch",
1620 desc => q/Returns a fleshed, date-limited set of all payments a user
1621 has made. By default, ordered by payment date. Optionally
1622 ordered by other columns in the top-level "mp" object/,
1624 {desc => 'Authentication token', type => 'string'},
1625 {desc => 'User ID', type => 'number'},
1626 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1628 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1629 and the related fully-realized payment object (e.g money.cash_payment)/}
1633 sub user_payments_list {
1634 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1636 my $e = new_editor(authtoken => $auth);
1637 return $e->event unless $e->checkauth;
1639 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1640 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1642 $order_by ||= ['payment_ts'];
1644 # all payments by user, between start_date and end_date
1645 my $payments = $e->json_query({
1646 select => {mp => ['id']},
1650 fkey => 'xact', field => 'id'}
1654 '+mbt' => {usr => $user_id},
1655 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1657 order_by => {mp => $order_by}
1660 for my $payment_id (@$payments) {
1661 my $payment = $e->retrieve_money_payment([
1669 'credit_card_payment',
1684 $conn->respond($payment);
1691 __PACKAGE__->register_method(
1692 method => "retrieve_circ_chain",
1693 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1696 desc => q/Given a circulation, this returns all circulation objects
1697 that are part of the same chain of renewals./,
1699 {desc => 'Authentication token', type => 'string'},
1700 {desc => 'Circ ID', type => 'number'},
1702 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1706 __PACKAGE__->register_method(
1707 method => "retrieve_circ_chain",
1708 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1710 desc => q/Given a circulation, this returns a summary of the circulation objects
1711 that are part of the same chain of renewals./,
1713 {desc => 'Authentication token', type => 'string'},
1714 {desc => 'Circ ID', type => 'number'},
1716 return => {desc => q/Circulation Chain Summary/}
1720 sub retrieve_circ_chain {
1721 my($self, $conn, $auth, $circ_id) = @_;
1723 my $e = new_editor(authtoken => $auth);
1724 return $e->event unless $e->checkauth;
1725 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1727 if($self->api_name =~ /summary/) {
1728 return $U->create_circ_chain_summary($e, $circ_id);
1732 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1734 for my $circ_info (@$chain) {
1735 my $circ = Fieldmapper::action::circulation->new;
1736 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1737 $conn->respond($circ);
1744 __PACKAGE__->register_method(
1745 method => "retrieve_prev_circ_chain",
1746 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1749 desc => q/Given a circulation, this returns all circulation objects
1750 that are part of the previous chain of renewals./,
1752 {desc => 'Authentication token', type => 'string'},
1753 {desc => 'Circ ID', type => 'number'},
1755 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1759 __PACKAGE__->register_method(
1760 method => "retrieve_prev_circ_chain",
1761 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1763 desc => q/Given a circulation, this returns a summary of the circulation objects
1764 that are part of the previous chain of renewals./,
1766 {desc => 'Authentication token', type => 'string'},
1767 {desc => 'Circ ID', type => 'number'},
1769 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1773 sub retrieve_prev_circ_chain {
1774 my($self, $conn, $auth, $circ_id) = @_;
1776 my $e = new_editor(authtoken => $auth);
1777 return $e->event unless $e->checkauth;
1778 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1780 if($self->api_name =~ /summary/) {
1781 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1782 my $target_copy = $$first_circ{'target_copy'};
1783 my $usr = $$first_circ{'usr'};
1784 my $last_circ_from_prev_chain = $e->json_query({
1785 'select' => { 'circ' => ['id','usr'] },
1788 target_copy => $target_copy,
1789 xact_start => { '<' => $$first_circ{'xact_start'} }
1791 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1794 return undef unless $last_circ_from_prev_chain;
1795 return undef unless $$last_circ_from_prev_chain{'id'};
1796 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1797 return undef unless $sum;
1798 my $obj = Fieldmapper::action::circ_chain_summary->new;
1799 $obj->$_($sum->{$_}) for keys %$sum;
1800 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1804 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1805 my $target_copy = $$first_circ{'target_copy'};
1806 my $last_circ_from_prev_chain = $e->json_query({
1807 'select' => { 'circ' => ['id'] },
1810 target_copy => $target_copy,
1811 xact_start => { '<' => $$first_circ{'xact_start'} }
1813 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1816 return undef unless $last_circ_from_prev_chain;
1817 return undef unless $$last_circ_from_prev_chain{'id'};
1818 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1820 for my $circ_info (@$chain) {
1821 my $circ = Fieldmapper::action::circulation->new;
1822 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1823 $conn->respond($circ);
1831 __PACKAGE__->register_method(
1832 method => "get_copy_due_date",
1833 api_name => "open-ils.circ.copy.due_date.retrieve",
1836 Given a copy ID, returns the due date for the copy if it's
1837 currently circulating. Otherwise, returns null. Note, this is a public
1838 method requiring no authentication. Only the due date is exposed.
1841 {desc => 'Copy ID', type => 'number'}
1843 return => {desc => q/
1844 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1849 sub get_copy_due_date {
1850 my($self, $conn, $copy_id) = @_;
1851 my $e = new_editor();
1853 my $circ = $e->json_query({
1854 select => {circ => ['due_date']},
1857 target_copy => $copy_id,
1858 checkin_time => undef,
1860 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1861 {stop_fines => undef}
1865 })->[0] or return undef;
1867 return $circ->{due_date};
1874 # {"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}}