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 => 'ranged_billing_types',
84 api_name => 'open-ils.circ.billing_type.ranged.retrieve.all');
86 sub ranged_billing_types {
87 my($self, $conn, $auth, $org_id, $depth) = @_;
88 my $e = new_editor(authtoken => $auth);
89 return $e->event unless $e->checkauth;
90 return $e->event unless $e->allowed('VIEW_BILLING_TYPE', $org_id);
91 return $e->search_config_billing_type(
92 {owner => $U->get_org_full_path($org_id, $depth)});
97 # ------------------------------------------------------------------------
98 # Returns an array of {circ, record} hashes checked out by the user.
99 # ------------------------------------------------------------------------
100 __PACKAGE__->register_method(
101 method => "checkouts_by_user",
102 api_name => "open-ils.circ.actor.user.checked_out",
104 NOTES => <<" NOTES");
105 Returns a list of open circulations as a pile of objects. Each object
106 contains the relevant copy, circ, and record
109 sub checkouts_by_user {
110 my($self, $client, $auth, $user_id) = @_;
112 my $e = new_editor(authtoken=>$auth);
113 return $e->event unless $e->checkauth;
115 my $circ_ids = $e->search_action_circulation(
117 checkin_time => undef,
119 {stop_fines => undef},
120 {stop_fines => ['MAXFINES','LONGOVERDUE']}
126 for my $id (@$circ_ids) {
127 my $circ = $e->retrieve_action_circulation([
131 circ => ['target_copy'],
132 acp => ['call_number'],
138 # un-flesh for consistency
139 my $c = $circ->target_copy;
140 $circ->target_copy($c->id);
142 my $cn = $c->call_number;
143 $c->call_number($cn->id);
151 record => $U->record_to_mvr($t)
161 __PACKAGE__->register_method(
162 method => "checkouts_by_user_slim",
163 api_name => "open-ils.circ.actor.user.checked_out.slim",
164 NOTES => <<" NOTES");
165 Returns a list of open circulation objects
169 sub checkouts_by_user_slim {
170 my( $self, $client, $user_session, $user_id ) = @_;
172 my( $requestor, $target, $copy, $record, $evt );
174 ( $requestor, $target, $evt ) =
175 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
178 $logger->debug( 'User ' . $requestor->id .
179 " retrieving checked out items for user " . $target->id );
181 # XXX Make the call correct..
182 return $apputils->simplereq(
184 "open-ils.cstore.direct.action.open_circulation.search.atomic",
185 { usr => $target->id, checkin_time => undef } );
186 # { usr => $target->id } );
190 __PACKAGE__->register_method(
191 method => "checkouts_by_user_opac",
192 api_name => "open-ils.circ.actor.user.checked_out.opac",);
195 sub checkouts_by_user_opac {
196 my( $self, $client, $auth, $user_id ) = @_;
198 my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
199 return $e->event unless $e->checkauth;
200 $user_id ||= $e->requestor->id;
201 return $e->event unless
202 my $patron = $e->retrieve_actor_user($user_id);
205 my $search = {usr => $user_id, stop_fines => undef};
207 if( $user_id ne $e->requestor->id ) {
208 $data = $e->search_action_circulation(
209 $search, {checkperm=>1, permorg=>$patron->home_ou})
213 $data = $e->search_action_circulation($search);
220 __PACKAGE__->register_method(
221 method => "title_from_transaction",
222 api_name => "open-ils.circ.circ_transaction.find_title",
223 NOTES => <<" NOTES");
224 Returns a mods object for the title that is linked to from the
225 copy from the hold that created the given transaction
228 sub title_from_transaction {
229 my( $self, $client, $login_session, $transactionid ) = @_;
231 my( $user, $circ, $title, $evt );
233 ( $user, $evt ) = $apputils->checkses( $login_session );
236 ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
239 ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
242 return $apputils->record_to_mvr($title);
245 __PACKAGE__->register_method(
246 method => "staff_age_to_lost",
247 api_name => "open-ils.circ.circulation.age_to_lost",
250 This fires a circ.staff_age_to_lost Action-Trigger event against all
251 overdue circulations in scope of the specified context library and
252 user profile, which effectively marks the associated items as Lost.
253 This is likely to be done at the end of a semester in an academic
256 @param args : circ_lib, user_profile
260 sub staff_age_to_lost {
261 my( $self, $conn, $auth, $args ) = @_;
263 my $orgs = $U->get_org_descendants($args->{'circ_lib'});
264 my $profiles = $U->fetch_permission_group_descendants($args->{'user_profile'});
266 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
268 my $method = 'open-ils.trigger.passive.event.autocreate.batch';
269 my $hook = 'circ.staff_age_to_lost';
270 my $context_org = 'circ_lib';
271 my $opt_granularity = undef;
273 "checkin_time" => undef,
274 "due_date" => { "<" => "now" },
276 { "stop_fines" => ["MAXFINES", "LONGOVERDUE"] }, # FIXME: CLAIMSRETURNED also?
277 { "stop_fines" => undef }
281 "select" => {"au" => ["id"]},
284 "profile" => $profiles,
285 "id" => { "=" => {"+circ" => "usr"} }
289 "select" => {"aou" => ["id"]},
293 {"id" => { "=" => {"+circ" => "circ_lib"} }},
300 my $req_timeout = 10800;
301 my $chunk_size = 100;
304 my $req = $ses->request($method, $hook, $context_org, $filter, $opt_granularity);
305 my @event_ids; my @chunked_ids;
306 while (my $resp = $req->recv(timeout => $req_timeout)) {
307 push(@event_ids, $resp->content);
308 push(@chunked_ids, $resp->content);
309 if (scalar(@chunked_ids) > $chunk_size) {
310 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
314 if (scalar(@chunked_ids) > 0) {
315 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
319 $logger->info("staff_age_to_lost: created ".scalar(@event_ids)." events for circ.staff_age_to_lost");
320 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>scalar(@event_ids)});
321 } elsif($req->complete) {
322 $logger->info("staff_age_to_lost: no events to create for circ.staff_age_to_lost");
323 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>0});
325 $logger->warn("staff_age_to_lost: timeout occurred during event creation for circ.staff_age_to_lost");
326 $conn->respond_complete({'total_progress'=>$progress-1,'error'=>'timeout'});
333 __PACKAGE__->register_method(
334 method => "new_set_circ_lost",
335 api_name => "open-ils.circ.circulation.set_lost",
337 Sets the copy and related open circulation to lost
339 @param args : barcode
344 # ---------------------------------------------------------------------
345 # Sets a circulation to lost. updates copy status to lost
346 # applies copy and/or prcoessing fees depending on org settings
347 # ---------------------------------------------------------------------
348 sub new_set_circ_lost {
349 my( $self, $conn, $auth, $args ) = @_;
351 my $e = new_editor(authtoken=>$auth, xact=>1);
352 return $e->die_event unless $e->checkauth;
354 my $copy = $e->search_asset_copy({barcode=>$$args{barcode}, deleted=>'f'})->[0]
355 or return $e->die_event;
357 my $evt = OpenILS::Application::Cat::AssetCommon->set_item_lost($e, $copy->id);
365 __PACKAGE__->register_method(
366 method => "set_circ_claims_returned",
367 api_name => "open-ils.circ.circulation.set_claims_returned",
369 desc => q/Sets the circ for a given item as claims returned
370 If a backdate is provided, overdue fines will be voided
371 back to the backdate/,
373 {desc => 'Authentication token', type => 'string'},
374 {desc => 'Arguments, including "barcode" and optional "backdate"', type => 'object'}
376 return => {desc => q/1 on success, failure event on error, and
377 PATRON_EXCEEDS_CLAIMS_RETURN_COUNT if the patron exceeds the
378 configured claims return maximum/}
382 __PACKAGE__->register_method(
383 method => "set_circ_claims_returned",
384 api_name => "open-ils.circ.circulation.set_claims_returned.override",
386 desc => q/This adds support for overrideing the configured max
387 claims returned amount.
388 @see open-ils.circ.circulation.set_claims_returned./,
392 sub set_circ_claims_returned {
393 my( $self, $conn, $auth, $args, $oargs ) = @_;
395 my $e = new_editor(authtoken=>$auth, xact=>1);
396 return $e->die_event unless $e->checkauth;
398 $oargs = { all => 1 } unless defined $oargs;
400 my $barcode = $$args{barcode};
401 my $backdate = $$args{backdate};
403 my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0]
404 or return $e->die_event;
406 my $circ = $e->search_action_circulation(
407 {checkin_time => undef, target_copy => $copy->id})->[0]
408 or return $e->die_event;
410 $backdate = $circ->due_date if $$args{use_due_date};
412 $logger->info("marking circ for item $barcode as claims returned".
413 (($backdate) ? " with backdate $backdate" : ''));
415 my $patron = $e->retrieve_actor_user($circ->usr);
416 my $max_count = $U->ou_ancestor_setting_value(
417 $circ->circ_lib, 'circ.max_patron_claim_return_count', $e);
419 # If the patron has too instances of many claims returned,
420 # require an override to continue. A configured max of
421 # 0 means all attempts require an override
422 if(defined $max_count and $patron->claims_returned_count >= $max_count) {
424 if($self->api_name =~ /override/ && ($oargs->{all} || grep { $_ eq 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT' } @{$oargs->{events}})) {
426 # see if we're allowed to override
427 return $e->die_event unless
428 $e->allowed('SET_CIRC_CLAIMS_RETURNED.override', $circ->circ_lib);
432 # exit early and return the max claims return event
434 return OpenILS::Event->new(
435 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT',
437 patron_count => $patron->claims_returned_count,
438 max_count => $max_count
444 $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib)
445 or return $e->die_event;
447 $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
448 $circ->stop_fines_time('now') unless $circ->stop_fines_time;
451 $backdate = cleanse_ISO8601($backdate);
453 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
454 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($backdate);
455 $backdate = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
457 # clean it up once again; need a : in the timezone offset. E.g. -06:00 not -0600
458 $backdate = cleanse_ISO8601($backdate);
460 # make it look like the circ stopped at the cliams returned time
461 $circ->stop_fines_time($backdate);
462 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
466 $e->update_action_circulation($circ) or return $e->die_event;
468 # see if there is a configured post-claims-return copy status
469 if(my $stat = $U->ou_ancestor_setting_value($circ->circ_lib, 'circ.claim_return.copy_status')) {
470 $copy->status($stat);
471 $copy->edit_date('now');
472 $copy->editor($e->requestor->id);
473 $e->update_asset_copy($copy) or return $e->die_event;
481 __PACKAGE__->register_method(
482 method => "post_checkin_backdate_circ",
483 api_name => "open-ils.circ.post_checkin_backdate",
485 desc => q/Back-date an already checked in circulation/,
487 {desc => 'Authentication token', type => 'string'},
488 {desc => 'Circ ID', type => 'number'},
489 {desc => 'ISO8601 backdate', type => 'string'},
491 return => {desc => q/1 on success, failure event on error/}
495 __PACKAGE__->register_method(
496 method => "post_checkin_backdate_circ",
497 api_name => "open-ils.circ.post_checkin_backdate.batch",
500 desc => q/@see open-ils.circ.post_checkin_backdate. Batch mode/,
502 {desc => 'Authentication token', type => 'string'},
503 {desc => 'List of Circ ID', type => 'array'},
504 {desc => 'ISO8601 backdate', type => 'string'},
506 return => {desc => q/Set of: 1 on success, failure event on error/}
511 sub post_checkin_backdate_circ {
512 my( $self, $conn, $auth, $circ_id, $backdate ) = @_;
513 my $e = new_editor(authtoken=>$auth);
514 return $e->die_event unless $e->checkauth;
515 if($self->api_name =~ /batch/) {
516 foreach my $c (@$circ_id) {
517 $conn->respond(post_checkin_backdate_circ_impl($e, $c, $backdate));
520 $conn->respond_complete(post_checkin_backdate_circ_impl($e, $circ_id, $backdate));
528 sub post_checkin_backdate_circ_impl {
529 my($e, $circ_id, $backdate) = @_;
533 my $circ = $e->retrieve_action_circulation($circ_id)
534 or return $e->die_event;
536 # anyone with checkin perms can backdate (more restrictive?)
537 return $e->die_event unless $e->allowed('COPY_CHECKIN', $circ->circ_lib);
539 # don't allow back-dating an open circulation
540 return OpenILS::Event->new('BAD_PARAMS') unless
541 $backdate and $circ->checkin_time;
543 # update the checkin and stop_fines times to reflect the new backdate
544 $circ->stop_fines_time(cleanse_ISO8601($backdate));
545 $circ->checkin_time(cleanse_ISO8601($backdate));
546 $e->update_action_circulation($circ) or return $e->die_event;
548 # now void the overdues "erased" by the back-dating
549 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
552 # If the circ was closed before and the balance owned !=0, re-open the transaction
553 $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
562 __PACKAGE__->register_method (
563 method => 'set_circ_due_date',
564 api_name => 'open-ils.circ.circulation.due_date.update',
566 Updates the due_date on the given circ
568 @param circid The id of the circ to update
569 @param date The timestamp of the new due date
573 sub set_circ_due_date {
574 my( $self, $conn, $auth, $circ_id, $date ) = @_;
576 my $e = new_editor(xact=>1, authtoken=>$auth);
577 return $e->die_event unless $e->checkauth;
578 my $circ = $e->retrieve_action_circulation($circ_id)
579 or return $e->die_event;
581 return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
582 $date = cleanse_ISO8601($date);
584 if (!(interval_to_seconds($circ->duration) % 86400)) { # duration is divisible by days
585 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
586 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($date);
587 $date = cleanse_ISO8601( $new_date->ymd . 'T' . $original_date->strftime('%T%z') );
590 $circ->due_date($date);
591 $e->update_action_circulation($circ) or return $e->die_event;
598 __PACKAGE__->register_method(
599 method => "create_in_house_use",
600 api_name => 'open-ils.circ.in_house_use.create',
602 Creates an in-house use action.
603 @param $authtoken The login session key
604 @param params A hash of params including
605 'location' The org unit id where the in-house use occurs
606 'copyid' The copy in question
607 'count' The number of in-house uses to apply to this copy
608 @return An array of id's representing the id's of the newly created
609 in-house use objects or an event on an error
612 __PACKAGE__->register_method(
613 method => "create_in_house_use",
614 api_name => 'open-ils.circ.non_cat_in_house_use.create',
618 sub create_in_house_use {
619 my( $self, $client, $auth, $params ) = @_;
622 my $org = $params->{location};
623 my $copyid = $params->{copyid};
624 my $count = $params->{count} || 1;
625 my $nc_type = $params->{non_cat_type};
626 my $use_time = $params->{use_time} || 'now';
628 my $e = new_editor(xact=>1,authtoken=>$auth);
629 return $e->event unless $e->checkauth;
630 return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
632 my $non_cat = 1 if $self->api_name =~ /non_cat/;
636 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
638 $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
644 if( $use_time ne 'now' ) {
645 $use_time = cleanse_ISO8601($use_time);
646 $logger->debug("in_house_use setting use time to $use_time");
657 $ihu = Fieldmapper::action::non_cat_in_house_use->new;
658 $ihu->item_type($nc_type);
659 $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
660 $cmeth = "create_action_non_cat_in_house_use";
663 $ihu = Fieldmapper::action::in_house_use->new;
665 $method = 'open-ils.storage.direct.action.in_house_use.create';
666 $cmeth = "create_action_in_house_use";
669 $ihu->staff($e->requestor->id);
670 $ihu->org_unit($org);
671 $ihu->use_time($use_time);
673 $ihu = $e->$cmeth($ihu) or return $e->event;
674 push( @ids, $ihu->id );
685 __PACKAGE__->register_method(
686 method => "view_circs",
687 api_name => "open-ils.circ.copy_checkout_history.retrieve",
689 Retrieves the last X circs for a given copy
690 @param authtoken The login session key
691 @param copyid The copy to check
692 @param count How far to go back in the item history
693 @return An array of circ ids
696 # ----------------------------------------------------------------------
697 # Returns $count most recent circs. If count exceeds the configured
698 # max, use the configured max instead
699 # ----------------------------------------------------------------------
701 my( $self, $client, $authtoken, $copyid, $count ) = @_;
703 my $e = new_editor(authtoken => $authtoken);
704 return $e->event unless $e->checkauth;
706 my $copy = $e->retrieve_asset_copy([
709 flesh_fields => {acp => ['call_number']}
711 ]) or return $e->event;
713 return $e->event unless $e->allowed(
714 'VIEW_COPY_CHECKOUT_HISTORY',
715 ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ?
716 $copy->circ_lib : $copy->call_number->owning_lib);
718 my $max_history = $U->ou_ancestor_setting_value(
719 $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
721 if(defined $max_history) {
722 $count = $max_history unless defined $count and $count < $max_history;
724 $count = 4 unless defined $count;
727 return $e->search_action_circulation([
728 {target_copy => $copyid},
729 {limit => $count, order_by => { circ => "xact_start DESC" }}
734 __PACKAGE__->register_method(
735 method => "circ_count",
736 api_name => "open-ils.circ.circulation.count",
738 Returns the number of times the item has circulated
739 @param copyid The copy to check
743 my( $self, $client, $copyid, $range ) = @_;
744 my $e = OpenILS::Utils::Editor->new;
745 return $e->request('open-ils.storage.asset.copy.circ_count', $copyid, $range);
750 __PACKAGE__->register_method(
751 method => 'fetch_notes',
753 api_name => 'open-ils.circ.copy_note.retrieve.all',
755 Returns an array of copy note objects.
756 @param args A named hash of parameters including:
757 authtoken : Required if viewing non-public notes
758 itemid : The id of the item whose notes we want to retrieve
759 pub : True if all the caller wants are public notes
760 @return An array of note objects
763 __PACKAGE__->register_method(
764 method => 'fetch_notes',
765 api_name => 'open-ils.circ.call_number_note.retrieve.all',
766 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
768 __PACKAGE__->register_method(
769 method => 'fetch_notes',
770 api_name => 'open-ils.circ.title_note.retrieve.all',
771 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
774 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
776 my( $self, $connection, $args ) = @_;
778 my $id = $$args{itemid};
779 my $authtoken = $$args{authtoken};
782 if( $self->api_name =~ /copy/ ) {
784 return $U->cstorereq(
785 'open-ils.cstore.direct.asset.copy_note.search.atomic',
786 { owning_copy => $id, pub => 't' } );
788 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
790 return $U->cstorereq(
791 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
794 } elsif( $self->api_name =~ /call_number/ ) {
796 return $U->cstorereq(
797 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
798 { call_number => $id, pub => 't' } );
800 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
802 return $U->cstorereq(
803 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
806 } elsif( $self->api_name =~ /title/ ) {
808 return $U->cstorereq(
809 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
810 { record => $id, pub => 't' } );
812 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
814 return $U->cstorereq(
815 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
822 __PACKAGE__->register_method(
823 method => 'has_notes',
824 api_name => 'open-ils.circ.copy.has_notes');
825 __PACKAGE__->register_method(
826 method => 'has_notes',
827 api_name => 'open-ils.circ.call_number.has_notes');
828 __PACKAGE__->register_method(
829 method => 'has_notes',
830 api_name => 'open-ils.circ.title.has_notes');
834 my( $self, $conn, $authtoken, $id ) = @_;
835 my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
836 return $editor->event unless $editor->checkauth;
838 my $n = $editor->search_asset_copy_note(
839 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
841 $n = $editor->search_asset_call_number_note(
842 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
844 $n = $editor->search_biblio_record_note(
845 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
852 __PACKAGE__->register_method(
853 method => 'create_copy_note',
854 api_name => 'open-ils.circ.copy_note.create',
856 Creates a new copy note
857 @param authtoken The login session key
858 @param note The note object to create
859 @return The id of the new note object
862 sub create_copy_note {
863 my( $self, $connection, $authtoken, $note ) = @_;
865 my $e = new_editor(xact=>1, authtoken=>$authtoken);
866 return $e->event unless $e->checkauth;
867 my $copy = $e->retrieve_asset_copy(
871 flesh_fields => { 'acp' => ['call_number'] }
876 return $e->event unless
877 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
879 $note->create_date('now');
880 $note->creator($e->requestor->id);
881 $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
884 $e->create_asset_copy_note($note) or return $e->event;
890 __PACKAGE__->register_method(
891 method => 'delete_copy_note',
892 api_name => 'open-ils.circ.copy_note.delete',
894 Deletes an existing copy note
895 @param authtoken The login session key
896 @param noteid The id of the note to delete
897 @return 1 on success - Event otherwise.
899 sub delete_copy_note {
900 my( $self, $conn, $authtoken, $noteid ) = @_;
902 my $e = new_editor(xact=>1, authtoken=>$authtoken);
903 return $e->die_event unless $e->checkauth;
905 my $note = $e->retrieve_asset_copy_note([
909 'acpn' => [ 'owning_copy' ],
910 'acp' => [ 'call_number' ],
913 ]) or return $e->die_event;
915 if( $note->creator ne $e->requestor->id ) {
916 return $e->die_event unless
917 $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
920 $e->delete_asset_copy_note($note) or return $e->die_event;
926 __PACKAGE__->register_method(
927 method => 'age_hold_rules',
928 api_name => 'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
932 my( $self, $conn ) = @_;
933 return new_editor()->retrieve_all_config_rules_age_hold_protect();
938 __PACKAGE__->register_method(
939 method => 'copy_details_barcode',
941 api_name => 'open-ils.circ.copy_details.retrieve.barcode');
942 sub copy_details_barcode {
943 my( $self, $conn, $auth, $barcode ) = @_;
944 my $e = new_editor();
945 my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
946 return $e->event unless $cid;
947 return copy_details( $self, $conn, $auth, $cid );
951 __PACKAGE__->register_method(
952 method => 'copy_details',
953 api_name => 'open-ils.circ.copy_details.retrieve');
956 my( $self, $conn, $auth, $copy_id ) = @_;
957 my $e = new_editor(authtoken=>$auth);
958 return $e->event unless $e->checkauth;
960 my $flesh = { flesh => 1 };
962 my $copy = $e->retrieve_asset_copy(
968 acp => ['call_number','parts','peer_record_maps'],
969 acn => ['record','prefix','suffix','label_class']
972 ]) or return $e->event;
975 # De-flesh the copy for backwards compatibility
977 my $vol = $copy->call_number;
979 $copy->call_number($vol->id);
980 my $record = $vol->record;
982 $vol->record($record->id);
983 $mvr = $U->record_to_mvr($record);
988 my $hold = $e->search_action_hold_request(
990 current_copy => $copy_id,
991 capture_time => { "!=" => undef },
992 fulfillment_time => undef,
993 cancel_time => undef,
997 OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
999 my $transit = $e->search_action_transit_copy(
1000 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
1002 # find the latest circ, open or closed
1003 my $circ = $e->search_action_circulation(
1005 { target_copy => $copy_id },
1011 'checkin_workstation',
1014 'recurring_fine_rule'
1017 order_by => { circ => 'xact_start desc' },
1027 transit => $transit,
1037 __PACKAGE__->register_method(
1038 method => 'mark_item',
1039 api_name => 'open-ils.circ.mark_item_damaged',
1041 Changes the status of a copy to "damaged". Requires MARK_ITEM_DAMAGED permission.
1042 @param authtoken The login session key
1043 @param copy_id The ID of the copy to mark as damaged
1044 @return 1 on success - Event otherwise.
1047 __PACKAGE__->register_method(
1048 method => 'mark_item',
1049 api_name => 'open-ils.circ.mark_item_missing',
1051 Changes the status of a copy to "missing". Requires MARK_ITEM_MISSING permission.
1052 @param authtoken The login session key
1053 @param copy_id The ID of the copy to mark as missing
1054 @return 1 on success - Event otherwise.
1057 __PACKAGE__->register_method(
1058 method => 'mark_item',
1059 api_name => 'open-ils.circ.mark_item_bindery',
1061 Changes the status of a copy to "bindery". Requires MARK_ITEM_BINDERY permission.
1062 @param authtoken The login session key
1063 @param copy_id The ID of the copy to mark as bindery
1064 @return 1 on success - Event otherwise.
1067 __PACKAGE__->register_method(
1068 method => 'mark_item',
1069 api_name => 'open-ils.circ.mark_item_on_order',
1071 Changes the status of a copy to "on order". Requires MARK_ITEM_ON_ORDER permission.
1072 @param authtoken The login session key
1073 @param copy_id The ID of the copy to mark as on order
1074 @return 1 on success - Event otherwise.
1077 __PACKAGE__->register_method(
1078 method => 'mark_item',
1079 api_name => 'open-ils.circ.mark_item_ill',
1081 Changes the status of a copy to "inter-library loan". Requires MARK_ITEM_ILL permission.
1082 @param authtoken The login session key
1083 @param copy_id The ID of the copy to mark as inter-library loan
1084 @return 1 on success - Event otherwise.
1087 __PACKAGE__->register_method(
1088 method => 'mark_item',
1089 api_name => 'open-ils.circ.mark_item_cataloging',
1091 Changes the status of a copy to "cataloging". Requires MARK_ITEM_CATALOGING permission.
1092 @param authtoken The login session key
1093 @param copy_id The ID of the copy to mark as cataloging
1094 @return 1 on success - Event otherwise.
1097 __PACKAGE__->register_method(
1098 method => 'mark_item',
1099 api_name => 'open-ils.circ.mark_item_reserves',
1101 Changes the status of a copy to "reserves". Requires MARK_ITEM_RESERVES permission.
1102 @param authtoken The login session key
1103 @param copy_id The ID of the copy to mark as reserves
1104 @return 1 on success - Event otherwise.
1107 __PACKAGE__->register_method(
1108 method => 'mark_item',
1109 api_name => 'open-ils.circ.mark_item_discard',
1111 Changes the status of a copy to "discard". Requires MARK_ITEM_DISCARD permission.
1112 @param authtoken The login session key
1113 @param copy_id The ID of the copy to mark as discard
1114 @return 1 on success - Event otherwise.
1119 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1120 my $e = new_editor(authtoken=>$auth, xact =>1);
1121 return $e->die_event unless $e->checkauth;
1124 my $copy = $e->retrieve_asset_copy([
1126 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1127 or return $e->die_event;
1130 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1131 $copy->circ_lib : $copy->call_number->owning_lib;
1133 my $perm = 'MARK_ITEM_MISSING';
1134 my $stat = OILS_COPY_STATUS_MISSING;
1136 if( $self->api_name =~ /damaged/ ) {
1137 $perm = 'MARK_ITEM_DAMAGED';
1138 $stat = OILS_COPY_STATUS_DAMAGED;
1139 my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
1140 return $evt if $evt;
1142 } elsif ( $self->api_name =~ /bindery/ ) {
1143 $perm = 'MARK_ITEM_BINDERY';
1144 $stat = OILS_COPY_STATUS_BINDERY;
1145 } elsif ( $self->api_name =~ /on_order/ ) {
1146 $perm = 'MARK_ITEM_ON_ORDER';
1147 $stat = OILS_COPY_STATUS_ON_ORDER;
1148 } elsif ( $self->api_name =~ /ill/ ) {
1149 $perm = 'MARK_ITEM_ILL';
1150 $stat = OILS_COPY_STATUS_ILL;
1151 } elsif ( $self->api_name =~ /cataloging/ ) {
1152 $perm = 'MARK_ITEM_CATALOGING';
1153 $stat = OILS_COPY_STATUS_CATALOGING;
1154 } elsif ( $self->api_name =~ /reserves/ ) {
1155 $perm = 'MARK_ITEM_RESERVES';
1156 $stat = OILS_COPY_STATUS_RESERVES;
1157 } elsif ( $self->api_name =~ /discard/ ) {
1158 $perm = 'MARK_ITEM_DISCARD';
1159 $stat = OILS_COPY_STATUS_DISCARD;
1162 # caller may proceed if either perm is allowed
1163 return $e->die_event unless $e->allowed([$perm, 'UPDATE_COPY'], $owning_lib);
1165 $copy->status($stat);
1166 $copy->edit_date('now');
1167 $copy->editor($e->requestor->id);
1169 $e->update_asset_copy($copy) or return $e->die_event;
1171 my $holds = $e->search_action_hold_request(
1173 current_copy => $copy->id,
1174 fulfillment_time => undef,
1175 cancel_time => undef,
1181 if( $self->api_name =~ /damaged/ ) {
1182 # now that we've committed the changes, create related A/T events
1183 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1184 $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
1187 $logger->debug("resetting holds that target the marked copy");
1188 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1193 sub handle_mark_damaged {
1194 my($e, $copy, $owning_lib, $args) = @_;
1196 my $apply = $args->{apply_fines} || '';
1197 return undef if $apply eq 'noapply';
1199 my $new_amount = $args->{override_amount};
1200 my $new_btype = $args->{override_btype};
1201 my $new_note = $args->{override_note};
1203 # grab the last circulation
1204 my $circ = $e->search_action_circulation([
1205 { target_copy => $copy->id},
1207 order_by => {circ => "xact_start DESC"},
1209 flesh_fields => {circ => ['target_copy', 'usr'], au => ['card']}
1213 return undef unless $circ;
1215 my $charge_price = $U->ou_ancestor_setting_value(
1216 $owning_lib, 'circ.charge_on_damaged', $e);
1218 my $proc_fee = $U->ou_ancestor_setting_value(
1219 $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
1221 my $void_overdue = $U->ou_ancestor_setting_value(
1222 $owning_lib, 'circ.damaged.void_ovedue', $e) || 0;
1224 return undef unless $charge_price or $proc_fee;
1226 my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
1227 my $total = $copy_price + $proc_fee;
1231 if($new_amount and $new_btype) {
1233 # Allow staff to override the amount to charge for a damaged item
1234 # Consider the case where the item is only partially damaged
1235 # This value is meant to take the place of the item price and
1236 # optional processing fee.
1238 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1239 $e, $new_amount, $new_btype, 'Damaged Item Override', $circ->id, $new_note);
1240 return $evt if $evt;
1244 if($charge_price and $copy_price) {
1245 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1246 $e, $copy_price, 7, 'Damaged Item', $circ->id);
1247 return $evt if $evt;
1251 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1252 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
1253 return $evt if $evt;
1257 # the assumption is that you would not void the overdues unless you
1258 # were also charging for the item and/or applying a processing fee
1260 my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
1261 return $evt if $evt;
1264 my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
1265 return $evt if $evt;
1267 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1268 $ses->request('open-ils.trigger.event.autocreate', 'checkout.damaged', $circ, $circ->circ_lib);
1270 my $evt2 = OpenILS::Utils::Penalty->calculate_penalties($e, $circ->usr->id, $e->requestor->ws_ou);
1271 return $evt2 if $evt2;
1276 return OpenILS::Event->new('DAMAGE_CHARGE',
1287 # ----------------------------------------------------------------------
1288 __PACKAGE__->register_method(
1289 method => 'mark_item_missing_pieces',
1290 api_name => 'open-ils.circ.mark_item_missing_pieces',
1292 Changes the status of a copy to "damaged" or to a custom status based on the
1293 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1295 @param authtoken The login session key
1296 @param copy_id The ID of the copy to mark as damaged
1297 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1301 sub mark_item_missing_pieces {
1302 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1303 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1304 ### FIXME: Even better, we're going to use two transactions, the first to affect pertinent holds before checkout can
1306 my $e2 = new_editor(authtoken=>$auth, xact =>1);
1307 return $e2->die_event unless $e2->checkauth;
1310 my $copy = $e2->retrieve_asset_copy([
1312 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1313 or return $e2->die_event;
1316 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1317 $copy->circ_lib : $copy->call_number->owning_lib;
1319 return $e2->die_event unless $e2->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1321 #### grab the last circulation
1322 my $circ = $e2->search_action_circulation([
1323 { target_copy => $copy->id},
1325 order_by => {circ => "xact_start DESC"}
1330 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1332 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1335 my $holds = $e2->search_action_hold_request(
1337 current_copy => $copy->id,
1338 fulfillment_time => undef,
1339 cancel_time => undef,
1343 $logger->debug("resetting holds that target the marked copy");
1344 OpenILS::Application::Circ::Holds->_reset_hold($e2->requestor, $_) for @$holds;
1347 if (! $e2->commit) {
1348 return $e2->die_event;
1351 my $e = new_editor(authtoken=>$auth, xact =>1);
1352 return $e->die_event unless $e->checkauth;
1354 if (! $circ->checkin_time) { # if circ active, attempt renew
1355 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1356 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1357 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1358 $circ = $res->[0]->{payload}{'circ'};
1359 $circ->target_copy( $copy->id );
1360 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1362 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1367 'copy_id'=>$circ->target_copy,
1368 'patron_id'=>$circ->usr,
1369 'skip_deposit_fee'=>1,
1370 'skip_rental_fee'=>1
1373 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1375 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1376 $e, $copy, $e->requestor, 1 );
1378 if ($hold) { # needed for hold? then due now
1380 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1381 my $due_date = DateTime->now(time_zone => 'local');
1382 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1384 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1388 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params,{ all => 1 });
1389 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1390 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1391 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1392 $circ = $res->[0]->{payload}{'circ'};
1394 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1400 ### Update the item status
1402 my $custom_stat = $U->ou_ancestor_setting_value(
1403 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1404 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1406 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1407 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1409 $copy->status($stat);
1410 $copy->edit_date('now');
1411 $copy->editor($e->requestor->id);
1413 $e->update_asset_copy($copy) or return $e->die_event;
1417 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1418 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1420 return OpenILS::Event->new('SUCCESS',
1424 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1425 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1430 return $e->die_event;
1438 # ----------------------------------------------------------------------
1439 __PACKAGE__->register_method(
1440 method => 'magic_fetch',
1441 api_name => 'open-ils.agent.fetch'
1444 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1447 my( $self, $conn, $auth, $args ) = @_;
1448 my $e = new_editor( authtoken => $auth );
1449 return $e->event unless $e->checkauth;
1451 my $hint = $$args{hint};
1452 my $id = $$args{id};
1454 # Is the call allowed to fetch this type of object?
1455 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1457 # Find the class the implements the given hint
1458 my ($class) = grep {
1459 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1461 $class =~ s/Fieldmapper:://og;
1462 $class =~ s/::/_/og;
1463 my $method = "retrieve_$class";
1465 my $obj = $e->$method($id) or return $e->event;
1468 # ----------------------------------------------------------------------
1471 __PACKAGE__->register_method(
1472 method => "fleshed_circ_retrieve",
1474 api_name => "open-ils.circ.fleshed.retrieve",);
1476 sub fleshed_circ_retrieve {
1477 my( $self, $client, $id ) = @_;
1478 my $e = new_editor();
1479 my $circ = $e->retrieve_action_circulation(
1485 circ => [ qw/ target_copy / ],
1486 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number parts / ],
1487 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1488 acn => [ qw/ record / ],
1492 ) or return $e->event;
1494 my $copy = $circ->target_copy;
1495 my $vol = $copy->call_number;
1496 my $rec = $circ->target_copy->call_number->record;
1498 $vol->record($rec->id);
1499 $copy->call_number($vol->id);
1500 $circ->target_copy($copy->id);
1504 if( $rec->id == OILS_PRECAT_RECORD ) {
1508 $mvr = $U->record_to_mvr($rec);
1509 $rec->marc(''); # drop the bulky marc data
1523 __PACKAGE__->register_method(
1524 method => "test_batch_circ_events",
1525 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1528 # method for testing the behavior of a given event definition
1529 sub test_batch_circ_events {
1530 my($self, $conn, $auth, $event_def, $barcode) = @_;
1532 my $e = new_editor(authtoken => $auth);
1533 return $e->event unless $e->checkauth;
1534 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1536 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1537 or return $e->event;
1539 my $circ = $e->search_action_circulation(
1540 {target_copy => $copy->id, checkin_time => undef})->[0]
1541 or return $e->event;
1543 return undef unless $circ;
1545 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1549 __PACKAGE__->register_method(
1550 method => "fire_circ_events",
1551 api_name => "open-ils.circ.fire_circ_trigger_events",
1553 General event def runner for circ objects. If no event def ID
1554 is provided, the hook will be used to find the best event_def
1555 match based on the context org unit
1559 __PACKAGE__->register_method(
1560 method => "fire_circ_events",
1561 api_name => "open-ils.circ.fire_hold_trigger_events",
1563 General event def runner for hold objects. If no event def ID
1564 is provided, the hook will be used to find the best event_def
1565 match based on the context org unit
1569 __PACKAGE__->register_method(
1570 method => "fire_circ_events",
1571 api_name => "open-ils.circ.fire_user_trigger_events",
1573 General event def runner for user objects. If no event def ID
1574 is provided, the hook will be used to find the best event_def
1575 match based on the context org unit
1580 sub fire_circ_events {
1581 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1583 my $e = new_editor(authtoken => $auth, xact => 1);
1584 return $e->event unless $e->checkauth;
1588 if($self->api_name =~ /hold/) {
1589 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1590 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1591 } elsif($self->api_name =~ /user/) {
1592 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1593 $targets = $e->batch_retrieve_actor_user($target_ids);
1595 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1596 $targets = $e->batch_retrieve_action_circulation($target_ids);
1598 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1599 # simply making this method authoritative because of weirdness
1600 # with transaction handling in A/T code that causes rollback
1601 # failure down the line if handling many targets
1603 return undef unless @$targets;
1604 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1607 __PACKAGE__->register_method(
1608 method => "user_payments_list",
1609 api_name => "open-ils.circ.user_payments.filtered.batch",
1612 desc => q/Returns a fleshed, date-limited set of all payments a user
1613 has made. By default, ordered by payment date. Optionally
1614 ordered by other columns in the top-level "mp" object/,
1616 {desc => 'Authentication token', type => 'string'},
1617 {desc => 'User ID', type => 'number'},
1618 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1620 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1621 and the related fully-realized payment object (e.g money.cash_payment)/}
1625 sub user_payments_list {
1626 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1628 my $e = new_editor(authtoken => $auth);
1629 return $e->event unless $e->checkauth;
1631 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1632 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1634 $order_by ||= ['payment_ts'];
1636 # all payments by user, between start_date and end_date
1637 my $payments = $e->json_query({
1638 select => {mp => ['id']},
1642 fkey => 'xact', field => 'id'}
1646 '+mbt' => {usr => $user_id},
1647 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1649 order_by => {mp => $order_by}
1652 for my $payment_id (@$payments) {
1653 my $payment = $e->retrieve_money_payment([
1661 'credit_card_payment',
1676 $conn->respond($payment);
1683 __PACKAGE__->register_method(
1684 method => "retrieve_circ_chain",
1685 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1688 desc => q/Given a circulation, this returns all circulation objects
1689 that are part of the same chain of renewals./,
1691 {desc => 'Authentication token', type => 'string'},
1692 {desc => 'Circ ID', type => 'number'},
1694 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1698 __PACKAGE__->register_method(
1699 method => "retrieve_circ_chain",
1700 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1702 desc => q/Given a circulation, this returns a summary of the circulation objects
1703 that are part of the same chain of renewals./,
1705 {desc => 'Authentication token', type => 'string'},
1706 {desc => 'Circ ID', type => 'number'},
1708 return => {desc => q/Circulation Chain Summary/}
1712 sub retrieve_circ_chain {
1713 my($self, $conn, $auth, $circ_id) = @_;
1715 my $e = new_editor(authtoken => $auth);
1716 return $e->event unless $e->checkauth;
1717 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1719 if($self->api_name =~ /summary/) {
1720 return $U->create_circ_chain_summary($e, $circ_id);
1724 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1726 for my $circ_info (@$chain) {
1727 my $circ = Fieldmapper::action::circulation->new;
1728 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1729 $conn->respond($circ);
1736 __PACKAGE__->register_method(
1737 method => "retrieve_prev_circ_chain",
1738 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1741 desc => q/Given a circulation, this returns all circulation objects
1742 that are part of the previous chain of renewals./,
1744 {desc => 'Authentication token', type => 'string'},
1745 {desc => 'Circ ID', type => 'number'},
1747 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1751 __PACKAGE__->register_method(
1752 method => "retrieve_prev_circ_chain",
1753 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1755 desc => q/Given a circulation, this returns a summary of the circulation objects
1756 that are part of the previous chain of renewals./,
1758 {desc => 'Authentication token', type => 'string'},
1759 {desc => 'Circ ID', type => 'number'},
1761 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1765 sub retrieve_prev_circ_chain {
1766 my($self, $conn, $auth, $circ_id) = @_;
1768 my $e = new_editor(authtoken => $auth);
1769 return $e->event unless $e->checkauth;
1770 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1772 if($self->api_name =~ /summary/) {
1773 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1774 my $target_copy = $$first_circ{'target_copy'};
1775 my $usr = $$first_circ{'usr'};
1776 my $last_circ_from_prev_chain = $e->json_query({
1777 'select' => { 'circ' => ['id','usr'] },
1780 target_copy => $target_copy,
1781 xact_start => { '<' => $$first_circ{'xact_start'} }
1783 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1786 return undef unless $last_circ_from_prev_chain;
1787 return undef unless $$last_circ_from_prev_chain{'id'};
1788 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1789 return undef unless $sum;
1790 my $obj = Fieldmapper::action::circ_chain_summary->new;
1791 $obj->$_($sum->{$_}) for keys %$sum;
1792 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1796 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1797 my $target_copy = $$first_circ{'target_copy'};
1798 my $last_circ_from_prev_chain = $e->json_query({
1799 'select' => { 'circ' => ['id'] },
1802 target_copy => $target_copy,
1803 xact_start => { '<' => $$first_circ{'xact_start'} }
1805 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1808 return undef unless $last_circ_from_prev_chain;
1809 return undef unless $$last_circ_from_prev_chain{'id'};
1810 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1812 for my $circ_info (@$chain) {
1813 my $circ = Fieldmapper::action::circulation->new;
1814 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1815 $conn->respond($circ);
1823 __PACKAGE__->register_method(
1824 method => "get_copy_due_date",
1825 api_name => "open-ils.circ.copy.due_date.retrieve",
1828 Given a copy ID, returns the due date for the copy if it's
1829 currently circulating. Otherwise, returns null. Note, this is a public
1830 method requiring no authentication. Only the due date is exposed.
1833 {desc => 'Copy ID', type => 'number'}
1835 return => {desc => q/
1836 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1841 sub get_copy_due_date {
1842 my($self, $conn, $copy_id) = @_;
1843 my $e = new_editor();
1845 my $circ = $e->json_query({
1846 select => {circ => ['due_date']},
1849 target_copy => $copy_id,
1850 checkin_time => undef,
1852 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1853 {stop_fines => undef}
1857 })->[0] or return undef;
1859 return $circ->{due_date};
1866 # {"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}}