1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use List::Util qw/max reduce/;
39 use UUID::Tiny qw/:std/;
42 OpenILS::Application::Actor::Container->initialize();
43 OpenILS::Application::Actor::UserGroups->initialize();
44 OpenILS::Application::Actor::ClosedDates->initialize();
47 my $apputils = "OpenILS::Application::AppUtils";
50 sub _d { warn "Patron:\n" . Dumper(shift()); }
53 my $set_user_settings;
57 #__PACKAGE__->register_method(
58 # method => "allowed_test",
59 # api_name => "open-ils.actor.allowed_test",
62 # my($self, $conn, $auth, $orgid, $permcode) = @_;
63 # my $e = new_editor(authtoken => $auth);
64 # return $e->die_event unless $e->checkauth;
68 # permcode => $permcode,
69 # result => $e->allowed($permcode, $orgid)
73 __PACKAGE__->register_method(
74 method => "update_user_setting",
75 api_name => "open-ils.actor.patron.settings.update",
77 sub update_user_setting {
78 my($self, $conn, $auth, $user_id, $settings) = @_;
79 my $e = new_editor(xact => 1, authtoken => $auth);
80 return $e->die_event unless $e->checkauth;
82 $user_id = $e->requestor->id unless defined $user_id;
84 unless($e->requestor->id == $user_id) {
85 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
86 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
89 for my $name (keys %$settings) {
90 my $val = $$settings{$name};
91 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
94 $val = OpenSRF::Utils::JSON->perl2JSON($val);
97 $e->update_actor_user_setting($set) or return $e->die_event;
99 $set = Fieldmapper::actor::user_setting->new;
103 $e->create_actor_user_setting($set) or return $e->die_event;
106 $e->delete_actor_user_setting($set) or return $e->die_event;
115 __PACKAGE__->register_method(
116 method => "set_ou_settings",
117 api_name => "open-ils.actor.org_unit.settings.update",
119 desc => "Updates the value for a given org unit setting. The permission to update " .
120 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
121 "permission specified in the update_perm column of the config.org_unit_setting_type " .
122 "table's row corresponding to the setting being changed." ,
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'Org unit ID', type => 'number'},
126 {desc => 'Hash of setting name-value pairs', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
132 sub set_ou_settings {
133 my( $self, $client, $auth, $org_id, $settings ) = @_;
135 my $e = new_editor(authtoken => $auth, xact => 1);
136 return $e->die_event unless $e->checkauth;
138 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
140 for my $name (keys %$settings) {
141 my $val = $$settings{$name};
143 my $type = $e->retrieve_config_org_unit_setting_type([
145 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
146 ]) or return $e->die_event;
147 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
149 # If there is no relevant permission, the default assumption will
150 # be, "no, the caller cannot change that value."
151 return $e->die_event unless ($all_allowed ||
152 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
155 $val = OpenSRF::Utils::JSON->perl2JSON($val);
158 $e->update_actor_org_unit_setting($set) or return $e->die_event;
160 $set = Fieldmapper::actor::org_unit_setting->new;
161 $set->org_unit($org_id);
164 $e->create_actor_org_unit_setting($set) or return $e->die_event;
167 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
175 __PACKAGE__->register_method(
176 method => "user_settings",
178 api_name => "open-ils.actor.patron.settings.retrieve",
181 my( $self, $client, $auth, $user_id, $setting ) = @_;
183 my $e = new_editor(authtoken => $auth);
184 return $e->event unless $e->checkauth;
185 $user_id = $e->requestor->id unless defined $user_id;
187 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
188 if($e->requestor->id != $user_id) {
189 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
193 my($e, $user_id, $setting) = @_;
194 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
195 return undef unless $val; # XXX this should really return undef, but needs testing
196 return OpenSRF::Utils::JSON->JSON2perl($val->value);
200 if(ref $setting eq 'ARRAY') {
202 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
205 return get_setting($e, $user_id, $setting);
208 my $s = $e->search_actor_user_setting({usr => $user_id});
209 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
214 __PACKAGE__->register_method(
215 method => "ranged_ou_settings",
216 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
218 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
219 "is implied for retrieving OU settings by the authenticated users' permissions.",
221 {desc => 'Authentication token', type => 'string'},
222 {desc => 'Org unit ID', type => 'number'},
224 return => {desc => 'A hashref of "ranged" settings, event on error'}
227 sub ranged_ou_settings {
228 my( $self, $client, $auth, $org_id ) = @_;
230 my $e = new_editor(authtoken => $auth);
231 return $e->event unless $e->checkauth;
234 my $org_list = $U->get_org_ancestors($org_id);
235 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
236 $org_list = [ reverse @$org_list ];
238 # start at the context org and capture the setting value
239 # without clobbering settings we've already captured
240 for my $this_org_id (@$org_list) {
242 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
244 for my $set (@sets) {
245 my $type = $e->retrieve_config_org_unit_setting_type([
247 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
250 # If there is no relevant permission, the default assumption will
251 # be, "yes, the caller can have that value."
252 if ($type && $type->view_perm) {
253 next if not $e->allowed($type->view_perm->code, $org_id);
256 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
257 unless defined $ranged_settings{$set->name};
261 return \%ranged_settings;
266 __PACKAGE__->register_method(
267 api_name => 'open-ils.actor.ou_setting.ancestor_default',
268 method => 'ou_ancestor_setting',
270 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
271 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
272 'user has permission to view that setting, if there is a permission associated with the setting.' ,
274 { desc => 'Org unit ID', type => 'number' },
275 { desc => 'setting name', type => 'string' },
276 { desc => 'authtoken (optional)', type => 'string' }
278 return => {desc => 'A value for the org unit setting, or undef'}
282 # ------------------------------------------------------------------
283 # Attempts to find the org setting value for a given org. if not
284 # found at the requested org, searches up the org tree until it
285 # finds a parent that has the requested setting.
286 # when found, returns { org => $id, value => $value }
287 # otherwise, returns NULL
288 # ------------------------------------------------------------------
289 sub ou_ancestor_setting {
290 my( $self, $client, $orgid, $name, $auth ) = @_;
291 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
294 __PACKAGE__->register_method(
295 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
296 method => 'ou_ancestor_setting_batch',
298 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
299 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
300 'user has permission to view that setting, if there is a permission associated with the setting.' ,
302 { desc => 'Org unit ID', type => 'number' },
303 { desc => 'setting name list', type => 'array' },
304 { desc => 'authtoken (optional)', type => 'string' }
306 return => {desc => 'A hash with name => value pairs for the org unit settings'}
309 sub ou_ancestor_setting_batch {
310 my( $self, $client, $orgid, $name_list, $auth ) = @_;
312 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
318 __PACKAGE__->register_method(
319 method => "update_patron",
320 api_name => "open-ils.actor.patron.update",
323 Update an existing user, or create a new one. Related objects,
324 like cards, addresses, survey responses, and stat cats,
325 can be updated by attaching them to the user object in their
326 respective fields. For examples, the billing address object
327 may be inserted into the 'billing_address' field, etc. For each
328 attached object, indicate if the object should be created,
329 updated, or deleted using the built-in 'isnew', 'ischanged',
330 and 'isdeleted' fields on the object.
333 { desc => 'Authentication token', type => 'string' },
334 { desc => 'Patron data object', type => 'object' }
336 return => {desc => 'A fleshed user object, event on error'}
341 my( $self, $client, $user_session, $patron ) = @_;
343 my $session = $apputils->start_db_session();
345 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
347 my( $user_obj, $evt ) = $U->checkses($user_session);
350 $evt = check_group_perm($session, $user_obj, $patron);
354 # $new_patron is the patron in progress. $patron is the original patron
355 # passed in with the method. new_patron will change as the components
356 # of patron are added/updated.
360 # unflesh the real items on the patron
361 $patron->card( $patron->card->id ) if(ref($patron->card));
362 $patron->billing_address( $patron->billing_address->id )
363 if(ref($patron->billing_address));
364 $patron->mailing_address( $patron->mailing_address->id )
365 if(ref($patron->mailing_address));
367 # create/update the patron first so we can use his id
368 if($patron->isnew()) {
369 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
371 } else { $new_patron = $patron; }
373 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
376 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
379 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
382 # re-update the patron if anything has happened to him during this process
383 if($new_patron->ischanged()) {
384 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
388 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
391 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
394 $apputils->commit_db_session($session);
396 $evt = apply_invalid_addr_penalty($patron);
399 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
401 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
403 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
406 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
409 sub apply_invalid_addr_penalty {
411 my $e = new_editor(xact => 1);
413 # grab the invalid address penalty if set
414 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
416 my ($addr_penalty) = grep
417 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
419 # do we enforce invalid address penalty
420 my $enforce = $U->ou_ancestor_setting_value(
421 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
423 my $addrs = $e->search_actor_user_address(
424 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
425 my $addr_count = scalar(@$addrs);
427 if($addr_count == 0 and $addr_penalty) {
429 # regardless of any settings, remove the penalty when the user has no invalid addresses
430 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
433 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
435 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
436 my $depth = $ptype->org_depth;
437 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
438 $ctx_org = $patron->home_ou unless defined $ctx_org;
440 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
441 $penalty->usr($patron->id);
442 $penalty->org_unit($ctx_org);
443 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
445 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
464 "standing_penalties",
470 push @$fields, "home_ou" if $home_ou;
471 return new_flesh_user($id, $fields, $e );
479 # clone and clear stuff that would break the database
483 my $new_patron = $patron->clone;
485 $new_patron->clear_billing_address();
486 $new_patron->clear_mailing_address();
487 $new_patron->clear_addresses();
488 $new_patron->clear_card();
489 $new_patron->clear_cards();
490 $new_patron->clear_id();
491 $new_patron->clear_isnew();
492 $new_patron->clear_ischanged();
493 $new_patron->clear_isdeleted();
494 $new_patron->clear_stat_cat_entries();
495 $new_patron->clear_permissions();
496 $new_patron->clear_standing_penalties();
506 my $user_obj = shift;
508 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
509 return (undef, $evt) if $evt;
511 my $ex = $session->request(
512 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
514 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
517 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
519 my $id = $session->request(
520 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
521 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
523 $logger->info("Successfully created new user [$id] in DB");
525 return ( $session->request(
526 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
530 sub check_group_perm {
531 my( $session, $requestor, $patron ) = @_;
534 # first let's see if the requestor has
535 # priveleges to update this user in any way
536 if( ! $patron->isnew ) {
537 my $p = $session->request(
538 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
540 # If we are the requestor (trying to update our own account)
541 # and we are not trying to change our profile, we're good
542 if( $p->id == $requestor->id and
543 $p->profile == $patron->profile ) {
548 $evt = group_perm_failed($session, $requestor, $p);
552 # They are allowed to edit this patron.. can they put the
553 # patron into the group requested?
554 $evt = group_perm_failed($session, $requestor, $patron);
560 sub group_perm_failed {
561 my( $session, $requestor, $patron ) = @_;
565 my $grpid = $patron->profile;
569 $logger->debug("user update looking for group perm for group $grpid");
570 $grp = $session->request(
571 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
572 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
574 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
576 $logger->info("user update checking perm $perm on user ".
577 $requestor->id." for update/create on user username=".$patron->usrname);
579 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
587 my( $session, $patron, $user_obj, $noperm) = @_;
589 $logger->info("Updating patron ".$patron->id." in DB");
594 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
595 return (undef, $evt) if $evt;
598 # update the password by itself to avoid the password protection magic
599 if( $patron->passwd ) {
600 my $s = $session->request(
601 'open-ils.storage.direct.actor.user.remote_update',
602 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
603 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
604 $patron->clear_passwd;
607 if(!$patron->ident_type) {
608 $patron->clear_ident_type;
609 $patron->clear_ident_value;
612 $evt = verify_last_xact($session, $patron);
613 return (undef, $evt) if $evt;
615 my $stat = $session->request(
616 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
617 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
622 sub verify_last_xact {
623 my( $session, $patron ) = @_;
624 return undef unless $patron->id and $patron->id > 0;
625 my $p = $session->request(
626 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
627 my $xact = $p->last_xact_id;
628 return undef unless $xact;
629 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
630 return OpenILS::Event->new('XACT_COLLISION')
631 if $xact != $patron->last_xact_id;
636 sub _check_dup_ident {
637 my( $session, $patron ) = @_;
639 return undef unless $patron->ident_value;
642 ident_type => $patron->ident_type,
643 ident_value => $patron->ident_value,
646 $logger->debug("patron update searching for dup ident values: " .
647 $patron->ident_type . ':' . $patron->ident_value);
649 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
651 my $dups = $session->request(
652 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
655 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
662 sub _add_update_addresses {
666 my $new_patron = shift;
670 my $current_id; # id of the address before creation
672 for my $address (@{$patron->addresses()}) {
674 next unless ref $address;
675 $current_id = $address->id();
677 if( $patron->billing_address() and
678 $patron->billing_address() == $current_id ) {
679 $logger->info("setting billing addr to $current_id");
680 $new_patron->billing_address($address->id());
681 $new_patron->ischanged(1);
684 if( $patron->mailing_address() and
685 $patron->mailing_address() == $current_id ) {
686 $new_patron->mailing_address($address->id());
687 $logger->info("setting mailing addr to $current_id");
688 $new_patron->ischanged(1);
692 if($address->isnew()) {
694 $address->usr($new_patron->id());
696 ($address, $evt) = _add_address($session,$address);
697 return (undef, $evt) if $evt;
699 # we need to get the new id
700 if( $patron->billing_address() and
701 $patron->billing_address() == $current_id ) {
702 $new_patron->billing_address($address->id());
703 $logger->info("setting billing addr to $current_id");
704 $new_patron->ischanged(1);
707 if( $patron->mailing_address() and
708 $patron->mailing_address() == $current_id ) {
709 $new_patron->mailing_address($address->id());
710 $logger->info("setting mailing addr to $current_id");
711 $new_patron->ischanged(1);
714 } elsif($address->ischanged() ) {
716 ($address, $evt) = _update_address($session, $address);
717 return (undef, $evt) if $evt;
719 } elsif($address->isdeleted() ) {
721 if( $address->id() == $new_patron->mailing_address() ) {
722 $new_patron->clear_mailing_address();
723 ($new_patron, $evt) = _update_patron($session, $new_patron);
724 return (undef, $evt) if $evt;
727 if( $address->id() == $new_patron->billing_address() ) {
728 $new_patron->clear_billing_address();
729 ($new_patron, $evt) = _update_patron($session, $new_patron);
730 return (undef, $evt) if $evt;
733 $evt = _delete_address($session, $address);
734 return (undef, $evt) if $evt;
738 return ( $new_patron, undef );
742 # adds an address to the db and returns the address with new id
744 my($session, $address) = @_;
745 $address->clear_id();
747 $logger->info("Creating new address at street ".$address->street1);
749 # put the address into the database
750 my $id = $session->request(
751 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
752 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
755 return ($address, undef);
759 sub _update_address {
760 my( $session, $address ) = @_;
762 $logger->info("Updating address ".$address->id." in the DB");
764 my $stat = $session->request(
765 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
767 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
768 return ($address, undef);
773 sub _add_update_cards {
777 my $new_patron = shift;
781 my $virtual_id; #id of the card before creation
782 for my $card (@{$patron->cards()}) {
784 $card->usr($new_patron->id());
786 if(ref($card) and $card->isnew()) {
788 $virtual_id = $card->id();
789 ( $card, $evt ) = _add_card($session,$card);
790 return (undef, $evt) if $evt;
792 #if(ref($patron->card)) { $patron->card($patron->card->id); }
793 if($patron->card() == $virtual_id) {
794 $new_patron->card($card->id());
795 $new_patron->ischanged(1);
798 } elsif( ref($card) and $card->ischanged() ) {
799 $evt = _update_card($session, $card);
800 return (undef, $evt) if $evt;
804 return ( $new_patron, undef );
808 # adds an card to the db and returns the card with new id
810 my( $session, $card ) = @_;
813 $logger->info("Adding new patron card ".$card->barcode);
815 my $id = $session->request(
816 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
817 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
818 $logger->info("Successfully created patron card $id");
821 return ( $card, undef );
825 # returns event on error. returns undef otherwise
827 my( $session, $card ) = @_;
828 $logger->info("Updating patron card ".$card->id);
830 my $stat = $session->request(
831 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
832 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
839 # returns event on error. returns undef otherwise
840 sub _delete_address {
841 my( $session, $address ) = @_;
843 $logger->info("Deleting address ".$address->id." from DB");
845 my $stat = $session->request(
846 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
848 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
854 sub _add_survey_responses {
855 my ($session, $patron, $new_patron) = @_;
857 $logger->info( "Updating survey responses for patron ".$new_patron->id );
859 my $responses = $patron->survey_responses;
863 $_->usr($new_patron->id) for (@$responses);
865 my $evt = $U->simplereq( "open-ils.circ",
866 "open-ils.circ.survey.submit.user_id", $responses );
868 return (undef, $evt) if defined($U->event_code($evt));
872 return ( $new_patron, undef );
876 sub _create_stat_maps {
878 my($session, $user_session, $patron, $new_patron) = @_;
880 my $maps = $patron->stat_cat_entries();
882 for my $map (@$maps) {
884 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
886 if ($map->isdeleted()) {
887 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
889 } elsif ($map->isnew()) {
890 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
895 $map->target_usr($new_patron->id);
898 $logger->info("Updating stat entry with method $method and map $map");
900 my $stat = $session->request($method, $map)->gather(1);
901 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
905 return ($new_patron, undef);
908 sub _create_perm_maps {
910 my($session, $user_session, $patron, $new_patron) = @_;
912 my $maps = $patron->permissions;
914 for my $map (@$maps) {
916 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
917 if ($map->isdeleted()) {
918 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
919 } elsif ($map->isnew()) {
920 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
925 $map->usr($new_patron->id);
927 #warn( "Updating permissions with method $method and session $user_session and map $map" );
928 $logger->info( "Updating permissions with method $method and map $map" );
930 my $stat = $session->request($method, $map)->gather(1);
931 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
935 return ($new_patron, undef);
939 __PACKAGE__->register_method(
940 method => "set_user_work_ous",
941 api_name => "open-ils.actor.user.work_ous.update",
944 sub set_user_work_ous {
950 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
953 my $session = $apputils->start_db_session();
955 for my $map (@$maps) {
957 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
958 if ($map->isdeleted()) {
959 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
960 } elsif ($map->isnew()) {
961 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
965 #warn( "Updating permissions with method $method and session $ses and map $map" );
966 $logger->info( "Updating work_ou map with method $method and map $map" );
968 my $stat = $session->request($method, $map)->gather(1);
969 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
973 $apputils->commit_db_session($session);
975 return scalar(@$maps);
979 __PACKAGE__->register_method(
980 method => "set_user_perms",
981 api_name => "open-ils.actor.user.permissions.update",
990 my $session = $apputils->start_db_session();
992 my( $user_obj, $evt ) = $U->checkses($ses);
995 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
998 $all = 1 if ($U->is_true($user_obj->super_user()));
999 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1001 for my $map (@$maps) {
1003 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1004 if ($map->isdeleted()) {
1005 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1006 } elsif ($map->isnew()) {
1007 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1011 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1012 #warn( "Updating permissions with method $method and session $ses and map $map" );
1013 $logger->info( "Updating permissions with method $method and map $map" );
1015 my $stat = $session->request($method, $map)->gather(1);
1016 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1020 $apputils->commit_db_session($session);
1022 return scalar(@$maps);
1026 __PACKAGE__->register_method(
1027 method => "user_retrieve_by_barcode",
1029 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1031 sub user_retrieve_by_barcode {
1032 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1034 my $e = new_editor(authtoken => $auth);
1035 return $e->event unless $e->checkauth;
1037 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1038 or return $e->event;
1040 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1041 return $e->event unless $e->allowed(
1042 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1049 __PACKAGE__->register_method(
1050 method => "get_user_by_id",
1052 api_name => "open-ils.actor.user.retrieve",
1055 sub get_user_by_id {
1056 my ($self, $client, $auth, $id) = @_;
1057 my $e = new_editor(authtoken=>$auth);
1058 return $e->event unless $e->checkauth;
1059 my $user = $e->retrieve_actor_user($id) or return $e->event;
1060 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1065 __PACKAGE__->register_method(
1066 method => "get_org_types",
1067 api_name => "open-ils.actor.org_types.retrieve",
1070 return $U->get_org_types();
1074 __PACKAGE__->register_method(
1075 method => "get_user_ident_types",
1076 api_name => "open-ils.actor.user.ident_types.retrieve",
1079 sub get_user_ident_types {
1080 return $ident_types if $ident_types;
1081 return $ident_types =
1082 new_editor()->retrieve_all_config_identification_type();
1086 __PACKAGE__->register_method(
1087 method => "get_org_unit",
1088 api_name => "open-ils.actor.org_unit.retrieve",
1092 my( $self, $client, $user_session, $org_id ) = @_;
1093 my $e = new_editor(authtoken => $user_session);
1095 return $e->event unless $e->checkauth;
1096 $org_id = $e->requestor->ws_ou;
1098 my $o = $e->retrieve_actor_org_unit($org_id)
1099 or return $e->event;
1103 __PACKAGE__->register_method(
1104 method => "search_org_unit",
1105 api_name => "open-ils.actor.org_unit_list.search",
1108 sub search_org_unit {
1110 my( $self, $client, $field, $value ) = @_;
1112 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1114 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1115 { $field => $value } );
1121 # build the org tree
1123 __PACKAGE__->register_method(
1124 method => "get_org_tree",
1125 api_name => "open-ils.actor.org_tree.retrieve",
1127 note => "Returns the entire org tree structure",
1133 return $U->get_org_tree($client->session->session_locale);
1137 __PACKAGE__->register_method(
1138 method => "get_org_descendants",
1139 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1142 # depth is optional. org_unit is the id
1143 sub get_org_descendants {
1144 my( $self, $client, $org_unit, $depth ) = @_;
1146 if(ref $org_unit eq 'ARRAY') {
1149 for my $i (0..scalar(@$org_unit)-1) {
1150 my $list = $U->simple_scalar_request(
1152 "open-ils.storage.actor.org_unit.descendants.atomic",
1153 $org_unit->[$i], $depth->[$i] );
1154 push(@trees, $U->build_org_tree($list));
1159 my $orglist = $apputils->simple_scalar_request(
1161 "open-ils.storage.actor.org_unit.descendants.atomic",
1162 $org_unit, $depth );
1163 return $U->build_org_tree($orglist);
1168 __PACKAGE__->register_method(
1169 method => "get_org_ancestors",
1170 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1173 # depth is optional. org_unit is the id
1174 sub get_org_ancestors {
1175 my( $self, $client, $org_unit, $depth ) = @_;
1176 my $orglist = $apputils->simple_scalar_request(
1178 "open-ils.storage.actor.org_unit.ancestors.atomic",
1179 $org_unit, $depth );
1180 return $U->build_org_tree($orglist);
1184 __PACKAGE__->register_method(
1185 method => "get_standings",
1186 api_name => "open-ils.actor.standings.retrieve"
1191 return $user_standings if $user_standings;
1192 return $user_standings =
1193 $apputils->simple_scalar_request(
1195 "open-ils.cstore.direct.config.standing.search.atomic",
1196 { id => { "!=" => undef } }
1201 __PACKAGE__->register_method(
1202 method => "get_my_org_path",
1203 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1206 sub get_my_org_path {
1207 my( $self, $client, $auth, $org_id ) = @_;
1208 my $e = new_editor(authtoken=>$auth);
1209 return $e->event unless $e->checkauth;
1210 $org_id = $e->requestor->ws_ou unless defined $org_id;
1212 return $apputils->simple_scalar_request(
1214 "open-ils.storage.actor.org_unit.full_path.atomic",
1219 __PACKAGE__->register_method(
1220 method => "patron_adv_search",
1221 api_name => "open-ils.actor.patron.search.advanced"
1223 sub patron_adv_search {
1224 my( $self, $client, $auth, $search_hash,
1225 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1227 my $e = new_editor(authtoken=>$auth);
1228 return $e->event unless $e->checkauth;
1229 return $e->event unless $e->allowed('VIEW_USER');
1231 # depth boundary outside of which patrons must opt-in, default to 0
1232 my $opt_boundary = 0;
1233 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1235 return $U->storagereq(
1236 "open-ils.storage.actor.user.crazy_search", $search_hash,
1237 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1241 __PACKAGE__->register_method(
1242 method => "update_passwd",
1243 api_name => "open-ils.actor.user.password.update",
1245 desc => "Update the operator's password",
1247 { desc => 'Authentication token', type => 'string' },
1248 { desc => 'New password', type => 'string' },
1249 { desc => 'Current password', type => 'string' }
1251 return => {desc => '1 on success, Event on error or incorrect current password'}
1255 __PACKAGE__->register_method(
1256 method => "update_passwd",
1257 api_name => "open-ils.actor.user.username.update",
1259 desc => "Update the operator's username",
1261 { desc => 'Authentication token', type => 'string' },
1262 { desc => 'New username', type => 'string' }
1264 return => {desc => '1 on success, Event on error'}
1268 __PACKAGE__->register_method(
1269 method => "update_passwd",
1270 api_name => "open-ils.actor.user.email.update",
1272 desc => "Update the operator's email address",
1274 { desc => 'Authentication token', type => 'string' },
1275 { desc => 'New email address', type => 'string' }
1277 return => {desc => '1 on success, Event on error'}
1282 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1283 my $e = new_editor(xact=>1, authtoken=>$auth);
1284 return $e->die_event unless $e->checkauth;
1286 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1287 or return $e->die_event;
1288 my $api = $self->api_name;
1290 if( $api =~ /password/o ) {
1291 # make sure the original password matches the in-database password
1292 if (md5_hex($orig_pw) ne $db_user->passwd) {
1294 return new OpenILS::Event('INCORRECT_PASSWORD');
1296 $db_user->passwd($new_val);
1300 # if we don't clear the password, the user will be updated with
1301 # a hashed version of the hashed version of their password
1302 $db_user->clear_passwd;
1304 if( $api =~ /username/o ) {
1306 # make sure no one else has this username
1307 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1310 return new OpenILS::Event('USERNAME_EXISTS');
1312 $db_user->usrname($new_val);
1314 } elsif( $api =~ /email/o ) {
1315 $db_user->email($new_val);
1319 $e->update_actor_user($db_user) or return $e->die_event;
1322 # update the cached user to pick up these changes
1323 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1329 __PACKAGE__->register_method(
1330 method => "check_user_perms",
1331 api_name => "open-ils.actor.user.perm.check",
1332 notes => <<" NOTES");
1333 Takes a login session, user id, an org id, and an array of perm type strings. For each
1334 perm type, if the user does *not* have the given permission it is added
1335 to a list which is returned from the method. If all permissions
1336 are allowed, an empty list is returned
1337 if the logged in user does not match 'user_id', then the logged in user must
1338 have VIEW_PERMISSION priveleges.
1341 sub check_user_perms {
1342 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1344 my( $staff, $evt ) = $apputils->checkses($login_session);
1345 return $evt if $evt;
1347 if($staff->id ne $user_id) {
1348 if( $evt = $apputils->check_perms(
1349 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1355 for my $perm (@$perm_types) {
1356 if($apputils->check_perms($user_id, $org_id, $perm)) {
1357 push @not_allowed, $perm;
1361 return \@not_allowed
1364 __PACKAGE__->register_method(
1365 method => "check_user_perms2",
1366 api_name => "open-ils.actor.user.perm.check.multi_org",
1368 Checks the permissions on a list of perms and orgs for a user
1369 @param authtoken The login session key
1370 @param user_id The id of the user to check
1371 @param orgs The array of org ids
1372 @param perms The array of permission names
1373 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1374 if the logged in user does not match 'user_id', then the logged in user must
1375 have VIEW_PERMISSION priveleges.
1378 sub check_user_perms2 {
1379 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1381 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1382 $authtoken, $user_id, 'VIEW_PERMISSION' );
1383 return $evt if $evt;
1386 for my $org (@$orgs) {
1387 for my $perm (@$perms) {
1388 if($apputils->check_perms($user_id, $org, $perm)) {
1389 push @not_allowed, [ $org, $perm ];
1394 return \@not_allowed
1398 __PACKAGE__->register_method(
1399 method => 'check_user_perms3',
1400 api_name => 'open-ils.actor.user.perm.highest_org',
1402 Returns the highest org unit id at which a user has a given permission
1403 If the requestor does not match the target user, the requestor must have
1404 'VIEW_PERMISSION' rights at the home org unit of the target user
1405 @param authtoken The login session key
1406 @param userid The id of the user in question
1407 @param perm The permission to check
1408 @return The org unit highest in the org tree within which the user has
1409 the requested permission
1412 sub check_user_perms3 {
1413 my($self, $client, $authtoken, $user_id, $perm) = @_;
1414 my $e = new_editor(authtoken=>$authtoken);
1415 return $e->event unless $e->checkauth;
1417 my $tree = $U->get_org_tree();
1419 unless($e->requestor->id == $user_id) {
1420 my $user = $e->retrieve_actor_user($user_id)
1421 or return $e->event;
1422 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1423 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1426 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1429 __PACKAGE__->register_method(
1430 method => 'user_has_work_perm_at',
1431 api_name => 'open-ils.actor.user.has_work_perm_at',
1435 Returns a set of org unit IDs which represent the highest orgs in
1436 the org tree where the user has the requested permission. The
1437 purpose of this method is to return the smallest set of org units
1438 which represent the full expanse of the user's ability to perform
1439 the requested action. The user whose perms this method should
1440 check is implied by the authtoken. /,
1442 {desc => 'authtoken', type => 'string'},
1443 {desc => 'permission name', type => 'string'},
1444 {desc => q/user id, optional. If present, check perms for
1445 this user instead of the logged in user/, type => 'number'},
1447 return => {desc => 'An array of org IDs'}
1451 sub user_has_work_perm_at {
1452 my($self, $conn, $auth, $perm, $user_id) = @_;
1453 my $e = new_editor(authtoken=>$auth);
1454 return $e->event unless $e->checkauth;
1455 if(defined $user_id) {
1456 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1457 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1459 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1462 __PACKAGE__->register_method(
1463 method => 'user_has_work_perm_at_batch',
1464 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1468 sub user_has_work_perm_at_batch {
1469 my($self, $conn, $auth, $perms, $user_id) = @_;
1470 my $e = new_editor(authtoken=>$auth);
1471 return $e->event unless $e->checkauth;
1472 if(defined $user_id) {
1473 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1474 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1477 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1483 __PACKAGE__->register_method(
1484 method => 'check_user_perms4',
1485 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1487 Returns the highest org unit id at which a user has a given permission
1488 If the requestor does not match the target user, the requestor must have
1489 'VIEW_PERMISSION' rights at the home org unit of the target user
1490 @param authtoken The login session key
1491 @param userid The id of the user in question
1492 @param perms An array of perm names to check
1493 @return An array of orgId's representing the org unit
1494 highest in the org tree within which the user has the requested permission
1495 The arrah of orgId's has matches the order of the perms array
1498 sub check_user_perms4 {
1499 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1501 my( $staff, $target, $org, $evt );
1503 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1504 $authtoken, $userid, 'VIEW_PERMISSION' );
1505 return $evt if $evt;
1508 return [] unless ref($perms);
1509 my $tree = $U->get_org_tree();
1511 for my $p (@$perms) {
1512 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1518 __PACKAGE__->register_method(
1519 method => "user_fines_summary",
1520 api_name => "open-ils.actor.user.fines.summary",
1523 desc => 'Returns a short summary of the users total open fines, ' .
1524 'excluding voided fines Params are login_session, user_id' ,
1526 {desc => 'Authentication token', type => 'string'},
1527 {desc => 'User ID', type => 'string'} # number?
1530 desc => "a 'mous' object, event on error",
1535 sub user_fines_summary {
1536 my( $self, $client, $auth, $user_id ) = @_;
1538 my $e = new_editor(authtoken=>$auth);
1539 return $e->event unless $e->checkauth;
1541 if( $user_id ne $e->requestor->id ) {
1542 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1543 return $e->event unless
1544 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1547 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1551 __PACKAGE__->register_method(
1552 method => "user_opac_vitals",
1553 api_name => "open-ils.actor.user.opac.vital_stats",
1557 desc => 'Returns a short summary of the users vital stats, including ' .
1558 'identification information, accumulated balance, number of holds, ' .
1559 'and current open circulation stats' ,
1561 {desc => 'Authentication token', type => 'string'},
1562 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1565 desc => "An object with four properties: user, fines, checkouts and holds."
1570 sub user_opac_vitals {
1571 my( $self, $client, $auth, $user_id ) = @_;
1573 my $e = new_editor(authtoken=>$auth);
1574 return $e->event unless $e->checkauth;
1576 $user_id ||= $e->requestor->id;
1578 my $user = $e->retrieve_actor_user( $user_id );
1581 ->method_lookup('open-ils.actor.user.fines.summary')
1582 ->run($auth => $user_id);
1583 return $fines if (defined($U->event_code($fines)));
1586 $fines = new Fieldmapper::money::open_user_summary ();
1587 $fines->balance_owed(0.00);
1588 $fines->total_owed(0.00);
1589 $fines->total_paid(0.00);
1590 $fines->usr($user_id);
1594 ->method_lookup('open-ils.actor.user.hold_requests.count')
1595 ->run($auth => $user_id);
1596 return $holds if (defined($U->event_code($holds)));
1599 ->method_lookup('open-ils.actor.user.checked_out.count')
1600 ->run($auth => $user_id);
1601 return $out if (defined($U->event_code($out)));
1603 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1607 first_given_name => $user->first_given_name,
1608 second_given_name => $user->second_given_name,
1609 family_name => $user->family_name,
1610 alias => $user->alias,
1611 usrname => $user->usrname
1613 fines => $fines->to_bare_hash,
1620 ##### a small consolidation of related method registrations
1621 my $common_params = [
1622 { desc => 'Authentication token', type => 'string' },
1623 { desc => 'User ID', type => 'string' },
1624 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1625 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1628 'open-ils.actor.user.transactions' => '',
1629 'open-ils.actor.user.transactions.fleshed' => '',
1630 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1631 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1632 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1633 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1636 foreach (keys %methods) {
1638 method => "user_transactions",
1641 desc => 'For a given user, retrieve a list of '
1642 . (/\.fleshed/ ? 'fleshed ' : '')
1643 . 'transactions' . $methods{$_}
1644 . ' optionally limited to transactions of a given type.',
1645 params => $common_params,
1647 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1648 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1652 $args{authoritative} = 1;
1653 __PACKAGE__->register_method(%args);
1656 # Now for the counts
1658 'open-ils.actor.user.transactions.count' => '',
1659 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1660 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1663 foreach (keys %methods) {
1665 method => "user_transactions",
1668 desc => 'For a given user, retrieve a count of open '
1669 . 'transactions' . $methods{$_}
1670 . ' optionally limited to transactions of a given type.',
1671 params => $common_params,
1672 return => { desc => "Integer count of transactions, or event on error" }
1675 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1676 __PACKAGE__->register_method(%args);
1679 __PACKAGE__->register_method(
1680 method => "user_transactions",
1681 api_name => "open-ils.actor.user.transactions.have_balance.total",
1684 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1685 . ' optionally limited to transactions of a given type.',
1686 params => $common_params,
1687 return => { desc => "Decimal balance value, or event on error" }
1692 sub user_transactions {
1693 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1696 my $e = new_editor(authtoken => $auth);
1697 return $e->event unless $e->checkauth;
1699 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1701 return $e->event unless
1702 $e->requestor->id == $user_id or
1703 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1705 my $api = $self->api_name();
1707 my $filter = ($api =~ /have_balance/o) ?
1708 { 'balance_owed' => { '<>' => 0 } }:
1709 { 'total_owed' => { '>' => 0 } };
1711 my $method = 'open-ils.actor.user.transactions.history.still_open';
1712 $method = "$method.authoritative" if $api =~ /authoritative/;
1713 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1715 if($api =~ /total/o) {
1717 $total += $_->balance_owed for @$trans;
1721 ($api =~ /count/o ) and return scalar @$trans;
1722 ($api !~ /fleshed/o) and return $trans;
1725 for my $t (@$trans) {
1727 if( $t->xact_type ne 'circulation' ) {
1728 push @resp, {transaction => $t};
1732 my $circ_data = flesh_circ($e, $t->id);
1733 push @resp, {transaction => $t, %$circ_data};
1740 __PACKAGE__->register_method(
1741 method => "user_transaction_retrieve",
1742 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1745 notes => "Returns a fleshed transaction record"
1748 __PACKAGE__->register_method(
1749 method => "user_transaction_retrieve",
1750 api_name => "open-ils.actor.user.transaction.retrieve",
1753 notes => "Returns a transaction record"
1756 sub user_transaction_retrieve {
1757 my($self, $client, $auth, $bill_id) = @_;
1759 my $e = new_editor(authtoken => $auth);
1760 return $e->event unless $e->checkauth;
1762 my $trans = $e->retrieve_money_billable_transaction_summary(
1763 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1765 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1767 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1769 return $trans unless $self->api_name =~ /flesh/;
1770 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1772 my $circ_data = flesh_circ($e, $trans->id, 1);
1774 return {transaction => $trans, %$circ_data};
1779 my $circ_id = shift;
1780 my $flesh_copy = shift;
1782 my $circ = $e->retrieve_action_circulation([
1786 circ => ['target_copy'],
1787 acp => ['call_number'],
1794 my $copy = $circ->target_copy;
1796 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1797 $mods = new Fieldmapper::metabib::virtual_record;
1798 $mods->doc_id(OILS_PRECAT_RECORD);
1799 $mods->title($copy->dummy_title);
1800 $mods->author($copy->dummy_author);
1803 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1807 $circ->target_copy($circ->target_copy->id);
1808 $copy->call_number($copy->call_number->id);
1810 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1814 __PACKAGE__->register_method(
1815 method => "hold_request_count",
1816 api_name => "open-ils.actor.user.hold_requests.count",
1819 notes => 'Returns hold ready/total counts'
1822 sub hold_request_count {
1823 my( $self, $client, $login_session, $userid ) = @_;
1825 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1826 $login_session, $userid, 'VIEW_HOLD' );
1827 return $evt if $evt;
1830 my $holds = $apputils->simple_scalar_request(
1832 "open-ils.cstore.direct.action.hold_request.search.atomic",
1835 fulfillment_time => {"=" => undef },
1836 cancel_time => undef,
1841 for my $h (@$holds) {
1842 next unless $h->capture_time and $h->current_copy;
1844 my $copy = $apputils->simple_scalar_request(
1846 "open-ils.cstore.direct.asset.copy.retrieve",
1850 if ($copy and $copy->status == 8) {
1855 return { total => scalar(@$holds), ready => scalar(@ready) };
1858 __PACKAGE__->register_method(
1859 method => "checked_out",
1860 api_name => "open-ils.actor.user.checked_out",
1864 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1865 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1866 . "(i.e., outstanding balance or some other pending action on the circ). "
1867 . "The .count method also includes a 'total' field which sums all open circs.",
1869 { desc => 'Authentication Token', type => 'string'},
1870 { desc => 'User ID', type => 'string'},
1873 desc => 'Returns event on error, or an object with ID lists, like: '
1874 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1879 __PACKAGE__->register_method(
1880 method => "checked_out",
1881 api_name => "open-ils.actor.user.checked_out.count",
1884 signature => q/@see open-ils.actor.user.checked_out/
1888 my( $self, $conn, $auth, $userid ) = @_;
1890 my $e = new_editor(authtoken=>$auth);
1891 return $e->event unless $e->checkauth;
1893 if( $userid ne $e->requestor->id ) {
1894 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1895 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1897 # see if there is a friend link allowing circ.view perms
1898 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1899 $e, $userid, $e->requestor->id, 'circ.view');
1900 return $e->event unless $allowed;
1904 my $count = $self->api_name =~ /count/;
1905 return _checked_out( $count, $e, $userid );
1909 my( $iscount, $e, $userid ) = @_;
1915 claims_returned => [],
1918 my $meth = 'retrieve_action_open_circ_';
1926 claims_returned => 0,
1933 my $data = $e->$meth($userid);
1937 $result{$_} += $data->$_() for (keys %result);
1938 $result{total} += $data->$_() for (keys %result);
1940 for my $k (keys %result) {
1941 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
1951 __PACKAGE__->register_method(
1952 method => "checked_in_with_fines",
1953 api_name => "open-ils.actor.user.checked_in_with_fines",
1956 signature => q/@see open-ils.actor.user.checked_out/
1959 sub checked_in_with_fines {
1960 my( $self, $conn, $auth, $userid ) = @_;
1962 my $e = new_editor(authtoken=>$auth);
1963 return $e->event unless $e->checkauth;
1965 if( $userid ne $e->requestor->id ) {
1966 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1969 # money is owed on these items and they are checked in
1970 my $open = $e->search_action_circulation(
1973 xact_finish => undef,
1974 checkin_time => { "!=" => undef },
1979 my( @lost, @cr, @lo );
1980 for my $c (@$open) {
1981 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1982 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1983 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1988 claims_returned => \@cr,
1989 long_overdue => \@lo
1995 my ($api, $desc, $auth) = @_;
1996 $desc = $desc ? (" " . $desc) : '';
1997 my $ids = ($api =~ /ids$/) ? 1 : 0;
2000 method => "user_transaction_history",
2001 api_name => "open-ils.actor.user.transactions.$api",
2003 desc => "For a given User ID, returns a list of billable transaction" .
2004 ($ids ? " id" : '') .
2005 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2006 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2008 {desc => 'Authentication token', type => 'string'},
2009 {desc => 'User ID', type => 'number'},
2010 {desc => 'Transaction type (optional)', type => 'number'},
2011 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2014 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2018 $auth and push @sig, (authoritative => 1);
2022 my %auth_hist_methods = (
2024 'history.have_charge' => 'that have an initial charge',
2025 'history.still_open' => 'that are not finished',
2026 'history.have_balance' => 'that have a balance',
2027 'history.have_bill' => 'that have billings',
2028 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2029 'history.have_payment' => 'that have at least 1 payment',
2032 foreach (keys %auth_hist_methods) {
2033 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2034 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2035 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2038 sub user_transaction_history {
2039 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2043 my $e = new_editor(authtoken=>$auth);
2044 return $e->die_event unless $e->checkauth;
2046 if ($e->requestor->id ne $userid) {
2047 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2050 my $api = $self->api_name;
2051 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2053 if(defined($type)) {
2054 $filter->{'xact_type'} = $type;
2057 if($api =~ /have_bill_or_payment/o) {
2059 # transactions that have a non-zero sum across all billings or at least 1 payment
2060 $filter->{'-or'} = {
2061 'balance_owed' => { '<>' => 0 },
2062 'last_payment_ts' => { '<>' => undef }
2065 } elsif($api =~ /have_payment/) {
2067 $filter->{last_payment_ts} ||= {'<>' => undef};
2069 } elsif( $api =~ /have_balance/o) {
2071 # transactions that have a non-zero overall balance
2072 $filter->{'balance_owed'} = { '<>' => 0 };
2074 } elsif( $api =~ /have_charge/o) {
2076 # transactions that have at least 1 billing, regardless of whether it was voided
2077 $filter->{'last_billing_ts'} = { '<>' => undef };
2079 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2081 # transactions that have non-zero sum across all billings. This will exclude
2082 # xacts where all billings have been voided
2083 $filter->{'total_owed'} = { '<>' => 0 };
2086 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2087 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2088 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2090 my $mbts = $e->search_money_billable_transaction_summary(
2091 [ { usr => $userid, @xact_finish, %$filter },
2096 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2097 return $mbts unless $api =~ /fleshed/;
2100 for my $t (@$mbts) {
2102 if( $t->xact_type ne 'circulation' ) {
2103 push @resp, {transaction => $t};
2107 my $circ_data = flesh_circ($e, $t->id);
2108 push @resp, {transaction => $t, %$circ_data};
2116 __PACKAGE__->register_method(
2117 method => "user_perms",
2118 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2120 notes => "Returns a list of permissions"
2124 my( $self, $client, $authtoken, $user ) = @_;
2126 my( $staff, $evt ) = $apputils->checkses($authtoken);
2127 return $evt if $evt;
2129 $user ||= $staff->id;
2131 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2135 return $apputils->simple_scalar_request(
2137 "open-ils.storage.permission.user_perms.atomic",
2141 __PACKAGE__->register_method(
2142 method => "retrieve_perms",
2143 api_name => "open-ils.actor.permissions.retrieve",
2144 notes => "Returns a list of permissions"
2146 sub retrieve_perms {
2147 my( $self, $client ) = @_;
2148 return $apputils->simple_scalar_request(
2150 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2151 { id => { '!=' => undef } }
2155 __PACKAGE__->register_method(
2156 method => "retrieve_groups",
2157 api_name => "open-ils.actor.groups.retrieve",
2158 notes => "Returns a list of user groups"
2160 sub retrieve_groups {
2161 my( $self, $client ) = @_;
2162 return new_editor()->retrieve_all_permission_grp_tree();
2165 __PACKAGE__->register_method(
2166 method => "retrieve_org_address",
2167 api_name => "open-ils.actor.org_unit.address.retrieve",
2168 notes => <<' NOTES');
2169 Returns an org_unit address by ID
2170 @param An org_address ID
2172 sub retrieve_org_address {
2173 my( $self, $client, $id ) = @_;
2174 return $apputils->simple_scalar_request(
2176 "open-ils.cstore.direct.actor.org_address.retrieve",
2181 __PACKAGE__->register_method(
2182 method => "retrieve_groups_tree",
2183 api_name => "open-ils.actor.groups.tree.retrieve",
2184 notes => "Returns a list of user groups"
2187 sub retrieve_groups_tree {
2188 my( $self, $client ) = @_;
2189 return new_editor()->search_permission_grp_tree(
2194 flesh_fields => { pgt => ["children"] },
2195 order_by => { pgt => 'name'}
2202 __PACKAGE__->register_method(
2203 method => "add_user_to_groups",
2204 api_name => "open-ils.actor.user.set_groups",
2205 notes => "Adds a user to one or more permission groups"
2208 sub add_user_to_groups {
2209 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2211 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2212 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2213 return $evt if $evt;
2215 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2216 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2217 return $evt if $evt;
2219 $apputils->simplereq(
2221 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2223 for my $group (@$groups) {
2224 my $link = Fieldmapper::permission::usr_grp_map->new;
2226 $link->usr($userid);
2228 my $id = $apputils->simplereq(
2230 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2236 __PACKAGE__->register_method(
2237 method => "get_user_perm_groups",
2238 api_name => "open-ils.actor.user.get_groups",
2239 notes => "Retrieve a user's permission groups."
2243 sub get_user_perm_groups {
2244 my( $self, $client, $authtoken, $userid ) = @_;
2246 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2247 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2248 return $evt if $evt;
2250 return $apputils->simplereq(
2252 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2256 __PACKAGE__->register_method(
2257 method => "get_user_work_ous",
2258 api_name => "open-ils.actor.user.get_work_ous",
2259 notes => "Retrieve a user's work org units."
2262 __PACKAGE__->register_method(
2263 method => "get_user_work_ous",
2264 api_name => "open-ils.actor.user.get_work_ous.ids",
2265 notes => "Retrieve a user's work org units."
2268 sub get_user_work_ous {
2269 my( $self, $client, $auth, $userid ) = @_;
2270 my $e = new_editor(authtoken=>$auth);
2271 return $e->event unless $e->checkauth;
2272 $userid ||= $e->requestor->id;
2274 if($e->requestor->id != $userid) {
2275 my $user = $e->retrieve_actor_user($userid)
2276 or return $e->event;
2277 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2280 return $e->search_permission_usr_work_ou_map({usr => $userid})
2281 unless $self->api_name =~ /.ids$/;
2283 # client just wants a list of org IDs
2284 return $U->get_user_work_ou_ids($e, $userid);
2289 __PACKAGE__->register_method(
2290 method => 'register_workstation',
2291 api_name => 'open-ils.actor.workstation.register.override',
2292 signature => q/@see open-ils.actor.workstation.register/
2295 __PACKAGE__->register_method(
2296 method => 'register_workstation',
2297 api_name => 'open-ils.actor.workstation.register',
2299 Registers a new workstion in the system
2300 @param authtoken The login session key
2301 @param name The name of the workstation id
2302 @param owner The org unit that owns this workstation
2303 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2304 if the name is already in use.
2308 sub register_workstation {
2309 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2311 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2312 return $e->die_event unless $e->checkauth;
2313 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2314 my $existing = $e->search_actor_workstation({name => $name})->[0];
2318 if( $self->api_name =~ /override/o ) {
2319 # workstation with the given name exists.
2321 if($owner ne $existing->owning_lib) {
2322 # if necessary, update the owning_lib of the workstation
2324 $logger->info("changing owning lib of workstation ".$existing->id.
2325 " from ".$existing->owning_lib." to $owner");
2326 return $e->die_event unless
2327 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2329 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2331 $existing->owning_lib($owner);
2332 return $e->die_event unless $e->update_actor_workstation($existing);
2338 "attempt to register an existing workstation. returning existing ID");
2341 return $existing->id;
2344 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2348 my $ws = Fieldmapper::actor::workstation->new;
2349 $ws->owning_lib($owner);
2351 $e->create_actor_workstation($ws) or return $e->die_event;
2353 return $ws->id; # note: editor sets the id on the new object for us
2356 __PACKAGE__->register_method(
2357 method => 'workstation_list',
2358 api_name => 'open-ils.actor.workstation.list',
2360 Returns a list of workstations registered at the given location
2361 @param authtoken The login session key
2362 @param ids A list of org_unit.id's for the workstation owners
2366 sub workstation_list {
2367 my( $self, $conn, $authtoken, @orgs ) = @_;
2369 my $e = new_editor(authtoken=>$authtoken);
2370 return $e->event unless $e->checkauth;
2375 unless $e->allowed('REGISTER_WORKSTATION', $o);
2376 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2382 __PACKAGE__->register_method(
2383 method => 'fetch_patron_note',
2384 api_name => 'open-ils.actor.note.retrieve.all',
2387 Returns a list of notes for a given user
2388 Requestor must have VIEW_USER permission if pub==false and
2389 @param authtoken The login session key
2390 @param args Hash of params including
2391 patronid : the patron's id
2392 pub : true if retrieving only public notes
2396 sub fetch_patron_note {
2397 my( $self, $conn, $authtoken, $args ) = @_;
2398 my $patronid = $$args{patronid};
2400 my($reqr, $evt) = $U->checkses($authtoken);
2401 return $evt if $evt;
2404 ($patron, $evt) = $U->fetch_user($patronid);
2405 return $evt if $evt;
2408 if( $patronid ne $reqr->id ) {
2409 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2410 return $evt if $evt;
2412 return $U->cstorereq(
2413 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2414 { usr => $patronid, pub => 't' } );
2417 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2418 return $evt if $evt;
2420 return $U->cstorereq(
2421 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2424 __PACKAGE__->register_method(
2425 method => 'create_user_note',
2426 api_name => 'open-ils.actor.note.create',
2428 Creates a new note for the given user
2429 @param authtoken The login session key
2430 @param note The note object
2433 sub create_user_note {
2434 my( $self, $conn, $authtoken, $note ) = @_;
2435 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2436 return $e->die_event unless $e->checkauth;
2438 my $user = $e->retrieve_actor_user($note->usr)
2439 or return $e->die_event;
2441 return $e->die_event unless
2442 $e->allowed('UPDATE_USER',$user->home_ou);
2444 $note->creator($e->requestor->id);
2445 $e->create_actor_usr_note($note) or return $e->die_event;
2451 __PACKAGE__->register_method(
2452 method => 'delete_user_note',
2453 api_name => 'open-ils.actor.note.delete',
2455 Deletes a note for the given user
2456 @param authtoken The login session key
2457 @param noteid The note id
2460 sub delete_user_note {
2461 my( $self, $conn, $authtoken, $noteid ) = @_;
2463 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2464 return $e->die_event unless $e->checkauth;
2465 my $note = $e->retrieve_actor_usr_note($noteid)
2466 or return $e->die_event;
2467 my $user = $e->retrieve_actor_user($note->usr)
2468 or return $e->die_event;
2469 return $e->die_event unless
2470 $e->allowed('UPDATE_USER', $user->home_ou);
2472 $e->delete_actor_usr_note($note) or return $e->die_event;
2478 __PACKAGE__->register_method(
2479 method => 'update_user_note',
2480 api_name => 'open-ils.actor.note.update',
2482 @param authtoken The login session key
2483 @param note The note
2487 sub update_user_note {
2488 my( $self, $conn, $auth, $note ) = @_;
2489 my $e = new_editor(authtoken=>$auth, xact=>1);
2490 return $e->die_event unless $e->checkauth;
2491 my $patron = $e->retrieve_actor_user($note->usr)
2492 or return $e->die_event;
2493 return $e->die_event unless
2494 $e->allowed('UPDATE_USER', $patron->home_ou);
2495 $e->update_actor_user_note($note)
2496 or return $e->die_event;
2503 __PACKAGE__->register_method(
2504 method => 'create_closed_date',
2505 api_name => 'open-ils.actor.org_unit.closed_date.create',
2507 Creates a new closing entry for the given org_unit
2508 @param authtoken The login session key
2509 @param note The closed_date object
2512 sub create_closed_date {
2513 my( $self, $conn, $authtoken, $cd ) = @_;
2515 my( $user, $evt ) = $U->checkses($authtoken);
2516 return $evt if $evt;
2518 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2519 return $evt if $evt;
2521 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2523 my $id = $U->storagereq(
2524 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2525 return $U->DB_UPDATE_FAILED($cd) unless $id;
2530 __PACKAGE__->register_method(
2531 method => 'delete_closed_date',
2532 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2534 Deletes a closing entry for the given org_unit
2535 @param authtoken The login session key
2536 @param noteid The close_date id
2539 sub delete_closed_date {
2540 my( $self, $conn, $authtoken, $cd ) = @_;
2542 my( $user, $evt ) = $U->checkses($authtoken);
2543 return $evt if $evt;
2546 ($cd_obj, $evt) = fetch_closed_date($cd);
2547 return $evt if $evt;
2549 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2550 return $evt if $evt;
2552 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2554 my $stat = $U->storagereq(
2555 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2556 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2561 __PACKAGE__->register_method(
2562 method => 'usrname_exists',
2563 api_name => 'open-ils.actor.username.exists',
2565 desc => 'Check if a username is already taken (by an undeleted patron)',
2567 {desc => 'Authentication token', type => 'string'},
2568 {desc => 'Username', type => 'string'}
2571 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2576 sub usrname_exists {
2577 my( $self, $conn, $auth, $usrname ) = @_;
2578 my $e = new_editor(authtoken=>$auth);
2579 return $e->event unless $e->checkauth;
2580 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2581 return $$a[0] if $a and @$a;
2585 __PACKAGE__->register_method(
2586 method => 'barcode_exists',
2587 api_name => 'open-ils.actor.barcode.exists',
2589 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2592 sub barcode_exists {
2593 my( $self, $conn, $auth, $barcode ) = @_;
2594 my $e = new_editor(authtoken=>$auth);
2595 return $e->event unless $e->checkauth;
2596 my $card = $e->search_actor_card({barcode => $barcode});
2602 #return undef unless @$card;
2603 #return $card->[0]->usr;
2607 __PACKAGE__->register_method(
2608 method => 'retrieve_net_levels',
2609 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2612 sub retrieve_net_levels {
2613 my( $self, $conn, $auth ) = @_;
2614 my $e = new_editor(authtoken=>$auth);
2615 return $e->event unless $e->checkauth;
2616 return $e->retrieve_all_config_net_access_level();
2619 # Retain the old typo API name just in case
2620 __PACKAGE__->register_method(
2621 method => 'fetch_org_by_shortname',
2622 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2624 __PACKAGE__->register_method(
2625 method => 'fetch_org_by_shortname',
2626 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2628 sub fetch_org_by_shortname {
2629 my( $self, $conn, $sname ) = @_;
2630 my $e = new_editor();
2631 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2632 return $e->event unless $org;
2637 __PACKAGE__->register_method(
2638 method => 'session_home_lib',
2639 api_name => 'open-ils.actor.session.home_lib',
2642 sub session_home_lib {
2643 my( $self, $conn, $auth ) = @_;
2644 my $e = new_editor(authtoken=>$auth);
2645 return undef unless $e->checkauth;
2646 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2647 return $org->shortname;
2650 __PACKAGE__->register_method(
2651 method => 'session_safe_token',
2652 api_name => 'open-ils.actor.session.safe_token',
2654 Returns a hashed session ID that is safe for export to the world.
2655 This safe token will expire after 1 hour of non-use.
2656 @param auth Active authentication token
2660 sub session_safe_token {
2661 my( $self, $conn, $auth ) = @_;
2662 my $e = new_editor(authtoken=>$auth);
2663 return undef unless $e->checkauth;
2665 my $safe_token = md5_hex($auth);
2667 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2669 # Add more like the following if needed...
2671 "safe-token-home_lib-shortname-$safe_token",
2672 $e->retrieve_actor_org_unit(
2673 $e->requestor->home_ou
2682 __PACKAGE__->register_method(
2683 method => 'safe_token_home_lib',
2684 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2686 Returns the home library shortname from the session
2687 asscociated with a safe token from generated by
2688 open-ils.actor.session.safe_token.
2689 @param safe_token Active safe token
2693 sub safe_token_home_lib {
2694 my( $self, $conn, $safe_token ) = @_;
2696 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2697 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2702 __PACKAGE__->register_method(
2703 method => 'slim_tree',
2704 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2707 my $tree = new_editor()->search_actor_org_unit(
2709 {"parent_ou" => undef },
2712 flesh_fields => { aou => ['children'] },
2713 order_by => { aou => 'name'},
2714 select => { aou => ["id","shortname", "name"]},
2719 return trim_tree($tree);
2725 return undef unless $tree;
2727 code => $tree->shortname,
2728 name => $tree->name,
2730 if( $tree->children and @{$tree->children} ) {
2731 $htree->{children} = [];
2732 for my $c (@{$tree->children}) {
2733 push( @{$htree->{children}}, trim_tree($c) );
2741 __PACKAGE__->register_method(
2742 method => "update_penalties",
2743 api_name => "open-ils.actor.user.penalties.update"
2746 sub update_penalties {
2747 my($self, $conn, $auth, $user_id) = @_;
2748 my $e = new_editor(authtoken=>$auth, xact => 1);
2749 return $e->die_event unless $e->checkauth;
2750 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2751 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2752 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2753 return $evt if $evt;
2759 __PACKAGE__->register_method(
2760 method => "apply_penalty",
2761 api_name => "open-ils.actor.user.penalty.apply"
2765 my($self, $conn, $auth, $penalty) = @_;
2767 my $e = new_editor(authtoken=>$auth, xact => 1);
2768 return $e->die_event unless $e->checkauth;
2770 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2771 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2773 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2776 (defined $ptype->org_depth) ?
2777 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2780 $penalty->org_unit($ctx_org);
2781 $penalty->staff($e->requestor->id);
2782 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2785 return $penalty->id;
2788 __PACKAGE__->register_method(
2789 method => "remove_penalty",
2790 api_name => "open-ils.actor.user.penalty.remove"
2793 sub remove_penalty {
2794 my($self, $conn, $auth, $penalty) = @_;
2795 my $e = new_editor(authtoken=>$auth, xact => 1);
2796 return $e->die_event unless $e->checkauth;
2797 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2798 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2800 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2805 __PACKAGE__->register_method(
2806 method => "update_penalty_note",
2807 api_name => "open-ils.actor.user.penalty.note.update"
2810 sub update_penalty_note {
2811 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2812 my $e = new_editor(authtoken=>$auth, xact => 1);
2813 return $e->die_event unless $e->checkauth;
2814 for my $penalty_id (@$penalty_ids) {
2815 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2816 if (! $penalty ) { return $e->die_event; }
2817 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2818 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2820 $penalty->note( $note ); $penalty->ischanged( 1 );
2822 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2828 __PACKAGE__->register_method(
2829 method => "ranged_penalty_thresholds",
2830 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2834 sub ranged_penalty_thresholds {
2835 my($self, $conn, $auth, $context_org) = @_;
2836 my $e = new_editor(authtoken=>$auth);
2837 return $e->event unless $e->checkauth;
2838 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2839 my $list = $e->search_permission_grp_penalty_threshold([
2840 {org_unit => $U->get_org_ancestors($context_org)},
2841 {order_by => {pgpt => 'id'}}
2843 $conn->respond($_) for @$list;
2849 __PACKAGE__->register_method(
2850 method => "user_retrieve_fleshed_by_id",
2852 api_name => "open-ils.actor.user.fleshed.retrieve",
2855 sub user_retrieve_fleshed_by_id {
2856 my( $self, $client, $auth, $user_id, $fields ) = @_;
2857 my $e = new_editor(authtoken => $auth);
2858 return $e->event unless $e->checkauth;
2860 if( $e->requestor->id != $user_id ) {
2861 return $e->event unless $e->allowed('VIEW_USER');
2867 "standing_penalties",
2871 "stat_cat_entries" ];
2872 return new_flesh_user($user_id, $fields, $e);
2876 sub new_flesh_user {
2879 my $fields = shift || [];
2882 my $fetch_penalties = 0;
2883 if(grep {$_ eq 'standing_penalties'} @$fields) {
2884 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2885 $fetch_penalties = 1;
2888 my $user = $e->retrieve_actor_user(
2893 "flesh_fields" => { "au" => $fields }
2896 ) or return $e->die_event;
2899 if( grep { $_ eq 'addresses' } @$fields ) {
2901 $user->addresses([]) unless @{$user->addresses};
2902 # don't expose "replaced" addresses by default
2903 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2905 if( ref $user->billing_address ) {
2906 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2907 push( @{$user->addresses}, $user->billing_address );
2911 if( ref $user->mailing_address ) {
2912 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2913 push( @{$user->addresses}, $user->mailing_address );
2918 if($fetch_penalties) {
2919 # grab the user penalties ranged for this location
2920 $user->standing_penalties(
2921 $e->search_actor_user_standing_penalty([
2924 {stop_date => undef},
2925 {stop_date => {'>' => 'now'}}
2927 org_unit => $U->get_org_ancestors($e->requestor->ws_ou)
2930 flesh_fields => {ausp => ['standing_penalty']}
2937 $user->clear_passwd();
2944 __PACKAGE__->register_method(
2945 method => "user_retrieve_parts",
2946 api_name => "open-ils.actor.user.retrieve.parts",
2949 sub user_retrieve_parts {
2950 my( $self, $client, $auth, $user_id, $fields ) = @_;
2951 my $e = new_editor(authtoken => $auth);
2952 return $e->event unless $e->checkauth;
2953 $user_id ||= $e->requestor->id;
2954 if( $e->requestor->id != $user_id ) {
2955 return $e->event unless $e->allowed('VIEW_USER');
2958 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2959 push(@resp, $user->$_()) for(@$fields);
2965 __PACKAGE__->register_method(
2966 method => 'user_opt_in_enabled',
2967 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2968 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2971 sub user_opt_in_enabled {
2972 my($self, $conn) = @_;
2973 my $sc = OpenSRF::Utils::SettingsClient->new;
2974 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2979 __PACKAGE__->register_method(
2980 method => 'user_opt_in_at_org',
2981 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2983 @param $auth The auth token
2984 @param user_id The ID of the user to test
2985 @return 1 if the user has opted in at the specified org,
2986 event on error, and 0 otherwise. /
2988 sub user_opt_in_at_org {
2989 my($self, $conn, $auth, $user_id) = @_;
2991 # see if we even need to enforce the opt-in value
2992 return 1 unless user_opt_in_enabled($self);
2994 my $e = new_editor(authtoken => $auth);
2995 return $e->event unless $e->checkauth;
2997 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2998 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3000 my $ws_org = $e->requestor->ws_ou;
3001 # user is automatically opted-in if they are from the local org
3002 return 1 if $user->home_ou eq $ws_org;
3004 # get the boundary setting
3005 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3007 # auto opt in if user falls within the opt boundary
3008 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3010 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3012 my $vals = $e->search_actor_usr_org_unit_opt_in(
3013 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3019 __PACKAGE__->register_method(
3020 method => 'create_user_opt_in_at_org',
3021 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3023 @param $auth The auth token
3024 @param user_id The ID of the user to test
3025 @return The ID of the newly created object, event on error./
3028 sub create_user_opt_in_at_org {
3029 my($self, $conn, $auth, $user_id, $org_id) = @_;
3031 my $e = new_editor(authtoken => $auth, xact=>1);
3032 return $e->die_event unless $e->checkauth;
3034 # if a specific org unit wasn't passed in, get one based on the defaults;
3036 my $wsou = $e->requestor->ws_ou;
3037 # get the default opt depth
3038 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3039 # get the org unit at that depth
3040 my $org = $e->json_query({
3041 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3043 $org_id = $org->{id};
3046 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3047 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3049 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3051 $opt_in->org_unit($org_id);
3052 $opt_in->usr($user_id);
3053 $opt_in->staff($e->requestor->id);
3054 $opt_in->opt_in_ts('now');
3055 $opt_in->opt_in_ws($e->requestor->wsid);
3057 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3058 or return $e->die_event;
3066 __PACKAGE__->register_method (
3067 method => 'retrieve_org_hours',
3068 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3070 Returns the hours of operation for a specified org unit
3071 @param authtoken The login session key
3072 @param org_id The org_unit ID
3076 sub retrieve_org_hours {
3077 my($self, $conn, $auth, $org_id) = @_;
3078 my $e = new_editor(authtoken => $auth);
3079 return $e->die_event unless $e->checkauth;
3080 $org_id ||= $e->requestor->ws_ou;
3081 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3085 __PACKAGE__->register_method (
3086 method => 'verify_user_password',
3087 api_name => 'open-ils.actor.verify_user_password',
3089 Given a barcode or username and the MD5 encoded password,
3090 returns 1 if the password is correct. Returns 0 otherwise.
3094 sub verify_user_password {
3095 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3096 my $e = new_editor(authtoken => $auth);
3097 return $e->die_event unless $e->checkauth;
3099 my $user_by_barcode;
3100 my $user_by_username;
3102 my $card = $e->search_actor_card([
3103 {barcode => $barcode},
3104 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3105 $user_by_barcode = $card->usr;
3106 $user = $user_by_barcode;
3109 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3110 $user = $user_by_username;
3112 return 0 if (!$user);
3113 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3114 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3115 return 1 if $user->passwd eq $password;
3119 __PACKAGE__->register_method (
3120 method => 'retrieve_usr_id_via_barcode_or_usrname',
3121 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3123 Given a barcode or username returns the id for the user or
3128 sub retrieve_usr_id_via_barcode_or_usrname {
3129 my($self, $conn, $auth, $barcode, $username) = @_;
3130 my $e = new_editor(authtoken => $auth);
3131 return $e->die_event unless $e->checkauth;
3132 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3134 my $user_by_barcode;
3135 my $user_by_username;
3136 $logger->info("$id_as_barcode is the ID as BARCODE");
3138 my $card = $e->search_actor_card([
3139 {barcode => $barcode},
3140 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3141 if ($id_as_barcode =~ /^t/i) {
3143 $user = $e->retrieve_actor_user($barcode);
3144 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3146 $user_by_barcode = $card->usr;
3147 $user = $user_by_barcode;
3150 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3151 $user_by_barcode = $card->usr;
3152 $user = $user_by_barcode;
3157 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3159 $user = $user_by_username;
3161 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3162 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3163 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3168 __PACKAGE__->register_method (
3169 method => 'merge_users',
3170 api_name => 'open-ils.actor.user.merge',
3173 Given a list of source users and destination user, transfer all data from the source
3174 to the dest user and delete the source user. All user related data is
3175 transferred, including circulations, holds, bookbags, etc.
3181 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3182 my $e = new_editor(xact => 1, authtoken => $auth);
3183 return $e->die_event unless $e->checkauth;
3185 # disallow the merge if any subordinate accounts are in collections
3186 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3187 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3189 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3190 my $del_addrs = ($U->ou_ancestor_setting_value(
3191 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3192 my $del_cards = ($U->ou_ancestor_setting_value(
3193 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3194 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3195 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3197 for my $src_id (@$user_ids) {
3198 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3200 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3201 if($src_user->home_ou ne $master_user->home_ou) {
3202 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3205 return $e->die_event unless
3206 $e->json_query({from => [
3221 __PACKAGE__->register_method (
3222 method => 'approve_user_address',
3223 api_name => 'open-ils.actor.user.pending_address.approve',
3230 sub approve_user_address {
3231 my($self, $conn, $auth, $addr) = @_;
3232 my $e = new_editor(xact => 1, authtoken => $auth);
3233 return $e->die_event unless $e->checkauth;
3235 # if the caller passes an address object, assume they want to
3236 # update it first before approving it
3237 $e->update_actor_user_address($addr) or return $e->die_event;
3239 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3241 my $user = $e->retrieve_actor_user($addr->usr);
3242 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3243 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3244 or return $e->die_event;
3246 return [values %$result]->[0];
3250 __PACKAGE__->register_method (
3251 method => 'retrieve_friends',
3252 api_name => 'open-ils.actor.friends.retrieve',
3255 returns { confirmed: [], pending_out: [], pending_in: []}
3256 pending_out are users I'm requesting friendship with
3257 pending_in are users requesting friendship with me
3262 sub retrieve_friends {
3263 my($self, $conn, $auth, $user_id, $options) = @_;
3264 my $e = new_editor(authtoken => $auth);
3265 return $e->event unless $e->checkauth;
3266 $user_id ||= $e->requestor->id;
3268 if($user_id != $e->requestor->id) {
3269 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3270 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3273 return OpenILS::Application::Actor::Friends->retrieve_friends(
3274 $e, $user_id, $options);
3279 __PACKAGE__->register_method (
3280 method => 'apply_friend_perms',
3281 api_name => 'open-ils.actor.friends.perms.apply',
3287 sub apply_friend_perms {
3288 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3289 my $e = new_editor(authtoken => $auth, xact => 1);
3290 return $e->die_event unless $e->checkauth;
3292 if($user_id != $e->requestor->id) {
3293 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3294 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3297 for my $perm (@perms) {
3299 OpenILS::Application::Actor::Friends->apply_friend_perm(
3300 $e, $user_id, $delegate_id, $perm);
3301 return $evt if $evt;
3309 __PACKAGE__->register_method (
3310 method => 'update_user_pending_address',
3311 api_name => 'open-ils.actor.user.address.pending.cud'
3314 sub update_user_pending_address {
3315 my($self, $conn, $auth, $addr) = @_;
3316 my $e = new_editor(authtoken => $auth, xact => 1);
3317 return $e->die_event unless $e->checkauth;
3319 if($addr->usr != $e->requestor->id) {
3320 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3321 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3325 $e->create_actor_user_address($addr) or return $e->die_event;
3326 } elsif($addr->isdeleted) {
3327 $e->delete_actor_user_address($addr) or return $e->die_event;
3329 $e->update_actor_user_address($addr) or return $e->die_event;
3337 __PACKAGE__->register_method (
3338 method => 'user_events',
3339 api_name => 'open-ils.actor.user.events.circ',
3342 __PACKAGE__->register_method (
3343 method => 'user_events',
3344 api_name => 'open-ils.actor.user.events.ahr',
3349 my($self, $conn, $auth, $user_id, $filters) = @_;
3350 my $e = new_editor(authtoken => $auth);
3351 return $e->event unless $e->checkauth;
3353 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3354 my $user_field = 'usr';
3357 $filters->{target} = {
3358 select => { $obj_type => ['id'] },
3360 where => {usr => $user_id}
3363 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3364 if($e->requestor->id != $user_id) {
3365 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3368 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3369 my $req = $ses->request('open-ils.trigger.events_by_target',
3370 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3372 while(my $resp = $req->recv) {
3373 my $val = $resp->content;
3374 my $tgt = $val->target;
3376 if($obj_type eq 'circ') {
3377 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3379 } elsif($obj_type eq 'ahr') {
3380 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3381 if $tgt->current_copy;
3384 $conn->respond($val) if $val;
3390 __PACKAGE__->register_method (
3391 method => 'copy_events',
3392 api_name => 'open-ils.actor.copy.events.circ',
3395 __PACKAGE__->register_method (
3396 method => 'copy_events',
3397 api_name => 'open-ils.actor.copy.events.ahr',
3402 my($self, $conn, $auth, $copy_id, $filters) = @_;
3403 my $e = new_editor(authtoken => $auth);
3404 return $e->event unless $e->checkauth;
3406 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3408 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3410 my $copy_field = 'target_copy';
3411 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3414 $filters->{target} = {
3415 select => { $obj_type => ['id'] },
3417 where => {$copy_field => $copy_id}
3421 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3422 my $req = $ses->request('open-ils.trigger.events_by_target',
3423 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3425 while(my $resp = $req->recv) {
3426 my $val = $resp->content;
3427 my $tgt = $val->target;
3429 my $user = $e->retrieve_actor_user($tgt->usr);
3430 if($e->requestor->id != $user->id) {
3431 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3434 $tgt->$copy_field($copy);
3437 $conn->respond($val) if $val;
3446 __PACKAGE__->register_method (
3447 method => 'update_events',
3448 api_name => 'open-ils.actor.user.event.cancel.batch',
3451 __PACKAGE__->register_method (
3452 method => 'update_events',
3453 api_name => 'open-ils.actor.user.event.reset.batch',
3458 my($self, $conn, $auth, $event_ids) = @_;
3459 my $e = new_editor(xact => 1, authtoken => $auth);
3460 return $e->die_event unless $e->checkauth;
3463 for my $id (@$event_ids) {
3465 # do a little dance to determine what user we are ultimately affecting
3466 my $event = $e->retrieve_action_trigger_event([
3469 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3471 ]) or return $e->die_event;
3474 if($event->event_def->hook->core_type eq 'circ') {
3475 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3476 } elsif($event->event_def->hook->core_type eq 'ahr') {
3477 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3482 my $user = $e->retrieve_actor_user($user_id);
3483 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3485 if($self->api_name =~ /cancel/) {
3486 $event->state('invalid');
3487 } elsif($self->api_name =~ /reset/) {
3488 $event->clear_start_time;
3489 $event->clear_update_time;
3490 $event->state('pending');
3493 $e->update_action_trigger_event($event) or return $e->die_event;
3494 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3498 return {complete => 1};
3502 __PACKAGE__->register_method (
3503 method => 'really_delete_user',
3504 api_name => 'open-ils.actor.user.delete',
3506 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3507 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3508 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3509 dest_usr_id is only required when deleting a user that performs staff functions.
3513 sub really_delete_user {
3514 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3515 my $e = new_editor(authtoken => $auth, xact => 1);
3516 return $e->die_event unless $e->checkauth;
3517 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3518 # No deleting yourself - UI is supposed to stop you first, though.
3519 return $e->die_event unless $e->requestor->id != $user->id;
3520 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3521 # Check if you are allowed to mess with this patron permission group at all
3522 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3523 my $evt = group_perm_failed($session, $e->requestor, $user);
3524 return $e->die_event($evt) if $evt;
3525 my $stat = $e->json_query(
3526 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3527 or return $e->die_event;
3534 __PACKAGE__->register_method (
3535 method => 'user_payments',
3536 api_name => 'open-ils.actor.user.payments.retrieve',
3539 Returns all payments for a given user. Default order is newest payments first.
3540 @param auth Authentication token
3541 @param user_id The user ID
3542 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3547 my($self, $conn, $auth, $user_id, $filters) = @_;
3550 my $e = new_editor(authtoken => $auth);
3551 return $e->die_event unless $e->checkauth;
3553 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3554 return $e->event unless
3555 $e->requestor->id == $user_id or
3556 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3558 # Find all payments for all transactions for user $user_id
3560 select => {mp => ['id']},
3565 select => {mbt => ['id']},
3567 where => {usr => $user_id}
3571 order_by => [{ # by default, order newest payments first
3573 field => 'payment_ts',
3578 for (qw/order_by limit offset/) {
3579 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3582 if(defined $filters->{where}) {
3583 foreach (keys %{$filters->{where}}) {
3584 # don't allow the caller to expand the result set to other users
3585 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3589 my $payment_ids = $e->json_query($query);
3590 for my $pid (@$payment_ids) {
3591 my $pay = $e->retrieve_money_payment([
3596 mbt => ['summary', 'circulation', 'grocery'],
3597 circ => ['target_copy'],
3598 acp => ['call_number'],
3606 xact_type => $pay->xact->summary->xact_type,
3607 last_billing_type => $pay->xact->summary->last_billing_type,
3610 if($pay->xact->summary->xact_type eq 'circulation') {
3611 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3612 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3615 $pay->xact($pay->xact->id); # de-flesh
3616 $conn->respond($resp);
3624 __PACKAGE__->register_method (
3625 method => 'negative_balance_users',
3626 api_name => 'open-ils.actor.users.negative_balance',
3629 Returns all users that have an overall negative balance
3630 @param auth Authentication token
3631 @param org_id The context org unit as an ID or list of IDs. This will be the home
3632 library of the user. If no org_unit is specified, no org unit filter is applied
3636 sub negative_balance_users {
3637 my($self, $conn, $auth, $org_id) = @_;
3639 my $e = new_editor(authtoken => $auth);
3640 return $e->die_event unless $e->checkauth;
3641 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3645 mous => ['usr', 'balance_owed'],
3648 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3649 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3666 where => {'+mous' => {balance_owed => {'<' => 0}}}
3669 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3671 my $list = $e->json_query($query, {timeout => 600});
3673 for my $data (@$list) {
3675 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3676 balance_owed => $data->{balance_owed},
3677 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3684 __PACKAGE__->register_method(
3685 method => "request_password_reset",
3686 api_name => "open-ils.actor.patron.password_reset.request",
3688 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3689 "method for changing a user's password. The UUID token is distributed via A/T " .
3690 "templates (i.e. email to the user).",
3692 { desc => 'user_id_type', type => 'string' },
3693 { desc => 'user_id', type => 'string' },
3694 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3696 return => {desc => '1 on success, Event on error'}
3699 sub request_password_reset {
3700 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3702 # Check to see if password reset requests are already being throttled:
3703 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3705 my $e = new_editor(xact => 1);
3708 # Get the user, if any, depending on the input value
3709 if ($user_id_type eq 'username') {
3710 $user = $e->search_actor_user({usrname => $user_id})->[0];
3713 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3715 } elsif ($user_id_type eq 'barcode') {
3716 my $card = $e->search_actor_card([
3717 {barcode => $user_id},
3718 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3721 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3726 # If the user doesn't have an email address, we can't help them
3727 if (!$user->email) {
3729 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3732 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3733 if ($email_must_match) {
3734 if ($user->email ne $email) {
3735 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3739 _reset_password_request($conn, $e, $user);
3742 # Once we have the user, we can issue the password reset request
3743 # XXX Add a wrapper method that accepts barcode + email input
3744 sub _reset_password_request {
3745 my ($conn, $e, $user) = @_;
3747 # 1. Get throttle threshold and time-to-live from OU_settings
3748 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3749 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3751 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3753 # 2. Get time of last request and number of active requests (num_active)
3754 my $active_requests = $e->json_query({
3760 transform => 'COUNT'
3763 column => 'request_time',
3769 has_been_reset => { '=' => 'f' },
3770 request_time => { '>' => $threshold_time }
3774 # Guard against no active requests
3775 if ($active_requests->[0]->{'request_time'}) {
3776 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3777 my $now = DateTime::Format::ISO8601->new();
3779 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3780 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3781 ($last_request->add_duration('1 minute') > $now)) {
3782 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3784 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3788 # TODO Check to see if the user is in a password-reset-restricted group
3790 # Otherwise, go ahead and try to get the user.
3792 # Check the number of active requests for this user
3793 $active_requests = $e->json_query({
3799 transform => 'COUNT'
3804 usr => { '=' => $user->id },
3805 has_been_reset => { '=' => 'f' },
3806 request_time => { '>' => $threshold_time }
3810 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3812 # if less than or equal to per-user threshold, proceed; otherwise, return event
3813 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3814 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3816 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3819 # Create the aupr object and insert into the database
3820 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3821 my $uuid = create_uuid_as_string(UUID_V4);
3822 $reset_request->uuid($uuid);
3823 $reset_request->usr($user->id);
3825 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3828 # Create an event to notify user of the URL to reset their password
3830 # Can we stuff this in the user_data param for trigger autocreate?
3831 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3833 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3834 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3837 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3842 __PACKAGE__->register_method(
3843 method => "commit_password_reset",
3844 api_name => "open-ils.actor.patron.password_reset.commit",
3846 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3847 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3848 "with the supplied password.",
3850 { desc => 'uuid', type => 'string' },
3851 { desc => 'password', type => 'string' },
3853 return => {desc => '1 on success, Event on error'}
3856 sub commit_password_reset {
3857 my($self, $conn, $uuid, $password) = @_;
3859 # Check to see if password reset requests are already being throttled:
3860 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3861 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3862 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3864 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3867 my $e = new_editor(xact => 1);
3869 my $aupr = $e->search_actor_usr_password_reset({
3876 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3878 my $user_id = $aupr->[0]->usr;
3879 my $user = $e->retrieve_actor_user($user_id);
3881 # Ensure we're still within the TTL for the request
3882 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3883 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3884 if ($threshold < DateTime->now(time_zone => 'local')) {
3886 $logger->info("Password reset request needed to be submitted before $threshold");
3887 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3890 # Check complexity of password against OU-defined regex
3891 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3895 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3896 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3897 $is_strong = check_password_strength_custom($password, $pw_regex);
3899 $is_strong = check_password_strength_default($password);
3904 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3907 # All is well; update the password
3908 $user->passwd($password);
3909 $e->update_actor_user($user);
3911 # And flag that this password reset request has been honoured
3912 $aupr->[0]->has_been_reset('t');
3913 $e->update_actor_usr_password_reset($aupr->[0]);
3919 sub check_password_strength_default {
3920 my $password = shift;
3921 # Use the default set of checks
3922 if ( (length($password) < 7) or
3923 ($password !~ m/.*\d+.*/) or
3924 ($password !~ m/.*[A-Za-z]+.*/)
3931 sub check_password_strength_custom {
3932 my ($password, $pw_regex) = @_;
3934 $pw_regex = qr/$pw_regex/;
3935 if ($password !~ /$pw_regex/) {
3943 __PACKAGE__->register_method(
3944 method => "event_def_opt_in_settings",
3945 api_name => "open-ils.actor.event_def.opt_in.settings",
3948 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3950 { desc => 'Authentication token', type => 'string'},
3952 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3957 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3964 sub event_def_opt_in_settings {
3965 my($self, $conn, $auth, $org_id) = @_;
3966 my $e = new_editor(authtoken => $auth);
3967 return $e->event unless $e->checkauth;
3969 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3970 return $e->event unless
3971 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3973 $org_id = $e->requestor->home_ou;
3976 # find all config.user_setting_type's related to event_defs for the requested org unit
3977 my $types = $e->json_query({
3978 select => {cust => ['name']},
3979 from => {atevdef => 'cust'},
3982 owner => $U->get_org_ancestors($org_id), # context org plus parents
3989 $conn->respond($_) for
3990 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
3997 __PACKAGE__->register_method(
3998 method => "user_visible_circs",
3999 api_name => "open-ils.actor.history.circ.visible",
4002 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4004 { desc => 'Authentication token', type => 'string'},
4005 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4006 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4009 desc => q/An object with 2 fields: circulation and summary.
4010 circulation is the "circ" object. summary is the related "accs" object/,
4016 __PACKAGE__->register_method(
4017 method => "user_visible_circs",
4018 api_name => "open-ils.actor.history.circ.visible.print",
4021 desc => 'Returns printable output for the set of opt-in visible circulations',
4023 { desc => 'Authentication token', type => 'string'},
4024 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4025 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4028 desc => q/An action_trigger.event object or error event./,
4034 __PACKAGE__->register_method(
4035 method => "user_visible_circs",
4036 api_name => "open-ils.actor.history.circ.visible.email",
4039 desc => 'Emails the set of opt-in visible circulations to the requestor',
4041 { desc => 'Authentication token', type => 'string'},
4042 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4043 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4046 desc => q/undef, or event on error/
4051 __PACKAGE__->register_method(
4052 method => "user_visible_circs",
4053 api_name => "open-ils.actor.history.hold.visible",
4056 desc => 'Returns the set of opt-in visible holds',
4058 { desc => 'Authentication token', type => 'string'},
4059 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4060 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4063 desc => q/An object with 1 field: "hold"/,
4069 __PACKAGE__->register_method(
4070 method => "user_visible_circs",
4071 api_name => "open-ils.actor.history.hold.visible.print",
4074 desc => 'Returns printable output for the set of opt-in visible holds',
4076 { desc => 'Authentication token', type => 'string'},
4077 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4078 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4081 desc => q/An action_trigger.event object or error event./,
4087 __PACKAGE__->register_method(
4088 method => "user_visible_circs",
4089 api_name => "open-ils.actor.history.hold.visible.email",
4092 desc => 'Emails the set of opt-in visible holds to the requestor',
4094 { desc => 'Authentication token', type => 'string'},
4095 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4096 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4099 desc => q/undef, or event on error/
4104 sub user_visible_circs {
4105 my($self, $conn, $auth, $user_id, $options) = @_;
4107 my $is_hold = ($self->api_name =~ /hold/);
4108 my $for_print = ($self->api_name =~ /print/);
4109 my $for_email = ($self->api_name =~ /email/);
4110 my $e = new_editor(authtoken => $auth);
4111 return $e->event unless $e->checkauth;
4113 $user_id ||= $e->requestor->id;
4115 $options->{limit} ||= 50;
4116 $options->{offset} ||= 0;
4118 if($user_id != $e->requestor->id) {
4119 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4120 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4121 return $e->event unless $e->allowed($perm, $user->home_ou);
4124 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4126 my $data = $e->json_query({
4127 from => [$db_func, $user_id],
4128 limit => $$options{limit},
4129 offset => $$options{offset}
4131 # TODO: I only want IDs. code below didn't get me there
4132 # {"select":{"au":[{"column":"id", "result_field":"id",
4133 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4138 return undef unless @$data;
4142 # collect the batch of objects
4146 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4147 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4151 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4152 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4155 } elsif ($for_email) {
4157 $conn->respond_complete(1) if $for_email; # no sense in waiting
4165 my $hold = $e->retrieve_action_hold_request($id);
4166 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4167 # events will be fired from action_trigger_runner
4171 my $circ = $e->retrieve_action_circulation($id);
4172 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4173 # events will be fired from action_trigger_runner
4177 } else { # just give me the data please
4185 my $hold = $e->retrieve_action_hold_request($id);
4186 $conn->respond({hold => $hold});
4190 my $circ = $e->retrieve_action_circulation($id);
4193 summary => $U->create_circ_chain_summary($e, $id)
4202 __PACKAGE__->register_method(
4203 method => "user_saved_search_cud",
4204 api_name => "open-ils.actor.user.saved_search.cud",
4207 desc => 'Create/Update/Delete Access to user saved searches',
4209 { desc => 'Authentication token', type => 'string' },
4210 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4213 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4219 __PACKAGE__->register_method(
4220 method => "user_saved_search_cud",
4221 api_name => "open-ils.actor.user.saved_search.retrieve",
4224 desc => 'Retrieve a saved search object',
4226 { desc => 'Authentication token', type => 'string' },
4227 { desc => 'Saved Search ID', type => 'number' }
4230 desc => q/The saved search object, Event on error/,
4236 sub user_saved_search_cud {
4237 my( $self, $client, $auth, $search ) = @_;
4238 my $e = new_editor( authtoken=>$auth );
4239 return $e->die_event unless $e->checkauth;
4241 my $o_search; # prior version of the object, if any
4242 my $res; # to be returned
4244 # branch on the operation type
4246 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4248 # Get the old version, to check ownership
4249 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4250 or return $e->die_event;
4252 # You can't read somebody else's search
4253 return OpenILS::Event->new('BAD_PARAMS')
4254 unless $o_search->owner == $e->requestor->id;
4260 $e->xact_begin; # start an editor transaction
4262 if( $search->isnew ) { # Create
4264 # You can't create a search for somebody else
4265 return OpenILS::Event->new('BAD_PARAMS')
4266 unless $search->owner == $e->requestor->id;
4268 $e->create_actor_usr_saved_search( $search )
4269 or return $e->die_event;
4273 } elsif( $search->ischanged ) { # Update
4275 # You can't change ownership of a search
4276 return OpenILS::Event->new('BAD_PARAMS')
4277 unless $search->owner == $e->requestor->id;
4279 # Get the old version, to check ownership
4280 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4281 or return $e->die_event;
4283 # You can't update somebody else's search
4284 return OpenILS::Event->new('BAD_PARAMS')
4285 unless $o_search->owner == $e->requestor->id;
4288 $e->update_actor_usr_saved_search( $search )
4289 or return $e->die_event;
4293 } elsif( $search->isdeleted ) { # Delete
4295 # Get the old version, to check ownership
4296 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4297 or return $e->die_event;
4299 # You can't delete somebody else's search
4300 return OpenILS::Event->new('BAD_PARAMS')
4301 unless $o_search->owner == $e->requestor->id;
4304 $e->delete_actor_usr_saved_search( $o_search )
4305 or return $e->die_event;
4316 __PACKAGE__->register_method(
4317 method => "get_barcodes",
4318 api_name => "open-ils.actor.get_barcodes"
4322 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4323 my $e = new_editor(authtoken => $auth);
4324 return $e->event unless $e->checkauth;
4325 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4327 my $db_result = $e->json_query(
4329 'evergreen.get_barcodes',
4330 $org_id, $context, $barcode,
4334 if($context =~ /actor/) {
4335 my $filter_result = ();
4337 foreach my $result (@$db_result) {
4338 if($result->{type} eq 'actor') {
4339 if($e->requestor->id != $result->{id}) {
4340 $patron = $e->retrieve_actor_user($result->{id});
4342 push(@$filter_result, $e->event);
4345 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4346 push(@$filter_result, $result);
4349 push(@$filter_result, $e->event);
4353 push(@$filter_result, $result);
4357 push(@$filter_result, $result);
4360 return $filter_result;