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 OpenILS::Utils::DateTime 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::Carousel;
30 use OpenILS::Application::Actor::Container;
31 use OpenILS::Application::Actor::ClosedDates;
32 use OpenILS::Application::Actor::UserGroups;
33 use OpenILS::Application::Actor::Friends;
34 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Application::Actor::Settings;
37 use OpenILS::Utils::CStoreEditor qw/:funcs/;
38 use OpenILS::Utils::Penalty;
39 use OpenILS::Utils::BadContact;
40 use List::Util qw/max reduce/;
42 use UUID::Tiny qw/:std/;
45 OpenILS::Application::Actor::Container->initialize();
46 OpenILS::Application::Actor::UserGroups->initialize();
47 OpenILS::Application::Actor::ClosedDates->initialize();
50 my $apputils = "OpenILS::Application::AppUtils";
53 sub _d { warn "Patron:\n" . Dumper(shift()); }
56 my $set_user_settings;
60 #__PACKAGE__->register_method(
61 # method => "allowed_test",
62 # api_name => "open-ils.actor.allowed_test",
65 # my($self, $conn, $auth, $orgid, $permcode) = @_;
66 # my $e = new_editor(authtoken => $auth);
67 # return $e->die_event unless $e->checkauth;
71 # permcode => $permcode,
72 # result => $e->allowed($permcode, $orgid)
76 __PACKAGE__->register_method(
77 method => "update_user_setting",
78 api_name => "open-ils.actor.patron.settings.update",
80 sub update_user_setting {
81 my($self, $conn, $auth, $user_id, $settings) = @_;
82 my $e = new_editor(xact => 1, authtoken => $auth);
83 return $e->die_event unless $e->checkauth;
85 $user_id = $e->requestor->id unless defined $user_id;
87 unless($e->requestor->id == $user_id) {
88 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
89 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
92 for my $name (keys %$settings) {
93 my $val = $$settings{$name};
94 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
97 $val = OpenSRF::Utils::JSON->perl2JSON($val);
100 $e->update_actor_user_setting($set) or return $e->die_event;
102 $set = Fieldmapper::actor::user_setting->new;
106 $e->create_actor_user_setting($set) or return $e->die_event;
109 $e->delete_actor_user_setting($set) or return $e->die_event;
118 __PACKAGE__->register_method(
119 method => "update_privacy_waiver",
120 api_name => "open-ils.actor.patron.privacy_waiver.update",
122 desc => "Replaces any existing privacy waiver entries for the patron with the supplied values.",
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'User ID', type => 'number'},
126 {desc => 'Arrayref of privacy waiver entries', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
131 sub update_privacy_waiver {
132 my($self, $conn, $auth, $user_id, $waiver) = @_;
133 my $e = new_editor(xact => 1, authtoken => $auth);
134 return $e->die_event unless $e->checkauth;
136 $user_id = $e->requestor->id unless defined $user_id;
138 unless($e->requestor->id == $user_id) {
139 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
140 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
143 foreach my $w (@$waiver) {
144 $w->{usr} = $user_id unless $w->{usr};
145 if ($w->{id} && $w->{id} ne 'new') {
146 my $existing_rows = $e->search_actor_usr_privacy_waiver({usr => $user_id, id => $w->{id}});
147 if ($existing_rows) {
148 my $existing = $existing_rows->[0];
149 # delete existing if name is empty
150 if (!$w->{name} or $w->{name} =~ /^\s*$/) {
151 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
153 # delete existing if none of the boxes were checked
154 } elsif (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}) {
155 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
157 # otherwise, update existing waiver entry
159 $existing->name($w->{name});
160 $existing->place_holds($w->{place_holds});
161 $existing->pickup_holds($w->{pickup_holds});
162 $existing->checkout_items($w->{checkout_items});
163 $existing->view_history($w->{view_history});
164 $e->update_actor_usr_privacy_waiver($existing) or return $e->die_event;
167 $logger->warn("No privacy waiver entry found for user $user_id with ID " . $w->{id});
171 # ignore new entries with empty name or with no boxes checked
172 next if (!$w->{name} or $w->{name} =~ /^\s*$/);
173 next if (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history});
174 my $new = Fieldmapper::actor::usr_privacy_waiver->new;
175 $new->usr($w->{usr});
176 $new->name($w->{name});
177 $new->place_holds($w->{place_holds});
178 $new->pickup_holds($w->{pickup_holds});
179 $new->checkout_items($w->{checkout_items});
180 $new->view_history($w->{view_history});
181 $e->create_actor_usr_privacy_waiver($new) or return $e->die_event;
190 __PACKAGE__->register_method(
191 method => "set_ou_settings",
192 api_name => "open-ils.actor.org_unit.settings.update",
194 desc => "Updates the value for a given org unit setting. The permission to update " .
195 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
196 "permission specified in the update_perm column of the config.org_unit_setting_type " .
197 "table's row corresponding to the setting being changed." ,
199 {desc => 'Authentication token', type => 'string'},
200 {desc => 'Org unit ID', type => 'number'},
201 {desc => 'Hash of setting name-value pairs', type => 'object'}
203 return => {desc => '1 on success, Event on error'}
207 sub set_ou_settings {
208 my( $self, $client, $auth, $org_id, $settings ) = @_;
210 my $e = new_editor(authtoken => $auth, xact => 1);
211 return $e->die_event unless $e->checkauth;
213 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
215 for my $name (keys %$settings) {
216 my $val = $$settings{$name};
218 my $type = $e->retrieve_config_org_unit_setting_type([
220 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
221 ]) or return $e->die_event;
222 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
224 # If there is no relevant permission, the default assumption will
225 # be, "no, the caller cannot change that value."
226 return $e->die_event unless ($all_allowed ||
227 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
230 $val = OpenSRF::Utils::JSON->perl2JSON($val);
233 $e->update_actor_org_unit_setting($set) or return $e->die_event;
235 $set = Fieldmapper::actor::org_unit_setting->new;
236 $set->org_unit($org_id);
239 $e->create_actor_org_unit_setting($set) or return $e->die_event;
242 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
250 __PACKAGE__->register_method(
251 method => "user_settings",
253 api_name => "open-ils.actor.patron.settings.retrieve",
256 my( $self, $client, $auth, $user_id, $setting ) = @_;
258 my $e = new_editor(authtoken => $auth);
259 return $e->event unless $e->checkauth;
260 $user_id = $e->requestor->id unless defined $user_id;
262 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
263 if($e->requestor->id != $user_id) {
264 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
268 my($e, $user_id, $setting) = @_;
269 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
270 return undef unless $val; # XXX this should really return undef, but needs testing
271 return OpenSRF::Utils::JSON->JSON2perl($val->value);
275 if(ref $setting eq 'ARRAY') {
277 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
280 return get_setting($e, $user_id, $setting);
283 my $s = $e->search_actor_user_setting({usr => $user_id});
284 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
289 __PACKAGE__->register_method(
290 method => "ranged_ou_settings",
291 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
293 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
294 "is implied for retrieving OU settings by the authenticated users' permissions.",
296 {desc => 'Authentication token', type => 'string'},
297 {desc => 'Org unit ID', type => 'number'},
299 return => {desc => 'A hashref of "ranged" settings, event on error'}
302 sub ranged_ou_settings {
303 my( $self, $client, $auth, $org_id ) = @_;
305 my $e = new_editor(authtoken => $auth);
306 return $e->event unless $e->checkauth;
309 my $org_list = $U->get_org_ancestors($org_id);
310 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
311 $org_list = [ reverse @$org_list ];
313 # start at the context org and capture the setting value
314 # without clobbering settings we've already captured
315 for my $this_org_id (@$org_list) {
317 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
319 for my $set (@sets) {
320 my $type = $e->retrieve_config_org_unit_setting_type([
322 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
325 # If there is no relevant permission, the default assumption will
326 # be, "yes, the caller can have that value."
327 if ($type && $type->view_perm) {
328 next if not $e->allowed($type->view_perm->code, $org_id);
331 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
332 unless defined $ranged_settings{$set->name};
336 return \%ranged_settings;
341 __PACKAGE__->register_method(
342 api_name => 'open-ils.actor.ou_setting.ancestor_default',
343 method => 'ou_ancestor_setting',
345 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
346 'This method will make sure that the given user has permission to view that setting, if there is a ' .
347 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
348 'the user lacks the permisssion, undef will be returned.' ,
350 { desc => 'Org unit ID', type => 'number' },
351 { desc => 'setting name', type => 'string' },
352 { desc => 'authtoken (optional)', type => 'string' }
354 return => {desc => 'A value for the org unit setting, or undef'}
358 # ------------------------------------------------------------------
359 # Attempts to find the org setting value for a given org. if not
360 # found at the requested org, searches up the org tree until it
361 # finds a parent that has the requested setting.
362 # when found, returns { org => $id, value => $value }
363 # otherwise, returns NULL
364 # ------------------------------------------------------------------
365 sub ou_ancestor_setting {
366 my( $self, $client, $orgid, $name, $auth ) = @_;
367 # Make sure $auth is set to something if not given.
369 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
372 __PACKAGE__->register_method(
373 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
374 method => 'ou_ancestor_setting_batch',
376 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
377 'This method will make sure that the given user has permission to view that setting, if there is a ' .
378 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
379 'the user lacks the permisssion, undef will be returned.' ,
381 { desc => 'Org unit ID', type => 'number' },
382 { desc => 'setting name list', type => 'array' },
383 { desc => 'authtoken (optional)', type => 'string' }
385 return => {desc => 'A hash with name => value pairs for the org unit settings'}
388 sub ou_ancestor_setting_batch {
389 my( $self, $client, $orgid, $name_list, $auth ) = @_;
391 # splitting the list of settings to fetch values
392 # so that ones that *don't* require view_perm checks
393 # can be fetched in one fell swoop, which is
394 # significantly faster in cases where a large
395 # number of settings need to be fetched.
396 my %perm_check_required = ();
397 my @perm_check_not_required = ();
399 # Note that ->ou_ancestor_setting also can check
400 # to see if the setting has a view_perm, but testing
401 # suggests that the redundant checks do not significantly
402 # increase the time it takes to fetch the values of
403 # permission-controlled settings.
404 my $e = new_editor();
405 my $res = $e->search_config_org_unit_setting_type({
407 view_perm => { "!=" => undef },
409 %perm_check_required = map { $_->name() => 1 } @$res;
410 foreach my $setting (@$name_list) {
411 push @perm_check_not_required, $setting
412 unless exists($perm_check_required{$setting});
416 if (@perm_check_not_required) {
417 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
419 $values{$_} = $U->ou_ancestor_setting(
422 ) for keys(%perm_check_required);
428 __PACKAGE__->register_method(
429 method => "update_patron",
430 api_name => "open-ils.actor.patron.update",
433 Update an existing user, or create a new one. Related objects,
434 like cards, addresses, survey responses, and stat cats,
435 can be updated by attaching them to the user object in their
436 respective fields. For examples, the billing address object
437 may be inserted into the 'billing_address' field, etc. For each
438 attached object, indicate if the object should be created,
439 updated, or deleted using the built-in 'isnew', 'ischanged',
440 and 'isdeleted' fields on the object.
443 { desc => 'Authentication token', type => 'string' },
444 { desc => 'Patron data object', type => 'object' }
446 return => {desc => 'A fleshed user object, event on error'}
451 my( $self, $client, $auth, $patron ) = @_;
453 my $e = new_editor(xact => 1, authtoken => $auth);
454 return $e->event unless $e->checkauth;
456 $logger->info($patron->isnew ? "Creating new patron..." :
457 "Updating Patron: " . $patron->id);
459 my $evt = check_group_perm($e, $e->requestor, $patron);
462 # $new_patron is the patron in progress. $patron is the original patron
463 # passed in with the method. new_patron will change as the components
464 # of patron are added/updated.
468 # unflesh the real items on the patron
469 $patron->card( $patron->card->id ) if(ref($patron->card));
470 $patron->billing_address( $patron->billing_address->id )
471 if(ref($patron->billing_address));
472 $patron->mailing_address( $patron->mailing_address->id )
473 if(ref($patron->mailing_address));
475 # create/update the patron first so we can use his id
477 # $patron is the obj from the client (new data) and $new_patron is the
478 # patron object properly built for db insertion, so we need a third variable
479 # if we want to represent the old patron.
482 my $barred_hook = '';
484 if($patron->isnew()) {
485 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
487 if($U->is_true($patron->barred)) {
488 return $e->die_event unless
489 $e->allowed('BAR_PATRON', $patron->home_ou);
492 $new_patron = $patron;
494 # Did auth checking above already.
495 $old_patron = $e->retrieve_actor_user($patron->id) or
496 return $e->die_event;
498 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
499 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
500 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
502 $barred_hook = $U->is_true($new_patron->barred) ?
503 'au.barred' : 'au.unbarred';
506 # update the password by itself to avoid the password protection magic
507 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
508 modify_migrated_user_password($e, $patron->id, $patron->passwd);
509 $new_patron->passwd(''); # subsequent update will set
510 # actor.usr.passwd to MD5('')
514 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
517 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
520 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
523 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
526 # re-update the patron if anything has happened to him during this process
527 if($new_patron->ischanged()) {
528 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
532 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
535 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
538 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
541 $evt = apply_invalid_addr_penalty($e, $patron);
546 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
548 $tses->request('open-ils.trigger.event.autocreate',
549 'au.create', $new_patron, $new_patron->home_ou);
551 $tses->request('open-ils.trigger.event.autocreate',
552 'au.update', $new_patron, $new_patron->home_ou);
554 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
555 $new_patron, $new_patron->home_ou) if $barred_hook;
558 $e->xact_begin; # $e->rollback is called in new_flesh_user
559 return flesh_user($new_patron->id(), $e);
562 sub apply_invalid_addr_penalty {
566 # grab the invalid address penalty if set
567 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
569 my ($addr_penalty) = grep
570 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
572 # do we enforce invalid address penalty
573 my $enforce = $U->ou_ancestor_setting_value(
574 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
576 my $addrs = $e->search_actor_user_address(
577 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
578 my $addr_count = scalar(@$addrs);
580 if($addr_count == 0 and $addr_penalty) {
582 # regardless of any settings, remove the penalty when the user has no invalid addresses
583 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
586 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
588 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
589 my $depth = $ptype->org_depth;
590 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
591 $ctx_org = $patron->home_ou unless defined $ctx_org;
593 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
594 $penalty->usr($patron->id);
595 $penalty->org_unit($ctx_org);
596 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
598 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
613 "standing_penalties",
623 push @$fields, "home_ou" if $home_ou;
624 return new_flesh_user($id, $fields, $e );
632 # clone and clear stuff that would break the database
636 my $new_patron = $patron->clone;
638 $new_patron->clear_billing_address();
639 $new_patron->clear_mailing_address();
640 $new_patron->clear_addresses();
641 $new_patron->clear_card();
642 $new_patron->clear_cards();
643 $new_patron->clear_id();
644 $new_patron->clear_isnew();
645 $new_patron->clear_ischanged();
646 $new_patron->clear_isdeleted();
647 $new_patron->clear_stat_cat_entries();
648 $new_patron->clear_waiver_entries();
649 $new_patron->clear_permissions();
650 $new_patron->clear_standing_penalties();
661 return (undef, $e->die_event) unless
662 $e->allowed('CREATE_USER', $patron->home_ou);
664 my $ex = $e->search_actor_user(
665 {usrname => $patron->usrname}, {idlist => 1});
666 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
668 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
670 # do a dance to get the password hashed securely
671 my $saved_password = $patron->passwd;
673 $e->create_actor_user($patron) or return (undef, $e->die_event);
674 modify_migrated_user_password($e, $patron->id, $saved_password);
676 my $id = $patron->id; # added by CStoreEditor
678 $logger->info("Successfully created new user [$id] in DB");
679 return ($e->retrieve_actor_user($id), undef);
683 sub check_group_perm {
684 my( $e, $requestor, $patron ) = @_;
687 # first let's see if the requestor has
688 # priveleges to update this user in any way
689 if( ! $patron->isnew ) {
690 my $p = $e->retrieve_actor_user($patron->id);
692 # If we are the requestor (trying to update our own account)
693 # and we are not trying to change our profile, we're good
694 if( $p->id == $requestor->id and
695 $p->profile == $patron->profile ) {
700 $evt = group_perm_failed($e, $requestor, $p);
704 # They are allowed to edit this patron.. can they put the
705 # patron into the group requested?
706 $evt = group_perm_failed($e, $requestor, $patron);
712 sub group_perm_failed {
713 my( $e, $requestor, $patron ) = @_;
717 my $grpid = $patron->profile;
721 $logger->debug("user update looking for group perm for group $grpid");
722 $grp = $e->retrieve_permission_grp_tree($grpid);
724 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
726 $logger->info("user update checking perm $perm on user ".
727 $requestor->id." for update/create on user username=".$patron->usrname);
729 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
735 my( $e, $patron, $noperm) = @_;
737 $logger->info("Updating patron ".$patron->id." in DB");
742 return (undef, $e->die_event)
743 unless $e->allowed('UPDATE_USER', $patron->home_ou);
746 if(!$patron->ident_type) {
747 $patron->clear_ident_type;
748 $patron->clear_ident_value;
751 $evt = verify_last_xact($e, $patron);
752 return (undef, $evt) if $evt;
754 $e->update_actor_user($patron) or return (undef, $e->die_event);
756 # re-fetch the user to pick up the latest last_xact_id value
757 # to avoid collisions.
758 $patron = $e->retrieve_actor_user($patron->id);
763 sub verify_last_xact {
764 my( $e, $patron ) = @_;
765 return undef unless $patron->id and $patron->id > 0;
766 my $p = $e->retrieve_actor_user($patron->id);
767 my $xact = $p->last_xact_id;
768 return undef unless $xact;
769 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
770 return OpenILS::Event->new('XACT_COLLISION')
771 if $xact ne $patron->last_xact_id;
776 sub _check_dup_ident {
777 my( $session, $patron ) = @_;
779 return undef unless $patron->ident_value;
782 ident_type => $patron->ident_type,
783 ident_value => $patron->ident_value,
786 $logger->debug("patron update searching for dup ident values: " .
787 $patron->ident_type . ':' . $patron->ident_value);
789 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
791 my $dups = $session->request(
792 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
795 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
802 sub _add_update_addresses {
806 my $new_patron = shift;
810 my $current_id; # id of the address before creation
812 my $addresses = $patron->addresses();
814 for my $address (@$addresses) {
816 next unless ref $address;
817 $current_id = $address->id();
819 if( $patron->billing_address() and
820 $patron->billing_address() == $current_id ) {
821 $logger->info("setting billing addr to $current_id");
822 $new_patron->billing_address($address->id());
823 $new_patron->ischanged(1);
826 if( $patron->mailing_address() and
827 $patron->mailing_address() == $current_id ) {
828 $new_patron->mailing_address($address->id());
829 $logger->info("setting mailing addr to $current_id");
830 $new_patron->ischanged(1);
834 if($address->isnew()) {
836 $address->usr($new_patron->id());
838 ($address, $evt) = _add_address($e,$address);
839 return (undef, $evt) if $evt;
841 # we need to get the new id
842 if( $patron->billing_address() and
843 $patron->billing_address() == $current_id ) {
844 $new_patron->billing_address($address->id());
845 $logger->info("setting billing addr to $current_id");
846 $new_patron->ischanged(1);
849 if( $patron->mailing_address() and
850 $patron->mailing_address() == $current_id ) {
851 $new_patron->mailing_address($address->id());
852 $logger->info("setting mailing addr to $current_id");
853 $new_patron->ischanged(1);
856 } elsif($address->ischanged() ) {
858 ($address, $evt) = _update_address($e, $address);
859 return (undef, $evt) if $evt;
861 } elsif($address->isdeleted() ) {
863 if( $address->id() == $new_patron->mailing_address() ) {
864 $new_patron->clear_mailing_address();
865 ($new_patron, $evt) = _update_patron($e, $new_patron);
866 return (undef, $evt) if $evt;
869 if( $address->id() == $new_patron->billing_address() ) {
870 $new_patron->clear_billing_address();
871 ($new_patron, $evt) = _update_patron($e, $new_patron);
872 return (undef, $evt) if $evt;
875 $evt = _delete_address($e, $address);
876 return (undef, $evt) if $evt;
880 return ( $new_patron, undef );
884 # adds an address to the db and returns the address with new id
886 my($e, $address) = @_;
887 $address->clear_id();
889 $logger->info("Creating new address at street ".$address->street1);
891 # put the address into the database
892 $e->create_actor_user_address($address) or return (undef, $e->die_event);
893 return ($address, undef);
897 sub _update_address {
898 my( $e, $address ) = @_;
900 $logger->info("Updating address ".$address->id." in the DB");
902 $e->update_actor_user_address($address) or return (undef, $e->die_event);
904 return ($address, undef);
909 sub _add_update_cards {
913 my $new_patron = shift;
917 my $virtual_id; #id of the card before creation
919 my $cards = $patron->cards();
920 for my $card (@$cards) {
922 $card->usr($new_patron->id());
924 if(ref($card) and $card->isnew()) {
926 $virtual_id = $card->id();
927 ( $card, $evt ) = _add_card($e, $card);
928 return (undef, $evt) if $evt;
930 #if(ref($patron->card)) { $patron->card($patron->card->id); }
931 if($patron->card() == $virtual_id) {
932 $new_patron->card($card->id());
933 $new_patron->ischanged(1);
936 } elsif( ref($card) and $card->ischanged() ) {
937 $evt = _update_card($e, $card);
938 return (undef, $evt) if $evt;
942 return ( $new_patron, undef );
946 # adds an card to the db and returns the card with new id
948 my( $e, $card ) = @_;
951 $logger->info("Adding new patron card ".$card->barcode);
953 $e->create_actor_card($card) or return (undef, $e->die_event);
955 return ( $card, undef );
959 # returns event on error. returns undef otherwise
961 my( $e, $card ) = @_;
962 $logger->info("Updating patron card ".$card->id);
964 $e->update_actor_card($card) or return $e->die_event;
969 sub _add_update_waiver_entries {
972 my $new_patron = shift;
975 my $waiver_entries = $patron->waiver_entries();
976 for my $waiver (@$waiver_entries) {
977 next unless ref $waiver;
978 $waiver->usr($new_patron->id());
979 if ($waiver->isnew()) {
980 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
981 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
982 $logger->info("Adding new patron waiver entry");
984 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
985 } elsif ($waiver->ischanged()) {
986 $logger->info("Updating patron waiver entry " . $waiver->id);
987 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
988 } elsif ($waiver->isdeleted()) {
989 $logger->info("Deleting patron waiver entry " . $waiver->id);
990 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
993 return ($new_patron, undef);
997 # returns event on error. returns undef otherwise
998 sub _delete_address {
999 my( $e, $address ) = @_;
1001 $logger->info("Deleting address ".$address->id." from DB");
1003 $e->delete_actor_user_address($address) or return $e->die_event;
1009 sub _add_survey_responses {
1010 my ($e, $patron, $new_patron) = @_;
1012 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1014 my $responses = $patron->survey_responses;
1018 $_->usr($new_patron->id) for (@$responses);
1020 my $evt = $U->simplereq( "open-ils.circ",
1021 "open-ils.circ.survey.submit.user_id", $responses );
1023 return (undef, $evt) if defined($U->event_code($evt));
1027 return ( $new_patron, undef );
1030 sub _clear_badcontact_penalties {
1031 my ($e, $old_patron, $new_patron) = @_;
1033 return ($new_patron, undef) unless $old_patron;
1035 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1037 # This ignores whether the caller of update_patron has any permission
1038 # to remove penalties, but these penalties no longer make sense
1039 # if an email address field (for example) is changed (and the caller must
1040 # have perms to do *that*) so there's no reason not to clear the penalties.
1042 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1044 "+csp" => {"name" => [values(%$PNM)]},
1045 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1047 "join" => {"csp" => {}},
1049 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1051 ]) or return (undef, $e->die_event);
1053 return ($new_patron, undef) unless @$bad_contact_penalties;
1055 my @penalties_to_clear;
1056 my ($field, $penalty_name);
1058 # For each field that might have an associated bad contact penalty,
1059 # check for such penalties and add them to the to-clear list if that
1060 # field has changed.
1061 while (($field, $penalty_name) = each(%$PNM)) {
1062 if ($old_patron->$field ne $new_patron->$field) {
1063 push @penalties_to_clear, grep {
1064 $_->standing_penalty->name eq $penalty_name
1065 } @$bad_contact_penalties;
1069 foreach (@penalties_to_clear) {
1070 # Note that this "archives" penalties, in the terminology of the staff
1071 # client, instead of just deleting them. This may assist reporting,
1072 # or preserving old contact information when it is still potentially
1074 $_->standing_penalty($_->standing_penalty->id); # deflesh
1075 $_->stop_date('now');
1076 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1079 return ($new_patron, undef);
1083 sub _create_stat_maps {
1085 my($e, $patron, $new_patron) = @_;
1087 my $maps = $patron->stat_cat_entries();
1089 for my $map (@$maps) {
1091 my $method = "update_actor_stat_cat_entry_user_map";
1093 if ($map->isdeleted()) {
1094 $method = "delete_actor_stat_cat_entry_user_map";
1096 } elsif ($map->isnew()) {
1097 $method = "create_actor_stat_cat_entry_user_map";
1102 $map->target_usr($new_patron->id);
1104 $logger->info("Updating stat entry with method $method and map $map");
1106 $e->$method($map) or return (undef, $e->die_event);
1109 return ($new_patron, undef);
1112 sub _create_perm_maps {
1114 my($e, $patron, $new_patron) = @_;
1116 my $maps = $patron->permissions;
1118 for my $map (@$maps) {
1120 my $method = "update_permission_usr_perm_map";
1121 if ($map->isdeleted()) {
1122 $method = "delete_permission_usr_perm_map";
1123 } elsif ($map->isnew()) {
1124 $method = "create_permission_usr_perm_map";
1128 $map->usr($new_patron->id);
1130 $logger->info( "Updating permissions with method $method and map $map" );
1132 $e->$method($map) or return (undef, $e->die_event);
1135 return ($new_patron, undef);
1139 __PACKAGE__->register_method(
1140 method => "set_user_work_ous",
1141 api_name => "open-ils.actor.user.work_ous.update",
1144 sub set_user_work_ous {
1150 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1151 return $evt if $evt;
1153 my $session = $apputils->start_db_session();
1154 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1156 for my $map (@$maps) {
1158 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1159 if ($map->isdeleted()) {
1160 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1161 } elsif ($map->isnew()) {
1162 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1166 #warn( "Updating permissions with method $method and session $ses and map $map" );
1167 $logger->info( "Updating work_ou map with method $method and map $map" );
1169 my $stat = $session->request($method, $map)->gather(1);
1170 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1174 $apputils->commit_db_session($session);
1176 return scalar(@$maps);
1180 __PACKAGE__->register_method(
1181 method => "set_user_perms",
1182 api_name => "open-ils.actor.user.permissions.update",
1185 sub set_user_perms {
1191 my $session = $apputils->start_db_session();
1193 my( $user_obj, $evt ) = $U->checkses($ses);
1194 return $evt if $evt;
1195 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1197 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1200 $all = 1 if ($U->is_true($user_obj->super_user()));
1201 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1203 for my $map (@$maps) {
1205 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1206 if ($map->isdeleted()) {
1207 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1208 } elsif ($map->isnew()) {
1209 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1213 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1214 #warn( "Updating permissions with method $method and session $ses and map $map" );
1215 $logger->info( "Updating permissions with method $method and map $map" );
1217 my $stat = $session->request($method, $map)->gather(1);
1218 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1222 $apputils->commit_db_session($session);
1224 return scalar(@$maps);
1228 __PACKAGE__->register_method(
1229 method => "user_retrieve_by_barcode",
1231 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1233 sub user_retrieve_by_barcode {
1234 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1236 my $e = new_editor(authtoken => $auth);
1237 return $e->event unless $e->checkauth;
1239 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1240 or return $e->event;
1242 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1243 return $e->event unless $e->allowed(
1244 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1251 __PACKAGE__->register_method(
1252 method => "get_user_by_id",
1254 api_name => "open-ils.actor.user.retrieve",
1257 sub get_user_by_id {
1258 my ($self, $client, $auth, $id) = @_;
1259 my $e = new_editor(authtoken=>$auth);
1260 return $e->event unless $e->checkauth;
1261 my $user = $e->retrieve_actor_user($id) or return $e->event;
1262 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1267 __PACKAGE__->register_method(
1268 method => "get_org_types",
1269 api_name => "open-ils.actor.org_types.retrieve",
1272 return $U->get_org_types();
1276 __PACKAGE__->register_method(
1277 method => "get_user_ident_types",
1278 api_name => "open-ils.actor.user.ident_types.retrieve",
1281 sub get_user_ident_types {
1282 return $ident_types if $ident_types;
1283 return $ident_types =
1284 new_editor()->retrieve_all_config_identification_type();
1288 __PACKAGE__->register_method(
1289 method => "get_org_unit",
1290 api_name => "open-ils.actor.org_unit.retrieve",
1294 my( $self, $client, $user_session, $org_id ) = @_;
1295 my $e = new_editor(authtoken => $user_session);
1297 return $e->event unless $e->checkauth;
1298 $org_id = $e->requestor->ws_ou;
1300 my $o = $e->retrieve_actor_org_unit($org_id)
1301 or return $e->event;
1305 __PACKAGE__->register_method(
1306 method => "search_org_unit",
1307 api_name => "open-ils.actor.org_unit_list.search",
1310 sub search_org_unit {
1312 my( $self, $client, $field, $value ) = @_;
1314 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1316 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1317 { $field => $value } );
1323 # build the org tree
1325 __PACKAGE__->register_method(
1326 method => "get_org_tree",
1327 api_name => "open-ils.actor.org_tree.retrieve",
1329 note => "Returns the entire org tree structure",
1335 return $U->get_org_tree($client->session->session_locale);
1339 __PACKAGE__->register_method(
1340 method => "get_org_descendants",
1341 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1344 # depth is optional. org_unit is the id
1345 sub get_org_descendants {
1346 my( $self, $client, $org_unit, $depth ) = @_;
1348 if(ref $org_unit eq 'ARRAY') {
1351 for my $i (0..scalar(@$org_unit)-1) {
1352 my $list = $U->simple_scalar_request(
1354 "open-ils.storage.actor.org_unit.descendants.atomic",
1355 $org_unit->[$i], $depth->[$i] );
1356 push(@trees, $U->build_org_tree($list));
1361 my $orglist = $apputils->simple_scalar_request(
1363 "open-ils.storage.actor.org_unit.descendants.atomic",
1364 $org_unit, $depth );
1365 return $U->build_org_tree($orglist);
1370 __PACKAGE__->register_method(
1371 method => "get_org_ancestors",
1372 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1375 # depth is optional. org_unit is the id
1376 sub get_org_ancestors {
1377 my( $self, $client, $org_unit, $depth ) = @_;
1378 my $orglist = $apputils->simple_scalar_request(
1380 "open-ils.storage.actor.org_unit.ancestors.atomic",
1381 $org_unit, $depth );
1382 return $U->build_org_tree($orglist);
1386 __PACKAGE__->register_method(
1387 method => "get_standings",
1388 api_name => "open-ils.actor.standings.retrieve"
1393 return $user_standings if $user_standings;
1394 return $user_standings =
1395 $apputils->simple_scalar_request(
1397 "open-ils.cstore.direct.config.standing.search.atomic",
1398 { id => { "!=" => undef } }
1403 __PACKAGE__->register_method(
1404 method => "get_my_org_path",
1405 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1408 sub get_my_org_path {
1409 my( $self, $client, $auth, $org_id ) = @_;
1410 my $e = new_editor(authtoken=>$auth);
1411 return $e->event unless $e->checkauth;
1412 $org_id = $e->requestor->ws_ou unless defined $org_id;
1414 return $apputils->simple_scalar_request(
1416 "open-ils.storage.actor.org_unit.full_path.atomic",
1421 __PACKAGE__->register_method(
1422 method => "patron_adv_search",
1423 api_name => "open-ils.actor.patron.search.advanced"
1426 __PACKAGE__->register_method(
1427 method => "patron_adv_search",
1428 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1430 # Flush the response stream at most 5 patrons in for UI responsiveness.
1431 max_bundle_count => 5,
1433 desc => q/Returns a stream of fleshed user objects instead of
1434 a pile of identifiers/
1438 sub patron_adv_search {
1439 my( $self, $client, $auth, $search_hash, $search_limit,
1440 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1442 # API params sanity checks.
1443 # Exit early with empty result if no filter exists.
1444 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1445 my $fleshed = ($self->api_name =~ /fleshed/);
1446 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1448 for my $key (keys %$search_hash) {
1449 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1453 return ($fleshed ? undef : []) unless $search_ok;
1455 my $e = new_editor(authtoken=>$auth);
1456 return $e->event unless $e->checkauth;
1457 return $e->event unless $e->allowed('VIEW_USER');
1459 # depth boundary outside of which patrons must opt-in, default to 0
1460 my $opt_boundary = 0;
1461 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1463 if (not defined $search_ou) {
1464 my $depth = $U->ou_ancestor_setting_value(
1465 $e->requestor->ws_ou,
1466 'circ.patron_edit.duplicate_patron_check_depth'
1469 if (defined $depth) {
1470 $search_ou = $U->org_unit_ancestor_at_depth(
1471 $e->requestor->ws_ou, $depth
1476 my $ids = $U->storagereq(
1477 "open-ils.storage.actor.user.crazy_search", $search_hash,
1478 $search_limit, $search_sort, $include_inactive,
1479 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1481 return $ids unless $self->api_name =~ /fleshed/;
1483 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1489 # A migrated (main) password has the form:
1490 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1491 sub modify_migrated_user_password {
1492 my ($e, $user_id, $passwd) = @_;
1494 # new password gets a new salt
1495 my $new_salt = $e->json_query({
1496 from => ['actor.create_salt', 'main']})->[0];
1497 $new_salt = $new_salt->{'actor.create_salt'};
1504 md5_hex($new_salt . md5_hex($passwd)),
1512 __PACKAGE__->register_method(
1513 method => "update_passwd",
1514 api_name => "open-ils.actor.user.password.update",
1516 desc => "Update the operator's password",
1518 { desc => 'Authentication token', type => 'string' },
1519 { desc => 'New password', type => 'string' },
1520 { desc => 'Current password', type => 'string' }
1522 return => {desc => '1 on success, Event on error or incorrect current password'}
1526 __PACKAGE__->register_method(
1527 method => "update_passwd",
1528 api_name => "open-ils.actor.user.username.update",
1530 desc => "Update the operator's username",
1532 { desc => 'Authentication token', type => 'string' },
1533 { desc => 'New username', type => 'string' },
1534 { desc => 'Current password', type => 'string' }
1536 return => {desc => '1 on success, Event on error or incorrect current password'}
1540 __PACKAGE__->register_method(
1541 method => "update_passwd",
1542 api_name => "open-ils.actor.user.email.update",
1544 desc => "Update the operator's email address",
1546 { desc => 'Authentication token', type => 'string' },
1547 { desc => 'New email address', type => 'string' },
1548 { desc => 'Current password', type => 'string' }
1550 return => {desc => '1 on success, Event on error or incorrect current password'}
1555 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1556 my $e = new_editor(xact=>1, authtoken=>$auth);
1557 return $e->die_event unless $e->checkauth;
1559 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1560 or return $e->die_event;
1561 my $api = $self->api_name;
1563 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1565 return new OpenILS::Event('INCORRECT_PASSWORD');
1568 if( $api =~ /password/o ) {
1569 # NOTE: with access to the plain text password we could crypt
1570 # the password without the extra MD5 pre-hashing. Other changes
1571 # would be required. Noting here for future reference.
1572 modify_migrated_user_password($e, $db_user->id, $new_val);
1573 $db_user->passwd('');
1577 # if we don't clear the password, the user will be updated with
1578 # a hashed version of the hashed version of their password
1579 $db_user->clear_passwd;
1581 if( $api =~ /username/o ) {
1583 # make sure no one else has this username
1584 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1587 return new OpenILS::Event('USERNAME_EXISTS');
1589 $db_user->usrname($new_val);
1591 } elsif( $api =~ /email/o ) {
1592 $db_user->email($new_val);
1596 $e->update_actor_user($db_user) or return $e->die_event;
1599 # update the cached user to pick up these changes
1600 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1606 __PACKAGE__->register_method(
1607 method => "check_user_perms",
1608 api_name => "open-ils.actor.user.perm.check",
1609 notes => <<" NOTES");
1610 Takes a login session, user id, an org id, and an array of perm type strings. For each
1611 perm type, if the user does *not* have the given permission it is added
1612 to a list which is returned from the method. If all permissions
1613 are allowed, an empty list is returned
1614 if the logged in user does not match 'user_id', then the logged in user must
1615 have VIEW_PERMISSION priveleges.
1618 sub check_user_perms {
1619 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1621 my( $staff, $evt ) = $apputils->checkses($login_session);
1622 return $evt if $evt;
1624 if($staff->id ne $user_id) {
1625 if( $evt = $apputils->check_perms(
1626 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1632 for my $perm (@$perm_types) {
1633 if($apputils->check_perms($user_id, $org_id, $perm)) {
1634 push @not_allowed, $perm;
1638 return \@not_allowed
1641 __PACKAGE__->register_method(
1642 method => "check_user_perms2",
1643 api_name => "open-ils.actor.user.perm.check.multi_org",
1645 Checks the permissions on a list of perms and orgs for a user
1646 @param authtoken The login session key
1647 @param user_id The id of the user to check
1648 @param orgs The array of org ids
1649 @param perms The array of permission names
1650 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1651 if the logged in user does not match 'user_id', then the logged in user must
1652 have VIEW_PERMISSION priveleges.
1655 sub check_user_perms2 {
1656 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1658 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1659 $authtoken, $user_id, 'VIEW_PERMISSION' );
1660 return $evt if $evt;
1663 for my $org (@$orgs) {
1664 for my $perm (@$perms) {
1665 if($apputils->check_perms($user_id, $org, $perm)) {
1666 push @not_allowed, [ $org, $perm ];
1671 return \@not_allowed
1675 __PACKAGE__->register_method(
1676 method => 'check_user_perms3',
1677 api_name => 'open-ils.actor.user.perm.highest_org',
1679 Returns the highest org unit id at which a user has a given permission
1680 If the requestor does not match the target user, the requestor must have
1681 'VIEW_PERMISSION' rights at the home org unit of the target user
1682 @param authtoken The login session key
1683 @param userid The id of the user in question
1684 @param perm The permission to check
1685 @return The org unit highest in the org tree within which the user has
1686 the requested permission
1689 sub check_user_perms3 {
1690 my($self, $client, $authtoken, $user_id, $perm) = @_;
1691 my $e = new_editor(authtoken=>$authtoken);
1692 return $e->event unless $e->checkauth;
1694 my $tree = $U->get_org_tree();
1696 unless($e->requestor->id == $user_id) {
1697 my $user = $e->retrieve_actor_user($user_id)
1698 or return $e->event;
1699 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1700 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1703 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1706 __PACKAGE__->register_method(
1707 method => 'user_has_work_perm_at',
1708 api_name => 'open-ils.actor.user.has_work_perm_at',
1712 Returns a set of org unit IDs which represent the highest orgs in
1713 the org tree where the user has the requested permission. The
1714 purpose of this method is to return the smallest set of org units
1715 which represent the full expanse of the user's ability to perform
1716 the requested action. The user whose perms this method should
1717 check is implied by the authtoken. /,
1719 {desc => 'authtoken', type => 'string'},
1720 {desc => 'permission name', type => 'string'},
1721 {desc => q/user id, optional. If present, check perms for
1722 this user instead of the logged in user/, type => 'number'},
1724 return => {desc => 'An array of org IDs'}
1728 sub user_has_work_perm_at {
1729 my($self, $conn, $auth, $perm, $user_id) = @_;
1730 my $e = new_editor(authtoken=>$auth);
1731 return $e->event unless $e->checkauth;
1732 if(defined $user_id) {
1733 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1734 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1736 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1739 __PACKAGE__->register_method(
1740 method => 'user_has_work_perm_at_batch',
1741 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1745 sub user_has_work_perm_at_batch {
1746 my($self, $conn, $auth, $perms, $user_id) = @_;
1747 my $e = new_editor(authtoken=>$auth);
1748 return $e->event unless $e->checkauth;
1749 if(defined $user_id) {
1750 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1751 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1754 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1760 __PACKAGE__->register_method(
1761 method => 'check_user_perms4',
1762 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1764 Returns the highest org unit id at which a user has a given permission
1765 If the requestor does not match the target user, the requestor must have
1766 'VIEW_PERMISSION' rights at the home org unit of the target user
1767 @param authtoken The login session key
1768 @param userid The id of the user in question
1769 @param perms An array of perm names to check
1770 @return An array of orgId's representing the org unit
1771 highest in the org tree within which the user has the requested permission
1772 The arrah of orgId's has matches the order of the perms array
1775 sub check_user_perms4 {
1776 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1778 my( $staff, $target, $org, $evt );
1780 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1781 $authtoken, $userid, 'VIEW_PERMISSION' );
1782 return $evt if $evt;
1785 return [] unless ref($perms);
1786 my $tree = $U->get_org_tree();
1788 for my $p (@$perms) {
1789 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1795 __PACKAGE__->register_method(
1796 method => "user_fines_summary",
1797 api_name => "open-ils.actor.user.fines.summary",
1800 desc => 'Returns a short summary of the users total open fines, ' .
1801 'excluding voided fines Params are login_session, user_id' ,
1803 {desc => 'Authentication token', type => 'string'},
1804 {desc => 'User ID', type => 'string'} # number?
1807 desc => "a 'mous' object, event on error",
1812 sub user_fines_summary {
1813 my( $self, $client, $auth, $user_id ) = @_;
1815 my $e = new_editor(authtoken=>$auth);
1816 return $e->event unless $e->checkauth;
1818 if( $user_id ne $e->requestor->id ) {
1819 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1820 return $e->event unless
1821 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1824 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1828 __PACKAGE__->register_method(
1829 method => "user_opac_vitals",
1830 api_name => "open-ils.actor.user.opac.vital_stats",
1834 desc => 'Returns a short summary of the users vital stats, including ' .
1835 'identification information, accumulated balance, number of holds, ' .
1836 'and current open circulation stats' ,
1838 {desc => 'Authentication token', type => 'string'},
1839 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1842 desc => "An object with four properties: user, fines, checkouts and holds."
1847 sub user_opac_vitals {
1848 my( $self, $client, $auth, $user_id ) = @_;
1850 my $e = new_editor(authtoken=>$auth);
1851 return $e->event unless $e->checkauth;
1853 $user_id ||= $e->requestor->id;
1855 my $user = $e->retrieve_actor_user( $user_id );
1858 ->method_lookup('open-ils.actor.user.fines.summary')
1859 ->run($auth => $user_id);
1860 return $fines if (defined($U->event_code($fines)));
1863 $fines = new Fieldmapper::money::open_user_summary ();
1864 $fines->balance_owed(0.00);
1865 $fines->total_owed(0.00);
1866 $fines->total_paid(0.00);
1867 $fines->usr($user_id);
1871 ->method_lookup('open-ils.actor.user.hold_requests.count')
1872 ->run($auth => $user_id);
1873 return $holds if (defined($U->event_code($holds)));
1876 ->method_lookup('open-ils.actor.user.checked_out.count')
1877 ->run($auth => $user_id);
1878 return $out if (defined($U->event_code($out)));
1880 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1882 my $unread_msgs = $e->search_actor_usr_message([
1883 {usr => $user_id, read_date => undef, deleted => 'f'},
1889 first_given_name => $user->first_given_name,
1890 second_given_name => $user->second_given_name,
1891 family_name => $user->family_name,
1892 alias => $user->alias,
1893 usrname => $user->usrname
1895 fines => $fines->to_bare_hash,
1898 messages => { unread => scalar(@$unread_msgs) }
1903 ##### a small consolidation of related method registrations
1904 my $common_params = [
1905 { desc => 'Authentication token', type => 'string' },
1906 { desc => 'User ID', type => 'string' },
1907 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1908 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1911 'open-ils.actor.user.transactions' => '',
1912 'open-ils.actor.user.transactions.fleshed' => '',
1913 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1914 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1915 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1916 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1919 foreach (keys %methods) {
1921 method => "user_transactions",
1924 desc => 'For a given user, retrieve a list of '
1925 . (/\.fleshed/ ? 'fleshed ' : '')
1926 . 'transactions' . $methods{$_}
1927 . ' optionally limited to transactions of a given type.',
1928 params => $common_params,
1930 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1931 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1935 $args{authoritative} = 1;
1936 __PACKAGE__->register_method(%args);
1939 # Now for the counts
1941 'open-ils.actor.user.transactions.count' => '',
1942 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1943 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1946 foreach (keys %methods) {
1948 method => "user_transactions",
1951 desc => 'For a given user, retrieve a count of open '
1952 . 'transactions' . $methods{$_}
1953 . ' optionally limited to transactions of a given type.',
1954 params => $common_params,
1955 return => { desc => "Integer count of transactions, or event on error" }
1958 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1959 __PACKAGE__->register_method(%args);
1962 __PACKAGE__->register_method(
1963 method => "user_transactions",
1964 api_name => "open-ils.actor.user.transactions.have_balance.total",
1967 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1968 . ' optionally limited to transactions of a given type.',
1969 params => $common_params,
1970 return => { desc => "Decimal balance value, or event on error" }
1975 sub user_transactions {
1976 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1979 my $e = new_editor(authtoken => $auth);
1980 return $e->event unless $e->checkauth;
1982 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1984 return $e->event unless
1985 $e->requestor->id == $user_id or
1986 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1988 my $api = $self->api_name();
1990 my $filter = ($api =~ /have_balance/o) ?
1991 { 'balance_owed' => { '<>' => 0 } }:
1992 { 'total_owed' => { '>' => 0 } };
1994 my $method = 'open-ils.actor.user.transactions.history.still_open';
1995 $method = "$method.authoritative" if $api =~ /authoritative/;
1996 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1998 if($api =~ /total/o) {
2000 $total += $_->balance_owed for @$trans;
2004 ($api =~ /count/o ) and return scalar @$trans;
2005 ($api !~ /fleshed/o) and return $trans;
2008 for my $t (@$trans) {
2010 if( $t->xact_type ne 'circulation' ) {
2011 push @resp, {transaction => $t};
2015 my $circ_data = flesh_circ($e, $t->id);
2016 push @resp, {transaction => $t, %$circ_data};
2023 __PACKAGE__->register_method(
2024 method => "user_transaction_retrieve",
2025 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2028 notes => "Returns a fleshed transaction record"
2031 __PACKAGE__->register_method(
2032 method => "user_transaction_retrieve",
2033 api_name => "open-ils.actor.user.transaction.retrieve",
2036 notes => "Returns a transaction record"
2039 sub user_transaction_retrieve {
2040 my($self, $client, $auth, $bill_id) = @_;
2042 my $e = new_editor(authtoken => $auth);
2043 return $e->event unless $e->checkauth;
2045 my $trans = $e->retrieve_money_billable_transaction_summary(
2046 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2048 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2050 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2052 return $trans unless $self->api_name =~ /flesh/;
2053 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2055 my $circ_data = flesh_circ($e, $trans->id, 1);
2057 return {transaction => $trans, %$circ_data};
2062 my $circ_id = shift;
2063 my $flesh_copy = shift;
2065 my $circ = $e->retrieve_action_circulation([
2069 circ => ['target_copy'],
2070 acp => ['call_number'],
2077 my $copy = $circ->target_copy;
2079 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2080 $mods = new Fieldmapper::metabib::virtual_record;
2081 $mods->doc_id(OILS_PRECAT_RECORD);
2082 $mods->title($copy->dummy_title);
2083 $mods->author($copy->dummy_author);
2086 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2090 $circ->target_copy($circ->target_copy->id);
2091 $copy->call_number($copy->call_number->id);
2093 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2097 __PACKAGE__->register_method(
2098 method => "hold_request_count",
2099 api_name => "open-ils.actor.user.hold_requests.count",
2103 Returns hold ready vs. total counts.
2104 If a context org unit is provided, a third value
2105 is returned with key 'behind_desk', which reports
2106 how many holds are ready at the pickup library
2107 with the behind_desk flag set to true.
2111 sub hold_request_count {
2112 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2113 my $e = new_editor(authtoken => $authtoken);
2114 return $e->event unless $e->checkauth;
2116 $user_id = $e->requestor->id unless defined $user_id;
2118 if($e->requestor->id ne $user_id) {
2119 my $user = $e->retrieve_actor_user($user_id);
2120 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2123 my $holds = $e->json_query({
2124 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2128 fulfillment_time => {"=" => undef },
2129 cancel_time => undef,
2134 $_->{current_shelf_lib} and # avoid undef warnings
2135 $_->{pickup_lib} eq $_->{current_shelf_lib}
2139 total => scalar(@$holds),
2140 ready => scalar(@ready)
2144 # count of holds ready at pickup lib with behind_desk true.
2145 $resp->{behind_desk} = scalar(
2147 $_->{pickup_lib} == $ctx_org and
2148 $U->is_true($_->{behind_desk})
2156 __PACKAGE__->register_method(
2157 method => "checked_out",
2158 api_name => "open-ils.actor.user.checked_out",
2162 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2163 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2164 . "(i.e., outstanding balance or some other pending action on the circ). "
2165 . "The .count method also includes a 'total' field which sums all open circs.",
2167 { desc => 'Authentication Token', type => 'string'},
2168 { desc => 'User ID', type => 'string'},
2171 desc => 'Returns event on error, or an object with ID lists, like: '
2172 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2177 __PACKAGE__->register_method(
2178 method => "checked_out",
2179 api_name => "open-ils.actor.user.checked_out.count",
2182 signature => q/@see open-ils.actor.user.checked_out/
2186 my( $self, $conn, $auth, $userid ) = @_;
2188 my $e = new_editor(authtoken=>$auth);
2189 return $e->event unless $e->checkauth;
2191 if( $userid ne $e->requestor->id ) {
2192 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2193 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2195 # see if there is a friend link allowing circ.view perms
2196 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2197 $e, $userid, $e->requestor->id, 'circ.view');
2198 return $e->event unless $allowed;
2202 my $count = $self->api_name =~ /count/;
2203 return _checked_out( $count, $e, $userid );
2207 my( $iscount, $e, $userid ) = @_;
2213 claims_returned => [],
2216 my $meth = 'retrieve_action_open_circ_';
2224 claims_returned => 0,
2231 my $data = $e->$meth($userid);
2235 $result{$_} += $data->$_() for (keys %result);
2236 $result{total} += $data->$_() for (keys %result);
2238 for my $k (keys %result) {
2239 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2249 __PACKAGE__->register_method(
2250 method => "checked_in_with_fines",
2251 api_name => "open-ils.actor.user.checked_in_with_fines",
2254 signature => q/@see open-ils.actor.user.checked_out/
2257 sub checked_in_with_fines {
2258 my( $self, $conn, $auth, $userid ) = @_;
2260 my $e = new_editor(authtoken=>$auth);
2261 return $e->event unless $e->checkauth;
2263 if( $userid ne $e->requestor->id ) {
2264 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2267 # money is owed on these items and they are checked in
2268 my $open = $e->search_action_circulation(
2271 xact_finish => undef,
2272 checkin_time => { "!=" => undef },
2277 my( @lost, @cr, @lo );
2278 for my $c (@$open) {
2279 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2280 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2281 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2286 claims_returned => \@cr,
2287 long_overdue => \@lo
2293 my ($api, $desc, $auth) = @_;
2294 $desc = $desc ? (" " . $desc) : '';
2295 my $ids = ($api =~ /ids$/) ? 1 : 0;
2298 method => "user_transaction_history",
2299 api_name => "open-ils.actor.user.transactions.$api",
2301 desc => "For a given User ID, returns a list of billable transaction" .
2302 ($ids ? " id" : '') .
2303 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2304 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2306 {desc => 'Authentication token', type => 'string'},
2307 {desc => 'User ID', type => 'number'},
2308 {desc => 'Transaction type (optional)', type => 'number'},
2309 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2312 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2316 $auth and push @sig, (authoritative => 1);
2320 my %auth_hist_methods = (
2322 'history.have_charge' => 'that have an initial charge',
2323 'history.still_open' => 'that are not finished',
2324 'history.have_balance' => 'that have a balance',
2325 'history.have_bill' => 'that have billings',
2326 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2327 'history.have_payment' => 'that have at least 1 payment',
2330 foreach (keys %auth_hist_methods) {
2331 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2332 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2333 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2336 sub user_transaction_history {
2337 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2341 my $e = new_editor(authtoken=>$auth);
2342 return $e->die_event unless $e->checkauth;
2344 if ($e->requestor->id ne $userid) {
2345 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2348 my $api = $self->api_name;
2349 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2351 if(defined($type)) {
2352 $filter->{'xact_type'} = $type;
2355 if($api =~ /have_bill_or_payment/o) {
2357 # transactions that have a non-zero sum across all billings or at least 1 payment
2358 $filter->{'-or'} = {
2359 'balance_owed' => { '<>' => 0 },
2360 'last_payment_ts' => { '<>' => undef }
2363 } elsif($api =~ /have_payment/) {
2365 $filter->{last_payment_ts} ||= {'<>' => undef};
2367 } elsif( $api =~ /have_balance/o) {
2369 # transactions that have a non-zero overall balance
2370 $filter->{'balance_owed'} = { '<>' => 0 };
2372 } elsif( $api =~ /have_charge/o) {
2374 # transactions that have at least 1 billing, regardless of whether it was voided
2375 $filter->{'last_billing_ts'} = { '<>' => undef };
2377 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2379 # transactions that have non-zero sum across all billings. This will exclude
2380 # xacts where all billings have been voided
2381 $filter->{'total_owed'} = { '<>' => 0 };
2384 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2385 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2386 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2388 my $mbts = $e->search_money_billable_transaction_summary(
2389 [ { usr => $userid, @xact_finish, %$filter },
2394 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2395 return $mbts unless $api =~ /fleshed/;
2398 for my $t (@$mbts) {
2400 if( $t->xact_type ne 'circulation' ) {
2401 push @resp, {transaction => $t};
2405 my $circ_data = flesh_circ($e, $t->id);
2406 push @resp, {transaction => $t, %$circ_data};
2414 __PACKAGE__->register_method(
2415 method => "user_perms",
2416 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2418 notes => "Returns a list of permissions"
2422 my( $self, $client, $authtoken, $user ) = @_;
2424 my( $staff, $evt ) = $apputils->checkses($authtoken);
2425 return $evt if $evt;
2427 $user ||= $staff->id;
2429 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2433 return $apputils->simple_scalar_request(
2435 "open-ils.storage.permission.user_perms.atomic",
2439 __PACKAGE__->register_method(
2440 method => "retrieve_perms",
2441 api_name => "open-ils.actor.permissions.retrieve",
2442 notes => "Returns a list of permissions"
2444 sub retrieve_perms {
2445 my( $self, $client ) = @_;
2446 return $apputils->simple_scalar_request(
2448 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2449 { id => { '!=' => undef } }
2453 __PACKAGE__->register_method(
2454 method => "retrieve_groups",
2455 api_name => "open-ils.actor.groups.retrieve",
2456 notes => "Returns a list of user groups"
2458 sub retrieve_groups {
2459 my( $self, $client ) = @_;
2460 return new_editor()->retrieve_all_permission_grp_tree();
2463 __PACKAGE__->register_method(
2464 method => "retrieve_org_address",
2465 api_name => "open-ils.actor.org_unit.address.retrieve",
2466 notes => <<' NOTES');
2467 Returns an org_unit address by ID
2468 @param An org_address ID
2470 sub retrieve_org_address {
2471 my( $self, $client, $id ) = @_;
2472 return $apputils->simple_scalar_request(
2474 "open-ils.cstore.direct.actor.org_address.retrieve",
2479 __PACKAGE__->register_method(
2480 method => "retrieve_groups_tree",
2481 api_name => "open-ils.actor.groups.tree.retrieve",
2482 notes => "Returns a list of user groups"
2485 sub retrieve_groups_tree {
2486 my( $self, $client ) = @_;
2487 return new_editor()->search_permission_grp_tree(
2492 flesh_fields => { pgt => ["children"] },
2493 order_by => { pgt => 'name'}
2500 __PACKAGE__->register_method(
2501 method => "add_user_to_groups",
2502 api_name => "open-ils.actor.user.set_groups",
2503 notes => "Adds a user to one or more permission groups"
2506 sub add_user_to_groups {
2507 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2509 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2510 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2511 return $evt if $evt;
2513 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2514 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2515 return $evt if $evt;
2517 $apputils->simplereq(
2519 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2521 for my $group (@$groups) {
2522 my $link = Fieldmapper::permission::usr_grp_map->new;
2524 $link->usr($userid);
2526 my $id = $apputils->simplereq(
2528 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2534 __PACKAGE__->register_method(
2535 method => "get_user_perm_groups",
2536 api_name => "open-ils.actor.user.get_groups",
2537 notes => "Retrieve a user's permission groups."
2541 sub get_user_perm_groups {
2542 my( $self, $client, $authtoken, $userid ) = @_;
2544 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2545 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2546 return $evt if $evt;
2548 return $apputils->simplereq(
2550 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2554 __PACKAGE__->register_method(
2555 method => "get_user_work_ous",
2556 api_name => "open-ils.actor.user.get_work_ous",
2557 notes => "Retrieve a user's work org units."
2560 __PACKAGE__->register_method(
2561 method => "get_user_work_ous",
2562 api_name => "open-ils.actor.user.get_work_ous.ids",
2563 notes => "Retrieve a user's work org units."
2566 sub get_user_work_ous {
2567 my( $self, $client, $auth, $userid ) = @_;
2568 my $e = new_editor(authtoken=>$auth);
2569 return $e->event unless $e->checkauth;
2570 $userid ||= $e->requestor->id;
2572 if($e->requestor->id != $userid) {
2573 my $user = $e->retrieve_actor_user($userid)
2574 or return $e->event;
2575 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2578 return $e->search_permission_usr_work_ou_map({usr => $userid})
2579 unless $self->api_name =~ /.ids$/;
2581 # client just wants a list of org IDs
2582 return $U->get_user_work_ou_ids($e, $userid);
2587 __PACKAGE__->register_method(
2588 method => 'register_workstation',
2589 api_name => 'open-ils.actor.workstation.register.override',
2590 signature => q/@see open-ils.actor.workstation.register/
2593 __PACKAGE__->register_method(
2594 method => 'register_workstation',
2595 api_name => 'open-ils.actor.workstation.register',
2597 Registers a new workstion in the system
2598 @param authtoken The login session key
2599 @param name The name of the workstation id
2600 @param owner The org unit that owns this workstation
2601 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2602 if the name is already in use.
2606 sub register_workstation {
2607 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2609 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2610 return $e->die_event unless $e->checkauth;
2611 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2612 my $existing = $e->search_actor_workstation({name => $name})->[0];
2613 $oargs = { all => 1 } unless defined $oargs;
2617 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2618 # workstation with the given name exists.
2620 if($owner ne $existing->owning_lib) {
2621 # if necessary, update the owning_lib of the workstation
2623 $logger->info("changing owning lib of workstation ".$existing->id.
2624 " from ".$existing->owning_lib." to $owner");
2625 return $e->die_event unless
2626 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2628 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2630 $existing->owning_lib($owner);
2631 return $e->die_event unless $e->update_actor_workstation($existing);
2637 "attempt to register an existing workstation. returning existing ID");
2640 return $existing->id;
2643 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2647 my $ws = Fieldmapper::actor::workstation->new;
2648 $ws->owning_lib($owner);
2650 $e->create_actor_workstation($ws) or return $e->die_event;
2652 return $ws->id; # note: editor sets the id on the new object for us
2655 __PACKAGE__->register_method(
2656 method => 'workstation_list',
2657 api_name => 'open-ils.actor.workstation.list',
2659 Returns a list of workstations registered at the given location
2660 @param authtoken The login session key
2661 @param ids A list of org_unit.id's for the workstation owners
2665 sub workstation_list {
2666 my( $self, $conn, $authtoken, @orgs ) = @_;
2668 my $e = new_editor(authtoken=>$authtoken);
2669 return $e->event unless $e->checkauth;
2674 unless $e->allowed('REGISTER_WORKSTATION', $o);
2675 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2681 __PACKAGE__->register_method(
2682 method => 'fetch_patron_note',
2683 api_name => 'open-ils.actor.note.retrieve.all',
2686 Returns a list of notes for a given user
2687 Requestor must have VIEW_USER permission if pub==false and
2688 @param authtoken The login session key
2689 @param args Hash of params including
2690 patronid : the patron's id
2691 pub : true if retrieving only public notes
2695 sub fetch_patron_note {
2696 my( $self, $conn, $authtoken, $args ) = @_;
2697 my $patronid = $$args{patronid};
2699 my($reqr, $evt) = $U->checkses($authtoken);
2700 return $evt if $evt;
2703 ($patron, $evt) = $U->fetch_user($patronid);
2704 return $evt if $evt;
2707 if( $patronid ne $reqr->id ) {
2708 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2709 return $evt if $evt;
2711 return $U->cstorereq(
2712 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2713 { usr => $patronid, pub => 't' } );
2716 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2717 return $evt if $evt;
2719 return $U->cstorereq(
2720 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2723 __PACKAGE__->register_method(
2724 method => 'create_user_note',
2725 api_name => 'open-ils.actor.note.create',
2727 Creates a new note for the given user
2728 @param authtoken The login session key
2729 @param note The note object
2732 sub create_user_note {
2733 my( $self, $conn, $authtoken, $note ) = @_;
2734 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2735 return $e->die_event unless $e->checkauth;
2737 my $user = $e->retrieve_actor_user($note->usr)
2738 or return $e->die_event;
2740 return $e->die_event unless
2741 $e->allowed('UPDATE_USER',$user->home_ou);
2743 $note->creator($e->requestor->id);
2744 $e->create_actor_usr_note($note) or return $e->die_event;
2750 __PACKAGE__->register_method(
2751 method => 'delete_user_note',
2752 api_name => 'open-ils.actor.note.delete',
2754 Deletes a note for the given user
2755 @param authtoken The login session key
2756 @param noteid The note id
2759 sub delete_user_note {
2760 my( $self, $conn, $authtoken, $noteid ) = @_;
2762 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2763 return $e->die_event unless $e->checkauth;
2764 my $note = $e->retrieve_actor_usr_note($noteid)
2765 or return $e->die_event;
2766 my $user = $e->retrieve_actor_user($note->usr)
2767 or return $e->die_event;
2768 return $e->die_event unless
2769 $e->allowed('UPDATE_USER', $user->home_ou);
2771 $e->delete_actor_usr_note($note) or return $e->die_event;
2777 __PACKAGE__->register_method(
2778 method => 'update_user_note',
2779 api_name => 'open-ils.actor.note.update',
2781 @param authtoken The login session key
2782 @param note The note
2786 sub update_user_note {
2787 my( $self, $conn, $auth, $note ) = @_;
2788 my $e = new_editor(authtoken=>$auth, xact=>1);
2789 return $e->die_event unless $e->checkauth;
2790 my $patron = $e->retrieve_actor_user($note->usr)
2791 or return $e->die_event;
2792 return $e->die_event unless
2793 $e->allowed('UPDATE_USER', $patron->home_ou);
2794 $e->update_actor_user_note($note)
2795 or return $e->die_event;
2800 __PACKAGE__->register_method(
2801 method => 'fetch_patron_messages',
2802 api_name => 'open-ils.actor.message.retrieve',
2805 Returns a list of notes for a given user, not
2806 including ones marked deleted
2807 @param authtoken The login session key
2808 @param patronid patron ID
2809 @param options hash containing optional limit and offset
2813 sub fetch_patron_messages {
2814 my( $self, $conn, $auth, $patronid, $options ) = @_;
2818 my $e = new_editor(authtoken => $auth);
2819 return $e->die_event unless $e->checkauth;
2821 if ($e->requestor->id ne $patronid) {
2822 return $e->die_event unless $e->allowed('VIEW_USER');
2825 my $select_clause = { usr => $patronid };
2826 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2827 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2828 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2830 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2835 __PACKAGE__->register_method(
2836 method => 'usrname_exists',
2837 api_name => 'open-ils.actor.username.exists',
2839 desc => 'Check if a username is already taken (by an undeleted patron)',
2841 {desc => 'Authentication token', type => 'string'},
2842 {desc => 'Username', type => 'string'}
2845 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2850 sub usrname_exists {
2851 my( $self, $conn, $auth, $usrname ) = @_;
2852 my $e = new_editor(authtoken=>$auth);
2853 return $e->event unless $e->checkauth;
2854 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2855 return $$a[0] if $a and @$a;
2859 __PACKAGE__->register_method(
2860 method => 'barcode_exists',
2861 api_name => 'open-ils.actor.barcode.exists',
2863 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2866 sub barcode_exists {
2867 my( $self, $conn, $auth, $barcode ) = @_;
2868 my $e = new_editor(authtoken=>$auth);
2869 return $e->event unless $e->checkauth;
2870 my $card = $e->search_actor_card({barcode => $barcode});
2876 #return undef unless @$card;
2877 #return $card->[0]->usr;
2881 __PACKAGE__->register_method(
2882 method => 'retrieve_net_levels',
2883 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2886 sub retrieve_net_levels {
2887 my( $self, $conn, $auth ) = @_;
2888 my $e = new_editor(authtoken=>$auth);
2889 return $e->event unless $e->checkauth;
2890 return $e->retrieve_all_config_net_access_level();
2893 # Retain the old typo API name just in case
2894 __PACKAGE__->register_method(
2895 method => 'fetch_org_by_shortname',
2896 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2898 __PACKAGE__->register_method(
2899 method => 'fetch_org_by_shortname',
2900 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2902 sub fetch_org_by_shortname {
2903 my( $self, $conn, $sname ) = @_;
2904 my $e = new_editor();
2905 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2906 return $e->event unless $org;
2911 __PACKAGE__->register_method(
2912 method => 'session_home_lib',
2913 api_name => 'open-ils.actor.session.home_lib',
2916 sub session_home_lib {
2917 my( $self, $conn, $auth ) = @_;
2918 my $e = new_editor(authtoken=>$auth);
2919 return undef unless $e->checkauth;
2920 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2921 return $org->shortname;
2924 __PACKAGE__->register_method(
2925 method => 'session_safe_token',
2926 api_name => 'open-ils.actor.session.safe_token',
2928 Returns a hashed session ID that is safe for export to the world.
2929 This safe token will expire after 1 hour of non-use.
2930 @param auth Active authentication token
2934 sub session_safe_token {
2935 my( $self, $conn, $auth ) = @_;
2936 my $e = new_editor(authtoken=>$auth);
2937 return undef unless $e->checkauth;
2939 my $safe_token = md5_hex($auth);
2941 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2943 # add more user fields as needed
2945 "safe-token-user-$safe_token", {
2946 id => $e->requestor->id,
2947 home_ou_shortname => $e->retrieve_actor_org_unit(
2948 $e->requestor->home_ou)->shortname,
2957 __PACKAGE__->register_method(
2958 method => 'safe_token_home_lib',
2959 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2961 Returns the home library shortname from the session
2962 asscociated with a safe token from generated by
2963 open-ils.actor.session.safe_token.
2964 @param safe_token Active safe token
2965 @param who Optional user activity "ewho" value
2969 sub safe_token_home_lib {
2970 my( $self, $conn, $safe_token, $who ) = @_;
2971 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2973 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2974 return unless $blob;
2976 $U->log_user_activity($blob->{id}, $who, 'verify');
2977 return $blob->{home_ou_shortname};
2981 __PACKAGE__->register_method(
2982 method => "update_penalties",
2983 api_name => "open-ils.actor.user.penalties.update"
2986 sub update_penalties {
2987 my($self, $conn, $auth, $user_id) = @_;
2988 my $e = new_editor(authtoken=>$auth, xact => 1);
2989 return $e->die_event unless $e->checkauth;
2990 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2991 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2992 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2993 return $evt if $evt;
2999 __PACKAGE__->register_method(
3000 method => "apply_penalty",
3001 api_name => "open-ils.actor.user.penalty.apply"
3005 my($self, $conn, $auth, $penalty) = @_;
3007 my $e = new_editor(authtoken=>$auth, xact => 1);
3008 return $e->die_event unless $e->checkauth;
3010 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3011 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3013 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3016 (defined $ptype->org_depth) ?
3017 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
3020 $penalty->org_unit($ctx_org);
3021 $penalty->staff($e->requestor->id);
3022 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3025 return $penalty->id;
3028 __PACKAGE__->register_method(
3029 method => "remove_penalty",
3030 api_name => "open-ils.actor.user.penalty.remove"
3033 sub remove_penalty {
3034 my($self, $conn, $auth, $penalty) = @_;
3035 my $e = new_editor(authtoken=>$auth, xact => 1);
3036 return $e->die_event unless $e->checkauth;
3037 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3038 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3040 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3045 __PACKAGE__->register_method(
3046 method => "update_penalty_note",
3047 api_name => "open-ils.actor.user.penalty.note.update"
3050 sub update_penalty_note {
3051 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3052 my $e = new_editor(authtoken=>$auth, xact => 1);
3053 return $e->die_event unless $e->checkauth;
3054 for my $penalty_id (@$penalty_ids) {
3055 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3056 if (! $penalty ) { return $e->die_event; }
3057 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3058 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3060 $penalty->note( $note ); $penalty->ischanged( 1 );
3062 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3068 __PACKAGE__->register_method(
3069 method => "ranged_penalty_thresholds",
3070 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3074 sub ranged_penalty_thresholds {
3075 my($self, $conn, $auth, $context_org) = @_;
3076 my $e = new_editor(authtoken=>$auth);
3077 return $e->event unless $e->checkauth;
3078 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3079 my $list = $e->search_permission_grp_penalty_threshold([
3080 {org_unit => $U->get_org_ancestors($context_org)},
3081 {order_by => {pgpt => 'id'}}
3083 $conn->respond($_) for @$list;
3089 __PACKAGE__->register_method(
3090 method => "user_retrieve_fleshed_by_id",
3092 api_name => "open-ils.actor.user.fleshed.retrieve",
3095 sub user_retrieve_fleshed_by_id {
3096 my( $self, $client, $auth, $user_id, $fields ) = @_;
3097 my $e = new_editor(authtoken => $auth);
3098 return $e->event unless $e->checkauth;
3100 if( $e->requestor->id != $user_id ) {
3101 return $e->event unless $e->allowed('VIEW_USER');
3108 "standing_penalties",
3116 return new_flesh_user($user_id, $fields, $e);
3120 sub new_flesh_user {
3123 my $fields = shift || [];
3126 my $fetch_penalties = 0;
3127 if(grep {$_ eq 'standing_penalties'} @$fields) {
3128 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3129 $fetch_penalties = 1;
3132 my $fetch_usr_act = 0;
3133 if(grep {$_ eq 'usr_activity'} @$fields) {
3134 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3138 my $user = $e->retrieve_actor_user(
3143 "flesh_fields" => { "au" => $fields }
3146 ) or return $e->die_event;
3149 if( grep { $_ eq 'addresses' } @$fields ) {
3151 $user->addresses([]) unless @{$user->addresses};
3152 # don't expose "replaced" addresses by default
3153 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3155 if( ref $user->billing_address ) {
3156 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3157 push( @{$user->addresses}, $user->billing_address );
3161 if( ref $user->mailing_address ) {
3162 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3163 push( @{$user->addresses}, $user->mailing_address );
3168 if($fetch_penalties) {
3169 # grab the user penalties ranged for this location
3170 $user->standing_penalties(
3171 $e->search_actor_user_standing_penalty([
3174 {stop_date => undef},
3175 {stop_date => {'>' => 'now'}}
3177 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3180 flesh_fields => {ausp => ['standing_penalty']}
3186 # retrieve the most recent usr_activity entry
3187 if ($fetch_usr_act) {
3189 # max number to return for simple patron fleshing
3190 my $limit = $U->ou_ancestor_setting_value(
3191 $e->requestor->ws_ou,
3192 'circ.patron.usr_activity_retrieve.max');
3196 flesh_fields => {auact => ['etype']},
3197 order_by => {auact => 'event_time DESC'},
3200 # 0 == none, <0 == return all
3201 $limit = 1 unless defined $limit;
3202 $opts->{limit} = $limit if $limit > 0;
3204 $user->usr_activity(
3206 [] : # skip the DB call
3207 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3212 $user->clear_passwd();
3219 __PACKAGE__->register_method(
3220 method => "user_retrieve_parts",
3221 api_name => "open-ils.actor.user.retrieve.parts",
3224 sub user_retrieve_parts {
3225 my( $self, $client, $auth, $user_id, $fields ) = @_;
3226 my $e = new_editor(authtoken => $auth);
3227 return $e->event unless $e->checkauth;
3228 $user_id ||= $e->requestor->id;
3229 if( $e->requestor->id != $user_id ) {
3230 return $e->event unless $e->allowed('VIEW_USER');
3233 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3234 push(@resp, $user->$_()) for(@$fields);
3240 __PACKAGE__->register_method(
3241 method => 'user_opt_in_enabled',
3242 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3243 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3246 sub user_opt_in_enabled {
3247 my($self, $conn) = @_;
3248 my $sc = OpenSRF::Utils::SettingsClient->new;
3249 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3254 __PACKAGE__->register_method(
3255 method => 'user_opt_in_at_org',
3256 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3258 @param $auth The auth token
3259 @param user_id The ID of the user to test
3260 @return 1 if the user has opted in at the specified org,
3261 2 if opt-in is disallowed for the user's home org,
3262 event on error, and 0 otherwise. /
3264 sub user_opt_in_at_org {
3265 my($self, $conn, $auth, $user_id) = @_;
3267 # see if we even need to enforce the opt-in value
3268 return 1 unless user_opt_in_enabled($self);
3270 my $e = new_editor(authtoken => $auth);
3271 return $e->event unless $e->checkauth;
3273 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3274 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3276 my $ws_org = $e->requestor->ws_ou;
3277 # user is automatically opted-in if they are from the local org
3278 return 1 if $user->home_ou eq $ws_org;
3280 # get the boundary setting
3281 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3283 # auto opt in if user falls within the opt boundary
3284 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3286 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3288 # check whether opt-in is restricted at the user's home library
3289 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3290 if ($opt_restrict_depth) {
3291 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3292 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3294 # opt-in is disallowed unless the workstation org is within the home
3295 # library's opt-in scope
3296 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3299 my $vals = $e->search_actor_usr_org_unit_opt_in(
3300 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3306 __PACKAGE__->register_method(
3307 method => 'create_user_opt_in_at_org',
3308 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3310 @param $auth The auth token
3311 @param user_id The ID of the user to test
3312 @return The ID of the newly created object, event on error./
3315 sub create_user_opt_in_at_org {
3316 my($self, $conn, $auth, $user_id, $org_id) = @_;
3318 my $e = new_editor(authtoken => $auth, xact=>1);
3319 return $e->die_event unless $e->checkauth;
3321 # if a specific org unit wasn't passed in, get one based on the defaults;
3323 my $wsou = $e->requestor->ws_ou;
3324 # get the default opt depth
3325 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3326 # get the org unit at that depth
3327 my $org = $e->json_query({
3328 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3329 $org_id = $org->{id};
3332 # fall back to the workstation OU, the pre-opt-in-boundary way
3333 $org_id = $e->requestor->ws_ou;
3336 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3337 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3339 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3341 $opt_in->org_unit($org_id);
3342 $opt_in->usr($user_id);
3343 $opt_in->staff($e->requestor->id);
3344 $opt_in->opt_in_ts('now');
3345 $opt_in->opt_in_ws($e->requestor->wsid);
3347 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3348 or return $e->die_event;
3356 __PACKAGE__->register_method (
3357 method => 'retrieve_org_hours',
3358 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3360 Returns the hours of operation for a specified org unit
3361 @param authtoken The login session key
3362 @param org_id The org_unit ID
3366 sub retrieve_org_hours {
3367 my($self, $conn, $auth, $org_id) = @_;
3368 my $e = new_editor(authtoken => $auth);
3369 return $e->die_event unless $e->checkauth;
3370 $org_id ||= $e->requestor->ws_ou;
3371 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3375 __PACKAGE__->register_method (
3376 method => 'verify_user_password',
3377 api_name => 'open-ils.actor.verify_user_password',
3379 Given a barcode or username and the MD5 encoded password,
3380 returns 1 if the password is correct. Returns 0 otherwise.
3384 sub verify_user_password {
3385 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3386 my $e = new_editor(authtoken => $auth);
3387 return $e->die_event unless $e->checkauth;
3389 my $user_by_barcode;
3390 my $user_by_username;
3392 my $card = $e->search_actor_card([
3393 {barcode => $barcode},
3394 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3395 $user_by_barcode = $card->usr;
3396 $user = $user_by_barcode;
3399 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3400 $user = $user_by_username;
3402 return 0 if (!$user || $U->is_true($user->deleted));
3403 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3404 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3405 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3408 __PACKAGE__->register_method (
3409 method => 'retrieve_usr_id_via_barcode_or_usrname',
3410 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3412 Given a barcode or username returns the id for the user or
3417 sub retrieve_usr_id_via_barcode_or_usrname {
3418 my($self, $conn, $auth, $barcode, $username) = @_;
3419 my $e = new_editor(authtoken => $auth);
3420 return $e->die_event unless $e->checkauth;
3421 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3423 my $user_by_barcode;
3424 my $user_by_username;
3425 $logger->info("$id_as_barcode is the ID as BARCODE");
3427 my $card = $e->search_actor_card([
3428 {barcode => $barcode},
3429 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3430 if ($id_as_barcode =~ /^t/i) {
3432 $user = $e->retrieve_actor_user($barcode);
3433 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3435 $user_by_barcode = $card->usr;
3436 $user = $user_by_barcode;
3439 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3440 $user_by_barcode = $card->usr;
3441 $user = $user_by_barcode;
3446 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3448 $user = $user_by_username;
3450 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3451 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3452 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3457 __PACKAGE__->register_method (
3458 method => 'merge_users',
3459 api_name => 'open-ils.actor.user.merge',
3462 Given a list of source users and destination user, transfer all data from the source
3463 to the dest user and delete the source user. All user related data is
3464 transferred, including circulations, holds, bookbags, etc.
3470 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3471 my $e = new_editor(xact => 1, authtoken => $auth);
3472 return $e->die_event unless $e->checkauth;
3474 # disallow the merge if any subordinate accounts are in collections
3475 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3476 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3478 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3479 if $master_id == $e->requestor->id;
3481 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3482 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3483 return $evt if $evt;
3485 my $del_addrs = ($U->ou_ancestor_setting_value(
3486 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3487 my $del_cards = ($U->ou_ancestor_setting_value(
3488 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3489 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3490 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3492 for my $src_id (@$user_ids) {
3494 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3495 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3496 return $evt if $evt;
3498 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3499 if $src_id == $e->requestor->id;
3501 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3502 if($src_user->home_ou ne $master_user->home_ou) {
3503 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3506 return $e->die_event unless
3507 $e->json_query({from => [
3522 __PACKAGE__->register_method (
3523 method => 'approve_user_address',
3524 api_name => 'open-ils.actor.user.pending_address.approve',
3531 sub approve_user_address {
3532 my($self, $conn, $auth, $addr) = @_;
3533 my $e = new_editor(xact => 1, authtoken => $auth);
3534 return $e->die_event unless $e->checkauth;
3536 # if the caller passes an address object, assume they want to
3537 # update it first before approving it
3538 $e->update_actor_user_address($addr) or return $e->die_event;
3540 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3542 my $user = $e->retrieve_actor_user($addr->usr);
3543 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3544 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3545 or return $e->die_event;
3547 return [values %$result]->[0];
3551 __PACKAGE__->register_method (
3552 method => 'retrieve_friends',
3553 api_name => 'open-ils.actor.friends.retrieve',
3556 returns { confirmed: [], pending_out: [], pending_in: []}
3557 pending_out are users I'm requesting friendship with
3558 pending_in are users requesting friendship with me
3563 sub retrieve_friends {
3564 my($self, $conn, $auth, $user_id, $options) = @_;
3565 my $e = new_editor(authtoken => $auth);
3566 return $e->event unless $e->checkauth;
3567 $user_id ||= $e->requestor->id;
3569 if($user_id != $e->requestor->id) {
3570 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3571 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3574 return OpenILS::Application::Actor::Friends->retrieve_friends(
3575 $e, $user_id, $options);
3580 __PACKAGE__->register_method (
3581 method => 'apply_friend_perms',
3582 api_name => 'open-ils.actor.friends.perms.apply',
3588 sub apply_friend_perms {
3589 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3590 my $e = new_editor(authtoken => $auth, xact => 1);
3591 return $e->die_event unless $e->checkauth;
3593 if($user_id != $e->requestor->id) {
3594 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3595 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3598 for my $perm (@perms) {
3600 OpenILS::Application::Actor::Friends->apply_friend_perm(
3601 $e, $user_id, $delegate_id, $perm);
3602 return $evt if $evt;
3610 __PACKAGE__->register_method (
3611 method => 'update_user_pending_address',
3612 api_name => 'open-ils.actor.user.address.pending.cud'
3615 sub update_user_pending_address {
3616 my($self, $conn, $auth, $addr) = @_;
3617 my $e = new_editor(authtoken => $auth, xact => 1);
3618 return $e->die_event unless $e->checkauth;
3620 if($addr->usr != $e->requestor->id) {
3621 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3622 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3626 $e->create_actor_user_address($addr) or return $e->die_event;
3627 } elsif($addr->isdeleted) {
3628 $e->delete_actor_user_address($addr) or return $e->die_event;
3630 $e->update_actor_user_address($addr) or return $e->die_event;
3638 __PACKAGE__->register_method (
3639 method => 'user_events',
3640 api_name => 'open-ils.actor.user.events.circ',
3643 __PACKAGE__->register_method (
3644 method => 'user_events',
3645 api_name => 'open-ils.actor.user.events.ahr',
3650 my($self, $conn, $auth, $user_id, $filters) = @_;
3651 my $e = new_editor(authtoken => $auth);
3652 return $e->event unless $e->checkauth;
3654 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3655 my $user_field = 'usr';
3658 $filters->{target} = {
3659 select => { $obj_type => ['id'] },
3661 where => {usr => $user_id}
3664 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3665 if($e->requestor->id != $user_id) {
3666 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3669 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3670 my $req = $ses->request('open-ils.trigger.events_by_target',
3671 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3673 while(my $resp = $req->recv) {
3674 my $val = $resp->content;
3675 my $tgt = $val->target;
3677 if($obj_type eq 'circ') {
3678 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3680 } elsif($obj_type eq 'ahr') {
3681 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3682 if $tgt->current_copy;
3685 $conn->respond($val) if $val;
3691 __PACKAGE__->register_method (
3692 method => 'copy_events',
3693 api_name => 'open-ils.actor.copy.events.circ',
3696 __PACKAGE__->register_method (
3697 method => 'copy_events',
3698 api_name => 'open-ils.actor.copy.events.ahr',
3703 my($self, $conn, $auth, $copy_id, $filters) = @_;
3704 my $e = new_editor(authtoken => $auth);
3705 return $e->event unless $e->checkauth;
3707 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3709 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3711 my $copy_field = 'target_copy';
3712 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3715 $filters->{target} = {
3716 select => { $obj_type => ['id'] },
3718 where => {$copy_field => $copy_id}
3722 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3723 my $req = $ses->request('open-ils.trigger.events_by_target',
3724 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3726 while(my $resp = $req->recv) {
3727 my $val = $resp->content;
3728 my $tgt = $val->target;
3730 my $user = $e->retrieve_actor_user($tgt->usr);
3731 if($e->requestor->id != $user->id) {
3732 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3735 $tgt->$copy_field($copy);
3738 $conn->respond($val) if $val;
3745 __PACKAGE__->register_method (
3746 method => 'get_itemsout_notices',
3747 api_name => 'open-ils.actor.user.itemsout.notices',
3752 sub get_itemsout_notices{
3753 my( $self, $conn, $auth, $circId, $patronId) = @_;
3755 my $e = new_editor(authtoken => $auth);
3756 return $e->event unless $e->checkauth;
3758 my $requestorId = $e->requestor->id;
3760 if( $patronId ne $requestorId ){
3761 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3762 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3765 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3766 #my $req = $ses->request('open-ils.trigger.events_by_target',
3767 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3768 # ^ Above removed in favor of faster json_query.
3771 # select complete_time
3772 # from action_trigger.event atev
3773 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3774 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3775 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3778 my $ctx_loc = $e->requestor->ws_ou;
3779 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value($ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3781 select => { atev => ["complete_time"] },
3784 atevdef => { field => "id",fkey => "event_def", join => { ath => { field => "key", fkey => "hook" }} }
3787 where => {"+ath" => { key => "checkout.due" },"+atevdef" => { active => 't' },"+atev" => { target => $circId, state => 'complete' }}
3790 if ($exclude_courtesy_notices){
3791 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3794 my %resblob = ( numNotices => 0, lastDt => undef );
3796 my $res = $e->json_query($query);
3797 for my $ndate (@$res) {
3798 $resblob{numNotices}++;
3799 if( !defined $resblob{lastDt}){
3800 $resblob{lastDt} = $$ndate{complete_time};
3803 if ($resblob{lastDt} lt $$ndate{complete_time}){
3804 $resblob{lastDt} = $$ndate{complete_time};
3808 $conn->respond(\%resblob);
3812 __PACKAGE__->register_method (
3813 method => 'update_events',
3814 api_name => 'open-ils.actor.user.event.cancel.batch',
3817 __PACKAGE__->register_method (
3818 method => 'update_events',
3819 api_name => 'open-ils.actor.user.event.reset.batch',
3824 my($self, $conn, $auth, $event_ids) = @_;
3825 my $e = new_editor(xact => 1, authtoken => $auth);
3826 return $e->die_event unless $e->checkauth;
3829 for my $id (@$event_ids) {
3831 # do a little dance to determine what user we are ultimately affecting
3832 my $event = $e->retrieve_action_trigger_event([
3835 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3837 ]) or return $e->die_event;
3840 if($event->event_def->hook->core_type eq 'circ') {
3841 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3842 } elsif($event->event_def->hook->core_type eq 'ahr') {
3843 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3848 my $user = $e->retrieve_actor_user($user_id);
3849 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3851 if($self->api_name =~ /cancel/) {
3852 $event->state('invalid');
3853 } elsif($self->api_name =~ /reset/) {
3854 $event->clear_start_time;
3855 $event->clear_update_time;
3856 $event->state('pending');
3859 $e->update_action_trigger_event($event) or return $e->die_event;
3860 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3864 return {complete => 1};
3868 __PACKAGE__->register_method (
3869 method => 'really_delete_user',
3870 api_name => 'open-ils.actor.user.delete.override',
3871 signature => q/@see open-ils.actor.user.delete/
3874 __PACKAGE__->register_method (
3875 method => 'really_delete_user',
3876 api_name => 'open-ils.actor.user.delete',
3878 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3879 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3880 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3881 dest_usr_id is only required when deleting a user that performs staff functions.
3885 sub really_delete_user {
3886 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3887 my $e = new_editor(authtoken => $auth, xact => 1);
3888 return $e->die_event unless $e->checkauth;
3889 $oargs = { all => 1 } unless defined $oargs;
3891 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3892 my $open_bills = $e->json_query({
3893 select => { mbts => ['id'] },
3896 xact_finish => { '=' => undef },
3897 usr => { '=' => $user_id },
3899 }) or return $e->die_event;
3901 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3903 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3905 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3906 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3907 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3909 # No deleting yourself - UI is supposed to stop you first, though.
3910 return $e->die_event unless $e->requestor->id != $user->id;
3911 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3912 # Check if you are allowed to mess with this patron permission group at all
3913 my $evt = group_perm_failed($e, $e->requestor, $user);
3914 return $e->die_event($evt) if $evt;
3915 my $stat = $e->json_query(
3916 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3917 or return $e->die_event;
3923 __PACKAGE__->register_method (
3924 method => 'user_payments',
3925 api_name => 'open-ils.actor.user.payments.retrieve',
3928 Returns all payments for a given user. Default order is newest payments first.
3929 @param auth Authentication token
3930 @param user_id The user ID
3931 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3936 my($self, $conn, $auth, $user_id, $filters) = @_;
3939 my $e = new_editor(authtoken => $auth);
3940 return $e->die_event unless $e->checkauth;
3942 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3943 return $e->event unless
3944 $e->requestor->id == $user_id or
3945 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3947 # Find all payments for all transactions for user $user_id
3949 select => {mp => ['id']},
3954 select => {mbt => ['id']},
3956 where => {usr => $user_id}
3961 { # by default, order newest payments first
3963 field => 'payment_ts',
3966 # secondary sort in ID as a tie-breaker, since payments created
3967 # within the same transaction will have identical payment_ts's
3974 for (qw/order_by limit offset/) {
3975 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3978 if(defined $filters->{where}) {
3979 foreach (keys %{$filters->{where}}) {
3980 # don't allow the caller to expand the result set to other users
3981 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3985 my $payment_ids = $e->json_query($query);
3986 for my $pid (@$payment_ids) {
3987 my $pay = $e->retrieve_money_payment([
3992 mbt => ['summary', 'circulation', 'grocery'],
3993 circ => ['target_copy'],
3994 acp => ['call_number'],
4002 xact_type => $pay->xact->summary->xact_type,
4003 last_billing_type => $pay->xact->summary->last_billing_type,
4006 if($pay->xact->summary->xact_type eq 'circulation') {
4007 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4008 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4011 $pay->xact($pay->xact->id); # de-flesh
4012 $conn->respond($resp);
4020 __PACKAGE__->register_method (
4021 method => 'negative_balance_users',
4022 api_name => 'open-ils.actor.users.negative_balance',
4025 Returns all users that have an overall negative balance
4026 @param auth Authentication token
4027 @param org_id The context org unit as an ID or list of IDs. This will be the home
4028 library of the user. If no org_unit is specified, no org unit filter is applied
4032 sub negative_balance_users {
4033 my($self, $conn, $auth, $org_id) = @_;
4035 my $e = new_editor(authtoken => $auth);
4036 return $e->die_event unless $e->checkauth;
4037 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4041 mous => ['usr', 'balance_owed'],
4044 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4045 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4062 where => {'+mous' => {balance_owed => {'<' => 0}}}
4065 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4067 my $list = $e->json_query($query, {timeout => 600});
4069 for my $data (@$list) {
4071 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4072 balance_owed => $data->{balance_owed},
4073 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4080 __PACKAGE__->register_method(
4081 method => "request_password_reset",
4082 api_name => "open-ils.actor.patron.password_reset.request",
4084 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4085 "method for changing a user's password. The UUID token is distributed via A/T " .
4086 "templates (i.e. email to the user).",
4088 { desc => 'user_id_type', type => 'string' },
4089 { desc => 'user_id', type => 'string' },
4090 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4092 return => {desc => '1 on success, Event on error'}
4095 sub request_password_reset {
4096 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4098 # Check to see if password reset requests are already being throttled:
4099 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4101 my $e = new_editor(xact => 1);
4104 # Get the user, if any, depending on the input value
4105 if ($user_id_type eq 'username') {
4106 $user = $e->search_actor_user({usrname => $user_id})->[0];
4109 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4111 } elsif ($user_id_type eq 'barcode') {
4112 my $card = $e->search_actor_card([
4113 {barcode => $user_id},
4114 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4117 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4122 # If the user doesn't have an email address, we can't help them
4123 if (!$user->email) {
4125 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4128 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4129 if ($email_must_match) {
4130 if (lc($user->email) ne lc($email)) {
4131 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4135 _reset_password_request($conn, $e, $user);
4138 # Once we have the user, we can issue the password reset request
4139 # XXX Add a wrapper method that accepts barcode + email input
4140 sub _reset_password_request {
4141 my ($conn, $e, $user) = @_;
4143 # 1. Get throttle threshold and time-to-live from OU_settings
4144 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4145 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4147 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4149 # 2. Get time of last request and number of active requests (num_active)
4150 my $active_requests = $e->json_query({
4156 transform => 'COUNT'
4159 column => 'request_time',
4165 has_been_reset => { '=' => 'f' },
4166 request_time => { '>' => $threshold_time }
4170 # Guard against no active requests
4171 if ($active_requests->[0]->{'request_time'}) {
4172 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4173 my $now = DateTime::Format::ISO8601->new();
4175 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4176 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4177 ($last_request->add_duration('1 minute') > $now)) {
4178 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4180 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4184 # TODO Check to see if the user is in a password-reset-restricted group
4186 # Otherwise, go ahead and try to get the user.
4188 # Check the number of active requests for this user
4189 $active_requests = $e->json_query({
4195 transform => 'COUNT'
4200 usr => { '=' => $user->id },
4201 has_been_reset => { '=' => 'f' },
4202 request_time => { '>' => $threshold_time }
4206 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4208 # if less than or equal to per-user threshold, proceed; otherwise, return event
4209 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4210 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4212 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4215 # Create the aupr object and insert into the database
4216 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4217 my $uuid = create_uuid_as_string(UUID_V4);
4218 $reset_request->uuid($uuid);
4219 $reset_request->usr($user->id);
4221 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4224 # Create an event to notify user of the URL to reset their password
4226 # Can we stuff this in the user_data param for trigger autocreate?
4227 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4229 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4230 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4233 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4238 __PACKAGE__->register_method(
4239 method => "commit_password_reset",
4240 api_name => "open-ils.actor.patron.password_reset.commit",
4242 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4243 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4244 "with the supplied password.",
4246 { desc => 'uuid', type => 'string' },
4247 { desc => 'password', type => 'string' },
4249 return => {desc => '1 on success, Event on error'}
4252 sub commit_password_reset {
4253 my($self, $conn, $uuid, $password) = @_;
4255 # Check to see if password reset requests are already being throttled:
4256 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4257 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4258 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4260 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4263 my $e = new_editor(xact => 1);
4265 my $aupr = $e->search_actor_usr_password_reset({
4272 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4274 my $user_id = $aupr->[0]->usr;
4275 my $user = $e->retrieve_actor_user($user_id);
4277 # Ensure we're still within the TTL for the request
4278 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4279 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4280 if ($threshold < DateTime->now(time_zone => 'local')) {
4282 $logger->info("Password reset request needed to be submitted before $threshold");
4283 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4286 # Check complexity of password against OU-defined regex
4287 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4291 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4292 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4293 $is_strong = check_password_strength_custom($password, $pw_regex);
4295 $is_strong = check_password_strength_default($password);
4300 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4303 # All is well; update the password
4304 modify_migrated_user_password($e, $user->id, $password);
4306 # And flag that this password reset request has been honoured
4307 $aupr->[0]->has_been_reset('t');
4308 $e->update_actor_usr_password_reset($aupr->[0]);
4314 sub check_password_strength_default {
4315 my $password = shift;
4316 # Use the default set of checks
4317 if ( (length($password) < 7) or
4318 ($password !~ m/.*\d+.*/) or
4319 ($password !~ m/.*[A-Za-z]+.*/)
4326 sub check_password_strength_custom {
4327 my ($password, $pw_regex) = @_;
4329 $pw_regex = qr/$pw_regex/;
4330 if ($password !~ /$pw_regex/) {
4338 __PACKAGE__->register_method(
4339 method => "event_def_opt_in_settings",
4340 api_name => "open-ils.actor.event_def.opt_in.settings",
4343 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4345 { desc => 'Authentication token', type => 'string'},
4347 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4352 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4359 sub event_def_opt_in_settings {
4360 my($self, $conn, $auth, $org_id) = @_;
4361 my $e = new_editor(authtoken => $auth);
4362 return $e->event unless $e->checkauth;
4364 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4365 return $e->event unless
4366 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4368 $org_id = $e->requestor->home_ou;
4371 # find all config.user_setting_type's related to event_defs for the requested org unit
4372 my $types = $e->json_query({
4373 select => {cust => ['name']},
4374 from => {atevdef => 'cust'},
4377 owner => $U->get_org_ancestors($org_id), # context org plus parents
4384 $conn->respond($_) for
4385 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4392 __PACKAGE__->register_method(
4393 method => "user_circ_history",
4394 api_name => "open-ils.actor.history.circ",
4398 desc => 'Returns user circ history objects for the calling user',
4400 { desc => 'Authentication token', type => 'string'},
4401 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4404 desc => q/Stream of 'auch' circ history objects/,
4410 __PACKAGE__->register_method(
4411 method => "user_circ_history",
4412 api_name => "open-ils.actor.history.circ.clear",
4415 desc => 'Delete all user circ history entries for the calling user',
4417 { desc => 'Authentication token', type => 'string'},
4418 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4421 desc => q/1 on success, event on error/,
4427 __PACKAGE__->register_method(
4428 method => "user_circ_history",
4429 api_name => "open-ils.actor.history.circ.print",
4432 desc => q/Returns printable output for the caller's circ history objects/,
4434 { desc => 'Authentication token', type => 'string'},
4435 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4438 desc => q/An action_trigger.event object or error event./,
4444 __PACKAGE__->register_method(
4445 method => "user_circ_history",
4446 api_name => "open-ils.actor.history.circ.email",
4449 desc => q/Emails the caller's circ history/,
4451 { desc => 'Authentication token', type => 'string'},
4452 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4453 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4456 desc => q/undef, or event on error/
4461 sub user_circ_history {
4462 my ($self, $conn, $auth, $options) = @_;
4465 my $for_print = ($self->api_name =~ /print/);
4466 my $for_email = ($self->api_name =~ /email/);
4467 my $for_clear = ($self->api_name =~ /clear/);
4469 # No perm check is performed. Caller may only access his/her own
4470 # circ history entries.
4471 my $e = new_editor(authtoken => $auth);
4472 return $e->event unless $e->checkauth;
4475 if (!$for_clear) { # clear deletes all
4476 $limits{offset} = $options->{offset} if defined $options->{offset};
4477 $limits{limit} = $options->{limit} if defined $options->{limit};
4480 my %circ_id_filter = $options->{circ_ids} ?
4481 (id => $options->{circ_ids}) : ();
4483 my $circs = $e->search_action_user_circ_history([
4484 { usr => $e->requestor->id,
4487 { # order newest to oldest by default
4488 order_by => {auch => 'xact_start DESC'},
4491 {substream => 1} # could be a large list
4495 return $U->fire_object_event(undef,
4496 'circ.format.history.print', $circs, $e->requestor->home_ou);
4499 $e->xact_begin if $for_clear;
4500 $conn->respond_complete(1) if $for_email; # no sense in waiting
4502 for my $circ (@$circs) {
4505 # events will be fired from action_trigger_runner
4506 $U->create_events_for_hook('circ.format.history.email',
4507 $circ, $e->editor->home_ou, undef, undef, 1);
4509 } elsif ($for_clear) {
4511 $e->delete_action_user_circ_history($circ)
4512 or return $e->die_event;
4515 $conn->respond($circ);
4528 __PACKAGE__->register_method(
4529 method => "user_visible_holds",
4530 api_name => "open-ils.actor.history.hold.visible",
4533 desc => 'Returns the set of opt-in visible holds',
4535 { desc => 'Authentication token', type => 'string'},
4536 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4537 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4540 desc => q/An object with 1 field: "hold"/,
4546 __PACKAGE__->register_method(
4547 method => "user_visible_holds",
4548 api_name => "open-ils.actor.history.hold.visible.print",
4551 desc => 'Returns printable output for the set of opt-in visible holds',
4553 { desc => 'Authentication token', type => 'string'},
4554 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4555 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4558 desc => q/An action_trigger.event object or error event./,
4564 __PACKAGE__->register_method(
4565 method => "user_visible_holds",
4566 api_name => "open-ils.actor.history.hold.visible.email",
4569 desc => 'Emails the set of opt-in visible holds to the requestor',
4571 { desc => 'Authentication token', type => 'string'},
4572 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4573 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4576 desc => q/undef, or event on error/
4581 sub user_visible_holds {
4582 my($self, $conn, $auth, $user_id, $options) = @_;
4585 my $for_print = ($self->api_name =~ /print/);
4586 my $for_email = ($self->api_name =~ /email/);
4587 my $e = new_editor(authtoken => $auth);
4588 return $e->event unless $e->checkauth;
4590 $user_id ||= $e->requestor->id;
4592 $options->{limit} ||= 50;
4593 $options->{offset} ||= 0;
4595 if($user_id != $e->requestor->id) {
4596 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4597 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4598 return $e->event unless $e->allowed($perm, $user->home_ou);
4601 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4603 my $data = $e->json_query({
4604 from => [$db_func, $user_id],
4605 limit => $$options{limit},
4606 offset => $$options{offset}
4608 # TODO: I only want IDs. code below didn't get me there
4609 # {"select":{"au":[{"column":"id", "result_field":"id",
4610 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4615 return undef unless @$data;
4619 # collect the batch of objects
4623 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4624 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4628 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4629 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4632 } elsif ($for_email) {
4634 $conn->respond_complete(1) if $for_email; # no sense in waiting
4642 my $hold = $e->retrieve_action_hold_request($id);
4643 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4644 # events will be fired from action_trigger_runner
4648 my $circ = $e->retrieve_action_circulation($id);
4649 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4650 # events will be fired from action_trigger_runner
4654 } else { # just give me the data please
4662 my $hold = $e->retrieve_action_hold_request($id);
4663 $conn->respond({hold => $hold});
4667 my $circ = $e->retrieve_action_circulation($id);
4670 summary => $U->create_circ_chain_summary($e, $id)
4679 __PACKAGE__->register_method(
4680 method => "user_saved_search_cud",
4681 api_name => "open-ils.actor.user.saved_search.cud",
4684 desc => 'Create/Update/Delete Access to user saved searches',
4686 { desc => 'Authentication token', type => 'string' },
4687 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4690 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4696 __PACKAGE__->register_method(
4697 method => "user_saved_search_cud",
4698 api_name => "open-ils.actor.user.saved_search.retrieve",
4701 desc => 'Retrieve a saved search object',
4703 { desc => 'Authentication token', type => 'string' },
4704 { desc => 'Saved Search ID', type => 'number' }
4707 desc => q/The saved search object, Event on error/,
4713 sub user_saved_search_cud {
4714 my( $self, $client, $auth, $search ) = @_;
4715 my $e = new_editor( authtoken=>$auth );
4716 return $e->die_event unless $e->checkauth;
4718 my $o_search; # prior version of the object, if any
4719 my $res; # to be returned
4721 # branch on the operation type
4723 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4725 # Get the old version, to check ownership
4726 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4727 or return $e->die_event;
4729 # You can't read somebody else's search
4730 return OpenILS::Event->new('BAD_PARAMS')
4731 unless $o_search->owner == $e->requestor->id;
4737 $e->xact_begin; # start an editor transaction
4739 if( $search->isnew ) { # Create
4741 # You can't create a search for somebody else
4742 return OpenILS::Event->new('BAD_PARAMS')
4743 unless $search->owner == $e->requestor->id;
4745 $e->create_actor_usr_saved_search( $search )
4746 or return $e->die_event;
4750 } elsif( $search->ischanged ) { # Update
4752 # You can't change ownership of a search
4753 return OpenILS::Event->new('BAD_PARAMS')
4754 unless $search->owner == $e->requestor->id;
4756 # Get the old version, to check ownership
4757 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4758 or return $e->die_event;
4760 # You can't update somebody else's search
4761 return OpenILS::Event->new('BAD_PARAMS')
4762 unless $o_search->owner == $e->requestor->id;
4765 $e->update_actor_usr_saved_search( $search )
4766 or return $e->die_event;
4770 } elsif( $search->isdeleted ) { # Delete
4772 # Get the old version, to check ownership
4773 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4774 or return $e->die_event;
4776 # You can't delete somebody else's search
4777 return OpenILS::Event->new('BAD_PARAMS')
4778 unless $o_search->owner == $e->requestor->id;
4781 $e->delete_actor_usr_saved_search( $o_search )
4782 or return $e->die_event;
4793 __PACKAGE__->register_method(
4794 method => "get_barcodes",
4795 api_name => "open-ils.actor.get_barcodes"
4799 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4800 my $e = new_editor(authtoken => $auth);
4801 return $e->event unless $e->checkauth;
4802 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4804 my $db_result = $e->json_query(
4806 'evergreen.get_barcodes',
4807 $org_id, $context, $barcode,
4811 if($context =~ /actor/) {
4812 my $filter_result = ();
4814 foreach my $result (@$db_result) {
4815 if($result->{type} eq 'actor') {
4816 if($e->requestor->id != $result->{id}) {
4817 $patron = $e->retrieve_actor_user($result->{id});
4819 push(@$filter_result, $e->event);
4822 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4823 push(@$filter_result, $result);
4826 push(@$filter_result, $e->event);
4830 push(@$filter_result, $result);
4834 push(@$filter_result, $result);
4837 return $filter_result;
4843 __PACKAGE__->register_method(
4844 method => 'address_alert_test',
4845 api_name => 'open-ils.actor.address_alert.test',
4847 desc => "Tests a set of address fields to determine if they match with an address_alert",
4849 {desc => 'Authentication token', type => 'string'},
4850 {desc => 'Org Unit', type => 'number'},
4851 {desc => 'Fields', type => 'hash'},
4853 return => {desc => 'List of matching address_alerts'}
4857 sub address_alert_test {
4858 my ($self, $client, $auth, $org_unit, $fields) = @_;
4859 return [] unless $fields and grep {$_} values %$fields;
4861 my $e = new_editor(authtoken => $auth);
4862 return $e->event unless $e->checkauth;
4863 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4864 $org_unit ||= $e->requestor->ws_ou;
4866 my $alerts = $e->json_query({
4868 'actor.address_alert_matches',
4876 $$fields{post_code},
4877 $$fields{mailing_address},
4878 $$fields{billing_address}
4882 # map the json_query hashes to real objects
4884 map {$e->retrieve_actor_address_alert($_)}
4885 (map {$_->{id}} @$alerts)
4889 __PACKAGE__->register_method(
4890 method => "mark_users_contact_invalid",
4891 api_name => "open-ils.actor.invalidate.email",
4893 desc => "Given a patron or email address, clear the email field for one patron or all patrons with that email address and put the old email address into a note and/or create a standing penalty, depending on OU settings",
4895 {desc => "Authentication token", type => "string"},
4896 {desc => "Patron ID (optional if Email address specified)", type => "number"},
4897 {desc => "Additional note text (optional)", type => "string"},
4898 {desc => "penalty org unit ID (optional)", type => "number"},
4899 {desc => "Email address (optional)", type => "string"}
4901 return => {desc => "Event describing success or failure", type => "object"}
4905 __PACKAGE__->register_method(
4906 method => "mark_users_contact_invalid",
4907 api_name => "open-ils.actor.invalidate.day_phone",
4909 desc => "Given a patron or phone number, clear the day_phone field for one patron or all patrons with that day_phone number and put the old day_phone into a note and/or create a standing penalty, depending on OU settings",
4911 {desc => "Authentication token", type => "string"},
4912 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4913 {desc => "Additional note text (optional)", type => "string"},
4914 {desc => "penalty org unit ID (optional)", type => "number"},
4915 {desc => "Phone Number (optional)", type => "string"}
4917 return => {desc => "Event describing success or failure", type => "object"}
4921 __PACKAGE__->register_method(
4922 method => "mark_users_contact_invalid",
4923 api_name => "open-ils.actor.invalidate.evening_phone",
4925 desc => "Given a patron or phone number, clear the evening_phone field for one patron or all patrons with that evening_phone number and put the old evening_phone into a note and/or create a standing penalty, depending on OU settings",
4927 {desc => "Authentication token", type => "string"},
4928 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4929 {desc => "Additional note text (optional)", type => "string"},
4930 {desc => "penalty org unit ID (optional)", type => "number"},
4931 {desc => "Phone Number (optional)", type => "string"}
4933 return => {desc => "Event describing success or failure", type => "object"}
4937 __PACKAGE__->register_method(
4938 method => "mark_users_contact_invalid",
4939 api_name => "open-ils.actor.invalidate.other_phone",
4941 desc => "Given a patron or phone number, clear the other_phone field for one patron or all patrons with that other_phone number and put the old other_phone into a note and/or create a standing penalty, depending on OU settings",
4943 {desc => "Authentication token", type => "string"},
4944 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4945 {desc => "Additional note text (optional)", type => "string"},
4946 {desc => "penalty org unit ID (optional, default to top of org tree)",
4948 {desc => "Phone Number (optional)", type => "string"}
4950 return => {desc => "Event describing success or failure", type => "object"}
4954 sub mark_users_contact_invalid {
4955 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
4957 # This method invalidates an email address or a phone_number which
4958 # removes the bad email address or phone number, copying its contents
4959 # to a patron note, and institutes a standing penalty for "bad email"
4960 # or "bad phone number" which is cleared when the user is saved or
4961 # optionally only when the user is saved with an email address or
4962 # phone number (or staff manually delete the penalty).
4964 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4966 my $e = new_editor(authtoken => $auth, xact => 1);
4967 return $e->die_event unless $e->checkauth;
4970 if (defined $patron_id && $patron_id ne "") {
4971 $howfind = {usr => $patron_id};
4972 } elsif (defined $contact && $contact ne "") {
4973 $howfind = {$contact_type => $contact};
4975 # Error out if no patron id set or no contact is set.
4976 return OpenILS::Event->new('BAD_PARAMS');
4979 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4980 $e, $contact_type, $howfind,
4981 $addl_note, $penalty_ou, $e->requestor->id
4985 # Putting the following method in open-ils.actor is a bad fit, except in that
4986 # it serves an interface that lives under 'actor' in the templates directory,
4987 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4989 __PACKAGE__->register_method(
4990 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4991 method => "get_all_at_reactors_in_use",
4996 { name => 'authtoken', type => 'string' }
4999 desc => 'list of reactor names', type => 'array'
5004 sub get_all_at_reactors_in_use {
5005 my ($self, $conn, $auth) = @_;
5007 my $e = new_editor(authtoken => $auth);
5008 $e->checkauth or return $e->die_event;
5009 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5011 my $reactors = $e->json_query({
5013 atevdef => [{column => "reactor", transform => "distinct"}]
5015 from => {atevdef => {}}
5018 return $e->die_event unless ref $reactors eq "ARRAY";
5021 return [ map { $_->{reactor} } @$reactors ];
5024 __PACKAGE__->register_method(
5025 method => "filter_group_entry_crud",
5026 api_name => "open-ils.actor.filter_group_entry.crud",
5029 Provides CRUD access to filter group entry objects. These are not full accessible
5030 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5031 are not accessible via PCRUD (because they have no fields against which to link perms)
5034 {desc => "Authentication token", type => "string"},
5035 {desc => "Entry ID / Entry Object", type => "number"},
5036 {desc => "Additional note text (optional)", type => "string"},
5037 {desc => "penalty org unit ID (optional, default to top of org tree)",
5041 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5047 sub filter_group_entry_crud {
5048 my ($self, $conn, $auth, $arg) = @_;
5050 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5051 my $e = new_editor(authtoken => $auth, xact => 1);
5052 return $e->die_event unless $e->checkauth;
5058 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5059 or return $e->die_event;
5061 return $e->die_event unless $e->allowed(
5062 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5064 my $query = $arg->query;
5065 $query = $e->create_actor_search_query($query) or return $e->die_event;
5066 $arg->query($query->id);
5067 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5068 $entry->query($query);
5073 } elsif ($arg->ischanged) {
5075 my $entry = $e->retrieve_actor_search_filter_group_entry([
5078 flesh_fields => {asfge => ['grp']}
5080 ]) or return $e->die_event;
5082 return $e->die_event unless $e->allowed(
5083 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5085 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5086 $arg->query($arg->query->id);
5087 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5088 $arg->query($query);
5093 } elsif ($arg->isdeleted) {
5095 my $entry = $e->retrieve_actor_search_filter_group_entry([
5098 flesh_fields => {asfge => ['grp', 'query']}
5100 ]) or return $e->die_event;
5102 return $e->die_event unless $e->allowed(
5103 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5105 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5106 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5119 my $entry = $e->retrieve_actor_search_filter_group_entry([
5122 flesh_fields => {asfge => ['grp', 'query']}
5124 ]) or return $e->die_event;
5126 return $e->die_event unless $e->allowed(
5127 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5128 $entry->grp->owner);
5131 $entry->grp($entry->grp->id); # for consistency