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",
471 push @$fields, "home_ou" if $home_ou;
472 return new_flesh_user($id, $fields, $e );
480 # clone and clear stuff that would break the database
484 my $new_patron = $patron->clone;
486 $new_patron->clear_billing_address();
487 $new_patron->clear_mailing_address();
488 $new_patron->clear_addresses();
489 $new_patron->clear_card();
490 $new_patron->clear_cards();
491 $new_patron->clear_id();
492 $new_patron->clear_isnew();
493 $new_patron->clear_ischanged();
494 $new_patron->clear_isdeleted();
495 $new_patron->clear_stat_cat_entries();
496 $new_patron->clear_permissions();
497 $new_patron->clear_standing_penalties();
507 my $user_obj = shift;
509 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
510 return (undef, $evt) if $evt;
512 my $ex = $session->request(
513 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
515 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
518 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
520 my $id = $session->request(
521 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
522 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
524 $logger->info("Successfully created new user [$id] in DB");
526 return ( $session->request(
527 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
531 sub check_group_perm {
532 my( $session, $requestor, $patron ) = @_;
535 # first let's see if the requestor has
536 # priveleges to update this user in any way
537 if( ! $patron->isnew ) {
538 my $p = $session->request(
539 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
541 # If we are the requestor (trying to update our own account)
542 # and we are not trying to change our profile, we're good
543 if( $p->id == $requestor->id and
544 $p->profile == $patron->profile ) {
549 $evt = group_perm_failed($session, $requestor, $p);
553 # They are allowed to edit this patron.. can they put the
554 # patron into the group requested?
555 $evt = group_perm_failed($session, $requestor, $patron);
561 sub group_perm_failed {
562 my( $session, $requestor, $patron ) = @_;
566 my $grpid = $patron->profile;
570 $logger->debug("user update looking for group perm for group $grpid");
571 $grp = $session->request(
572 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
573 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
575 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
577 $logger->info("user update checking perm $perm on user ".
578 $requestor->id." for update/create on user username=".$patron->usrname);
580 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
588 my( $session, $patron, $user_obj, $noperm) = @_;
590 $logger->info("Updating patron ".$patron->id." in DB");
595 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
596 return (undef, $evt) if $evt;
599 # update the password by itself to avoid the password protection magic
600 if( $patron->passwd ) {
601 my $s = $session->request(
602 'open-ils.storage.direct.actor.user.remote_update',
603 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
604 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
605 $patron->clear_passwd;
608 if(!$patron->ident_type) {
609 $patron->clear_ident_type;
610 $patron->clear_ident_value;
613 $evt = verify_last_xact($session, $patron);
614 return (undef, $evt) if $evt;
616 my $stat = $session->request(
617 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
618 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
623 sub verify_last_xact {
624 my( $session, $patron ) = @_;
625 return undef unless $patron->id and $patron->id > 0;
626 my $p = $session->request(
627 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
628 my $xact = $p->last_xact_id;
629 return undef unless $xact;
630 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
631 return OpenILS::Event->new('XACT_COLLISION')
632 if $xact ne $patron->last_xact_id;
637 sub _check_dup_ident {
638 my( $session, $patron ) = @_;
640 return undef unless $patron->ident_value;
643 ident_type => $patron->ident_type,
644 ident_value => $patron->ident_value,
647 $logger->debug("patron update searching for dup ident values: " .
648 $patron->ident_type . ':' . $patron->ident_value);
650 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
652 my $dups = $session->request(
653 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
656 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
663 sub _add_update_addresses {
667 my $new_patron = shift;
671 my $current_id; # id of the address before creation
673 my $addresses = $patron->addresses();
675 for my $address (@$addresses) {
677 next unless ref $address;
678 $current_id = $address->id();
680 if( $patron->billing_address() and
681 $patron->billing_address() == $current_id ) {
682 $logger->info("setting billing addr to $current_id");
683 $new_patron->billing_address($address->id());
684 $new_patron->ischanged(1);
687 if( $patron->mailing_address() and
688 $patron->mailing_address() == $current_id ) {
689 $new_patron->mailing_address($address->id());
690 $logger->info("setting mailing addr to $current_id");
691 $new_patron->ischanged(1);
695 if($address->isnew()) {
697 $address->usr($new_patron->id());
699 ($address, $evt) = _add_address($session,$address);
700 return (undef, $evt) if $evt;
702 # we need to get the new id
703 if( $patron->billing_address() and
704 $patron->billing_address() == $current_id ) {
705 $new_patron->billing_address($address->id());
706 $logger->info("setting billing addr to $current_id");
707 $new_patron->ischanged(1);
710 if( $patron->mailing_address() and
711 $patron->mailing_address() == $current_id ) {
712 $new_patron->mailing_address($address->id());
713 $logger->info("setting mailing addr to $current_id");
714 $new_patron->ischanged(1);
717 } elsif($address->ischanged() ) {
719 ($address, $evt) = _update_address($session, $address);
720 return (undef, $evt) if $evt;
722 } elsif($address->isdeleted() ) {
724 if( $address->id() == $new_patron->mailing_address() ) {
725 $new_patron->clear_mailing_address();
726 ($new_patron, $evt) = _update_patron($session, $new_patron);
727 return (undef, $evt) if $evt;
730 if( $address->id() == $new_patron->billing_address() ) {
731 $new_patron->clear_billing_address();
732 ($new_patron, $evt) = _update_patron($session, $new_patron);
733 return (undef, $evt) if $evt;
736 $evt = _delete_address($session, $address);
737 return (undef, $evt) if $evt;
741 return ( $new_patron, undef );
745 # adds an address to the db and returns the address with new id
747 my($session, $address) = @_;
748 $address->clear_id();
750 $logger->info("Creating new address at street ".$address->street1);
752 # put the address into the database
753 my $id = $session->request(
754 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
755 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
758 return ($address, undef);
762 sub _update_address {
763 my( $session, $address ) = @_;
765 $logger->info("Updating address ".$address->id." in the DB");
767 my $stat = $session->request(
768 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
770 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
771 return ($address, undef);
776 sub _add_update_cards {
780 my $new_patron = shift;
784 my $virtual_id; #id of the card before creation
786 my $cards = $patron->cards();
787 for my $card (@$cards) {
789 $card->usr($new_patron->id());
791 if(ref($card) and $card->isnew()) {
793 $virtual_id = $card->id();
794 ( $card, $evt ) = _add_card($session,$card);
795 return (undef, $evt) if $evt;
797 #if(ref($patron->card)) { $patron->card($patron->card->id); }
798 if($patron->card() == $virtual_id) {
799 $new_patron->card($card->id());
800 $new_patron->ischanged(1);
803 } elsif( ref($card) and $card->ischanged() ) {
804 $evt = _update_card($session, $card);
805 return (undef, $evt) if $evt;
809 return ( $new_patron, undef );
813 # adds an card to the db and returns the card with new id
815 my( $session, $card ) = @_;
818 $logger->info("Adding new patron card ".$card->barcode);
820 my $id = $session->request(
821 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
822 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
823 $logger->info("Successfully created patron card $id");
826 return ( $card, undef );
830 # returns event on error. returns undef otherwise
832 my( $session, $card ) = @_;
833 $logger->info("Updating patron card ".$card->id);
835 my $stat = $session->request(
836 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
837 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
844 # returns event on error. returns undef otherwise
845 sub _delete_address {
846 my( $session, $address ) = @_;
848 $logger->info("Deleting address ".$address->id." from DB");
850 my $stat = $session->request(
851 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
853 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
859 sub _add_survey_responses {
860 my ($session, $patron, $new_patron) = @_;
862 $logger->info( "Updating survey responses for patron ".$new_patron->id );
864 my $responses = $patron->survey_responses;
868 $_->usr($new_patron->id) for (@$responses);
870 my $evt = $U->simplereq( "open-ils.circ",
871 "open-ils.circ.survey.submit.user_id", $responses );
873 return (undef, $evt) if defined($U->event_code($evt));
877 return ( $new_patron, undef );
881 sub _create_stat_maps {
883 my($session, $user_session, $patron, $new_patron) = @_;
885 my $maps = $patron->stat_cat_entries();
887 for my $map (@$maps) {
889 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
891 if ($map->isdeleted()) {
892 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
894 } elsif ($map->isnew()) {
895 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
900 $map->target_usr($new_patron->id);
903 $logger->info("Updating stat entry with method $method and map $map");
905 my $stat = $session->request($method, $map)->gather(1);
906 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
910 return ($new_patron, undef);
913 sub _create_perm_maps {
915 my($session, $user_session, $patron, $new_patron) = @_;
917 my $maps = $patron->permissions;
919 for my $map (@$maps) {
921 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
922 if ($map->isdeleted()) {
923 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
924 } elsif ($map->isnew()) {
925 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
930 $map->usr($new_patron->id);
932 #warn( "Updating permissions with method $method and session $user_session and map $map" );
933 $logger->info( "Updating permissions with method $method and map $map" );
935 my $stat = $session->request($method, $map)->gather(1);
936 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
940 return ($new_patron, undef);
944 __PACKAGE__->register_method(
945 method => "set_user_work_ous",
946 api_name => "open-ils.actor.user.work_ous.update",
949 sub set_user_work_ous {
955 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
958 my $session = $apputils->start_db_session();
960 for my $map (@$maps) {
962 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
963 if ($map->isdeleted()) {
964 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
965 } elsif ($map->isnew()) {
966 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
970 #warn( "Updating permissions with method $method and session $ses and map $map" );
971 $logger->info( "Updating work_ou map with method $method and map $map" );
973 my $stat = $session->request($method, $map)->gather(1);
974 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
978 $apputils->commit_db_session($session);
980 return scalar(@$maps);
984 __PACKAGE__->register_method(
985 method => "set_user_perms",
986 api_name => "open-ils.actor.user.permissions.update",
995 my $session = $apputils->start_db_session();
997 my( $user_obj, $evt ) = $U->checkses($ses);
1000 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1003 $all = 1 if ($U->is_true($user_obj->super_user()));
1004 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1006 for my $map (@$maps) {
1008 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1009 if ($map->isdeleted()) {
1010 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1011 } elsif ($map->isnew()) {
1012 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1016 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1017 #warn( "Updating permissions with method $method and session $ses and map $map" );
1018 $logger->info( "Updating permissions with method $method and map $map" );
1020 my $stat = $session->request($method, $map)->gather(1);
1021 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1025 $apputils->commit_db_session($session);
1027 return scalar(@$maps);
1031 __PACKAGE__->register_method(
1032 method => "user_retrieve_by_barcode",
1034 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1036 sub user_retrieve_by_barcode {
1037 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1039 my $e = new_editor(authtoken => $auth);
1040 return $e->event unless $e->checkauth;
1042 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1043 or return $e->event;
1045 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1046 return $e->event unless $e->allowed(
1047 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1054 __PACKAGE__->register_method(
1055 method => "get_user_by_id",
1057 api_name => "open-ils.actor.user.retrieve",
1060 sub get_user_by_id {
1061 my ($self, $client, $auth, $id) = @_;
1062 my $e = new_editor(authtoken=>$auth);
1063 return $e->event unless $e->checkauth;
1064 my $user = $e->retrieve_actor_user($id) or return $e->event;
1065 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1070 __PACKAGE__->register_method(
1071 method => "get_org_types",
1072 api_name => "open-ils.actor.org_types.retrieve",
1075 return $U->get_org_types();
1079 __PACKAGE__->register_method(
1080 method => "get_user_ident_types",
1081 api_name => "open-ils.actor.user.ident_types.retrieve",
1084 sub get_user_ident_types {
1085 return $ident_types if $ident_types;
1086 return $ident_types =
1087 new_editor()->retrieve_all_config_identification_type();
1091 __PACKAGE__->register_method(
1092 method => "get_org_unit",
1093 api_name => "open-ils.actor.org_unit.retrieve",
1097 my( $self, $client, $user_session, $org_id ) = @_;
1098 my $e = new_editor(authtoken => $user_session);
1100 return $e->event unless $e->checkauth;
1101 $org_id = $e->requestor->ws_ou;
1103 my $o = $e->retrieve_actor_org_unit($org_id)
1104 or return $e->event;
1108 __PACKAGE__->register_method(
1109 method => "search_org_unit",
1110 api_name => "open-ils.actor.org_unit_list.search",
1113 sub search_org_unit {
1115 my( $self, $client, $field, $value ) = @_;
1117 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1119 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1120 { $field => $value } );
1126 # build the org tree
1128 __PACKAGE__->register_method(
1129 method => "get_org_tree",
1130 api_name => "open-ils.actor.org_tree.retrieve",
1132 note => "Returns the entire org tree structure",
1138 return $U->get_org_tree($client->session->session_locale);
1142 __PACKAGE__->register_method(
1143 method => "get_org_descendants",
1144 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1147 # depth is optional. org_unit is the id
1148 sub get_org_descendants {
1149 my( $self, $client, $org_unit, $depth ) = @_;
1151 if(ref $org_unit eq 'ARRAY') {
1154 for my $i (0..scalar(@$org_unit)-1) {
1155 my $list = $U->simple_scalar_request(
1157 "open-ils.storage.actor.org_unit.descendants.atomic",
1158 $org_unit->[$i], $depth->[$i] );
1159 push(@trees, $U->build_org_tree($list));
1164 my $orglist = $apputils->simple_scalar_request(
1166 "open-ils.storage.actor.org_unit.descendants.atomic",
1167 $org_unit, $depth );
1168 return $U->build_org_tree($orglist);
1173 __PACKAGE__->register_method(
1174 method => "get_org_ancestors",
1175 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1178 # depth is optional. org_unit is the id
1179 sub get_org_ancestors {
1180 my( $self, $client, $org_unit, $depth ) = @_;
1181 my $orglist = $apputils->simple_scalar_request(
1183 "open-ils.storage.actor.org_unit.ancestors.atomic",
1184 $org_unit, $depth );
1185 return $U->build_org_tree($orglist);
1189 __PACKAGE__->register_method(
1190 method => "get_standings",
1191 api_name => "open-ils.actor.standings.retrieve"
1196 return $user_standings if $user_standings;
1197 return $user_standings =
1198 $apputils->simple_scalar_request(
1200 "open-ils.cstore.direct.config.standing.search.atomic",
1201 { id => { "!=" => undef } }
1206 __PACKAGE__->register_method(
1207 method => "get_my_org_path",
1208 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1211 sub get_my_org_path {
1212 my( $self, $client, $auth, $org_id ) = @_;
1213 my $e = new_editor(authtoken=>$auth);
1214 return $e->event unless $e->checkauth;
1215 $org_id = $e->requestor->ws_ou unless defined $org_id;
1217 return $apputils->simple_scalar_request(
1219 "open-ils.storage.actor.org_unit.full_path.atomic",
1224 __PACKAGE__->register_method(
1225 method => "patron_adv_search",
1226 api_name => "open-ils.actor.patron.search.advanced"
1228 sub patron_adv_search {
1229 my( $self, $client, $auth, $search_hash,
1230 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1232 my $e = new_editor(authtoken=>$auth);
1233 return $e->event unless $e->checkauth;
1234 return $e->event unless $e->allowed('VIEW_USER');
1236 # depth boundary outside of which patrons must opt-in, default to 0
1237 my $opt_boundary = 0;
1238 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1240 return $U->storagereq(
1241 "open-ils.storage.actor.user.crazy_search", $search_hash,
1242 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1246 __PACKAGE__->register_method(
1247 method => "update_passwd",
1248 api_name => "open-ils.actor.user.password.update",
1250 desc => "Update the operator's password",
1252 { desc => 'Authentication token', type => 'string' },
1253 { desc => 'New password', type => 'string' },
1254 { desc => 'Current password', type => 'string' }
1256 return => {desc => '1 on success, Event on error or incorrect current password'}
1260 __PACKAGE__->register_method(
1261 method => "update_passwd",
1262 api_name => "open-ils.actor.user.username.update",
1264 desc => "Update the operator's username",
1266 { desc => 'Authentication token', type => 'string' },
1267 { desc => 'New username', type => 'string' },
1268 { desc => 'Current password', type => 'string' }
1270 return => {desc => '1 on success, Event on error or incorrect current password'}
1274 __PACKAGE__->register_method(
1275 method => "update_passwd",
1276 api_name => "open-ils.actor.user.email.update",
1278 desc => "Update the operator's email address",
1280 { desc => 'Authentication token', type => 'string' },
1281 { desc => 'New email address', type => 'string' },
1282 { desc => 'Current password', type => 'string' }
1284 return => {desc => '1 on success, Event on error or incorrect current password'}
1289 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1290 my $e = new_editor(xact=>1, authtoken=>$auth);
1291 return $e->die_event unless $e->checkauth;
1293 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1294 or return $e->die_event;
1295 my $api = $self->api_name;
1297 # make sure the original password matches the in-database password
1298 if (md5_hex($orig_pw) ne $db_user->passwd) {
1300 return new OpenILS::Event('INCORRECT_PASSWORD');
1303 if( $api =~ /password/o ) {
1305 $db_user->passwd($new_val);
1309 # if we don't clear the password, the user will be updated with
1310 # a hashed version of the hashed version of their password
1311 $db_user->clear_passwd;
1313 if( $api =~ /username/o ) {
1315 # make sure no one else has this username
1316 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1319 return new OpenILS::Event('USERNAME_EXISTS');
1321 $db_user->usrname($new_val);
1323 } elsif( $api =~ /email/o ) {
1324 $db_user->email($new_val);
1328 $e->update_actor_user($db_user) or return $e->die_event;
1331 # update the cached user to pick up these changes
1332 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1338 __PACKAGE__->register_method(
1339 method => "check_user_perms",
1340 api_name => "open-ils.actor.user.perm.check",
1341 notes => <<" NOTES");
1342 Takes a login session, user id, an org id, and an array of perm type strings. For each
1343 perm type, if the user does *not* have the given permission it is added
1344 to a list which is returned from the method. If all permissions
1345 are allowed, an empty list is returned
1346 if the logged in user does not match 'user_id', then the logged in user must
1347 have VIEW_PERMISSION priveleges.
1350 sub check_user_perms {
1351 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1353 my( $staff, $evt ) = $apputils->checkses($login_session);
1354 return $evt if $evt;
1356 if($staff->id ne $user_id) {
1357 if( $evt = $apputils->check_perms(
1358 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1364 for my $perm (@$perm_types) {
1365 if($apputils->check_perms($user_id, $org_id, $perm)) {
1366 push @not_allowed, $perm;
1370 return \@not_allowed
1373 __PACKAGE__->register_method(
1374 method => "check_user_perms2",
1375 api_name => "open-ils.actor.user.perm.check.multi_org",
1377 Checks the permissions on a list of perms and orgs for a user
1378 @param authtoken The login session key
1379 @param user_id The id of the user to check
1380 @param orgs The array of org ids
1381 @param perms The array of permission names
1382 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1383 if the logged in user does not match 'user_id', then the logged in user must
1384 have VIEW_PERMISSION priveleges.
1387 sub check_user_perms2 {
1388 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1390 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1391 $authtoken, $user_id, 'VIEW_PERMISSION' );
1392 return $evt if $evt;
1395 for my $org (@$orgs) {
1396 for my $perm (@$perms) {
1397 if($apputils->check_perms($user_id, $org, $perm)) {
1398 push @not_allowed, [ $org, $perm ];
1403 return \@not_allowed
1407 __PACKAGE__->register_method(
1408 method => 'check_user_perms3',
1409 api_name => 'open-ils.actor.user.perm.highest_org',
1411 Returns the highest org unit id at which a user has a given permission
1412 If the requestor does not match the target user, the requestor must have
1413 'VIEW_PERMISSION' rights at the home org unit of the target user
1414 @param authtoken The login session key
1415 @param userid The id of the user in question
1416 @param perm The permission to check
1417 @return The org unit highest in the org tree within which the user has
1418 the requested permission
1421 sub check_user_perms3 {
1422 my($self, $client, $authtoken, $user_id, $perm) = @_;
1423 my $e = new_editor(authtoken=>$authtoken);
1424 return $e->event unless $e->checkauth;
1426 my $tree = $U->get_org_tree();
1428 unless($e->requestor->id == $user_id) {
1429 my $user = $e->retrieve_actor_user($user_id)
1430 or return $e->event;
1431 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1432 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1435 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1438 __PACKAGE__->register_method(
1439 method => 'user_has_work_perm_at',
1440 api_name => 'open-ils.actor.user.has_work_perm_at',
1444 Returns a set of org unit IDs which represent the highest orgs in
1445 the org tree where the user has the requested permission. The
1446 purpose of this method is to return the smallest set of org units
1447 which represent the full expanse of the user's ability to perform
1448 the requested action. The user whose perms this method should
1449 check is implied by the authtoken. /,
1451 {desc => 'authtoken', type => 'string'},
1452 {desc => 'permission name', type => 'string'},
1453 {desc => q/user id, optional. If present, check perms for
1454 this user instead of the logged in user/, type => 'number'},
1456 return => {desc => 'An array of org IDs'}
1460 sub user_has_work_perm_at {
1461 my($self, $conn, $auth, $perm, $user_id) = @_;
1462 my $e = new_editor(authtoken=>$auth);
1463 return $e->event unless $e->checkauth;
1464 if(defined $user_id) {
1465 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1466 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1468 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1471 __PACKAGE__->register_method(
1472 method => 'user_has_work_perm_at_batch',
1473 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1477 sub user_has_work_perm_at_batch {
1478 my($self, $conn, $auth, $perms, $user_id) = @_;
1479 my $e = new_editor(authtoken=>$auth);
1480 return $e->event unless $e->checkauth;
1481 if(defined $user_id) {
1482 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1483 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1486 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1492 __PACKAGE__->register_method(
1493 method => 'check_user_perms4',
1494 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1496 Returns the highest org unit id at which a user has a given permission
1497 If the requestor does not match the target user, the requestor must have
1498 'VIEW_PERMISSION' rights at the home org unit of the target user
1499 @param authtoken The login session key
1500 @param userid The id of the user in question
1501 @param perms An array of perm names to check
1502 @return An array of orgId's representing the org unit
1503 highest in the org tree within which the user has the requested permission
1504 The arrah of orgId's has matches the order of the perms array
1507 sub check_user_perms4 {
1508 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1510 my( $staff, $target, $org, $evt );
1512 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1513 $authtoken, $userid, 'VIEW_PERMISSION' );
1514 return $evt if $evt;
1517 return [] unless ref($perms);
1518 my $tree = $U->get_org_tree();
1520 for my $p (@$perms) {
1521 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1527 __PACKAGE__->register_method(
1528 method => "user_fines_summary",
1529 api_name => "open-ils.actor.user.fines.summary",
1532 desc => 'Returns a short summary of the users total open fines, ' .
1533 'excluding voided fines Params are login_session, user_id' ,
1535 {desc => 'Authentication token', type => 'string'},
1536 {desc => 'User ID', type => 'string'} # number?
1539 desc => "a 'mous' object, event on error",
1544 sub user_fines_summary {
1545 my( $self, $client, $auth, $user_id ) = @_;
1547 my $e = new_editor(authtoken=>$auth);
1548 return $e->event unless $e->checkauth;
1550 if( $user_id ne $e->requestor->id ) {
1551 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1552 return $e->event unless
1553 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1556 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1560 __PACKAGE__->register_method(
1561 method => "user_opac_vitals",
1562 api_name => "open-ils.actor.user.opac.vital_stats",
1566 desc => 'Returns a short summary of the users vital stats, including ' .
1567 'identification information, accumulated balance, number of holds, ' .
1568 'and current open circulation stats' ,
1570 {desc => 'Authentication token', type => 'string'},
1571 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1574 desc => "An object with four properties: user, fines, checkouts and holds."
1579 sub user_opac_vitals {
1580 my( $self, $client, $auth, $user_id ) = @_;
1582 my $e = new_editor(authtoken=>$auth);
1583 return $e->event unless $e->checkauth;
1585 $user_id ||= $e->requestor->id;
1587 my $user = $e->retrieve_actor_user( $user_id );
1590 ->method_lookup('open-ils.actor.user.fines.summary')
1591 ->run($auth => $user_id);
1592 return $fines if (defined($U->event_code($fines)));
1595 $fines = new Fieldmapper::money::open_user_summary ();
1596 $fines->balance_owed(0.00);
1597 $fines->total_owed(0.00);
1598 $fines->total_paid(0.00);
1599 $fines->usr($user_id);
1603 ->method_lookup('open-ils.actor.user.hold_requests.count')
1604 ->run($auth => $user_id);
1605 return $holds if (defined($U->event_code($holds)));
1608 ->method_lookup('open-ils.actor.user.checked_out.count')
1609 ->run($auth => $user_id);
1610 return $out if (defined($U->event_code($out)));
1612 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1616 first_given_name => $user->first_given_name,
1617 second_given_name => $user->second_given_name,
1618 family_name => $user->family_name,
1619 alias => $user->alias,
1620 usrname => $user->usrname
1622 fines => $fines->to_bare_hash,
1629 ##### a small consolidation of related method registrations
1630 my $common_params = [
1631 { desc => 'Authentication token', type => 'string' },
1632 { desc => 'User ID', type => 'string' },
1633 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1634 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1637 'open-ils.actor.user.transactions' => '',
1638 'open-ils.actor.user.transactions.fleshed' => '',
1639 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1640 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1641 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1642 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1645 foreach (keys %methods) {
1647 method => "user_transactions",
1650 desc => 'For a given user, retrieve a list of '
1651 . (/\.fleshed/ ? 'fleshed ' : '')
1652 . 'transactions' . $methods{$_}
1653 . ' optionally limited to transactions of a given type.',
1654 params => $common_params,
1656 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1657 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1661 $args{authoritative} = 1;
1662 __PACKAGE__->register_method(%args);
1665 # Now for the counts
1667 'open-ils.actor.user.transactions.count' => '',
1668 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1669 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1672 foreach (keys %methods) {
1674 method => "user_transactions",
1677 desc => 'For a given user, retrieve a count of open '
1678 . 'transactions' . $methods{$_}
1679 . ' optionally limited to transactions of a given type.',
1680 params => $common_params,
1681 return => { desc => "Integer count of transactions, or event on error" }
1684 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1685 __PACKAGE__->register_method(%args);
1688 __PACKAGE__->register_method(
1689 method => "user_transactions",
1690 api_name => "open-ils.actor.user.transactions.have_balance.total",
1693 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1694 . ' optionally limited to transactions of a given type.',
1695 params => $common_params,
1696 return => { desc => "Decimal balance value, or event on error" }
1701 sub user_transactions {
1702 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1705 my $e = new_editor(authtoken => $auth);
1706 return $e->event unless $e->checkauth;
1708 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1710 return $e->event unless
1711 $e->requestor->id == $user_id or
1712 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1714 my $api = $self->api_name();
1716 my $filter = ($api =~ /have_balance/o) ?
1717 { 'balance_owed' => { '<>' => 0 } }:
1718 { 'total_owed' => { '>' => 0 } };
1720 my $method = 'open-ils.actor.user.transactions.history.still_open';
1721 $method = "$method.authoritative" if $api =~ /authoritative/;
1722 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1724 if($api =~ /total/o) {
1726 $total += $_->balance_owed for @$trans;
1730 ($api =~ /count/o ) and return scalar @$trans;
1731 ($api !~ /fleshed/o) and return $trans;
1734 for my $t (@$trans) {
1736 if( $t->xact_type ne 'circulation' ) {
1737 push @resp, {transaction => $t};
1741 my $circ_data = flesh_circ($e, $t->id);
1742 push @resp, {transaction => $t, %$circ_data};
1749 __PACKAGE__->register_method(
1750 method => "user_transaction_retrieve",
1751 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1754 notes => "Returns a fleshed transaction record"
1757 __PACKAGE__->register_method(
1758 method => "user_transaction_retrieve",
1759 api_name => "open-ils.actor.user.transaction.retrieve",
1762 notes => "Returns a transaction record"
1765 sub user_transaction_retrieve {
1766 my($self, $client, $auth, $bill_id) = @_;
1768 my $e = new_editor(authtoken => $auth);
1769 return $e->event unless $e->checkauth;
1771 my $trans = $e->retrieve_money_billable_transaction_summary(
1772 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1774 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1776 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1778 return $trans unless $self->api_name =~ /flesh/;
1779 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1781 my $circ_data = flesh_circ($e, $trans->id, 1);
1783 return {transaction => $trans, %$circ_data};
1788 my $circ_id = shift;
1789 my $flesh_copy = shift;
1791 my $circ = $e->retrieve_action_circulation([
1795 circ => ['target_copy'],
1796 acp => ['call_number'],
1803 my $copy = $circ->target_copy;
1805 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1806 $mods = new Fieldmapper::metabib::virtual_record;
1807 $mods->doc_id(OILS_PRECAT_RECORD);
1808 $mods->title($copy->dummy_title);
1809 $mods->author($copy->dummy_author);
1812 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1816 $circ->target_copy($circ->target_copy->id);
1817 $copy->call_number($copy->call_number->id);
1819 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1823 __PACKAGE__->register_method(
1824 method => "hold_request_count",
1825 api_name => "open-ils.actor.user.hold_requests.count",
1828 notes => 'Returns hold ready/total counts'
1831 sub hold_request_count {
1832 my( $self, $client, $authtoken, $user_id ) = @_;
1833 my $e = new_editor(authtoken => $authtoken);
1834 return $e->event unless $e->checkauth;
1836 $user_id = $e->requestor->id unless defined $user_id;
1838 if($e->requestor->id ne $user_id) {
1839 my $user = $e->retrieve_actor_user($user_id);
1840 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1843 my $holds = $e->json_query({
1844 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1848 fulfillment_time => {"=" => undef },
1849 cancel_time => undef,
1854 total => scalar(@$holds),
1857 $_->{current_shelf_lib} and # avoid undef warnings
1858 $_->{pickup_lib} eq $_->{current_shelf_lib}
1864 __PACKAGE__->register_method(
1865 method => "checked_out",
1866 api_name => "open-ils.actor.user.checked_out",
1870 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1871 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1872 . "(i.e., outstanding balance or some other pending action on the circ). "
1873 . "The .count method also includes a 'total' field which sums all open circs.",
1875 { desc => 'Authentication Token', type => 'string'},
1876 { desc => 'User ID', type => 'string'},
1879 desc => 'Returns event on error, or an object with ID lists, like: '
1880 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1885 __PACKAGE__->register_method(
1886 method => "checked_out",
1887 api_name => "open-ils.actor.user.checked_out.count",
1890 signature => q/@see open-ils.actor.user.checked_out/
1894 my( $self, $conn, $auth, $userid ) = @_;
1896 my $e = new_editor(authtoken=>$auth);
1897 return $e->event unless $e->checkauth;
1899 if( $userid ne $e->requestor->id ) {
1900 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1901 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1903 # see if there is a friend link allowing circ.view perms
1904 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1905 $e, $userid, $e->requestor->id, 'circ.view');
1906 return $e->event unless $allowed;
1910 my $count = $self->api_name =~ /count/;
1911 return _checked_out( $count, $e, $userid );
1915 my( $iscount, $e, $userid ) = @_;
1921 claims_returned => [],
1924 my $meth = 'retrieve_action_open_circ_';
1932 claims_returned => 0,
1939 my $data = $e->$meth($userid);
1943 $result{$_} += $data->$_() for (keys %result);
1944 $result{total} += $data->$_() for (keys %result);
1946 for my $k (keys %result) {
1947 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
1957 __PACKAGE__->register_method(
1958 method => "checked_in_with_fines",
1959 api_name => "open-ils.actor.user.checked_in_with_fines",
1962 signature => q/@see open-ils.actor.user.checked_out/
1965 sub checked_in_with_fines {
1966 my( $self, $conn, $auth, $userid ) = @_;
1968 my $e = new_editor(authtoken=>$auth);
1969 return $e->event unless $e->checkauth;
1971 if( $userid ne $e->requestor->id ) {
1972 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1975 # money is owed on these items and they are checked in
1976 my $open = $e->search_action_circulation(
1979 xact_finish => undef,
1980 checkin_time => { "!=" => undef },
1985 my( @lost, @cr, @lo );
1986 for my $c (@$open) {
1987 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1988 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1989 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1994 claims_returned => \@cr,
1995 long_overdue => \@lo
2001 my ($api, $desc, $auth) = @_;
2002 $desc = $desc ? (" " . $desc) : '';
2003 my $ids = ($api =~ /ids$/) ? 1 : 0;
2006 method => "user_transaction_history",
2007 api_name => "open-ils.actor.user.transactions.$api",
2009 desc => "For a given User ID, returns a list of billable transaction" .
2010 ($ids ? " id" : '') .
2011 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2012 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2014 {desc => 'Authentication token', type => 'string'},
2015 {desc => 'User ID', type => 'number'},
2016 {desc => 'Transaction type (optional)', type => 'number'},
2017 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2020 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2024 $auth and push @sig, (authoritative => 1);
2028 my %auth_hist_methods = (
2030 'history.have_charge' => 'that have an initial charge',
2031 'history.still_open' => 'that are not finished',
2032 'history.have_balance' => 'that have a balance',
2033 'history.have_bill' => 'that have billings',
2034 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2035 'history.have_payment' => 'that have at least 1 payment',
2038 foreach (keys %auth_hist_methods) {
2039 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2040 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2041 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2044 sub user_transaction_history {
2045 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2049 my $e = new_editor(authtoken=>$auth);
2050 return $e->die_event unless $e->checkauth;
2052 if ($e->requestor->id ne $userid) {
2053 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2056 my $api = $self->api_name;
2057 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2059 if(defined($type)) {
2060 $filter->{'xact_type'} = $type;
2063 if($api =~ /have_bill_or_payment/o) {
2065 # transactions that have a non-zero sum across all billings or at least 1 payment
2066 $filter->{'-or'} = {
2067 'balance_owed' => { '<>' => 0 },
2068 'last_payment_ts' => { '<>' => undef }
2071 } elsif($api =~ /have_payment/) {
2073 $filter->{last_payment_ts} ||= {'<>' => undef};
2075 } elsif( $api =~ /have_balance/o) {
2077 # transactions that have a non-zero overall balance
2078 $filter->{'balance_owed'} = { '<>' => 0 };
2080 } elsif( $api =~ /have_charge/o) {
2082 # transactions that have at least 1 billing, regardless of whether it was voided
2083 $filter->{'last_billing_ts'} = { '<>' => undef };
2085 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2087 # transactions that have non-zero sum across all billings. This will exclude
2088 # xacts where all billings have been voided
2089 $filter->{'total_owed'} = { '<>' => 0 };
2092 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2093 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2094 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2096 my $mbts = $e->search_money_billable_transaction_summary(
2097 [ { usr => $userid, @xact_finish, %$filter },
2102 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2103 return $mbts unless $api =~ /fleshed/;
2106 for my $t (@$mbts) {
2108 if( $t->xact_type ne 'circulation' ) {
2109 push @resp, {transaction => $t};
2113 my $circ_data = flesh_circ($e, $t->id);
2114 push @resp, {transaction => $t, %$circ_data};
2122 __PACKAGE__->register_method(
2123 method => "user_perms",
2124 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2126 notes => "Returns a list of permissions"
2130 my( $self, $client, $authtoken, $user ) = @_;
2132 my( $staff, $evt ) = $apputils->checkses($authtoken);
2133 return $evt if $evt;
2135 $user ||= $staff->id;
2137 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2141 return $apputils->simple_scalar_request(
2143 "open-ils.storage.permission.user_perms.atomic",
2147 __PACKAGE__->register_method(
2148 method => "retrieve_perms",
2149 api_name => "open-ils.actor.permissions.retrieve",
2150 notes => "Returns a list of permissions"
2152 sub retrieve_perms {
2153 my( $self, $client ) = @_;
2154 return $apputils->simple_scalar_request(
2156 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2157 { id => { '!=' => undef } }
2161 __PACKAGE__->register_method(
2162 method => "retrieve_groups",
2163 api_name => "open-ils.actor.groups.retrieve",
2164 notes => "Returns a list of user groups"
2166 sub retrieve_groups {
2167 my( $self, $client ) = @_;
2168 return new_editor()->retrieve_all_permission_grp_tree();
2171 __PACKAGE__->register_method(
2172 method => "retrieve_org_address",
2173 api_name => "open-ils.actor.org_unit.address.retrieve",
2174 notes => <<' NOTES');
2175 Returns an org_unit address by ID
2176 @param An org_address ID
2178 sub retrieve_org_address {
2179 my( $self, $client, $id ) = @_;
2180 return $apputils->simple_scalar_request(
2182 "open-ils.cstore.direct.actor.org_address.retrieve",
2187 __PACKAGE__->register_method(
2188 method => "retrieve_groups_tree",
2189 api_name => "open-ils.actor.groups.tree.retrieve",
2190 notes => "Returns a list of user groups"
2193 sub retrieve_groups_tree {
2194 my( $self, $client ) = @_;
2195 return new_editor()->search_permission_grp_tree(
2200 flesh_fields => { pgt => ["children"] },
2201 order_by => { pgt => 'name'}
2208 __PACKAGE__->register_method(
2209 method => "add_user_to_groups",
2210 api_name => "open-ils.actor.user.set_groups",
2211 notes => "Adds a user to one or more permission groups"
2214 sub add_user_to_groups {
2215 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2217 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2218 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2219 return $evt if $evt;
2221 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2222 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2223 return $evt if $evt;
2225 $apputils->simplereq(
2227 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2229 for my $group (@$groups) {
2230 my $link = Fieldmapper::permission::usr_grp_map->new;
2232 $link->usr($userid);
2234 my $id = $apputils->simplereq(
2236 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2242 __PACKAGE__->register_method(
2243 method => "get_user_perm_groups",
2244 api_name => "open-ils.actor.user.get_groups",
2245 notes => "Retrieve a user's permission groups."
2249 sub get_user_perm_groups {
2250 my( $self, $client, $authtoken, $userid ) = @_;
2252 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2253 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2254 return $evt if $evt;
2256 return $apputils->simplereq(
2258 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2262 __PACKAGE__->register_method(
2263 method => "get_user_work_ous",
2264 api_name => "open-ils.actor.user.get_work_ous",
2265 notes => "Retrieve a user's work org units."
2268 __PACKAGE__->register_method(
2269 method => "get_user_work_ous",
2270 api_name => "open-ils.actor.user.get_work_ous.ids",
2271 notes => "Retrieve a user's work org units."
2274 sub get_user_work_ous {
2275 my( $self, $client, $auth, $userid ) = @_;
2276 my $e = new_editor(authtoken=>$auth);
2277 return $e->event unless $e->checkauth;
2278 $userid ||= $e->requestor->id;
2280 if($e->requestor->id != $userid) {
2281 my $user = $e->retrieve_actor_user($userid)
2282 or return $e->event;
2283 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2286 return $e->search_permission_usr_work_ou_map({usr => $userid})
2287 unless $self->api_name =~ /.ids$/;
2289 # client just wants a list of org IDs
2290 return $U->get_user_work_ou_ids($e, $userid);
2295 __PACKAGE__->register_method(
2296 method => 'register_workstation',
2297 api_name => 'open-ils.actor.workstation.register.override',
2298 signature => q/@see open-ils.actor.workstation.register/
2301 __PACKAGE__->register_method(
2302 method => 'register_workstation',
2303 api_name => 'open-ils.actor.workstation.register',
2305 Registers a new workstion in the system
2306 @param authtoken The login session key
2307 @param name The name of the workstation id
2308 @param owner The org unit that owns this workstation
2309 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2310 if the name is already in use.
2314 sub register_workstation {
2315 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2317 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2318 return $e->die_event unless $e->checkauth;
2319 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2320 my $existing = $e->search_actor_workstation({name => $name})->[0];
2324 if( $self->api_name =~ /override/o ) {
2325 # workstation with the given name exists.
2327 if($owner ne $existing->owning_lib) {
2328 # if necessary, update the owning_lib of the workstation
2330 $logger->info("changing owning lib of workstation ".$existing->id.
2331 " from ".$existing->owning_lib." to $owner");
2332 return $e->die_event unless
2333 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2335 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2337 $existing->owning_lib($owner);
2338 return $e->die_event unless $e->update_actor_workstation($existing);
2344 "attempt to register an existing workstation. returning existing ID");
2347 return $existing->id;
2350 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2354 my $ws = Fieldmapper::actor::workstation->new;
2355 $ws->owning_lib($owner);
2357 $e->create_actor_workstation($ws) or return $e->die_event;
2359 return $ws->id; # note: editor sets the id on the new object for us
2362 __PACKAGE__->register_method(
2363 method => 'workstation_list',
2364 api_name => 'open-ils.actor.workstation.list',
2366 Returns a list of workstations registered at the given location
2367 @param authtoken The login session key
2368 @param ids A list of org_unit.id's for the workstation owners
2372 sub workstation_list {
2373 my( $self, $conn, $authtoken, @orgs ) = @_;
2375 my $e = new_editor(authtoken=>$authtoken);
2376 return $e->event unless $e->checkauth;
2381 unless $e->allowed('REGISTER_WORKSTATION', $o);
2382 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2388 __PACKAGE__->register_method(
2389 method => 'fetch_patron_note',
2390 api_name => 'open-ils.actor.note.retrieve.all',
2393 Returns a list of notes for a given user
2394 Requestor must have VIEW_USER permission if pub==false and
2395 @param authtoken The login session key
2396 @param args Hash of params including
2397 patronid : the patron's id
2398 pub : true if retrieving only public notes
2402 sub fetch_patron_note {
2403 my( $self, $conn, $authtoken, $args ) = @_;
2404 my $patronid = $$args{patronid};
2406 my($reqr, $evt) = $U->checkses($authtoken);
2407 return $evt if $evt;
2410 ($patron, $evt) = $U->fetch_user($patronid);
2411 return $evt if $evt;
2414 if( $patronid ne $reqr->id ) {
2415 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2416 return $evt if $evt;
2418 return $U->cstorereq(
2419 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2420 { usr => $patronid, pub => 't' } );
2423 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2424 return $evt if $evt;
2426 return $U->cstorereq(
2427 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2430 __PACKAGE__->register_method(
2431 method => 'create_user_note',
2432 api_name => 'open-ils.actor.note.create',
2434 Creates a new note for the given user
2435 @param authtoken The login session key
2436 @param note The note object
2439 sub create_user_note {
2440 my( $self, $conn, $authtoken, $note ) = @_;
2441 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2442 return $e->die_event unless $e->checkauth;
2444 my $user = $e->retrieve_actor_user($note->usr)
2445 or return $e->die_event;
2447 return $e->die_event unless
2448 $e->allowed('UPDATE_USER',$user->home_ou);
2450 $note->creator($e->requestor->id);
2451 $e->create_actor_usr_note($note) or return $e->die_event;
2457 __PACKAGE__->register_method(
2458 method => 'delete_user_note',
2459 api_name => 'open-ils.actor.note.delete',
2461 Deletes a note for the given user
2462 @param authtoken The login session key
2463 @param noteid The note id
2466 sub delete_user_note {
2467 my( $self, $conn, $authtoken, $noteid ) = @_;
2469 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2470 return $e->die_event unless $e->checkauth;
2471 my $note = $e->retrieve_actor_usr_note($noteid)
2472 or return $e->die_event;
2473 my $user = $e->retrieve_actor_user($note->usr)
2474 or return $e->die_event;
2475 return $e->die_event unless
2476 $e->allowed('UPDATE_USER', $user->home_ou);
2478 $e->delete_actor_usr_note($note) or return $e->die_event;
2484 __PACKAGE__->register_method(
2485 method => 'update_user_note',
2486 api_name => 'open-ils.actor.note.update',
2488 @param authtoken The login session key
2489 @param note The note
2493 sub update_user_note {
2494 my( $self, $conn, $auth, $note ) = @_;
2495 my $e = new_editor(authtoken=>$auth, xact=>1);
2496 return $e->die_event unless $e->checkauth;
2497 my $patron = $e->retrieve_actor_user($note->usr)
2498 or return $e->die_event;
2499 return $e->die_event unless
2500 $e->allowed('UPDATE_USER', $patron->home_ou);
2501 $e->update_actor_user_note($note)
2502 or return $e->die_event;
2509 __PACKAGE__->register_method(
2510 method => 'create_closed_date',
2511 api_name => 'open-ils.actor.org_unit.closed_date.create',
2513 Creates a new closing entry for the given org_unit
2514 @param authtoken The login session key
2515 @param note The closed_date object
2518 sub create_closed_date {
2519 my( $self, $conn, $authtoken, $cd ) = @_;
2521 my( $user, $evt ) = $U->checkses($authtoken);
2522 return $evt if $evt;
2524 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2525 return $evt if $evt;
2527 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2529 my $id = $U->storagereq(
2530 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2531 return $U->DB_UPDATE_FAILED($cd) unless $id;
2536 __PACKAGE__->register_method(
2537 method => 'delete_closed_date',
2538 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2540 Deletes a closing entry for the given org_unit
2541 @param authtoken The login session key
2542 @param noteid The close_date id
2545 sub delete_closed_date {
2546 my( $self, $conn, $authtoken, $cd ) = @_;
2548 my( $user, $evt ) = $U->checkses($authtoken);
2549 return $evt if $evt;
2552 ($cd_obj, $evt) = fetch_closed_date($cd);
2553 return $evt if $evt;
2555 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2556 return $evt if $evt;
2558 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2560 my $stat = $U->storagereq(
2561 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2562 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2567 __PACKAGE__->register_method(
2568 method => 'usrname_exists',
2569 api_name => 'open-ils.actor.username.exists',
2571 desc => 'Check if a username is already taken (by an undeleted patron)',
2573 {desc => 'Authentication token', type => 'string'},
2574 {desc => 'Username', type => 'string'}
2577 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2582 sub usrname_exists {
2583 my( $self, $conn, $auth, $usrname ) = @_;
2584 my $e = new_editor(authtoken=>$auth);
2585 return $e->event unless $e->checkauth;
2586 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2587 return $$a[0] if $a and @$a;
2591 __PACKAGE__->register_method(
2592 method => 'barcode_exists',
2593 api_name => 'open-ils.actor.barcode.exists',
2595 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2598 sub barcode_exists {
2599 my( $self, $conn, $auth, $barcode ) = @_;
2600 my $e = new_editor(authtoken=>$auth);
2601 return $e->event unless $e->checkauth;
2602 my $card = $e->search_actor_card({barcode => $barcode});
2608 #return undef unless @$card;
2609 #return $card->[0]->usr;
2613 __PACKAGE__->register_method(
2614 method => 'retrieve_net_levels',
2615 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2618 sub retrieve_net_levels {
2619 my( $self, $conn, $auth ) = @_;
2620 my $e = new_editor(authtoken=>$auth);
2621 return $e->event unless $e->checkauth;
2622 return $e->retrieve_all_config_net_access_level();
2625 # Retain the old typo API name just in case
2626 __PACKAGE__->register_method(
2627 method => 'fetch_org_by_shortname',
2628 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2630 __PACKAGE__->register_method(
2631 method => 'fetch_org_by_shortname',
2632 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2634 sub fetch_org_by_shortname {
2635 my( $self, $conn, $sname ) = @_;
2636 my $e = new_editor();
2637 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2638 return $e->event unless $org;
2643 __PACKAGE__->register_method(
2644 method => 'session_home_lib',
2645 api_name => 'open-ils.actor.session.home_lib',
2648 sub session_home_lib {
2649 my( $self, $conn, $auth ) = @_;
2650 my $e = new_editor(authtoken=>$auth);
2651 return undef unless $e->checkauth;
2652 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2653 return $org->shortname;
2656 __PACKAGE__->register_method(
2657 method => 'session_safe_token',
2658 api_name => 'open-ils.actor.session.safe_token',
2660 Returns a hashed session ID that is safe for export to the world.
2661 This safe token will expire after 1 hour of non-use.
2662 @param auth Active authentication token
2666 sub session_safe_token {
2667 my( $self, $conn, $auth ) = @_;
2668 my $e = new_editor(authtoken=>$auth);
2669 return undef unless $e->checkauth;
2671 my $safe_token = md5_hex($auth);
2673 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2675 # Add more like the following if needed...
2677 "safe-token-home_lib-shortname-$safe_token",
2678 $e->retrieve_actor_org_unit(
2679 $e->requestor->home_ou
2688 __PACKAGE__->register_method(
2689 method => 'safe_token_home_lib',
2690 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2692 Returns the home library shortname from the session
2693 asscociated with a safe token from generated by
2694 open-ils.actor.session.safe_token.
2695 @param safe_token Active safe token
2699 sub safe_token_home_lib {
2700 my( $self, $conn, $safe_token ) = @_;
2702 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2703 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2708 __PACKAGE__->register_method(
2709 method => 'slim_tree',
2710 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2713 my $tree = new_editor()->search_actor_org_unit(
2715 {"parent_ou" => undef },
2718 flesh_fields => { aou => ['children'] },
2719 order_by => { aou => 'name'},
2720 select => { aou => ["id","shortname", "name"]},
2725 return trim_tree($tree);
2731 return undef unless $tree;
2733 code => $tree->shortname,
2734 name => $tree->name,
2736 if( $tree->children and @{$tree->children} ) {
2737 $htree->{children} = [];
2738 for my $c (@{$tree->children}) {
2739 push( @{$htree->{children}}, trim_tree($c) );
2747 __PACKAGE__->register_method(
2748 method => "update_penalties",
2749 api_name => "open-ils.actor.user.penalties.update"
2752 sub update_penalties {
2753 my($self, $conn, $auth, $user_id) = @_;
2754 my $e = new_editor(authtoken=>$auth, xact => 1);
2755 return $e->die_event unless $e->checkauth;
2756 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2757 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2758 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2759 return $evt if $evt;
2765 __PACKAGE__->register_method(
2766 method => "apply_penalty",
2767 api_name => "open-ils.actor.user.penalty.apply"
2771 my($self, $conn, $auth, $penalty) = @_;
2773 my $e = new_editor(authtoken=>$auth, xact => 1);
2774 return $e->die_event unless $e->checkauth;
2776 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2777 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2779 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2782 (defined $ptype->org_depth) ?
2783 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2786 $penalty->org_unit($ctx_org);
2787 $penalty->staff($e->requestor->id);
2788 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2791 return $penalty->id;
2794 __PACKAGE__->register_method(
2795 method => "remove_penalty",
2796 api_name => "open-ils.actor.user.penalty.remove"
2799 sub remove_penalty {
2800 my($self, $conn, $auth, $penalty) = @_;
2801 my $e = new_editor(authtoken=>$auth, xact => 1);
2802 return $e->die_event unless $e->checkauth;
2803 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2804 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2806 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2811 __PACKAGE__->register_method(
2812 method => "update_penalty_note",
2813 api_name => "open-ils.actor.user.penalty.note.update"
2816 sub update_penalty_note {
2817 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2818 my $e = new_editor(authtoken=>$auth, xact => 1);
2819 return $e->die_event unless $e->checkauth;
2820 for my $penalty_id (@$penalty_ids) {
2821 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2822 if (! $penalty ) { return $e->die_event; }
2823 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2824 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2826 $penalty->note( $note ); $penalty->ischanged( 1 );
2828 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2834 __PACKAGE__->register_method(
2835 method => "ranged_penalty_thresholds",
2836 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2840 sub ranged_penalty_thresholds {
2841 my($self, $conn, $auth, $context_org) = @_;
2842 my $e = new_editor(authtoken=>$auth);
2843 return $e->event unless $e->checkauth;
2844 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2845 my $list = $e->search_permission_grp_penalty_threshold([
2846 {org_unit => $U->get_org_ancestors($context_org)},
2847 {order_by => {pgpt => 'id'}}
2849 $conn->respond($_) for @$list;
2855 __PACKAGE__->register_method(
2856 method => "user_retrieve_fleshed_by_id",
2858 api_name => "open-ils.actor.user.fleshed.retrieve",
2861 sub user_retrieve_fleshed_by_id {
2862 my( $self, $client, $auth, $user_id, $fields ) = @_;
2863 my $e = new_editor(authtoken => $auth);
2864 return $e->event unless $e->checkauth;
2866 if( $e->requestor->id != $user_id ) {
2867 return $e->event unless $e->allowed('VIEW_USER');
2873 "standing_penalties",
2877 "stat_cat_entries" ];
2878 return new_flesh_user($user_id, $fields, $e);
2882 sub new_flesh_user {
2885 my $fields = shift || [];
2888 my $fetch_penalties = 0;
2889 if(grep {$_ eq 'standing_penalties'} @$fields) {
2890 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2891 $fetch_penalties = 1;
2894 my $user = $e->retrieve_actor_user(
2899 "flesh_fields" => { "au" => $fields }
2902 ) or return $e->die_event;
2905 if( grep { $_ eq 'addresses' } @$fields ) {
2907 $user->addresses([]) unless @{$user->addresses};
2908 # don't expose "replaced" addresses by default
2909 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2911 if( ref $user->billing_address ) {
2912 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2913 push( @{$user->addresses}, $user->billing_address );
2917 if( ref $user->mailing_address ) {
2918 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2919 push( @{$user->addresses}, $user->mailing_address );
2924 if($fetch_penalties) {
2925 # grab the user penalties ranged for this location
2926 $user->standing_penalties(
2927 $e->search_actor_user_standing_penalty([
2930 {stop_date => undef},
2931 {stop_date => {'>' => 'now'}}
2933 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
2936 flesh_fields => {ausp => ['standing_penalty']}
2943 $user->clear_passwd();
2950 __PACKAGE__->register_method(
2951 method => "user_retrieve_parts",
2952 api_name => "open-ils.actor.user.retrieve.parts",
2955 sub user_retrieve_parts {
2956 my( $self, $client, $auth, $user_id, $fields ) = @_;
2957 my $e = new_editor(authtoken => $auth);
2958 return $e->event unless $e->checkauth;
2959 $user_id ||= $e->requestor->id;
2960 if( $e->requestor->id != $user_id ) {
2961 return $e->event unless $e->allowed('VIEW_USER');
2964 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2965 push(@resp, $user->$_()) for(@$fields);
2971 __PACKAGE__->register_method(
2972 method => 'user_opt_in_enabled',
2973 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2974 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2977 sub user_opt_in_enabled {
2978 my($self, $conn) = @_;
2979 my $sc = OpenSRF::Utils::SettingsClient->new;
2980 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2985 __PACKAGE__->register_method(
2986 method => 'user_opt_in_at_org',
2987 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2989 @param $auth The auth token
2990 @param user_id The ID of the user to test
2991 @return 1 if the user has opted in at the specified org,
2992 event on error, and 0 otherwise. /
2994 sub user_opt_in_at_org {
2995 my($self, $conn, $auth, $user_id) = @_;
2997 # see if we even need to enforce the opt-in value
2998 return 1 unless user_opt_in_enabled($self);
3000 my $e = new_editor(authtoken => $auth);
3001 return $e->event unless $e->checkauth;
3003 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3004 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3006 my $ws_org = $e->requestor->ws_ou;
3007 # user is automatically opted-in if they are from the local org
3008 return 1 if $user->home_ou eq $ws_org;
3010 # get the boundary setting
3011 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3013 # auto opt in if user falls within the opt boundary
3014 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3016 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3018 my $vals = $e->search_actor_usr_org_unit_opt_in(
3019 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3025 __PACKAGE__->register_method(
3026 method => 'create_user_opt_in_at_org',
3027 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3029 @param $auth The auth token
3030 @param user_id The ID of the user to test
3031 @return The ID of the newly created object, event on error./
3034 sub create_user_opt_in_at_org {
3035 my($self, $conn, $auth, $user_id, $org_id) = @_;
3037 my $e = new_editor(authtoken => $auth, xact=>1);
3038 return $e->die_event unless $e->checkauth;
3040 # if a specific org unit wasn't passed in, get one based on the defaults;
3042 my $wsou = $e->requestor->ws_ou;
3043 # get the default opt depth
3044 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3045 # get the org unit at that depth
3046 my $org = $e->json_query({
3047 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3049 $org_id = $org->{id};
3052 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3053 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3055 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3057 $opt_in->org_unit($org_id);
3058 $opt_in->usr($user_id);
3059 $opt_in->staff($e->requestor->id);
3060 $opt_in->opt_in_ts('now');
3061 $opt_in->opt_in_ws($e->requestor->wsid);
3063 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3064 or return $e->die_event;
3072 __PACKAGE__->register_method (
3073 method => 'retrieve_org_hours',
3074 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3076 Returns the hours of operation for a specified org unit
3077 @param authtoken The login session key
3078 @param org_id The org_unit ID
3082 sub retrieve_org_hours {
3083 my($self, $conn, $auth, $org_id) = @_;
3084 my $e = new_editor(authtoken => $auth);
3085 return $e->die_event unless $e->checkauth;
3086 $org_id ||= $e->requestor->ws_ou;
3087 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3091 __PACKAGE__->register_method (
3092 method => 'verify_user_password',
3093 api_name => 'open-ils.actor.verify_user_password',
3095 Given a barcode or username and the MD5 encoded password,
3096 returns 1 if the password is correct. Returns 0 otherwise.
3100 sub verify_user_password {
3101 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3102 my $e = new_editor(authtoken => $auth);
3103 return $e->die_event unless $e->checkauth;
3105 my $user_by_barcode;
3106 my $user_by_username;
3108 my $card = $e->search_actor_card([
3109 {barcode => $barcode},
3110 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3111 $user_by_barcode = $card->usr;
3112 $user = $user_by_barcode;
3115 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3116 $user = $user_by_username;
3118 return 0 if (!$user);
3119 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3120 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3121 return 1 if $user->passwd eq $password;
3125 __PACKAGE__->register_method (
3126 method => 'retrieve_usr_id_via_barcode_or_usrname',
3127 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3129 Given a barcode or username returns the id for the user or
3134 sub retrieve_usr_id_via_barcode_or_usrname {
3135 my($self, $conn, $auth, $barcode, $username) = @_;
3136 my $e = new_editor(authtoken => $auth);
3137 return $e->die_event unless $e->checkauth;
3138 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3140 my $user_by_barcode;
3141 my $user_by_username;
3142 $logger->info("$id_as_barcode is the ID as BARCODE");
3144 my $card = $e->search_actor_card([
3145 {barcode => $barcode},
3146 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3147 if ($id_as_barcode =~ /^t/i) {
3149 $user = $e->retrieve_actor_user($barcode);
3150 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3152 $user_by_barcode = $card->usr;
3153 $user = $user_by_barcode;
3156 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3157 $user_by_barcode = $card->usr;
3158 $user = $user_by_barcode;
3163 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3165 $user = $user_by_username;
3167 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3168 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3169 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3174 __PACKAGE__->register_method (
3175 method => 'merge_users',
3176 api_name => 'open-ils.actor.user.merge',
3179 Given a list of source users and destination user, transfer all data from the source
3180 to the dest user and delete the source user. All user related data is
3181 transferred, including circulations, holds, bookbags, etc.
3187 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3188 my $e = new_editor(xact => 1, authtoken => $auth);
3189 return $e->die_event unless $e->checkauth;
3191 # disallow the merge if any subordinate accounts are in collections
3192 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3193 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3195 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3196 my $del_addrs = ($U->ou_ancestor_setting_value(
3197 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3198 my $del_cards = ($U->ou_ancestor_setting_value(
3199 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3200 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3201 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3203 for my $src_id (@$user_ids) {
3204 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3206 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3207 if($src_user->home_ou ne $master_user->home_ou) {
3208 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3211 return $e->die_event unless
3212 $e->json_query({from => [
3227 __PACKAGE__->register_method (
3228 method => 'approve_user_address',
3229 api_name => 'open-ils.actor.user.pending_address.approve',
3236 sub approve_user_address {
3237 my($self, $conn, $auth, $addr) = @_;
3238 my $e = new_editor(xact => 1, authtoken => $auth);
3239 return $e->die_event unless $e->checkauth;
3241 # if the caller passes an address object, assume they want to
3242 # update it first before approving it
3243 $e->update_actor_user_address($addr) or return $e->die_event;
3245 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3247 my $user = $e->retrieve_actor_user($addr->usr);
3248 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3249 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3250 or return $e->die_event;
3252 return [values %$result]->[0];
3256 __PACKAGE__->register_method (
3257 method => 'retrieve_friends',
3258 api_name => 'open-ils.actor.friends.retrieve',
3261 returns { confirmed: [], pending_out: [], pending_in: []}
3262 pending_out are users I'm requesting friendship with
3263 pending_in are users requesting friendship with me
3268 sub retrieve_friends {
3269 my($self, $conn, $auth, $user_id, $options) = @_;
3270 my $e = new_editor(authtoken => $auth);
3271 return $e->event unless $e->checkauth;
3272 $user_id ||= $e->requestor->id;
3274 if($user_id != $e->requestor->id) {
3275 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3276 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3279 return OpenILS::Application::Actor::Friends->retrieve_friends(
3280 $e, $user_id, $options);
3285 __PACKAGE__->register_method (
3286 method => 'apply_friend_perms',
3287 api_name => 'open-ils.actor.friends.perms.apply',
3293 sub apply_friend_perms {
3294 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3295 my $e = new_editor(authtoken => $auth, xact => 1);
3296 return $e->die_event unless $e->checkauth;
3298 if($user_id != $e->requestor->id) {
3299 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3300 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3303 for my $perm (@perms) {
3305 OpenILS::Application::Actor::Friends->apply_friend_perm(
3306 $e, $user_id, $delegate_id, $perm);
3307 return $evt if $evt;
3315 __PACKAGE__->register_method (
3316 method => 'update_user_pending_address',
3317 api_name => 'open-ils.actor.user.address.pending.cud'
3320 sub update_user_pending_address {
3321 my($self, $conn, $auth, $addr) = @_;
3322 my $e = new_editor(authtoken => $auth, xact => 1);
3323 return $e->die_event unless $e->checkauth;
3325 if($addr->usr != $e->requestor->id) {
3326 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3327 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3331 $e->create_actor_user_address($addr) or return $e->die_event;
3332 } elsif($addr->isdeleted) {
3333 $e->delete_actor_user_address($addr) or return $e->die_event;
3335 $e->update_actor_user_address($addr) or return $e->die_event;
3343 __PACKAGE__->register_method (
3344 method => 'user_events',
3345 api_name => 'open-ils.actor.user.events.circ',
3348 __PACKAGE__->register_method (
3349 method => 'user_events',
3350 api_name => 'open-ils.actor.user.events.ahr',
3355 my($self, $conn, $auth, $user_id, $filters) = @_;
3356 my $e = new_editor(authtoken => $auth);
3357 return $e->event unless $e->checkauth;
3359 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3360 my $user_field = 'usr';
3363 $filters->{target} = {
3364 select => { $obj_type => ['id'] },
3366 where => {usr => $user_id}
3369 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3370 if($e->requestor->id != $user_id) {
3371 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3374 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3375 my $req = $ses->request('open-ils.trigger.events_by_target',
3376 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3378 while(my $resp = $req->recv) {
3379 my $val = $resp->content;
3380 my $tgt = $val->target;
3382 if($obj_type eq 'circ') {
3383 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3385 } elsif($obj_type eq 'ahr') {
3386 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3387 if $tgt->current_copy;
3390 $conn->respond($val) if $val;
3396 __PACKAGE__->register_method (
3397 method => 'copy_events',
3398 api_name => 'open-ils.actor.copy.events.circ',
3401 __PACKAGE__->register_method (
3402 method => 'copy_events',
3403 api_name => 'open-ils.actor.copy.events.ahr',
3408 my($self, $conn, $auth, $copy_id, $filters) = @_;
3409 my $e = new_editor(authtoken => $auth);
3410 return $e->event unless $e->checkauth;
3412 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3414 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3416 my $copy_field = 'target_copy';
3417 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3420 $filters->{target} = {
3421 select => { $obj_type => ['id'] },
3423 where => {$copy_field => $copy_id}
3427 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3428 my $req = $ses->request('open-ils.trigger.events_by_target',
3429 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3431 while(my $resp = $req->recv) {
3432 my $val = $resp->content;
3433 my $tgt = $val->target;
3435 my $user = $e->retrieve_actor_user($tgt->usr);
3436 if($e->requestor->id != $user->id) {
3437 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3440 $tgt->$copy_field($copy);
3443 $conn->respond($val) if $val;
3452 __PACKAGE__->register_method (
3453 method => 'update_events',
3454 api_name => 'open-ils.actor.user.event.cancel.batch',
3457 __PACKAGE__->register_method (
3458 method => 'update_events',
3459 api_name => 'open-ils.actor.user.event.reset.batch',
3464 my($self, $conn, $auth, $event_ids) = @_;
3465 my $e = new_editor(xact => 1, authtoken => $auth);
3466 return $e->die_event unless $e->checkauth;
3469 for my $id (@$event_ids) {
3471 # do a little dance to determine what user we are ultimately affecting
3472 my $event = $e->retrieve_action_trigger_event([
3475 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3477 ]) or return $e->die_event;
3480 if($event->event_def->hook->core_type eq 'circ') {
3481 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3482 } elsif($event->event_def->hook->core_type eq 'ahr') {
3483 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3488 my $user = $e->retrieve_actor_user($user_id);
3489 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3491 if($self->api_name =~ /cancel/) {
3492 $event->state('invalid');
3493 } elsif($self->api_name =~ /reset/) {
3494 $event->clear_start_time;
3495 $event->clear_update_time;
3496 $event->state('pending');
3499 $e->update_action_trigger_event($event) or return $e->die_event;
3500 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3504 return {complete => 1};
3508 __PACKAGE__->register_method (
3509 method => 'really_delete_user',
3510 api_name => 'open-ils.actor.user.delete',
3512 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3513 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3514 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3515 dest_usr_id is only required when deleting a user that performs staff functions.
3519 sub really_delete_user {
3520 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3521 my $e = new_editor(authtoken => $auth, xact => 1);
3522 return $e->die_event unless $e->checkauth;
3523 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3524 # No deleting yourself - UI is supposed to stop you first, though.
3525 return $e->die_event unless $e->requestor->id != $user->id;
3526 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3527 # Check if you are allowed to mess with this patron permission group at all
3528 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3529 my $evt = group_perm_failed($session, $e->requestor, $user);
3530 return $e->die_event($evt) if $evt;
3531 my $stat = $e->json_query(
3532 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3533 or return $e->die_event;
3540 __PACKAGE__->register_method (
3541 method => 'user_payments',
3542 api_name => 'open-ils.actor.user.payments.retrieve',
3545 Returns all payments for a given user. Default order is newest payments first.
3546 @param auth Authentication token
3547 @param user_id The user ID
3548 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3553 my($self, $conn, $auth, $user_id, $filters) = @_;
3556 my $e = new_editor(authtoken => $auth);
3557 return $e->die_event unless $e->checkauth;
3559 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3560 return $e->event unless
3561 $e->requestor->id == $user_id or
3562 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3564 # Find all payments for all transactions for user $user_id
3566 select => {mp => ['id']},
3571 select => {mbt => ['id']},
3573 where => {usr => $user_id}
3577 order_by => [{ # by default, order newest payments first
3579 field => 'payment_ts',
3584 for (qw/order_by limit offset/) {
3585 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3588 if(defined $filters->{where}) {
3589 foreach (keys %{$filters->{where}}) {
3590 # don't allow the caller to expand the result set to other users
3591 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3595 my $payment_ids = $e->json_query($query);
3596 for my $pid (@$payment_ids) {
3597 my $pay = $e->retrieve_money_payment([
3602 mbt => ['summary', 'circulation', 'grocery'],
3603 circ => ['target_copy'],
3604 acp => ['call_number'],
3612 xact_type => $pay->xact->summary->xact_type,
3613 last_billing_type => $pay->xact->summary->last_billing_type,
3616 if($pay->xact->summary->xact_type eq 'circulation') {
3617 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3618 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3621 $pay->xact($pay->xact->id); # de-flesh
3622 $conn->respond($resp);
3630 __PACKAGE__->register_method (
3631 method => 'negative_balance_users',
3632 api_name => 'open-ils.actor.users.negative_balance',
3635 Returns all users that have an overall negative balance
3636 @param auth Authentication token
3637 @param org_id The context org unit as an ID or list of IDs. This will be the home
3638 library of the user. If no org_unit is specified, no org unit filter is applied
3642 sub negative_balance_users {
3643 my($self, $conn, $auth, $org_id) = @_;
3645 my $e = new_editor(authtoken => $auth);
3646 return $e->die_event unless $e->checkauth;
3647 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3651 mous => ['usr', 'balance_owed'],
3654 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3655 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3672 where => {'+mous' => {balance_owed => {'<' => 0}}}
3675 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3677 my $list = $e->json_query($query, {timeout => 600});
3679 for my $data (@$list) {
3681 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3682 balance_owed => $data->{balance_owed},
3683 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3690 __PACKAGE__->register_method(
3691 method => "request_password_reset",
3692 api_name => "open-ils.actor.patron.password_reset.request",
3694 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3695 "method for changing a user's password. The UUID token is distributed via A/T " .
3696 "templates (i.e. email to the user).",
3698 { desc => 'user_id_type', type => 'string' },
3699 { desc => 'user_id', type => 'string' },
3700 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3702 return => {desc => '1 on success, Event on error'}
3705 sub request_password_reset {
3706 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3708 # Check to see if password reset requests are already being throttled:
3709 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3711 my $e = new_editor(xact => 1);
3714 # Get the user, if any, depending on the input value
3715 if ($user_id_type eq 'username') {
3716 $user = $e->search_actor_user({usrname => $user_id})->[0];
3719 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3721 } elsif ($user_id_type eq 'barcode') {
3722 my $card = $e->search_actor_card([
3723 {barcode => $user_id},
3724 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3727 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3732 # If the user doesn't have an email address, we can't help them
3733 if (!$user->email) {
3735 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3738 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3739 if ($email_must_match) {
3740 if ($user->email ne $email) {
3741 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3745 _reset_password_request($conn, $e, $user);
3748 # Once we have the user, we can issue the password reset request
3749 # XXX Add a wrapper method that accepts barcode + email input
3750 sub _reset_password_request {
3751 my ($conn, $e, $user) = @_;
3753 # 1. Get throttle threshold and time-to-live from OU_settings
3754 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3755 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3757 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3759 # 2. Get time of last request and number of active requests (num_active)
3760 my $active_requests = $e->json_query({
3766 transform => 'COUNT'
3769 column => 'request_time',
3775 has_been_reset => { '=' => 'f' },
3776 request_time => { '>' => $threshold_time }
3780 # Guard against no active requests
3781 if ($active_requests->[0]->{'request_time'}) {
3782 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3783 my $now = DateTime::Format::ISO8601->new();
3785 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3786 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3787 ($last_request->add_duration('1 minute') > $now)) {
3788 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3790 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3794 # TODO Check to see if the user is in a password-reset-restricted group
3796 # Otherwise, go ahead and try to get the user.
3798 # Check the number of active requests for this user
3799 $active_requests = $e->json_query({
3805 transform => 'COUNT'
3810 usr => { '=' => $user->id },
3811 has_been_reset => { '=' => 'f' },
3812 request_time => { '>' => $threshold_time }
3816 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3818 # if less than or equal to per-user threshold, proceed; otherwise, return event
3819 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3820 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3822 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3825 # Create the aupr object and insert into the database
3826 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3827 my $uuid = create_uuid_as_string(UUID_V4);
3828 $reset_request->uuid($uuid);
3829 $reset_request->usr($user->id);
3831 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3834 # Create an event to notify user of the URL to reset their password
3836 # Can we stuff this in the user_data param for trigger autocreate?
3837 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3839 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3840 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3843 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3848 __PACKAGE__->register_method(
3849 method => "commit_password_reset",
3850 api_name => "open-ils.actor.patron.password_reset.commit",
3852 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3853 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3854 "with the supplied password.",
3856 { desc => 'uuid', type => 'string' },
3857 { desc => 'password', type => 'string' },
3859 return => {desc => '1 on success, Event on error'}
3862 sub commit_password_reset {
3863 my($self, $conn, $uuid, $password) = @_;
3865 # Check to see if password reset requests are already being throttled:
3866 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3867 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3868 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3870 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3873 my $e = new_editor(xact => 1);
3875 my $aupr = $e->search_actor_usr_password_reset({
3882 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3884 my $user_id = $aupr->[0]->usr;
3885 my $user = $e->retrieve_actor_user($user_id);
3887 # Ensure we're still within the TTL for the request
3888 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3889 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3890 if ($threshold < DateTime->now(time_zone => 'local')) {
3892 $logger->info("Password reset request needed to be submitted before $threshold");
3893 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3896 # Check complexity of password against OU-defined regex
3897 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3901 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3902 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3903 $is_strong = check_password_strength_custom($password, $pw_regex);
3905 $is_strong = check_password_strength_default($password);
3910 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3913 # All is well; update the password
3914 $user->passwd($password);
3915 $e->update_actor_user($user);
3917 # And flag that this password reset request has been honoured
3918 $aupr->[0]->has_been_reset('t');
3919 $e->update_actor_usr_password_reset($aupr->[0]);
3925 sub check_password_strength_default {
3926 my $password = shift;
3927 # Use the default set of checks
3928 if ( (length($password) < 7) or
3929 ($password !~ m/.*\d+.*/) or
3930 ($password !~ m/.*[A-Za-z]+.*/)
3937 sub check_password_strength_custom {
3938 my ($password, $pw_regex) = @_;
3940 $pw_regex = qr/$pw_regex/;
3941 if ($password !~ /$pw_regex/) {
3949 __PACKAGE__->register_method(
3950 method => "event_def_opt_in_settings",
3951 api_name => "open-ils.actor.event_def.opt_in.settings",
3954 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3956 { desc => 'Authentication token', type => 'string'},
3958 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3963 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3970 sub event_def_opt_in_settings {
3971 my($self, $conn, $auth, $org_id) = @_;
3972 my $e = new_editor(authtoken => $auth);
3973 return $e->event unless $e->checkauth;
3975 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3976 return $e->event unless
3977 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3979 $org_id = $e->requestor->home_ou;
3982 # find all config.user_setting_type's related to event_defs for the requested org unit
3983 my $types = $e->json_query({
3984 select => {cust => ['name']},
3985 from => {atevdef => 'cust'},
3988 owner => $U->get_org_ancestors($org_id), # context org plus parents
3995 $conn->respond($_) for
3996 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4003 __PACKAGE__->register_method(
4004 method => "user_visible_circs",
4005 api_name => "open-ils.actor.history.circ.visible",
4008 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4010 { desc => 'Authentication token', type => 'string'},
4011 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4012 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4015 desc => q/An object with 2 fields: circulation and summary.
4016 circulation is the "circ" object. summary is the related "accs" object/,
4022 __PACKAGE__->register_method(
4023 method => "user_visible_circs",
4024 api_name => "open-ils.actor.history.circ.visible.print",
4027 desc => 'Returns printable output for the set of opt-in visible circulations',
4029 { desc => 'Authentication token', type => 'string'},
4030 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4031 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4034 desc => q/An action_trigger.event object or error event./,
4040 __PACKAGE__->register_method(
4041 method => "user_visible_circs",
4042 api_name => "open-ils.actor.history.circ.visible.email",
4045 desc => 'Emails the set of opt-in visible circulations to the requestor',
4047 { desc => 'Authentication token', type => 'string'},
4048 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4049 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4052 desc => q/undef, or event on error/
4057 __PACKAGE__->register_method(
4058 method => "user_visible_circs",
4059 api_name => "open-ils.actor.history.hold.visible",
4062 desc => 'Returns the set of opt-in visible holds',
4064 { desc => 'Authentication token', type => 'string'},
4065 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4066 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4069 desc => q/An object with 1 field: "hold"/,
4075 __PACKAGE__->register_method(
4076 method => "user_visible_circs",
4077 api_name => "open-ils.actor.history.hold.visible.print",
4080 desc => 'Returns printable output for the set of opt-in visible holds',
4082 { desc => 'Authentication token', type => 'string'},
4083 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4084 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4087 desc => q/An action_trigger.event object or error event./,
4093 __PACKAGE__->register_method(
4094 method => "user_visible_circs",
4095 api_name => "open-ils.actor.history.hold.visible.email",
4098 desc => 'Emails the set of opt-in visible holds to the requestor',
4100 { desc => 'Authentication token', type => 'string'},
4101 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4102 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4105 desc => q/undef, or event on error/
4110 sub user_visible_circs {
4111 my($self, $conn, $auth, $user_id, $options) = @_;
4113 my $is_hold = ($self->api_name =~ /hold/);
4114 my $for_print = ($self->api_name =~ /print/);
4115 my $for_email = ($self->api_name =~ /email/);
4116 my $e = new_editor(authtoken => $auth);
4117 return $e->event unless $e->checkauth;
4119 $user_id ||= $e->requestor->id;
4121 $options->{limit} ||= 50;
4122 $options->{offset} ||= 0;
4124 if($user_id != $e->requestor->id) {
4125 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4126 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4127 return $e->event unless $e->allowed($perm, $user->home_ou);
4130 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4132 my $data = $e->json_query({
4133 from => [$db_func, $user_id],
4134 limit => $$options{limit},
4135 offset => $$options{offset}
4137 # TODO: I only want IDs. code below didn't get me there
4138 # {"select":{"au":[{"column":"id", "result_field":"id",
4139 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4144 return undef unless @$data;
4148 # collect the batch of objects
4152 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4153 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4157 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4158 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4161 } elsif ($for_email) {
4163 $conn->respond_complete(1) if $for_email; # no sense in waiting
4171 my $hold = $e->retrieve_action_hold_request($id);
4172 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4173 # events will be fired from action_trigger_runner
4177 my $circ = $e->retrieve_action_circulation($id);
4178 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4179 # events will be fired from action_trigger_runner
4183 } else { # just give me the data please
4191 my $hold = $e->retrieve_action_hold_request($id);
4192 $conn->respond({hold => $hold});
4196 my $circ = $e->retrieve_action_circulation($id);
4199 summary => $U->create_circ_chain_summary($e, $id)
4208 __PACKAGE__->register_method(
4209 method => "user_saved_search_cud",
4210 api_name => "open-ils.actor.user.saved_search.cud",
4213 desc => 'Create/Update/Delete Access to user saved searches',
4215 { desc => 'Authentication token', type => 'string' },
4216 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4219 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4225 __PACKAGE__->register_method(
4226 method => "user_saved_search_cud",
4227 api_name => "open-ils.actor.user.saved_search.retrieve",
4230 desc => 'Retrieve a saved search object',
4232 { desc => 'Authentication token', type => 'string' },
4233 { desc => 'Saved Search ID', type => 'number' }
4236 desc => q/The saved search object, Event on error/,
4242 sub user_saved_search_cud {
4243 my( $self, $client, $auth, $search ) = @_;
4244 my $e = new_editor( authtoken=>$auth );
4245 return $e->die_event unless $e->checkauth;
4247 my $o_search; # prior version of the object, if any
4248 my $res; # to be returned
4250 # branch on the operation type
4252 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4254 # Get the old version, to check ownership
4255 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4256 or return $e->die_event;
4258 # You can't read somebody else's search
4259 return OpenILS::Event->new('BAD_PARAMS')
4260 unless $o_search->owner == $e->requestor->id;
4266 $e->xact_begin; # start an editor transaction
4268 if( $search->isnew ) { # Create
4270 # You can't create a search for somebody else
4271 return OpenILS::Event->new('BAD_PARAMS')
4272 unless $search->owner == $e->requestor->id;
4274 $e->create_actor_usr_saved_search( $search )
4275 or return $e->die_event;
4279 } elsif( $search->ischanged ) { # Update
4281 # You can't change ownership of a search
4282 return OpenILS::Event->new('BAD_PARAMS')
4283 unless $search->owner == $e->requestor->id;
4285 # Get the old version, to check ownership
4286 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4287 or return $e->die_event;
4289 # You can't update somebody else's search
4290 return OpenILS::Event->new('BAD_PARAMS')
4291 unless $o_search->owner == $e->requestor->id;
4294 $e->update_actor_usr_saved_search( $search )
4295 or return $e->die_event;
4299 } elsif( $search->isdeleted ) { # Delete
4301 # Get the old version, to check ownership
4302 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4303 or return $e->die_event;
4305 # You can't delete somebody else's search
4306 return OpenILS::Event->new('BAD_PARAMS')
4307 unless $o_search->owner == $e->requestor->id;
4310 $e->delete_actor_usr_saved_search( $o_search )
4311 or return $e->die_event;
4322 __PACKAGE__->register_method(
4323 method => "get_barcodes",
4324 api_name => "open-ils.actor.get_barcodes"
4328 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4329 my $e = new_editor(authtoken => $auth);
4330 return $e->event unless $e->checkauth;
4331 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4333 my $db_result = $e->json_query(
4335 'evergreen.get_barcodes',
4336 $org_id, $context, $barcode,
4340 if($context =~ /actor/) {
4341 my $filter_result = ();
4343 foreach my $result (@$db_result) {
4344 if($result->{type} eq 'actor') {
4345 if($e->requestor->id != $result->{id}) {
4346 $patron = $e->retrieve_actor_user($result->{id});
4348 push(@$filter_result, $e->event);
4351 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4352 push(@$filter_result, $result);
4355 push(@$filter_result, $e->event);
4359 push(@$filter_result, $result);
4363 push(@$filter_result, $result);
4366 return $filter_result;
4372 __PACKAGE__->register_method(
4373 method => 'address_alert_test',
4374 api_name => 'open-ils.actor.address_alert.test',
4376 desc => "Tests a set of address fields to determine if they match with an address_alert",
4378 {desc => 'Authentication token', type => 'string'},
4379 {desc => 'Org Unit', type => 'number'},
4380 {desc => 'Fields', type => 'hash'},
4382 return => {desc => 'List of matching address_alerts'}
4386 sub address_alert_test {
4387 my ($self, $client, $auth, $org_unit, $fields) = @_;
4388 return [] unless $fields and grep {$_} values %$fields;
4390 my $e = new_editor(authtoken => $auth);
4391 return $e->event unless $e->checkauth;
4392 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4393 $org_unit ||= $e->requestor->ws_ou;
4395 my $alerts = $e->json_query({
4397 'actor.address_alert_matches',
4405 $$fields{post_code},
4406 $$fields{mailing_address},
4407 $$fields{billing_address}
4411 # map the json_query hashes to real objects
4413 map {$e->retrieve_actor_address_alert($_)}
4414 (map {$_->{id}} @$alerts)