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 OpenILS::Utils::BadContact;
38 use List::Util qw/max reduce/;
40 use UUID::Tiny qw/:std/;
43 OpenILS::Application::Actor::Container->initialize();
44 OpenILS::Application::Actor::UserGroups->initialize();
45 OpenILS::Application::Actor::ClosedDates->initialize();
48 my $apputils = "OpenILS::Application::AppUtils";
51 sub _d { warn "Patron:\n" . Dumper(shift()); }
54 my $set_user_settings;
58 #__PACKAGE__->register_method(
59 # method => "allowed_test",
60 # api_name => "open-ils.actor.allowed_test",
63 # my($self, $conn, $auth, $orgid, $permcode) = @_;
64 # my $e = new_editor(authtoken => $auth);
65 # return $e->die_event unless $e->checkauth;
69 # permcode => $permcode,
70 # result => $e->allowed($permcode, $orgid)
74 __PACKAGE__->register_method(
75 method => "update_user_setting",
76 api_name => "open-ils.actor.patron.settings.update",
78 sub update_user_setting {
79 my($self, $conn, $auth, $user_id, $settings) = @_;
80 my $e = new_editor(xact => 1, authtoken => $auth);
81 return $e->die_event unless $e->checkauth;
83 $user_id = $e->requestor->id unless defined $user_id;
85 unless($e->requestor->id == $user_id) {
86 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
87 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
90 for my $name (keys %$settings) {
91 my $val = $$settings{$name};
92 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
95 $val = OpenSRF::Utils::JSON->perl2JSON($val);
98 $e->update_actor_user_setting($set) or return $e->die_event;
100 $set = Fieldmapper::actor::user_setting->new;
104 $e->create_actor_user_setting($set) or return $e->die_event;
107 $e->delete_actor_user_setting($set) or return $e->die_event;
116 __PACKAGE__->register_method(
117 method => "set_ou_settings",
118 api_name => "open-ils.actor.org_unit.settings.update",
120 desc => "Updates the value for a given org unit setting. The permission to update " .
121 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
122 "permission specified in the update_perm column of the config.org_unit_setting_type " .
123 "table's row corresponding to the setting being changed." ,
125 {desc => 'Authentication token', type => 'string'},
126 {desc => 'Org unit ID', type => 'number'},
127 {desc => 'Hash of setting name-value pairs', type => 'object'}
129 return => {desc => '1 on success, Event on error'}
133 sub set_ou_settings {
134 my( $self, $client, $auth, $org_id, $settings ) = @_;
136 my $e = new_editor(authtoken => $auth, xact => 1);
137 return $e->die_event unless $e->checkauth;
139 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
141 for my $name (keys %$settings) {
142 my $val = $$settings{$name};
144 my $type = $e->retrieve_config_org_unit_setting_type([
146 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
147 ]) or return $e->die_event;
148 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
150 # If there is no relevant permission, the default assumption will
151 # be, "no, the caller cannot change that value."
152 return $e->die_event unless ($all_allowed ||
153 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
156 $val = OpenSRF::Utils::JSON->perl2JSON($val);
159 $e->update_actor_org_unit_setting($set) or return $e->die_event;
161 $set = Fieldmapper::actor::org_unit_setting->new;
162 $set->org_unit($org_id);
165 $e->create_actor_org_unit_setting($set) or return $e->die_event;
168 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
176 __PACKAGE__->register_method(
177 method => "user_settings",
179 api_name => "open-ils.actor.patron.settings.retrieve",
182 my( $self, $client, $auth, $user_id, $setting ) = @_;
184 my $e = new_editor(authtoken => $auth);
185 return $e->event unless $e->checkauth;
186 $user_id = $e->requestor->id unless defined $user_id;
188 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
189 if($e->requestor->id != $user_id) {
190 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
194 my($e, $user_id, $setting) = @_;
195 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
196 return undef unless $val; # XXX this should really return undef, but needs testing
197 return OpenSRF::Utils::JSON->JSON2perl($val->value);
201 if(ref $setting eq 'ARRAY') {
203 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
206 return get_setting($e, $user_id, $setting);
209 my $s = $e->search_actor_user_setting({usr => $user_id});
210 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
215 __PACKAGE__->register_method(
216 method => "ranged_ou_settings",
217 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
219 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
220 "is implied for retrieving OU settings by the authenticated users' permissions.",
222 {desc => 'Authentication token', type => 'string'},
223 {desc => 'Org unit ID', type => 'number'},
225 return => {desc => 'A hashref of "ranged" settings, event on error'}
228 sub ranged_ou_settings {
229 my( $self, $client, $auth, $org_id ) = @_;
231 my $e = new_editor(authtoken => $auth);
232 return $e->event unless $e->checkauth;
235 my $org_list = $U->get_org_ancestors($org_id);
236 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
237 $org_list = [ reverse @$org_list ];
239 # start at the context org and capture the setting value
240 # without clobbering settings we've already captured
241 for my $this_org_id (@$org_list) {
243 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
245 for my $set (@sets) {
246 my $type = $e->retrieve_config_org_unit_setting_type([
248 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
251 # If there is no relevant permission, the default assumption will
252 # be, "yes, the caller can have that value."
253 if ($type && $type->view_perm) {
254 next if not $e->allowed($type->view_perm->code, $org_id);
257 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
258 unless defined $ranged_settings{$set->name};
262 return \%ranged_settings;
267 __PACKAGE__->register_method(
268 api_name => 'open-ils.actor.ou_setting.ancestor_default',
269 method => 'ou_ancestor_setting',
271 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
272 'This method will make sure that the given user has permission to view that setting, if there is a ' .
273 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
274 'the user lacks the permisssion, undef will be returned.' ,
276 { desc => 'Org unit ID', type => 'number' },
277 { desc => 'setting name', type => 'string' },
278 { desc => 'authtoken (optional)', type => 'string' }
280 return => {desc => 'A value for the org unit setting, or undef'}
284 # ------------------------------------------------------------------
285 # Attempts to find the org setting value for a given org. if not
286 # found at the requested org, searches up the org tree until it
287 # finds a parent that has the requested setting.
288 # when found, returns { org => $id, value => $value }
289 # otherwise, returns NULL
290 # ------------------------------------------------------------------
291 sub ou_ancestor_setting {
292 my( $self, $client, $orgid, $name, $auth ) = @_;
293 # Make sure $auth is set to something if not given.
295 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
298 __PACKAGE__->register_method(
299 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
300 method => 'ou_ancestor_setting_batch',
302 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
303 'This method will make sure that the given user has permission to view that setting, if there is a ' .
304 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
305 'the user lacks the permisssion, undef will be returned.' ,
307 { desc => 'Org unit ID', type => 'number' },
308 { desc => 'setting name list', type => 'array' },
309 { desc => 'authtoken (optional)', type => 'string' }
311 return => {desc => 'A hash with name => value pairs for the org unit settings'}
314 sub ou_ancestor_setting_batch {
315 my( $self, $client, $orgid, $name_list, $auth ) = @_;
317 # splitting the list of settings to fetch values
318 # so that ones that *don't* require view_perm checks
319 # can be fetched in one fell swoop, which is
320 # significantly faster in cases where a large
321 # number of settings need to be fetched.
322 my %perm_check_required = ();
323 my @perm_check_not_required = ();
325 # Note that ->ou_ancestor_setting also can check
326 # to see if the setting has a view_perm, but testing
327 # suggests that the redundant checks do not significantly
328 # increase the time it takes to fetch the values of
329 # permission-controlled settings.
330 my $e = new_editor();
331 my $res = $e->search_config_org_unit_setting_type({
333 view_perm => { "!=" => undef },
335 %perm_check_required = map { $_->name() => 1 } @$res;
336 foreach my $setting (@$name_list) {
337 push @perm_check_not_required, $setting
338 unless exists($perm_check_required{$setting});
342 if (@perm_check_not_required) {
343 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
345 $values{$_} = $U->ou_ancestor_setting(
348 ) for keys(%perm_check_required);
354 __PACKAGE__->register_method(
355 method => "update_patron",
356 api_name => "open-ils.actor.patron.update",
359 Update an existing user, or create a new one. Related objects,
360 like cards, addresses, survey responses, and stat cats,
361 can be updated by attaching them to the user object in their
362 respective fields. For examples, the billing address object
363 may be inserted into the 'billing_address' field, etc. For each
364 attached object, indicate if the object should be created,
365 updated, or deleted using the built-in 'isnew', 'ischanged',
366 and 'isdeleted' fields on the object.
369 { desc => 'Authentication token', type => 'string' },
370 { desc => 'Patron data object', type => 'object' }
372 return => {desc => 'A fleshed user object, event on error'}
377 my( $self, $client, $user_session, $patron ) = @_;
379 my $session = $apputils->start_db_session();
381 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
383 my( $user_obj, $evt ) = $U->checkses($user_session);
386 $evt = check_group_perm($session, $user_obj, $patron);
389 $apputils->set_audit_info($session, $user_session, $user_obj->id, $user_obj->wsid);
391 # $new_patron is the patron in progress. $patron is the original patron
392 # passed in with the method. new_patron will change as the components
393 # of patron are added/updated.
397 # unflesh the real items on the patron
398 $patron->card( $patron->card->id ) if(ref($patron->card));
399 $patron->billing_address( $patron->billing_address->id )
400 if(ref($patron->billing_address));
401 $patron->mailing_address( $patron->mailing_address->id )
402 if(ref($patron->mailing_address));
404 # create/update the patron first so we can use his id
406 # $patron is the obj from the client (new data) and $new_patron is the
407 # patron object properly built for db insertion, so we need a third variable
408 # if we want to represent the old patron.
411 my $barred_hook = '';
413 if($patron->isnew()) {
414 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
416 if($U->is_true($patron->barred)) {
417 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
421 $new_patron = $patron;
423 # Did auth checking above already.
425 $old_patron = $e->retrieve_actor_user($patron->id) or
426 return $e->die_event;
428 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
429 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
432 $barred_hook = $U->is_true($new_patron->barred) ?
433 'au.barred' : 'au.unbarred';
437 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
440 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
443 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
446 # re-update the patron if anything has happened to him during this process
447 if($new_patron->ischanged()) {
448 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
452 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
455 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
458 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
461 $apputils->commit_db_session($session);
463 $evt = apply_invalid_addr_penalty($patron);
466 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
468 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
470 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
472 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
473 $new_patron, $new_patron->home_ou) if $barred_hook;
476 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
479 sub apply_invalid_addr_penalty {
481 my $e = new_editor(xact => 1);
483 # grab the invalid address penalty if set
484 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
486 my ($addr_penalty) = grep
487 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
489 # do we enforce invalid address penalty
490 my $enforce = $U->ou_ancestor_setting_value(
491 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
493 my $addrs = $e->search_actor_user_address(
494 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
495 my $addr_count = scalar(@$addrs);
497 if($addr_count == 0 and $addr_penalty) {
499 # regardless of any settings, remove the penalty when the user has no invalid addresses
500 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
503 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
505 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
506 my $depth = $ptype->org_depth;
507 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
508 $ctx_org = $patron->home_ou unless defined $ctx_org;
510 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
511 $penalty->usr($patron->id);
512 $penalty->org_unit($ctx_org);
513 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
515 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
534 "standing_penalties",
542 push @$fields, "home_ou" if $home_ou;
543 return new_flesh_user($id, $fields, $e );
551 # clone and clear stuff that would break the database
555 my $new_patron = $patron->clone;
557 $new_patron->clear_billing_address();
558 $new_patron->clear_mailing_address();
559 $new_patron->clear_addresses();
560 $new_patron->clear_card();
561 $new_patron->clear_cards();
562 $new_patron->clear_id();
563 $new_patron->clear_isnew();
564 $new_patron->clear_ischanged();
565 $new_patron->clear_isdeleted();
566 $new_patron->clear_stat_cat_entries();
567 $new_patron->clear_permissions();
568 $new_patron->clear_standing_penalties();
578 my $user_obj = shift;
580 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
581 return (undef, $evt) if $evt;
583 my $ex = $session->request(
584 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
586 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
589 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
591 my $id = $session->request(
592 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
593 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
595 $logger->info("Successfully created new user [$id] in DB");
597 return ( $session->request(
598 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
602 sub check_group_perm {
603 my( $session, $requestor, $patron ) = @_;
606 # first let's see if the requestor has
607 # priveleges to update this user in any way
608 if( ! $patron->isnew ) {
609 my $p = $session->request(
610 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
612 # If we are the requestor (trying to update our own account)
613 # and we are not trying to change our profile, we're good
614 if( $p->id == $requestor->id and
615 $p->profile == $patron->profile ) {
620 $evt = group_perm_failed($session, $requestor, $p);
624 # They are allowed to edit this patron.. can they put the
625 # patron into the group requested?
626 $evt = group_perm_failed($session, $requestor, $patron);
632 sub group_perm_failed {
633 my( $session, $requestor, $patron ) = @_;
637 my $grpid = $patron->profile;
641 $logger->debug("user update looking for group perm for group $grpid");
642 $grp = $session->request(
643 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
644 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
646 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
648 $logger->info("user update checking perm $perm on user ".
649 $requestor->id." for update/create on user username=".$patron->usrname);
651 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
659 my( $session, $patron, $user_obj, $noperm) = @_;
661 $logger->info("Updating patron ".$patron->id." in DB");
666 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
667 return (undef, $evt) if $evt;
670 # update the password by itself to avoid the password protection magic
671 if( $patron->passwd ) {
672 my $s = $session->request(
673 'open-ils.storage.direct.actor.user.remote_update',
674 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
675 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
676 $patron->clear_passwd;
679 if(!$patron->ident_type) {
680 $patron->clear_ident_type;
681 $patron->clear_ident_value;
684 $evt = verify_last_xact($session, $patron);
685 return (undef, $evt) if $evt;
687 my $stat = $session->request(
688 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
689 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
694 sub verify_last_xact {
695 my( $session, $patron ) = @_;
696 return undef unless $patron->id and $patron->id > 0;
697 my $p = $session->request(
698 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
699 my $xact = $p->last_xact_id;
700 return undef unless $xact;
701 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
702 return OpenILS::Event->new('XACT_COLLISION')
703 if $xact ne $patron->last_xact_id;
708 sub _check_dup_ident {
709 my( $session, $patron ) = @_;
711 return undef unless $patron->ident_value;
714 ident_type => $patron->ident_type,
715 ident_value => $patron->ident_value,
718 $logger->debug("patron update searching for dup ident values: " .
719 $patron->ident_type . ':' . $patron->ident_value);
721 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
723 my $dups = $session->request(
724 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
727 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
734 sub _add_update_addresses {
738 my $new_patron = shift;
742 my $current_id; # id of the address before creation
744 my $addresses = $patron->addresses();
746 for my $address (@$addresses) {
748 next unless ref $address;
749 $current_id = $address->id();
751 if( $patron->billing_address() and
752 $patron->billing_address() == $current_id ) {
753 $logger->info("setting billing addr to $current_id");
754 $new_patron->billing_address($address->id());
755 $new_patron->ischanged(1);
758 if( $patron->mailing_address() and
759 $patron->mailing_address() == $current_id ) {
760 $new_patron->mailing_address($address->id());
761 $logger->info("setting mailing addr to $current_id");
762 $new_patron->ischanged(1);
766 if($address->isnew()) {
768 $address->usr($new_patron->id());
770 ($address, $evt) = _add_address($session,$address);
771 return (undef, $evt) if $evt;
773 # we need to get the new id
774 if( $patron->billing_address() and
775 $patron->billing_address() == $current_id ) {
776 $new_patron->billing_address($address->id());
777 $logger->info("setting billing addr to $current_id");
778 $new_patron->ischanged(1);
781 if( $patron->mailing_address() and
782 $patron->mailing_address() == $current_id ) {
783 $new_patron->mailing_address($address->id());
784 $logger->info("setting mailing addr to $current_id");
785 $new_patron->ischanged(1);
788 } elsif($address->ischanged() ) {
790 ($address, $evt) = _update_address($session, $address);
791 return (undef, $evt) if $evt;
793 } elsif($address->isdeleted() ) {
795 if( $address->id() == $new_patron->mailing_address() ) {
796 $new_patron->clear_mailing_address();
797 ($new_patron, $evt) = _update_patron($session, $new_patron);
798 return (undef, $evt) if $evt;
801 if( $address->id() == $new_patron->billing_address() ) {
802 $new_patron->clear_billing_address();
803 ($new_patron, $evt) = _update_patron($session, $new_patron);
804 return (undef, $evt) if $evt;
807 $evt = _delete_address($session, $address);
808 return (undef, $evt) if $evt;
812 return ( $new_patron, undef );
816 # adds an address to the db and returns the address with new id
818 my($session, $address) = @_;
819 $address->clear_id();
821 $logger->info("Creating new address at street ".$address->street1);
823 # put the address into the database
824 my $id = $session->request(
825 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
826 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
829 return ($address, undef);
833 sub _update_address {
834 my( $session, $address ) = @_;
836 $logger->info("Updating address ".$address->id." in the DB");
838 my $stat = $session->request(
839 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
841 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
842 return ($address, undef);
847 sub _add_update_cards {
851 my $new_patron = shift;
855 my $virtual_id; #id of the card before creation
857 my $cards = $patron->cards();
858 for my $card (@$cards) {
860 $card->usr($new_patron->id());
862 if(ref($card) and $card->isnew()) {
864 $virtual_id = $card->id();
865 ( $card, $evt ) = _add_card($session,$card);
866 return (undef, $evt) if $evt;
868 #if(ref($patron->card)) { $patron->card($patron->card->id); }
869 if($patron->card() == $virtual_id) {
870 $new_patron->card($card->id());
871 $new_patron->ischanged(1);
874 } elsif( ref($card) and $card->ischanged() ) {
875 $evt = _update_card($session, $card);
876 return (undef, $evt) if $evt;
880 return ( $new_patron, undef );
884 # adds an card to the db and returns the card with new id
886 my( $session, $card ) = @_;
889 $logger->info("Adding new patron card ".$card->barcode);
891 my $id = $session->request(
892 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
893 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
894 $logger->info("Successfully created patron card $id");
897 return ( $card, undef );
901 # returns event on error. returns undef otherwise
903 my( $session, $card ) = @_;
904 $logger->info("Updating patron card ".$card->id);
906 my $stat = $session->request(
907 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
908 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
915 # returns event on error. returns undef otherwise
916 sub _delete_address {
917 my( $session, $address ) = @_;
919 $logger->info("Deleting address ".$address->id." from DB");
921 my $stat = $session->request(
922 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
924 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
930 sub _add_survey_responses {
931 my ($session, $patron, $new_patron) = @_;
933 $logger->info( "Updating survey responses for patron ".$new_patron->id );
935 my $responses = $patron->survey_responses;
939 $_->usr($new_patron->id) for (@$responses);
941 my $evt = $U->simplereq( "open-ils.circ",
942 "open-ils.circ.survey.submit.user_id", $responses );
944 return (undef, $evt) if defined($U->event_code($evt));
948 return ( $new_patron, undef );
951 sub _clear_badcontact_penalties {
952 my ($session, $old_patron, $new_patron, $user_obj) = @_;
954 return ($new_patron, undef) unless $old_patron;
956 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
957 my $e = new_editor(xact => 1);
959 # This ignores whether the caller of update_patron has any permission
960 # to remove penalties, but these penalties no longer make sense
961 # if an email address field (for example) is changed (and the caller must
962 # have perms to do *that*) so there's no reason not to clear the penalties.
964 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
966 "+csp" => {"name" => [values(%$PNM)]},
967 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
969 "join" => {"csp" => {}},
971 "flesh_fields" => {"ausp" => ["standing_penalty"]}
973 ]) or return (undef, $e->die_event);
975 return ($new_patron, undef) unless @$bad_contact_penalties;
977 my @penalties_to_clear;
978 my ($field, $penalty_name);
980 # For each field that might have an associated bad contact penalty,
981 # check for such penalties and add them to the to-clear list if that
983 while (($field, $penalty_name) = each(%$PNM)) {
984 if ($old_patron->$field ne $new_patron->$field) {
985 push @penalties_to_clear, grep {
986 $_->standing_penalty->name eq $penalty_name
987 } @$bad_contact_penalties;
991 foreach (@penalties_to_clear) {
992 # Note that this "archives" penalties, in the terminology of the staff
993 # client, instead of just deleting them. This may assist reporting,
994 # or preserving old contact information when it is still potentially
996 $_->standing_penalty($_->standing_penalty->id); # deflesh
997 $_->stop_date('now');
998 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1002 return ($new_patron, undef);
1006 sub _create_stat_maps {
1008 my($session, $user_session, $patron, $new_patron) = @_;
1010 my $maps = $patron->stat_cat_entries();
1012 for my $map (@$maps) {
1014 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
1016 if ($map->isdeleted()) {
1017 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
1019 } elsif ($map->isnew()) {
1020 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
1025 $map->target_usr($new_patron->id);
1028 $logger->info("Updating stat entry with method $method and map $map");
1030 my $stat = $session->request($method, $map)->gather(1);
1031 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1035 return ($new_patron, undef);
1038 sub _create_perm_maps {
1040 my($session, $user_session, $patron, $new_patron) = @_;
1042 my $maps = $patron->permissions;
1044 for my $map (@$maps) {
1046 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1047 if ($map->isdeleted()) {
1048 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1049 } elsif ($map->isnew()) {
1050 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1055 $map->usr($new_patron->id);
1057 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1058 $logger->info( "Updating permissions with method $method and map $map" );
1060 my $stat = $session->request($method, $map)->gather(1);
1061 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1065 return ($new_patron, undef);
1069 __PACKAGE__->register_method(
1070 method => "set_user_work_ous",
1071 api_name => "open-ils.actor.user.work_ous.update",
1074 sub set_user_work_ous {
1080 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1081 return $evt if $evt;
1083 my $session = $apputils->start_db_session();
1084 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1086 for my $map (@$maps) {
1088 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1089 if ($map->isdeleted()) {
1090 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1091 } elsif ($map->isnew()) {
1092 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1096 #warn( "Updating permissions with method $method and session $ses and map $map" );
1097 $logger->info( "Updating work_ou map with method $method and map $map" );
1099 my $stat = $session->request($method, $map)->gather(1);
1100 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1104 $apputils->commit_db_session($session);
1106 return scalar(@$maps);
1110 __PACKAGE__->register_method(
1111 method => "set_user_perms",
1112 api_name => "open-ils.actor.user.permissions.update",
1115 sub set_user_perms {
1121 my $session = $apputils->start_db_session();
1123 my( $user_obj, $evt ) = $U->checkses($ses);
1124 return $evt if $evt;
1125 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1127 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1130 $all = 1 if ($U->is_true($user_obj->super_user()));
1131 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1133 for my $map (@$maps) {
1135 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1136 if ($map->isdeleted()) {
1137 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1138 } elsif ($map->isnew()) {
1139 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1143 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1144 #warn( "Updating permissions with method $method and session $ses and map $map" );
1145 $logger->info( "Updating permissions with method $method and map $map" );
1147 my $stat = $session->request($method, $map)->gather(1);
1148 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1152 $apputils->commit_db_session($session);
1154 return scalar(@$maps);
1158 __PACKAGE__->register_method(
1159 method => "user_retrieve_by_barcode",
1161 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1163 sub user_retrieve_by_barcode {
1164 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1166 my $e = new_editor(authtoken => $auth);
1167 return $e->event unless $e->checkauth;
1169 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1170 or return $e->event;
1172 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1173 return $e->event unless $e->allowed(
1174 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1181 __PACKAGE__->register_method(
1182 method => "get_user_by_id",
1184 api_name => "open-ils.actor.user.retrieve",
1187 sub get_user_by_id {
1188 my ($self, $client, $auth, $id) = @_;
1189 my $e = new_editor(authtoken=>$auth);
1190 return $e->event unless $e->checkauth;
1191 my $user = $e->retrieve_actor_user($id) or return $e->event;
1192 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1197 __PACKAGE__->register_method(
1198 method => "get_org_types",
1199 api_name => "open-ils.actor.org_types.retrieve",
1202 return $U->get_org_types();
1206 __PACKAGE__->register_method(
1207 method => "get_user_ident_types",
1208 api_name => "open-ils.actor.user.ident_types.retrieve",
1211 sub get_user_ident_types {
1212 return $ident_types if $ident_types;
1213 return $ident_types =
1214 new_editor()->retrieve_all_config_identification_type();
1218 __PACKAGE__->register_method(
1219 method => "get_org_unit",
1220 api_name => "open-ils.actor.org_unit.retrieve",
1224 my( $self, $client, $user_session, $org_id ) = @_;
1225 my $e = new_editor(authtoken => $user_session);
1227 return $e->event unless $e->checkauth;
1228 $org_id = $e->requestor->ws_ou;
1230 my $o = $e->retrieve_actor_org_unit($org_id)
1231 or return $e->event;
1235 __PACKAGE__->register_method(
1236 method => "search_org_unit",
1237 api_name => "open-ils.actor.org_unit_list.search",
1240 sub search_org_unit {
1242 my( $self, $client, $field, $value ) = @_;
1244 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1246 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1247 { $field => $value } );
1253 # build the org tree
1255 __PACKAGE__->register_method(
1256 method => "get_org_tree",
1257 api_name => "open-ils.actor.org_tree.retrieve",
1259 note => "Returns the entire org tree structure",
1265 return $U->get_org_tree($client->session->session_locale);
1269 __PACKAGE__->register_method(
1270 method => "get_org_descendants",
1271 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1274 # depth is optional. org_unit is the id
1275 sub get_org_descendants {
1276 my( $self, $client, $org_unit, $depth ) = @_;
1278 if(ref $org_unit eq 'ARRAY') {
1281 for my $i (0..scalar(@$org_unit)-1) {
1282 my $list = $U->simple_scalar_request(
1284 "open-ils.storage.actor.org_unit.descendants.atomic",
1285 $org_unit->[$i], $depth->[$i] );
1286 push(@trees, $U->build_org_tree($list));
1291 my $orglist = $apputils->simple_scalar_request(
1293 "open-ils.storage.actor.org_unit.descendants.atomic",
1294 $org_unit, $depth );
1295 return $U->build_org_tree($orglist);
1300 __PACKAGE__->register_method(
1301 method => "get_org_ancestors",
1302 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1305 # depth is optional. org_unit is the id
1306 sub get_org_ancestors {
1307 my( $self, $client, $org_unit, $depth ) = @_;
1308 my $orglist = $apputils->simple_scalar_request(
1310 "open-ils.storage.actor.org_unit.ancestors.atomic",
1311 $org_unit, $depth );
1312 return $U->build_org_tree($orglist);
1316 __PACKAGE__->register_method(
1317 method => "get_standings",
1318 api_name => "open-ils.actor.standings.retrieve"
1323 return $user_standings if $user_standings;
1324 return $user_standings =
1325 $apputils->simple_scalar_request(
1327 "open-ils.cstore.direct.config.standing.search.atomic",
1328 { id => { "!=" => undef } }
1333 __PACKAGE__->register_method(
1334 method => "get_my_org_path",
1335 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1338 sub get_my_org_path {
1339 my( $self, $client, $auth, $org_id ) = @_;
1340 my $e = new_editor(authtoken=>$auth);
1341 return $e->event unless $e->checkauth;
1342 $org_id = $e->requestor->ws_ou unless defined $org_id;
1344 return $apputils->simple_scalar_request(
1346 "open-ils.storage.actor.org_unit.full_path.atomic",
1351 __PACKAGE__->register_method(
1352 method => "patron_adv_search",
1353 api_name => "open-ils.actor.patron.search.advanced"
1356 __PACKAGE__->register_method(
1357 method => "patron_adv_search",
1358 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1360 # TODO: change when opensrf 'bundling' is merged.
1361 # set a relatively small bundle size so the caller can start
1362 # seeing results fairly quickly
1363 max_chunk_size => 4096, # bundling
1366 # pending opensrf work -- also, not sure if needed since we're not
1367 # actaully creating an alternate vesrion, only offering to return a
1371 desc => q/Returns a stream of fleshed user objects instead of
1372 a pile of identifiers/
1376 sub patron_adv_search {
1377 my( $self, $client, $auth, $search_hash, $search_limit,
1378 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1380 # API params sanity checks.
1381 # Exit early with empty result if no filter exists.
1382 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1383 my $fleshed = ($self->api_name =~ /fleshed/);
1384 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1386 for my $key (keys %$search_hash) {
1387 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1391 return ($fleshed ? undef : []) unless $search_ok;
1393 my $e = new_editor(authtoken=>$auth);
1394 return $e->event unless $e->checkauth;
1395 return $e->event unless $e->allowed('VIEW_USER');
1397 # depth boundary outside of which patrons must opt-in, default to 0
1398 my $opt_boundary = 0;
1399 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1401 if (not defined $search_ou) {
1402 my $depth = $U->ou_ancestor_setting_value(
1403 $e->requestor->ws_ou,
1404 'circ.patron_edit.duplicate_patron_check_depth'
1407 if (defined $depth) {
1408 $search_ou = $U->org_unit_ancestor_at_depth(
1409 $e->requestor->ws_ou, $depth
1414 my $ids = $U->storagereq(
1415 "open-ils.storage.actor.user.crazy_search", $search_hash,
1416 $search_limit, $search_sort, $include_inactive,
1417 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1419 return $ids unless $self->api_name =~ /fleshed/;
1421 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1427 __PACKAGE__->register_method(
1428 method => "update_passwd",
1429 api_name => "open-ils.actor.user.password.update",
1431 desc => "Update the operator's password",
1433 { desc => 'Authentication token', type => 'string' },
1434 { desc => 'New password', type => 'string' },
1435 { desc => 'Current password', type => 'string' }
1437 return => {desc => '1 on success, Event on error or incorrect current password'}
1441 __PACKAGE__->register_method(
1442 method => "update_passwd",
1443 api_name => "open-ils.actor.user.username.update",
1445 desc => "Update the operator's username",
1447 { desc => 'Authentication token', type => 'string' },
1448 { desc => 'New username', type => 'string' },
1449 { desc => 'Current password', type => 'string' }
1451 return => {desc => '1 on success, Event on error or incorrect current password'}
1455 __PACKAGE__->register_method(
1456 method => "update_passwd",
1457 api_name => "open-ils.actor.user.email.update",
1459 desc => "Update the operator's email address",
1461 { desc => 'Authentication token', type => 'string' },
1462 { desc => 'New email address', type => 'string' },
1463 { desc => 'Current password', type => 'string' }
1465 return => {desc => '1 on success, Event on error or incorrect current password'}
1470 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1471 my $e = new_editor(xact=>1, authtoken=>$auth);
1472 return $e->die_event unless $e->checkauth;
1474 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1475 or return $e->die_event;
1476 my $api = $self->api_name;
1478 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1480 return new OpenILS::Event('INCORRECT_PASSWORD');
1483 if( $api =~ /password/o ) {
1484 # NOTE: with access to the plain text password we could crypt
1485 # the password without the extra MD5 pre-hashing. Other changes
1486 # would be required. Noting here for future reference.
1488 # new password gets a new salt
1489 my $new_salt = $e->json_query({
1490 from => ['actor.create_salt', 'main']})->[0];
1491 $new_salt = $new_salt->{'actor.create_salt'};
1498 md5_hex($new_salt . md5_hex($new_val)),
1503 $db_user->passwd('');
1507 # if we don't clear the password, the user will be updated with
1508 # a hashed version of the hashed version of their password
1509 $db_user->clear_passwd;
1511 if( $api =~ /username/o ) {
1513 # make sure no one else has this username
1514 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1517 return new OpenILS::Event('USERNAME_EXISTS');
1519 $db_user->usrname($new_val);
1521 } elsif( $api =~ /email/o ) {
1522 $db_user->email($new_val);
1526 $e->update_actor_user($db_user) or return $e->die_event;
1529 # update the cached user to pick up these changes
1530 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1536 __PACKAGE__->register_method(
1537 method => "check_user_perms",
1538 api_name => "open-ils.actor.user.perm.check",
1539 notes => <<" NOTES");
1540 Takes a login session, user id, an org id, and an array of perm type strings. For each
1541 perm type, if the user does *not* have the given permission it is added
1542 to a list which is returned from the method. If all permissions
1543 are allowed, an empty list is returned
1544 if the logged in user does not match 'user_id', then the logged in user must
1545 have VIEW_PERMISSION priveleges.
1548 sub check_user_perms {
1549 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1551 my( $staff, $evt ) = $apputils->checkses($login_session);
1552 return $evt if $evt;
1554 if($staff->id ne $user_id) {
1555 if( $evt = $apputils->check_perms(
1556 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1562 for my $perm (@$perm_types) {
1563 if($apputils->check_perms($user_id, $org_id, $perm)) {
1564 push @not_allowed, $perm;
1568 return \@not_allowed
1571 __PACKAGE__->register_method(
1572 method => "check_user_perms2",
1573 api_name => "open-ils.actor.user.perm.check.multi_org",
1575 Checks the permissions on a list of perms and orgs for a user
1576 @param authtoken The login session key
1577 @param user_id The id of the user to check
1578 @param orgs The array of org ids
1579 @param perms The array of permission names
1580 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1581 if the logged in user does not match 'user_id', then the logged in user must
1582 have VIEW_PERMISSION priveleges.
1585 sub check_user_perms2 {
1586 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1588 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1589 $authtoken, $user_id, 'VIEW_PERMISSION' );
1590 return $evt if $evt;
1593 for my $org (@$orgs) {
1594 for my $perm (@$perms) {
1595 if($apputils->check_perms($user_id, $org, $perm)) {
1596 push @not_allowed, [ $org, $perm ];
1601 return \@not_allowed
1605 __PACKAGE__->register_method(
1606 method => 'check_user_perms3',
1607 api_name => 'open-ils.actor.user.perm.highest_org',
1609 Returns the highest org unit id at which a user has a given permission
1610 If the requestor does not match the target user, the requestor must have
1611 'VIEW_PERMISSION' rights at the home org unit of the target user
1612 @param authtoken The login session key
1613 @param userid The id of the user in question
1614 @param perm The permission to check
1615 @return The org unit highest in the org tree within which the user has
1616 the requested permission
1619 sub check_user_perms3 {
1620 my($self, $client, $authtoken, $user_id, $perm) = @_;
1621 my $e = new_editor(authtoken=>$authtoken);
1622 return $e->event unless $e->checkauth;
1624 my $tree = $U->get_org_tree();
1626 unless($e->requestor->id == $user_id) {
1627 my $user = $e->retrieve_actor_user($user_id)
1628 or return $e->event;
1629 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1630 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1633 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1636 __PACKAGE__->register_method(
1637 method => 'user_has_work_perm_at',
1638 api_name => 'open-ils.actor.user.has_work_perm_at',
1642 Returns a set of org unit IDs which represent the highest orgs in
1643 the org tree where the user has the requested permission. The
1644 purpose of this method is to return the smallest set of org units
1645 which represent the full expanse of the user's ability to perform
1646 the requested action. The user whose perms this method should
1647 check is implied by the authtoken. /,
1649 {desc => 'authtoken', type => 'string'},
1650 {desc => 'permission name', type => 'string'},
1651 {desc => q/user id, optional. If present, check perms for
1652 this user instead of the logged in user/, type => 'number'},
1654 return => {desc => 'An array of org IDs'}
1658 sub user_has_work_perm_at {
1659 my($self, $conn, $auth, $perm, $user_id) = @_;
1660 my $e = new_editor(authtoken=>$auth);
1661 return $e->event unless $e->checkauth;
1662 if(defined $user_id) {
1663 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1664 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1666 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1669 __PACKAGE__->register_method(
1670 method => 'user_has_work_perm_at_batch',
1671 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1675 sub user_has_work_perm_at_batch {
1676 my($self, $conn, $auth, $perms, $user_id) = @_;
1677 my $e = new_editor(authtoken=>$auth);
1678 return $e->event unless $e->checkauth;
1679 if(defined $user_id) {
1680 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1681 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1684 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1690 __PACKAGE__->register_method(
1691 method => 'check_user_perms4',
1692 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1694 Returns the highest org unit id at which a user has a given permission
1695 If the requestor does not match the target user, the requestor must have
1696 'VIEW_PERMISSION' rights at the home org unit of the target user
1697 @param authtoken The login session key
1698 @param userid The id of the user in question
1699 @param perms An array of perm names to check
1700 @return An array of orgId's representing the org unit
1701 highest in the org tree within which the user has the requested permission
1702 The arrah of orgId's has matches the order of the perms array
1705 sub check_user_perms4 {
1706 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1708 my( $staff, $target, $org, $evt );
1710 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1711 $authtoken, $userid, 'VIEW_PERMISSION' );
1712 return $evt if $evt;
1715 return [] unless ref($perms);
1716 my $tree = $U->get_org_tree();
1718 for my $p (@$perms) {
1719 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1725 __PACKAGE__->register_method(
1726 method => "user_fines_summary",
1727 api_name => "open-ils.actor.user.fines.summary",
1730 desc => 'Returns a short summary of the users total open fines, ' .
1731 'excluding voided fines Params are login_session, user_id' ,
1733 {desc => 'Authentication token', type => 'string'},
1734 {desc => 'User ID', type => 'string'} # number?
1737 desc => "a 'mous' object, event on error",
1742 sub user_fines_summary {
1743 my( $self, $client, $auth, $user_id ) = @_;
1745 my $e = new_editor(authtoken=>$auth);
1746 return $e->event unless $e->checkauth;
1748 if( $user_id ne $e->requestor->id ) {
1749 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1750 return $e->event unless
1751 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1754 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1758 __PACKAGE__->register_method(
1759 method => "user_opac_vitals",
1760 api_name => "open-ils.actor.user.opac.vital_stats",
1764 desc => 'Returns a short summary of the users vital stats, including ' .
1765 'identification information, accumulated balance, number of holds, ' .
1766 'and current open circulation stats' ,
1768 {desc => 'Authentication token', type => 'string'},
1769 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1772 desc => "An object with four properties: user, fines, checkouts and holds."
1777 sub user_opac_vitals {
1778 my( $self, $client, $auth, $user_id ) = @_;
1780 my $e = new_editor(authtoken=>$auth);
1781 return $e->event unless $e->checkauth;
1783 $user_id ||= $e->requestor->id;
1785 my $user = $e->retrieve_actor_user( $user_id );
1788 ->method_lookup('open-ils.actor.user.fines.summary')
1789 ->run($auth => $user_id);
1790 return $fines if (defined($U->event_code($fines)));
1793 $fines = new Fieldmapper::money::open_user_summary ();
1794 $fines->balance_owed(0.00);
1795 $fines->total_owed(0.00);
1796 $fines->total_paid(0.00);
1797 $fines->usr($user_id);
1801 ->method_lookup('open-ils.actor.user.hold_requests.count')
1802 ->run($auth => $user_id);
1803 return $holds if (defined($U->event_code($holds)));
1806 ->method_lookup('open-ils.actor.user.checked_out.count')
1807 ->run($auth => $user_id);
1808 return $out if (defined($U->event_code($out)));
1810 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1812 my $unread_msgs = $e->search_actor_usr_message([
1813 {usr => $user_id, read_date => undef, deleted => 'f'},
1819 first_given_name => $user->first_given_name,
1820 second_given_name => $user->second_given_name,
1821 family_name => $user->family_name,
1822 alias => $user->alias,
1823 usrname => $user->usrname
1825 fines => $fines->to_bare_hash,
1828 messages => { unread => scalar(@$unread_msgs) }
1833 ##### a small consolidation of related method registrations
1834 my $common_params = [
1835 { desc => 'Authentication token', type => 'string' },
1836 { desc => 'User ID', type => 'string' },
1837 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1838 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1841 'open-ils.actor.user.transactions' => '',
1842 'open-ils.actor.user.transactions.fleshed' => '',
1843 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1844 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1845 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1846 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1849 foreach (keys %methods) {
1851 method => "user_transactions",
1854 desc => 'For a given user, retrieve a list of '
1855 . (/\.fleshed/ ? 'fleshed ' : '')
1856 . 'transactions' . $methods{$_}
1857 . ' optionally limited to transactions of a given type.',
1858 params => $common_params,
1860 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1861 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1865 $args{authoritative} = 1;
1866 __PACKAGE__->register_method(%args);
1869 # Now for the counts
1871 'open-ils.actor.user.transactions.count' => '',
1872 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1873 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1876 foreach (keys %methods) {
1878 method => "user_transactions",
1881 desc => 'For a given user, retrieve a count of open '
1882 . 'transactions' . $methods{$_}
1883 . ' optionally limited to transactions of a given type.',
1884 params => $common_params,
1885 return => { desc => "Integer count of transactions, or event on error" }
1888 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1889 __PACKAGE__->register_method(%args);
1892 __PACKAGE__->register_method(
1893 method => "user_transactions",
1894 api_name => "open-ils.actor.user.transactions.have_balance.total",
1897 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1898 . ' optionally limited to transactions of a given type.',
1899 params => $common_params,
1900 return => { desc => "Decimal balance value, or event on error" }
1905 sub user_transactions {
1906 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1909 my $e = new_editor(authtoken => $auth);
1910 return $e->event unless $e->checkauth;
1912 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1914 return $e->event unless
1915 $e->requestor->id == $user_id or
1916 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1918 my $api = $self->api_name();
1920 my $filter = ($api =~ /have_balance/o) ?
1921 { 'balance_owed' => { '<>' => 0 } }:
1922 { 'total_owed' => { '>' => 0 } };
1924 my $method = 'open-ils.actor.user.transactions.history.still_open';
1925 $method = "$method.authoritative" if $api =~ /authoritative/;
1926 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1928 if($api =~ /total/o) {
1930 $total += $_->balance_owed for @$trans;
1934 ($api =~ /count/o ) and return scalar @$trans;
1935 ($api !~ /fleshed/o) and return $trans;
1938 for my $t (@$trans) {
1940 if( $t->xact_type ne 'circulation' ) {
1941 push @resp, {transaction => $t};
1945 my $circ_data = flesh_circ($e, $t->id);
1946 push @resp, {transaction => $t, %$circ_data};
1953 __PACKAGE__->register_method(
1954 method => "user_transaction_retrieve",
1955 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1958 notes => "Returns a fleshed transaction record"
1961 __PACKAGE__->register_method(
1962 method => "user_transaction_retrieve",
1963 api_name => "open-ils.actor.user.transaction.retrieve",
1966 notes => "Returns a transaction record"
1969 sub user_transaction_retrieve {
1970 my($self, $client, $auth, $bill_id) = @_;
1972 my $e = new_editor(authtoken => $auth);
1973 return $e->event unless $e->checkauth;
1975 my $trans = $e->retrieve_money_billable_transaction_summary(
1976 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1978 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1980 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1982 return $trans unless $self->api_name =~ /flesh/;
1983 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1985 my $circ_data = flesh_circ($e, $trans->id, 1);
1987 return {transaction => $trans, %$circ_data};
1992 my $circ_id = shift;
1993 my $flesh_copy = shift;
1995 my $circ = $e->retrieve_action_circulation([
1999 circ => ['target_copy'],
2000 acp => ['call_number'],
2007 my $copy = $circ->target_copy;
2009 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2010 $mods = new Fieldmapper::metabib::virtual_record;
2011 $mods->doc_id(OILS_PRECAT_RECORD);
2012 $mods->title($copy->dummy_title);
2013 $mods->author($copy->dummy_author);
2016 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2020 $circ->target_copy($circ->target_copy->id);
2021 $copy->call_number($copy->call_number->id);
2023 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2027 __PACKAGE__->register_method(
2028 method => "hold_request_count",
2029 api_name => "open-ils.actor.user.hold_requests.count",
2033 Returns hold ready vs. total counts.
2034 If a context org unit is provided, a third value
2035 is returned with key 'behind_desk', which reports
2036 how many holds are ready at the pickup library
2037 with the behind_desk flag set to true.
2041 sub hold_request_count {
2042 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2043 my $e = new_editor(authtoken => $authtoken);
2044 return $e->event unless $e->checkauth;
2046 $user_id = $e->requestor->id unless defined $user_id;
2048 if($e->requestor->id ne $user_id) {
2049 my $user = $e->retrieve_actor_user($user_id);
2050 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2053 my $holds = $e->json_query({
2054 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2058 fulfillment_time => {"=" => undef },
2059 cancel_time => undef,
2064 $_->{current_shelf_lib} and # avoid undef warnings
2065 $_->{pickup_lib} eq $_->{current_shelf_lib}
2069 total => scalar(@$holds),
2070 ready => scalar(@ready)
2074 # count of holds ready at pickup lib with behind_desk true.
2075 $resp->{behind_desk} = scalar(
2077 $_->{pickup_lib} == $ctx_org and
2078 $U->is_true($_->{behind_desk})
2086 __PACKAGE__->register_method(
2087 method => "checked_out",
2088 api_name => "open-ils.actor.user.checked_out",
2092 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2093 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2094 . "(i.e., outstanding balance or some other pending action on the circ). "
2095 . "The .count method also includes a 'total' field which sums all open circs.",
2097 { desc => 'Authentication Token', type => 'string'},
2098 { desc => 'User ID', type => 'string'},
2101 desc => 'Returns event on error, or an object with ID lists, like: '
2102 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2107 __PACKAGE__->register_method(
2108 method => "checked_out",
2109 api_name => "open-ils.actor.user.checked_out.count",
2112 signature => q/@see open-ils.actor.user.checked_out/
2116 my( $self, $conn, $auth, $userid ) = @_;
2118 my $e = new_editor(authtoken=>$auth);
2119 return $e->event unless $e->checkauth;
2121 if( $userid ne $e->requestor->id ) {
2122 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2123 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2125 # see if there is a friend link allowing circ.view perms
2126 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2127 $e, $userid, $e->requestor->id, 'circ.view');
2128 return $e->event unless $allowed;
2132 my $count = $self->api_name =~ /count/;
2133 return _checked_out( $count, $e, $userid );
2137 my( $iscount, $e, $userid ) = @_;
2143 claims_returned => [],
2146 my $meth = 'retrieve_action_open_circ_';
2154 claims_returned => 0,
2161 my $data = $e->$meth($userid);
2165 $result{$_} += $data->$_() for (keys %result);
2166 $result{total} += $data->$_() for (keys %result);
2168 for my $k (keys %result) {
2169 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2179 __PACKAGE__->register_method(
2180 method => "checked_in_with_fines",
2181 api_name => "open-ils.actor.user.checked_in_with_fines",
2184 signature => q/@see open-ils.actor.user.checked_out/
2187 sub checked_in_with_fines {
2188 my( $self, $conn, $auth, $userid ) = @_;
2190 my $e = new_editor(authtoken=>$auth);
2191 return $e->event unless $e->checkauth;
2193 if( $userid ne $e->requestor->id ) {
2194 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2197 # money is owed on these items and they are checked in
2198 my $open = $e->search_action_circulation(
2201 xact_finish => undef,
2202 checkin_time => { "!=" => undef },
2207 my( @lost, @cr, @lo );
2208 for my $c (@$open) {
2209 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2210 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2211 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2216 claims_returned => \@cr,
2217 long_overdue => \@lo
2223 my ($api, $desc, $auth) = @_;
2224 $desc = $desc ? (" " . $desc) : '';
2225 my $ids = ($api =~ /ids$/) ? 1 : 0;
2228 method => "user_transaction_history",
2229 api_name => "open-ils.actor.user.transactions.$api",
2231 desc => "For a given User ID, returns a list of billable transaction" .
2232 ($ids ? " id" : '') .
2233 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2234 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2236 {desc => 'Authentication token', type => 'string'},
2237 {desc => 'User ID', type => 'number'},
2238 {desc => 'Transaction type (optional)', type => 'number'},
2239 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2242 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2246 $auth and push @sig, (authoritative => 1);
2250 my %auth_hist_methods = (
2252 'history.have_charge' => 'that have an initial charge',
2253 'history.still_open' => 'that are not finished',
2254 'history.have_balance' => 'that have a balance',
2255 'history.have_bill' => 'that have billings',
2256 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2257 'history.have_payment' => 'that have at least 1 payment',
2260 foreach (keys %auth_hist_methods) {
2261 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2262 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2263 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2266 sub user_transaction_history {
2267 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2271 my $e = new_editor(authtoken=>$auth);
2272 return $e->die_event unless $e->checkauth;
2274 if ($e->requestor->id ne $userid) {
2275 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2278 my $api = $self->api_name;
2279 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2281 if(defined($type)) {
2282 $filter->{'xact_type'} = $type;
2285 if($api =~ /have_bill_or_payment/o) {
2287 # transactions that have a non-zero sum across all billings or at least 1 payment
2288 $filter->{'-or'} = {
2289 'balance_owed' => { '<>' => 0 },
2290 'last_payment_ts' => { '<>' => undef }
2293 } elsif($api =~ /have_payment/) {
2295 $filter->{last_payment_ts} ||= {'<>' => undef};
2297 } elsif( $api =~ /have_balance/o) {
2299 # transactions that have a non-zero overall balance
2300 $filter->{'balance_owed'} = { '<>' => 0 };
2302 } elsif( $api =~ /have_charge/o) {
2304 # transactions that have at least 1 billing, regardless of whether it was voided
2305 $filter->{'last_billing_ts'} = { '<>' => undef };
2307 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2309 # transactions that have non-zero sum across all billings. This will exclude
2310 # xacts where all billings have been voided
2311 $filter->{'total_owed'} = { '<>' => 0 };
2314 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2315 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2316 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2318 my $mbts = $e->search_money_billable_transaction_summary(
2319 [ { usr => $userid, @xact_finish, %$filter },
2324 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2325 return $mbts unless $api =~ /fleshed/;
2328 for my $t (@$mbts) {
2330 if( $t->xact_type ne 'circulation' ) {
2331 push @resp, {transaction => $t};
2335 my $circ_data = flesh_circ($e, $t->id);
2336 push @resp, {transaction => $t, %$circ_data};
2344 __PACKAGE__->register_method(
2345 method => "user_perms",
2346 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2348 notes => "Returns a list of permissions"
2352 my( $self, $client, $authtoken, $user ) = @_;
2354 my( $staff, $evt ) = $apputils->checkses($authtoken);
2355 return $evt if $evt;
2357 $user ||= $staff->id;
2359 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2363 return $apputils->simple_scalar_request(
2365 "open-ils.storage.permission.user_perms.atomic",
2369 __PACKAGE__->register_method(
2370 method => "retrieve_perms",
2371 api_name => "open-ils.actor.permissions.retrieve",
2372 notes => "Returns a list of permissions"
2374 sub retrieve_perms {
2375 my( $self, $client ) = @_;
2376 return $apputils->simple_scalar_request(
2378 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2379 { id => { '!=' => undef } }
2383 __PACKAGE__->register_method(
2384 method => "retrieve_groups",
2385 api_name => "open-ils.actor.groups.retrieve",
2386 notes => "Returns a list of user groups"
2388 sub retrieve_groups {
2389 my( $self, $client ) = @_;
2390 return new_editor()->retrieve_all_permission_grp_tree();
2393 __PACKAGE__->register_method(
2394 method => "retrieve_org_address",
2395 api_name => "open-ils.actor.org_unit.address.retrieve",
2396 notes => <<' NOTES');
2397 Returns an org_unit address by ID
2398 @param An org_address ID
2400 sub retrieve_org_address {
2401 my( $self, $client, $id ) = @_;
2402 return $apputils->simple_scalar_request(
2404 "open-ils.cstore.direct.actor.org_address.retrieve",
2409 __PACKAGE__->register_method(
2410 method => "retrieve_groups_tree",
2411 api_name => "open-ils.actor.groups.tree.retrieve",
2412 notes => "Returns a list of user groups"
2415 sub retrieve_groups_tree {
2416 my( $self, $client ) = @_;
2417 return new_editor()->search_permission_grp_tree(
2422 flesh_fields => { pgt => ["children"] },
2423 order_by => { pgt => 'name'}
2430 __PACKAGE__->register_method(
2431 method => "add_user_to_groups",
2432 api_name => "open-ils.actor.user.set_groups",
2433 notes => "Adds a user to one or more permission groups"
2436 sub add_user_to_groups {
2437 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2439 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2440 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2441 return $evt if $evt;
2443 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2444 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2445 return $evt if $evt;
2447 $apputils->simplereq(
2449 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2451 for my $group (@$groups) {
2452 my $link = Fieldmapper::permission::usr_grp_map->new;
2454 $link->usr($userid);
2456 my $id = $apputils->simplereq(
2458 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2464 __PACKAGE__->register_method(
2465 method => "get_user_perm_groups",
2466 api_name => "open-ils.actor.user.get_groups",
2467 notes => "Retrieve a user's permission groups."
2471 sub get_user_perm_groups {
2472 my( $self, $client, $authtoken, $userid ) = @_;
2474 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2475 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2476 return $evt if $evt;
2478 return $apputils->simplereq(
2480 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2484 __PACKAGE__->register_method(
2485 method => "get_user_work_ous",
2486 api_name => "open-ils.actor.user.get_work_ous",
2487 notes => "Retrieve a user's work org units."
2490 __PACKAGE__->register_method(
2491 method => "get_user_work_ous",
2492 api_name => "open-ils.actor.user.get_work_ous.ids",
2493 notes => "Retrieve a user's work org units."
2496 sub get_user_work_ous {
2497 my( $self, $client, $auth, $userid ) = @_;
2498 my $e = new_editor(authtoken=>$auth);
2499 return $e->event unless $e->checkauth;
2500 $userid ||= $e->requestor->id;
2502 if($e->requestor->id != $userid) {
2503 my $user = $e->retrieve_actor_user($userid)
2504 or return $e->event;
2505 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2508 return $e->search_permission_usr_work_ou_map({usr => $userid})
2509 unless $self->api_name =~ /.ids$/;
2511 # client just wants a list of org IDs
2512 return $U->get_user_work_ou_ids($e, $userid);
2517 __PACKAGE__->register_method(
2518 method => 'register_workstation',
2519 api_name => 'open-ils.actor.workstation.register.override',
2520 signature => q/@see open-ils.actor.workstation.register/
2523 __PACKAGE__->register_method(
2524 method => 'register_workstation',
2525 api_name => 'open-ils.actor.workstation.register',
2527 Registers a new workstion in the system
2528 @param authtoken The login session key
2529 @param name The name of the workstation id
2530 @param owner The org unit that owns this workstation
2531 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2532 if the name is already in use.
2536 sub register_workstation {
2537 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2539 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2540 return $e->die_event unless $e->checkauth;
2541 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2542 my $existing = $e->search_actor_workstation({name => $name})->[0];
2543 $oargs = { all => 1 } unless defined $oargs;
2547 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2548 # workstation with the given name exists.
2550 if($owner ne $existing->owning_lib) {
2551 # if necessary, update the owning_lib of the workstation
2553 $logger->info("changing owning lib of workstation ".$existing->id.
2554 " from ".$existing->owning_lib." to $owner");
2555 return $e->die_event unless
2556 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2558 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2560 $existing->owning_lib($owner);
2561 return $e->die_event unless $e->update_actor_workstation($existing);
2567 "attempt to register an existing workstation. returning existing ID");
2570 return $existing->id;
2573 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2577 my $ws = Fieldmapper::actor::workstation->new;
2578 $ws->owning_lib($owner);
2580 $e->create_actor_workstation($ws) or return $e->die_event;
2582 return $ws->id; # note: editor sets the id on the new object for us
2585 __PACKAGE__->register_method(
2586 method => 'workstation_list',
2587 api_name => 'open-ils.actor.workstation.list',
2589 Returns a list of workstations registered at the given location
2590 @param authtoken The login session key
2591 @param ids A list of org_unit.id's for the workstation owners
2595 sub workstation_list {
2596 my( $self, $conn, $authtoken, @orgs ) = @_;
2598 my $e = new_editor(authtoken=>$authtoken);
2599 return $e->event unless $e->checkauth;
2604 unless $e->allowed('REGISTER_WORKSTATION', $o);
2605 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2611 __PACKAGE__->register_method(
2612 method => 'fetch_patron_note',
2613 api_name => 'open-ils.actor.note.retrieve.all',
2616 Returns a list of notes for a given user
2617 Requestor must have VIEW_USER permission if pub==false and
2618 @param authtoken The login session key
2619 @param args Hash of params including
2620 patronid : the patron's id
2621 pub : true if retrieving only public notes
2625 sub fetch_patron_note {
2626 my( $self, $conn, $authtoken, $args ) = @_;
2627 my $patronid = $$args{patronid};
2629 my($reqr, $evt) = $U->checkses($authtoken);
2630 return $evt if $evt;
2633 ($patron, $evt) = $U->fetch_user($patronid);
2634 return $evt if $evt;
2637 if( $patronid ne $reqr->id ) {
2638 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2639 return $evt if $evt;
2641 return $U->cstorereq(
2642 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2643 { usr => $patronid, pub => 't' } );
2646 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2647 return $evt if $evt;
2649 return $U->cstorereq(
2650 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2653 __PACKAGE__->register_method(
2654 method => 'create_user_note',
2655 api_name => 'open-ils.actor.note.create',
2657 Creates a new note for the given user
2658 @param authtoken The login session key
2659 @param note The note object
2662 sub create_user_note {
2663 my( $self, $conn, $authtoken, $note ) = @_;
2664 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2665 return $e->die_event unless $e->checkauth;
2667 my $user = $e->retrieve_actor_user($note->usr)
2668 or return $e->die_event;
2670 return $e->die_event unless
2671 $e->allowed('UPDATE_USER',$user->home_ou);
2673 $note->creator($e->requestor->id);
2674 $e->create_actor_usr_note($note) or return $e->die_event;
2680 __PACKAGE__->register_method(
2681 method => 'delete_user_note',
2682 api_name => 'open-ils.actor.note.delete',
2684 Deletes a note for the given user
2685 @param authtoken The login session key
2686 @param noteid The note id
2689 sub delete_user_note {
2690 my( $self, $conn, $authtoken, $noteid ) = @_;
2692 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2693 return $e->die_event unless $e->checkauth;
2694 my $note = $e->retrieve_actor_usr_note($noteid)
2695 or return $e->die_event;
2696 my $user = $e->retrieve_actor_user($note->usr)
2697 or return $e->die_event;
2698 return $e->die_event unless
2699 $e->allowed('UPDATE_USER', $user->home_ou);
2701 $e->delete_actor_usr_note($note) or return $e->die_event;
2707 __PACKAGE__->register_method(
2708 method => 'update_user_note',
2709 api_name => 'open-ils.actor.note.update',
2711 @param authtoken The login session key
2712 @param note The note
2716 sub update_user_note {
2717 my( $self, $conn, $auth, $note ) = @_;
2718 my $e = new_editor(authtoken=>$auth, xact=>1);
2719 return $e->die_event unless $e->checkauth;
2720 my $patron = $e->retrieve_actor_user($note->usr)
2721 or return $e->die_event;
2722 return $e->die_event unless
2723 $e->allowed('UPDATE_USER', $patron->home_ou);
2724 $e->update_actor_user_note($note)
2725 or return $e->die_event;
2730 __PACKAGE__->register_method(
2731 method => 'fetch_patron_messages',
2732 api_name => 'open-ils.actor.message.retrieve',
2735 Returns a list of notes for a given user, not
2736 including ones marked deleted
2737 @param authtoken The login session key
2738 @param patronid patron ID
2739 @param options hash containing optional limit and offset
2743 sub fetch_patron_messages {
2744 my( $self, $conn, $auth, $patronid, $options ) = @_;
2748 my $e = new_editor(authtoken => $auth);
2749 return $e->die_event unless $e->checkauth;
2751 if ($e->requestor->id ne $patronid) {
2752 return $e->die_event unless $e->allowed('VIEW_USER');
2755 my $select_clause = { usr => $patronid };
2756 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2757 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2758 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2760 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2765 __PACKAGE__->register_method(
2766 method => 'usrname_exists',
2767 api_name => 'open-ils.actor.username.exists',
2769 desc => 'Check if a username is already taken (by an undeleted patron)',
2771 {desc => 'Authentication token', type => 'string'},
2772 {desc => 'Username', type => 'string'}
2775 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2780 sub usrname_exists {
2781 my( $self, $conn, $auth, $usrname ) = @_;
2782 my $e = new_editor(authtoken=>$auth);
2783 return $e->event unless $e->checkauth;
2784 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2785 return $$a[0] if $a and @$a;
2789 __PACKAGE__->register_method(
2790 method => 'barcode_exists',
2791 api_name => 'open-ils.actor.barcode.exists',
2793 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2796 sub barcode_exists {
2797 my( $self, $conn, $auth, $barcode ) = @_;
2798 my $e = new_editor(authtoken=>$auth);
2799 return $e->event unless $e->checkauth;
2800 my $card = $e->search_actor_card({barcode => $barcode});
2806 #return undef unless @$card;
2807 #return $card->[0]->usr;
2811 __PACKAGE__->register_method(
2812 method => 'retrieve_net_levels',
2813 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2816 sub retrieve_net_levels {
2817 my( $self, $conn, $auth ) = @_;
2818 my $e = new_editor(authtoken=>$auth);
2819 return $e->event unless $e->checkauth;
2820 return $e->retrieve_all_config_net_access_level();
2823 # Retain the old typo API name just in case
2824 __PACKAGE__->register_method(
2825 method => 'fetch_org_by_shortname',
2826 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2828 __PACKAGE__->register_method(
2829 method => 'fetch_org_by_shortname',
2830 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2832 sub fetch_org_by_shortname {
2833 my( $self, $conn, $sname ) = @_;
2834 my $e = new_editor();
2835 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2836 return $e->event unless $org;
2841 __PACKAGE__->register_method(
2842 method => 'session_home_lib',
2843 api_name => 'open-ils.actor.session.home_lib',
2846 sub session_home_lib {
2847 my( $self, $conn, $auth ) = @_;
2848 my $e = new_editor(authtoken=>$auth);
2849 return undef unless $e->checkauth;
2850 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2851 return $org->shortname;
2854 __PACKAGE__->register_method(
2855 method => 'session_safe_token',
2856 api_name => 'open-ils.actor.session.safe_token',
2858 Returns a hashed session ID that is safe for export to the world.
2859 This safe token will expire after 1 hour of non-use.
2860 @param auth Active authentication token
2864 sub session_safe_token {
2865 my( $self, $conn, $auth ) = @_;
2866 my $e = new_editor(authtoken=>$auth);
2867 return undef unless $e->checkauth;
2869 my $safe_token = md5_hex($auth);
2871 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2873 # add more user fields as needed
2875 "safe-token-user-$safe_token", {
2876 id => $e->requestor->id,
2877 home_ou_shortname => $e->retrieve_actor_org_unit(
2878 $e->requestor->home_ou)->shortname,
2887 __PACKAGE__->register_method(
2888 method => 'safe_token_home_lib',
2889 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2891 Returns the home library shortname from the session
2892 asscociated with a safe token from generated by
2893 open-ils.actor.session.safe_token.
2894 @param safe_token Active safe token
2895 @param who Optional user activity "ewho" value
2899 sub safe_token_home_lib {
2900 my( $self, $conn, $safe_token, $who ) = @_;
2901 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2903 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2904 return unless $blob;
2906 $U->log_user_activity($blob->{id}, $who, 'verify');
2907 return $blob->{home_ou_shortname};
2911 __PACKAGE__->register_method(
2912 method => "update_penalties",
2913 api_name => "open-ils.actor.user.penalties.update"
2916 sub update_penalties {
2917 my($self, $conn, $auth, $user_id) = @_;
2918 my $e = new_editor(authtoken=>$auth, xact => 1);
2919 return $e->die_event unless $e->checkauth;
2920 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2921 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2922 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2923 return $evt if $evt;
2929 __PACKAGE__->register_method(
2930 method => "apply_penalty",
2931 api_name => "open-ils.actor.user.penalty.apply"
2935 my($self, $conn, $auth, $penalty) = @_;
2937 my $e = new_editor(authtoken=>$auth, xact => 1);
2938 return $e->die_event unless $e->checkauth;
2940 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2941 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2943 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2946 (defined $ptype->org_depth) ?
2947 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2950 $penalty->org_unit($ctx_org);
2951 $penalty->staff($e->requestor->id);
2952 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2955 return $penalty->id;
2958 __PACKAGE__->register_method(
2959 method => "remove_penalty",
2960 api_name => "open-ils.actor.user.penalty.remove"
2963 sub remove_penalty {
2964 my($self, $conn, $auth, $penalty) = @_;
2965 my $e = new_editor(authtoken=>$auth, xact => 1);
2966 return $e->die_event unless $e->checkauth;
2967 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2968 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2970 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2975 __PACKAGE__->register_method(
2976 method => "update_penalty_note",
2977 api_name => "open-ils.actor.user.penalty.note.update"
2980 sub update_penalty_note {
2981 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2982 my $e = new_editor(authtoken=>$auth, xact => 1);
2983 return $e->die_event unless $e->checkauth;
2984 for my $penalty_id (@$penalty_ids) {
2985 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2986 if (! $penalty ) { return $e->die_event; }
2987 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2988 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2990 $penalty->note( $note ); $penalty->ischanged( 1 );
2992 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2998 __PACKAGE__->register_method(
2999 method => "ranged_penalty_thresholds",
3000 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3004 sub ranged_penalty_thresholds {
3005 my($self, $conn, $auth, $context_org) = @_;
3006 my $e = new_editor(authtoken=>$auth);
3007 return $e->event unless $e->checkauth;
3008 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3009 my $list = $e->search_permission_grp_penalty_threshold([
3010 {org_unit => $U->get_org_ancestors($context_org)},
3011 {order_by => {pgpt => 'id'}}
3013 $conn->respond($_) for @$list;
3019 __PACKAGE__->register_method(
3020 method => "user_retrieve_fleshed_by_id",
3022 api_name => "open-ils.actor.user.fleshed.retrieve",
3025 sub user_retrieve_fleshed_by_id {
3026 my( $self, $client, $auth, $user_id, $fields ) = @_;
3027 my $e = new_editor(authtoken => $auth);
3028 return $e->event unless $e->checkauth;
3030 if( $e->requestor->id != $user_id ) {
3031 return $e->event unless $e->allowed('VIEW_USER');
3038 "standing_penalties",
3044 return new_flesh_user($user_id, $fields, $e);
3048 sub new_flesh_user {
3051 my $fields = shift || [];
3054 my $fetch_penalties = 0;
3055 if(grep {$_ eq 'standing_penalties'} @$fields) {
3056 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3057 $fetch_penalties = 1;
3060 my $fetch_usr_act = 0;
3061 if(grep {$_ eq 'usr_activity'} @$fields) {
3062 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3066 my $user = $e->retrieve_actor_user(
3071 "flesh_fields" => { "au" => $fields }
3074 ) or return $e->die_event;
3077 if( grep { $_ eq 'addresses' } @$fields ) {
3079 $user->addresses([]) unless @{$user->addresses};
3080 # don't expose "replaced" addresses by default
3081 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3083 if( ref $user->billing_address ) {
3084 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3085 push( @{$user->addresses}, $user->billing_address );
3089 if( ref $user->mailing_address ) {
3090 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3091 push( @{$user->addresses}, $user->mailing_address );
3096 if($fetch_penalties) {
3097 # grab the user penalties ranged for this location
3098 $user->standing_penalties(
3099 $e->search_actor_user_standing_penalty([
3102 {stop_date => undef},
3103 {stop_date => {'>' => 'now'}}
3105 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3108 flesh_fields => {ausp => ['standing_penalty']}
3114 # retrieve the most recent usr_activity entry
3115 if ($fetch_usr_act) {
3117 # max number to return for simple patron fleshing
3118 my $limit = $U->ou_ancestor_setting_value(
3119 $e->requestor->ws_ou,
3120 'circ.patron.usr_activity_retrieve.max');
3124 flesh_fields => {auact => ['etype']},
3125 order_by => {auact => 'event_time DESC'},
3128 # 0 == none, <0 == return all
3129 $limit = 1 unless defined $limit;
3130 $opts->{limit} = $limit if $limit > 0;
3132 $user->usr_activity(
3134 [] : # skip the DB call
3135 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3140 $user->clear_passwd();
3147 __PACKAGE__->register_method(
3148 method => "user_retrieve_parts",
3149 api_name => "open-ils.actor.user.retrieve.parts",
3152 sub user_retrieve_parts {
3153 my( $self, $client, $auth, $user_id, $fields ) = @_;
3154 my $e = new_editor(authtoken => $auth);
3155 return $e->event unless $e->checkauth;
3156 $user_id ||= $e->requestor->id;
3157 if( $e->requestor->id != $user_id ) {
3158 return $e->event unless $e->allowed('VIEW_USER');
3161 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3162 push(@resp, $user->$_()) for(@$fields);
3168 __PACKAGE__->register_method(
3169 method => 'user_opt_in_enabled',
3170 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3171 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3174 sub user_opt_in_enabled {
3175 my($self, $conn) = @_;
3176 my $sc = OpenSRF::Utils::SettingsClient->new;
3177 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3182 __PACKAGE__->register_method(
3183 method => 'user_opt_in_at_org',
3184 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3186 @param $auth The auth token
3187 @param user_id The ID of the user to test
3188 @return 1 if the user has opted in at the specified org,
3189 event on error, and 0 otherwise. /
3191 sub user_opt_in_at_org {
3192 my($self, $conn, $auth, $user_id) = @_;
3194 # see if we even need to enforce the opt-in value
3195 return 1 unless user_opt_in_enabled($self);
3197 my $e = new_editor(authtoken => $auth);
3198 return $e->event unless $e->checkauth;
3200 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3201 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3203 my $ws_org = $e->requestor->ws_ou;
3204 # user is automatically opted-in if they are from the local org
3205 return 1 if $user->home_ou eq $ws_org;
3207 # get the boundary setting
3208 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3210 # auto opt in if user falls within the opt boundary
3211 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3213 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3215 my $vals = $e->search_actor_usr_org_unit_opt_in(
3216 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3222 __PACKAGE__->register_method(
3223 method => 'create_user_opt_in_at_org',
3224 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3226 @param $auth The auth token
3227 @param user_id The ID of the user to test
3228 @return The ID of the newly created object, event on error./
3231 sub create_user_opt_in_at_org {
3232 my($self, $conn, $auth, $user_id, $org_id) = @_;
3234 my $e = new_editor(authtoken => $auth, xact=>1);
3235 return $e->die_event unless $e->checkauth;
3237 # if a specific org unit wasn't passed in, get one based on the defaults;
3239 my $wsou = $e->requestor->ws_ou;
3240 # get the default opt depth
3241 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3242 # get the org unit at that depth
3243 my $org = $e->json_query({
3244 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3245 $org_id = $org->{id};
3248 # fall back to the workstation OU, the pre-opt-in-boundary way
3249 $org_id = $e->requestor->ws_ou;
3252 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3253 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3255 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3257 $opt_in->org_unit($org_id);
3258 $opt_in->usr($user_id);
3259 $opt_in->staff($e->requestor->id);
3260 $opt_in->opt_in_ts('now');
3261 $opt_in->opt_in_ws($e->requestor->wsid);
3263 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3264 or return $e->die_event;
3272 __PACKAGE__->register_method (
3273 method => 'retrieve_org_hours',
3274 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3276 Returns the hours of operation for a specified org unit
3277 @param authtoken The login session key
3278 @param org_id The org_unit ID
3282 sub retrieve_org_hours {
3283 my($self, $conn, $auth, $org_id) = @_;
3284 my $e = new_editor(authtoken => $auth);
3285 return $e->die_event unless $e->checkauth;
3286 $org_id ||= $e->requestor->ws_ou;
3287 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3291 __PACKAGE__->register_method (
3292 method => 'verify_user_password',
3293 api_name => 'open-ils.actor.verify_user_password',
3295 Given a barcode or username and the MD5 encoded password,
3296 returns 1 if the password is correct. Returns 0 otherwise.
3300 sub verify_user_password {
3301 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3302 my $e = new_editor(authtoken => $auth);
3303 return $e->die_event unless $e->checkauth;
3305 my $user_by_barcode;
3306 my $user_by_username;
3308 my $card = $e->search_actor_card([
3309 {barcode => $barcode},
3310 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3311 $user_by_barcode = $card->usr;
3312 $user = $user_by_barcode;
3315 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3316 $user = $user_by_username;
3318 return 0 if (!$user);
3319 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3320 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3321 return $U->verify_migrated_user_password(
3322 $e, $user_by_username->id, $password, 1);
3325 __PACKAGE__->register_method (
3326 method => 'retrieve_usr_id_via_barcode_or_usrname',
3327 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3329 Given a barcode or username returns the id for the user or
3334 sub retrieve_usr_id_via_barcode_or_usrname {
3335 my($self, $conn, $auth, $barcode, $username) = @_;
3336 my $e = new_editor(authtoken => $auth);
3337 return $e->die_event unless $e->checkauth;
3338 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3340 my $user_by_barcode;
3341 my $user_by_username;
3342 $logger->info("$id_as_barcode is the ID as BARCODE");
3344 my $card = $e->search_actor_card([
3345 {barcode => $barcode},
3346 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3347 if ($id_as_barcode =~ /^t/i) {
3349 $user = $e->retrieve_actor_user($barcode);
3350 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3352 $user_by_barcode = $card->usr;
3353 $user = $user_by_barcode;
3356 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3357 $user_by_barcode = $card->usr;
3358 $user = $user_by_barcode;
3363 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3365 $user = $user_by_username;
3367 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3368 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3369 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3374 __PACKAGE__->register_method (
3375 method => 'merge_users',
3376 api_name => 'open-ils.actor.user.merge',
3379 Given a list of source users and destination user, transfer all data from the source
3380 to the dest user and delete the source user. All user related data is
3381 transferred, including circulations, holds, bookbags, etc.
3387 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3388 my $e = new_editor(xact => 1, authtoken => $auth);
3389 return $e->die_event unless $e->checkauth;
3391 # disallow the merge if any subordinate accounts are in collections
3392 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3393 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3395 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3396 my $del_addrs = ($U->ou_ancestor_setting_value(
3397 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3398 my $del_cards = ($U->ou_ancestor_setting_value(
3399 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3400 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3401 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3403 for my $src_id (@$user_ids) {
3404 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3406 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3407 if($src_user->home_ou ne $master_user->home_ou) {
3408 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3411 return $e->die_event unless
3412 $e->json_query({from => [
3427 __PACKAGE__->register_method (
3428 method => 'approve_user_address',
3429 api_name => 'open-ils.actor.user.pending_address.approve',
3436 sub approve_user_address {
3437 my($self, $conn, $auth, $addr) = @_;
3438 my $e = new_editor(xact => 1, authtoken => $auth);
3439 return $e->die_event unless $e->checkauth;
3441 # if the caller passes an address object, assume they want to
3442 # update it first before approving it
3443 $e->update_actor_user_address($addr) or return $e->die_event;
3445 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3447 my $user = $e->retrieve_actor_user($addr->usr);
3448 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3449 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3450 or return $e->die_event;
3452 return [values %$result]->[0];
3456 __PACKAGE__->register_method (
3457 method => 'retrieve_friends',
3458 api_name => 'open-ils.actor.friends.retrieve',
3461 returns { confirmed: [], pending_out: [], pending_in: []}
3462 pending_out are users I'm requesting friendship with
3463 pending_in are users requesting friendship with me
3468 sub retrieve_friends {
3469 my($self, $conn, $auth, $user_id, $options) = @_;
3470 my $e = new_editor(authtoken => $auth);
3471 return $e->event unless $e->checkauth;
3472 $user_id ||= $e->requestor->id;
3474 if($user_id != $e->requestor->id) {
3475 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3476 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3479 return OpenILS::Application::Actor::Friends->retrieve_friends(
3480 $e, $user_id, $options);
3485 __PACKAGE__->register_method (
3486 method => 'apply_friend_perms',
3487 api_name => 'open-ils.actor.friends.perms.apply',
3493 sub apply_friend_perms {
3494 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3495 my $e = new_editor(authtoken => $auth, xact => 1);
3496 return $e->die_event unless $e->checkauth;
3498 if($user_id != $e->requestor->id) {
3499 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3500 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3503 for my $perm (@perms) {
3505 OpenILS::Application::Actor::Friends->apply_friend_perm(
3506 $e, $user_id, $delegate_id, $perm);
3507 return $evt if $evt;
3515 __PACKAGE__->register_method (
3516 method => 'update_user_pending_address',
3517 api_name => 'open-ils.actor.user.address.pending.cud'
3520 sub update_user_pending_address {
3521 my($self, $conn, $auth, $addr) = @_;
3522 my $e = new_editor(authtoken => $auth, xact => 1);
3523 return $e->die_event unless $e->checkauth;
3525 if($addr->usr != $e->requestor->id) {
3526 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3527 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3531 $e->create_actor_user_address($addr) or return $e->die_event;
3532 } elsif($addr->isdeleted) {
3533 $e->delete_actor_user_address($addr) or return $e->die_event;
3535 $e->update_actor_user_address($addr) or return $e->die_event;
3543 __PACKAGE__->register_method (
3544 method => 'user_events',
3545 api_name => 'open-ils.actor.user.events.circ',
3548 __PACKAGE__->register_method (
3549 method => 'user_events',
3550 api_name => 'open-ils.actor.user.events.ahr',
3555 my($self, $conn, $auth, $user_id, $filters) = @_;
3556 my $e = new_editor(authtoken => $auth);
3557 return $e->event unless $e->checkauth;
3559 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3560 my $user_field = 'usr';
3563 $filters->{target} = {
3564 select => { $obj_type => ['id'] },
3566 where => {usr => $user_id}
3569 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3570 if($e->requestor->id != $user_id) {
3571 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3574 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3575 my $req = $ses->request('open-ils.trigger.events_by_target',
3576 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3578 while(my $resp = $req->recv) {
3579 my $val = $resp->content;
3580 my $tgt = $val->target;
3582 if($obj_type eq 'circ') {
3583 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3585 } elsif($obj_type eq 'ahr') {
3586 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3587 if $tgt->current_copy;
3590 $conn->respond($val) if $val;
3596 __PACKAGE__->register_method (
3597 method => 'copy_events',
3598 api_name => 'open-ils.actor.copy.events.circ',
3601 __PACKAGE__->register_method (
3602 method => 'copy_events',
3603 api_name => 'open-ils.actor.copy.events.ahr',
3608 my($self, $conn, $auth, $copy_id, $filters) = @_;
3609 my $e = new_editor(authtoken => $auth);
3610 return $e->event unless $e->checkauth;
3612 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3614 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3616 my $copy_field = 'target_copy';
3617 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3620 $filters->{target} = {
3621 select => { $obj_type => ['id'] },
3623 where => {$copy_field => $copy_id}
3627 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3628 my $req = $ses->request('open-ils.trigger.events_by_target',
3629 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3631 while(my $resp = $req->recv) {
3632 my $val = $resp->content;
3633 my $tgt = $val->target;
3635 my $user = $e->retrieve_actor_user($tgt->usr);
3636 if($e->requestor->id != $user->id) {
3637 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3640 $tgt->$copy_field($copy);
3643 $conn->respond($val) if $val;
3652 __PACKAGE__->register_method (
3653 method => 'update_events',
3654 api_name => 'open-ils.actor.user.event.cancel.batch',
3657 __PACKAGE__->register_method (
3658 method => 'update_events',
3659 api_name => 'open-ils.actor.user.event.reset.batch',
3664 my($self, $conn, $auth, $event_ids) = @_;
3665 my $e = new_editor(xact => 1, authtoken => $auth);
3666 return $e->die_event unless $e->checkauth;
3669 for my $id (@$event_ids) {
3671 # do a little dance to determine what user we are ultimately affecting
3672 my $event = $e->retrieve_action_trigger_event([
3675 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3677 ]) or return $e->die_event;
3680 if($event->event_def->hook->core_type eq 'circ') {
3681 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3682 } elsif($event->event_def->hook->core_type eq 'ahr') {
3683 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3688 my $user = $e->retrieve_actor_user($user_id);
3689 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3691 if($self->api_name =~ /cancel/) {
3692 $event->state('invalid');
3693 } elsif($self->api_name =~ /reset/) {
3694 $event->clear_start_time;
3695 $event->clear_update_time;
3696 $event->state('pending');
3699 $e->update_action_trigger_event($event) or return $e->die_event;
3700 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3704 return {complete => 1};
3708 __PACKAGE__->register_method (
3709 method => 'really_delete_user',
3710 api_name => 'open-ils.actor.user.delete.override',
3711 signature => q/@see open-ils.actor.user.delete/
3714 __PACKAGE__->register_method (
3715 method => 'really_delete_user',
3716 api_name => 'open-ils.actor.user.delete',
3718 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3719 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3720 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3721 dest_usr_id is only required when deleting a user that performs staff functions.
3725 sub really_delete_user {
3726 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3727 my $e = new_editor(authtoken => $auth, xact => 1);
3728 return $e->die_event unless $e->checkauth;
3729 $oargs = { all => 1 } unless defined $oargs;
3731 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3732 my $open_bills = $e->json_query({
3733 select => { mbts => ['id'] },
3736 xact_finish => { '=' => undef },
3737 usr => { '=' => $user_id },
3739 }) or return $e->die_event;
3741 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3743 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3745 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3746 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3747 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3749 # No deleting yourself - UI is supposed to stop you first, though.
3750 return $e->die_event unless $e->requestor->id != $user->id;
3751 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3752 # Check if you are allowed to mess with this patron permission group at all
3753 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3754 my $evt = group_perm_failed($session, $e->requestor, $user);
3755 return $e->die_event($evt) if $evt;
3756 my $stat = $e->json_query(
3757 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3758 or return $e->die_event;
3764 __PACKAGE__->register_method (
3765 method => 'user_payments',
3766 api_name => 'open-ils.actor.user.payments.retrieve',
3769 Returns all payments for a given user. Default order is newest payments first.
3770 @param auth Authentication token
3771 @param user_id The user ID
3772 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3777 my($self, $conn, $auth, $user_id, $filters) = @_;
3780 my $e = new_editor(authtoken => $auth);
3781 return $e->die_event unless $e->checkauth;
3783 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3784 return $e->event unless
3785 $e->requestor->id == $user_id or
3786 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3788 # Find all payments for all transactions for user $user_id
3790 select => {mp => ['id']},
3795 select => {mbt => ['id']},
3797 where => {usr => $user_id}
3802 { # by default, order newest payments first
3804 field => 'payment_ts',
3807 # secondary sort in ID as a tie-breaker, since payments created
3808 # within the same transaction will have identical payment_ts's
3815 for (qw/order_by limit offset/) {
3816 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3819 if(defined $filters->{where}) {
3820 foreach (keys %{$filters->{where}}) {
3821 # don't allow the caller to expand the result set to other users
3822 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3826 my $payment_ids = $e->json_query($query);
3827 for my $pid (@$payment_ids) {
3828 my $pay = $e->retrieve_money_payment([
3833 mbt => ['summary', 'circulation', 'grocery'],
3834 circ => ['target_copy'],
3835 acp => ['call_number'],
3843 xact_type => $pay->xact->summary->xact_type,
3844 last_billing_type => $pay->xact->summary->last_billing_type,
3847 if($pay->xact->summary->xact_type eq 'circulation') {
3848 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3849 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3852 $pay->xact($pay->xact->id); # de-flesh
3853 $conn->respond($resp);
3861 __PACKAGE__->register_method (
3862 method => 'negative_balance_users',
3863 api_name => 'open-ils.actor.users.negative_balance',
3866 Returns all users that have an overall negative balance
3867 @param auth Authentication token
3868 @param org_id The context org unit as an ID or list of IDs. This will be the home
3869 library of the user. If no org_unit is specified, no org unit filter is applied
3873 sub negative_balance_users {
3874 my($self, $conn, $auth, $org_id) = @_;
3876 my $e = new_editor(authtoken => $auth);
3877 return $e->die_event unless $e->checkauth;
3878 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3882 mous => ['usr', 'balance_owed'],
3885 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3886 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3903 where => {'+mous' => {balance_owed => {'<' => 0}}}
3906 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3908 my $list = $e->json_query($query, {timeout => 600});
3910 for my $data (@$list) {
3912 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3913 balance_owed => $data->{balance_owed},
3914 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3921 __PACKAGE__->register_method(
3922 method => "request_password_reset",
3923 api_name => "open-ils.actor.patron.password_reset.request",
3925 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3926 "method for changing a user's password. The UUID token is distributed via A/T " .
3927 "templates (i.e. email to the user).",
3929 { desc => 'user_id_type', type => 'string' },
3930 { desc => 'user_id', type => 'string' },
3931 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3933 return => {desc => '1 on success, Event on error'}
3936 sub request_password_reset {
3937 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3939 # Check to see if password reset requests are already being throttled:
3940 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3942 my $e = new_editor(xact => 1);
3945 # Get the user, if any, depending on the input value
3946 if ($user_id_type eq 'username') {
3947 $user = $e->search_actor_user({usrname => $user_id})->[0];
3950 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3952 } elsif ($user_id_type eq 'barcode') {
3953 my $card = $e->search_actor_card([
3954 {barcode => $user_id},
3955 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3958 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3963 # If the user doesn't have an email address, we can't help them
3964 if (!$user->email) {
3966 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3969 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3970 if ($email_must_match) {
3971 if ($user->email ne $email) {
3972 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3976 _reset_password_request($conn, $e, $user);
3979 # Once we have the user, we can issue the password reset request
3980 # XXX Add a wrapper method that accepts barcode + email input
3981 sub _reset_password_request {
3982 my ($conn, $e, $user) = @_;
3984 # 1. Get throttle threshold and time-to-live from OU_settings
3985 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3986 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3988 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3990 # 2. Get time of last request and number of active requests (num_active)
3991 my $active_requests = $e->json_query({
3997 transform => 'COUNT'
4000 column => 'request_time',
4006 has_been_reset => { '=' => 'f' },
4007 request_time => { '>' => $threshold_time }
4011 # Guard against no active requests
4012 if ($active_requests->[0]->{'request_time'}) {
4013 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
4014 my $now = DateTime::Format::ISO8601->new();
4016 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4017 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4018 ($last_request->add_duration('1 minute') > $now)) {
4019 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4021 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4025 # TODO Check to see if the user is in a password-reset-restricted group
4027 # Otherwise, go ahead and try to get the user.
4029 # Check the number of active requests for this user
4030 $active_requests = $e->json_query({
4036 transform => 'COUNT'
4041 usr => { '=' => $user->id },
4042 has_been_reset => { '=' => 'f' },
4043 request_time => { '>' => $threshold_time }
4047 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4049 # if less than or equal to per-user threshold, proceed; otherwise, return event
4050 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4051 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4053 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4056 # Create the aupr object and insert into the database
4057 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4058 my $uuid = create_uuid_as_string(UUID_V4);
4059 $reset_request->uuid($uuid);
4060 $reset_request->usr($user->id);
4062 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4065 # Create an event to notify user of the URL to reset their password
4067 # Can we stuff this in the user_data param for trigger autocreate?
4068 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4070 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4071 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4074 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4079 __PACKAGE__->register_method(
4080 method => "commit_password_reset",
4081 api_name => "open-ils.actor.patron.password_reset.commit",
4083 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4084 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4085 "with the supplied password.",
4087 { desc => 'uuid', type => 'string' },
4088 { desc => 'password', type => 'string' },
4090 return => {desc => '1 on success, Event on error'}
4093 sub commit_password_reset {
4094 my($self, $conn, $uuid, $password) = @_;
4096 # Check to see if password reset requests are already being throttled:
4097 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4098 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4099 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4101 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4104 my $e = new_editor(xact => 1);
4106 my $aupr = $e->search_actor_usr_password_reset({
4113 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4115 my $user_id = $aupr->[0]->usr;
4116 my $user = $e->retrieve_actor_user($user_id);
4118 # Ensure we're still within the TTL for the request
4119 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4120 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4121 if ($threshold < DateTime->now(time_zone => 'local')) {
4123 $logger->info("Password reset request needed to be submitted before $threshold");
4124 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4127 # Check complexity of password against OU-defined regex
4128 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4132 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4133 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4134 $is_strong = check_password_strength_custom($password, $pw_regex);
4136 $is_strong = check_password_strength_default($password);
4141 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4144 # All is well; update the password
4145 $user->passwd($password);
4146 $e->update_actor_user($user);
4148 # And flag that this password reset request has been honoured
4149 $aupr->[0]->has_been_reset('t');
4150 $e->update_actor_usr_password_reset($aupr->[0]);
4156 sub check_password_strength_default {
4157 my $password = shift;
4158 # Use the default set of checks
4159 if ( (length($password) < 7) or
4160 ($password !~ m/.*\d+.*/) or
4161 ($password !~ m/.*[A-Za-z]+.*/)
4168 sub check_password_strength_custom {
4169 my ($password, $pw_regex) = @_;
4171 $pw_regex = qr/$pw_regex/;
4172 if ($password !~ /$pw_regex/) {
4180 __PACKAGE__->register_method(
4181 method => "event_def_opt_in_settings",
4182 api_name => "open-ils.actor.event_def.opt_in.settings",
4185 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4187 { desc => 'Authentication token', type => 'string'},
4189 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4194 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4201 sub event_def_opt_in_settings {
4202 my($self, $conn, $auth, $org_id) = @_;
4203 my $e = new_editor(authtoken => $auth);
4204 return $e->event unless $e->checkauth;
4206 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4207 return $e->event unless
4208 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4210 $org_id = $e->requestor->home_ou;
4213 # find all config.user_setting_type's related to event_defs for the requested org unit
4214 my $types = $e->json_query({
4215 select => {cust => ['name']},
4216 from => {atevdef => 'cust'},
4219 owner => $U->get_org_ancestors($org_id), # context org plus parents
4226 $conn->respond($_) for
4227 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4234 __PACKAGE__->register_method(
4235 method => "user_circ_history",
4236 api_name => "open-ils.actor.history.circ",
4240 desc => 'Returns user circ history objects for the calling user',
4242 { desc => 'Authentication token', type => 'string'},
4243 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4246 desc => q/Stream of 'auch' circ history objects/,
4252 __PACKAGE__->register_method(
4253 method => "user_circ_history",
4254 api_name => "open-ils.actor.history.circ.clear",
4257 desc => 'Delete all user circ history entries for the calling user',
4259 { desc => 'Authentication token', type => 'string'},
4262 desc => q/1 on success, event on error/,
4268 __PACKAGE__->register_method(
4269 method => "user_circ_history",
4270 api_name => "open-ils.actor.history.circ.print",
4273 desc => q/Returns printable output for the caller's circ history objects/,
4275 { desc => 'Authentication token', type => 'string'},
4276 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4279 desc => q/An action_trigger.event object or error event./,
4285 __PACKAGE__->register_method(
4286 method => "user_circ_history",
4287 api_name => "open-ils.actor.history.circ.email",
4290 desc => q/Emails the caller's circ history/,
4292 { desc => 'Authentication token', type => 'string'},
4293 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4294 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4297 desc => q/undef, or event on error/
4302 sub user_circ_history {
4303 my ($self, $conn, $auth, $options) = @_;
4306 my $for_print = ($self->api_name =~ /print/);
4307 my $for_email = ($self->api_name =~ /email/);
4308 my $for_clear = ($self->api_name =~ /clear/);
4310 # No perm check is performed. Caller may only access his/her own
4311 # circ history entries.
4312 my $e = new_editor(authtoken => $auth);
4313 return $e->event unless $e->checkauth;
4316 if (!$for_clear) { # clear deletes all
4317 $limits{offset} = $options->{offset} if defined $options->{offset};
4318 $limits{limit} = $options->{limit} if defined $options->{limit};
4321 my $circs = $e->search_action_user_circ_history([
4322 {usr => $e->requestor->id},
4323 { # order newest to oldest by default
4324 order_by => {auch => 'xact_start DESC'},
4327 {substream => 1} # could be a large list
4331 return $U->fire_object_event(undef,
4332 'circ.format.history.print', $circs, $e->requestor->home_ou);
4335 $e->xact_begin if $for_clear;
4336 $conn->respond_complete(1) if $for_email; # no sense in waiting
4338 for my $circ (@$circs) {
4341 # events will be fired from action_trigger_runner
4342 $U->create_events_for_hook('circ.format.history.email',
4343 $circ, $e->editor->home_ou, undef, undef, 1);
4345 } elsif ($for_clear) {
4347 $e->delete_action_user_circ_history($circ)
4348 or return $e->die_event;
4351 $conn->respond($circ);
4364 __PACKAGE__->register_method(
4365 method => "user_visible_circs",
4366 api_name => "open-ils.actor.history.hold.visible",
4369 desc => 'Returns the set of opt-in visible holds',
4371 { desc => 'Authentication token', type => 'string'},
4372 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4373 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4376 desc => q/An object with 1 field: "hold"/,
4382 __PACKAGE__->register_method(
4383 method => "user_visible_circs",
4384 api_name => "open-ils.actor.history.hold.visible.print",
4387 desc => 'Returns printable output for the set of opt-in visible holds',
4389 { desc => 'Authentication token', type => 'string'},
4390 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4391 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4394 desc => q/An action_trigger.event object or error event./,
4400 __PACKAGE__->register_method(
4401 method => "user_visible_circs",
4402 api_name => "open-ils.actor.history.hold.visible.email",
4405 desc => 'Emails the set of opt-in visible holds to the requestor',
4407 { desc => 'Authentication token', type => 'string'},
4408 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4409 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4412 desc => q/undef, or event on error/
4417 sub user_visible_holds {
4418 my($self, $conn, $auth, $user_id, $options) = @_;
4421 my $for_print = ($self->api_name =~ /print/);
4422 my $for_email = ($self->api_name =~ /email/);
4423 my $e = new_editor(authtoken => $auth);
4424 return $e->event unless $e->checkauth;
4426 $user_id ||= $e->requestor->id;
4428 $options->{limit} ||= 50;
4429 $options->{offset} ||= 0;
4431 if($user_id != $e->requestor->id) {
4432 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4433 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4434 return $e->event unless $e->allowed($perm, $user->home_ou);
4437 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4439 my $data = $e->json_query({
4440 from => [$db_func, $user_id],
4441 limit => $$options{limit},
4442 offset => $$options{offset}
4444 # TODO: I only want IDs. code below didn't get me there
4445 # {"select":{"au":[{"column":"id", "result_field":"id",
4446 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4451 return undef unless @$data;
4455 # collect the batch of objects
4459 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4460 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4464 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4465 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4468 } elsif ($for_email) {
4470 $conn->respond_complete(1) if $for_email; # no sense in waiting
4478 my $hold = $e->retrieve_action_hold_request($id);
4479 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4480 # events will be fired from action_trigger_runner
4484 my $circ = $e->retrieve_action_circulation($id);
4485 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4486 # events will be fired from action_trigger_runner
4490 } else { # just give me the data please
4498 my $hold = $e->retrieve_action_hold_request($id);
4499 $conn->respond({hold => $hold});
4503 my $circ = $e->retrieve_action_circulation($id);
4506 summary => $U->create_circ_chain_summary($e, $id)
4515 __PACKAGE__->register_method(
4516 method => "user_saved_search_cud",
4517 api_name => "open-ils.actor.user.saved_search.cud",
4520 desc => 'Create/Update/Delete Access to user saved searches',
4522 { desc => 'Authentication token', type => 'string' },
4523 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4526 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4532 __PACKAGE__->register_method(
4533 method => "user_saved_search_cud",
4534 api_name => "open-ils.actor.user.saved_search.retrieve",
4537 desc => 'Retrieve a saved search object',
4539 { desc => 'Authentication token', type => 'string' },
4540 { desc => 'Saved Search ID', type => 'number' }
4543 desc => q/The saved search object, Event on error/,
4549 sub user_saved_search_cud {
4550 my( $self, $client, $auth, $search ) = @_;
4551 my $e = new_editor( authtoken=>$auth );
4552 return $e->die_event unless $e->checkauth;
4554 my $o_search; # prior version of the object, if any
4555 my $res; # to be returned
4557 # branch on the operation type
4559 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4561 # Get the old version, to check ownership
4562 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4563 or return $e->die_event;
4565 # You can't read somebody else's search
4566 return OpenILS::Event->new('BAD_PARAMS')
4567 unless $o_search->owner == $e->requestor->id;
4573 $e->xact_begin; # start an editor transaction
4575 if( $search->isnew ) { # Create
4577 # You can't create a search for somebody else
4578 return OpenILS::Event->new('BAD_PARAMS')
4579 unless $search->owner == $e->requestor->id;
4581 $e->create_actor_usr_saved_search( $search )
4582 or return $e->die_event;
4586 } elsif( $search->ischanged ) { # Update
4588 # You can't change ownership of a search
4589 return OpenILS::Event->new('BAD_PARAMS')
4590 unless $search->owner == $e->requestor->id;
4592 # Get the old version, to check ownership
4593 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4594 or return $e->die_event;
4596 # You can't update somebody else's search
4597 return OpenILS::Event->new('BAD_PARAMS')
4598 unless $o_search->owner == $e->requestor->id;
4601 $e->update_actor_usr_saved_search( $search )
4602 or return $e->die_event;
4606 } elsif( $search->isdeleted ) { # Delete
4608 # Get the old version, to check ownership
4609 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4610 or return $e->die_event;
4612 # You can't delete somebody else's search
4613 return OpenILS::Event->new('BAD_PARAMS')
4614 unless $o_search->owner == $e->requestor->id;
4617 $e->delete_actor_usr_saved_search( $o_search )
4618 or return $e->die_event;
4629 __PACKAGE__->register_method(
4630 method => "get_barcodes",
4631 api_name => "open-ils.actor.get_barcodes"
4635 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4636 my $e = new_editor(authtoken => $auth);
4637 return $e->event unless $e->checkauth;
4638 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4640 my $db_result = $e->json_query(
4642 'evergreen.get_barcodes',
4643 $org_id, $context, $barcode,
4647 if($context =~ /actor/) {
4648 my $filter_result = ();
4650 foreach my $result (@$db_result) {
4651 if($result->{type} eq 'actor') {
4652 if($e->requestor->id != $result->{id}) {
4653 $patron = $e->retrieve_actor_user($result->{id});
4655 push(@$filter_result, $e->event);
4658 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4659 push(@$filter_result, $result);
4662 push(@$filter_result, $e->event);
4666 push(@$filter_result, $result);
4670 push(@$filter_result, $result);
4673 return $filter_result;
4679 __PACKAGE__->register_method(
4680 method => 'address_alert_test',
4681 api_name => 'open-ils.actor.address_alert.test',
4683 desc => "Tests a set of address fields to determine if they match with an address_alert",
4685 {desc => 'Authentication token', type => 'string'},
4686 {desc => 'Org Unit', type => 'number'},
4687 {desc => 'Fields', type => 'hash'},
4689 return => {desc => 'List of matching address_alerts'}
4693 sub address_alert_test {
4694 my ($self, $client, $auth, $org_unit, $fields) = @_;
4695 return [] unless $fields and grep {$_} values %$fields;
4697 my $e = new_editor(authtoken => $auth);
4698 return $e->event unless $e->checkauth;
4699 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4700 $org_unit ||= $e->requestor->ws_ou;
4702 my $alerts = $e->json_query({
4704 'actor.address_alert_matches',
4712 $$fields{post_code},
4713 $$fields{mailing_address},
4714 $$fields{billing_address}
4718 # map the json_query hashes to real objects
4720 map {$e->retrieve_actor_address_alert($_)}
4721 (map {$_->{id}} @$alerts)
4725 __PACKAGE__->register_method(
4726 method => "mark_users_contact_invalid",
4727 api_name => "open-ils.actor.invalidate.email",
4729 desc => "Given a patron, clear the email field and put the old email address into a note and/or create a standing penalty, depending on OU settings",
4731 {desc => "Authentication token", type => "string"},
4732 {desc => "Patron ID", type => "number"},
4733 {desc => "Additional note text (optional)", type => "string"},
4734 {desc => "penalty org unit ID (optional)", type => "number"}
4736 return => {desc => "Event describing success or failure", type => "object"}
4740 __PACKAGE__->register_method(
4741 method => "mark_users_contact_invalid",
4742 api_name => "open-ils.actor.invalidate.day_phone",
4744 desc => "Given a patron, clear the day_phone field and put the old day_phone into a note and/or create a standing penalty, depending on OU settings",
4746 {desc => "Authentication token", type => "string"},
4747 {desc => "Patron ID", type => "number"},
4748 {desc => "Additional note text (optional)", type => "string"},
4749 {desc => "penalty org unit ID (optional)", type => "number"}
4751 return => {desc => "Event describing success or failure", type => "object"}
4755 __PACKAGE__->register_method(
4756 method => "mark_users_contact_invalid",
4757 api_name => "open-ils.actor.invalidate.evening_phone",
4759 desc => "Given a patron, clear the evening_phone field and put the old evening_phone into a note and/or create a standing penalty, depending on OU settings",
4761 {desc => "Authentication token", type => "string"},
4762 {desc => "Patron ID", type => "number"},
4763 {desc => "Additional note text (optional)", type => "string"},
4764 {desc => "penalty org unit ID (optional)", type => "number"}
4766 return => {desc => "Event describing success or failure", type => "object"}
4770 __PACKAGE__->register_method(
4771 method => "mark_users_contact_invalid",
4772 api_name => "open-ils.actor.invalidate.other_phone",
4774 desc => "Given a patron, clear the other_phone field and put the old other_phone into a note and/or create a standing penalty, depending on OU settings",
4776 {desc => "Authentication token", type => "string"},
4777 {desc => "Patron ID", type => "number"},
4778 {desc => "Additional note text (optional)", type => "string"},
4779 {desc => "penalty org unit ID (optional, default to top of org tree)",
4782 return => {desc => "Event describing success or failure", type => "object"}
4786 sub mark_users_contact_invalid {
4787 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4789 # This method invalidates an email address or a phone_number which
4790 # removes the bad email address or phone number, copying its contents
4791 # to a patron note, and institutes a standing penalty for "bad email"
4792 # or "bad phone number" which is cleared when the user is saved or
4793 # optionally only when the user is saved with an email address or
4794 # phone number (or staff manually delete the penalty).
4796 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4798 my $e = new_editor(authtoken => $auth, xact => 1);
4799 return $e->die_event unless $e->checkauth;
4801 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4802 $e, $contact_type, {usr => $patron_id},
4803 $addl_note, $penalty_ou, $e->requestor->id
4807 # Putting the following method in open-ils.actor is a bad fit, except in that
4808 # it serves an interface that lives under 'actor' in the templates directory,
4809 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4811 __PACKAGE__->register_method(
4812 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4813 method => "get_all_at_reactors_in_use",
4818 { name => 'authtoken', type => 'string' }
4821 desc => 'list of reactor names', type => 'array'
4826 sub get_all_at_reactors_in_use {
4827 my ($self, $conn, $auth) = @_;
4829 my $e = new_editor(authtoken => $auth);
4830 $e->checkauth or return $e->die_event;
4831 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4833 my $reactors = $e->json_query({
4835 atevdef => [{column => "reactor", transform => "distinct"}]
4837 from => {atevdef => {}}
4840 return $e->die_event unless ref $reactors eq "ARRAY";
4843 return [ map { $_->{reactor} } @$reactors ];
4846 __PACKAGE__->register_method(
4847 method => "filter_group_entry_crud",
4848 api_name => "open-ils.actor.filter_group_entry.crud",
4851 Provides CRUD access to filter group entry objects. These are not full accessible
4852 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4853 are not accessible via PCRUD (because they have no fields against which to link perms)
4856 {desc => "Authentication token", type => "string"},
4857 {desc => "Entry ID / Entry Object", type => "number"},
4858 {desc => "Additional note text (optional)", type => "string"},
4859 {desc => "penalty org unit ID (optional, default to top of org tree)",
4863 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4869 sub filter_group_entry_crud {
4870 my ($self, $conn, $auth, $arg) = @_;
4872 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4873 my $e = new_editor(authtoken => $auth, xact => 1);
4874 return $e->die_event unless $e->checkauth;
4880 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4881 or return $e->die_event;
4883 return $e->die_event unless $e->allowed(
4884 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4886 my $query = $arg->query;
4887 $query = $e->create_actor_search_query($query) or return $e->die_event;
4888 $arg->query($query->id);
4889 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4890 $entry->query($query);
4895 } elsif ($arg->ischanged) {
4897 my $entry = $e->retrieve_actor_search_filter_group_entry([
4900 flesh_fields => {asfge => ['grp']}
4902 ]) or return $e->die_event;
4904 return $e->die_event unless $e->allowed(
4905 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4907 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4908 $arg->query($arg->query->id);
4909 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4910 $arg->query($query);
4915 } elsif ($arg->isdeleted) {
4917 my $entry = $e->retrieve_actor_search_filter_group_entry([
4920 flesh_fields => {asfge => ['grp', 'query']}
4922 ]) or return $e->die_event;
4924 return $e->die_event unless $e->allowed(
4925 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4927 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4928 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4941 my $entry = $e->retrieve_actor_search_filter_group_entry([
4944 flesh_fields => {asfge => ['grp', 'query']}
4946 ]) or return $e->die_event;
4948 return $e->die_event unless $e->allowed(
4949 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4950 $entry->grp->owner);
4953 $entry->grp($entry->grp->id); # for consistency