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::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
34 use OpenILS::Application::Actor::Settings;
36 use OpenILS::Utils::CStoreEditor qw/:funcs/;
37 use OpenILS::Utils::Penalty;
38 use OpenILS::Utils::BadContact;
39 use List::Util qw/max reduce/;
41 use UUID::Tiny qw/:std/;
44 OpenILS::Application::Actor::Container->initialize();
45 OpenILS::Application::Actor::UserGroups->initialize();
46 OpenILS::Application::Actor::ClosedDates->initialize();
49 my $apputils = "OpenILS::Application::AppUtils";
52 sub _d { warn "Patron:\n" . Dumper(shift()); }
55 my $set_user_settings;
59 #__PACKAGE__->register_method(
60 # method => "allowed_test",
61 # api_name => "open-ils.actor.allowed_test",
64 # my($self, $conn, $auth, $orgid, $permcode) = @_;
65 # my $e = new_editor(authtoken => $auth);
66 # return $e->die_event unless $e->checkauth;
70 # permcode => $permcode,
71 # result => $e->allowed($permcode, $orgid)
75 __PACKAGE__->register_method(
76 method => "update_user_setting",
77 api_name => "open-ils.actor.patron.settings.update",
79 sub update_user_setting {
80 my($self, $conn, $auth, $user_id, $settings) = @_;
81 my $e = new_editor(xact => 1, authtoken => $auth);
82 return $e->die_event unless $e->checkauth;
84 $user_id = $e->requestor->id unless defined $user_id;
86 unless($e->requestor->id == $user_id) {
87 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
88 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
91 for my $name (keys %$settings) {
92 my $val = $$settings{$name};
93 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
96 $val = OpenSRF::Utils::JSON->perl2JSON($val);
99 $e->update_actor_user_setting($set) or return $e->die_event;
101 $set = Fieldmapper::actor::user_setting->new;
105 $e->create_actor_user_setting($set) or return $e->die_event;
108 $e->delete_actor_user_setting($set) or return $e->die_event;
117 __PACKAGE__->register_method(
118 method => "update_privacy_waiver",
119 api_name => "open-ils.actor.patron.privacy_waiver.update",
121 desc => "Replaces any existing privacy waiver entries for the patron with the supplied values.",
123 {desc => 'Authentication token', type => 'string'},
124 {desc => 'User ID', type => 'number'},
125 {desc => 'Arrayref of privacy waiver entries', type => 'object'}
127 return => {desc => '1 on success, Event on error'}
130 sub update_privacy_waiver {
131 my($self, $conn, $auth, $user_id, $waiver) = @_;
132 my $e = new_editor(xact => 1, authtoken => $auth);
133 return $e->die_event unless $e->checkauth;
135 $user_id = $e->requestor->id unless defined $user_id;
137 unless($e->requestor->id == $user_id) {
138 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
139 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
142 foreach my $w (@$waiver) {
143 $w->{usr} = $user_id unless $w->{usr};
144 if ($w->{id} && $w->{id} ne 'new') {
145 my $existing_rows = $e->search_actor_usr_privacy_waiver({usr => $user_id, id => $w->{id}});
146 if ($existing_rows) {
147 my $existing = $existing_rows->[0];
148 # delete existing if name is empty
149 if (!$w->{name} or $w->{name} =~ /^\s*$/) {
150 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
152 # delete existing if none of the boxes were checked
153 } elsif (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}) {
154 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
156 # otherwise, update existing waiver entry
158 $existing->name($w->{name});
159 $existing->place_holds($w->{place_holds});
160 $existing->pickup_holds($w->{pickup_holds});
161 $existing->checkout_items($w->{checkout_items});
162 $existing->view_history($w->{view_history});
163 $e->update_actor_usr_privacy_waiver($existing) or return $e->die_event;
166 $logger->warn("No privacy waiver entry found for user $user_id with ID " . $w->{id});
170 # ignore new entries with empty name or with no boxes checked
171 next if (!$w->{name} or $w->{name} =~ /^\s*$/);
172 next if (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history});
173 my $new = Fieldmapper::actor::usr_privacy_waiver->new;
174 $new->usr($w->{usr});
175 $new->name($w->{name});
176 $new->place_holds($w->{place_holds});
177 $new->pickup_holds($w->{pickup_holds});
178 $new->checkout_items($w->{checkout_items});
179 $new->view_history($w->{view_history});
180 $e->create_actor_usr_privacy_waiver($new) or return $e->die_event;
189 __PACKAGE__->register_method(
190 method => "set_ou_settings",
191 api_name => "open-ils.actor.org_unit.settings.update",
193 desc => "Updates the value for a given org unit setting. The permission to update " .
194 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
195 "permission specified in the update_perm column of the config.org_unit_setting_type " .
196 "table's row corresponding to the setting being changed." ,
198 {desc => 'Authentication token', type => 'string'},
199 {desc => 'Org unit ID', type => 'number'},
200 {desc => 'Hash of setting name-value pairs', type => 'object'}
202 return => {desc => '1 on success, Event on error'}
206 sub set_ou_settings {
207 my( $self, $client, $auth, $org_id, $settings ) = @_;
209 my $e = new_editor(authtoken => $auth, xact => 1);
210 return $e->die_event unless $e->checkauth;
212 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
214 for my $name (keys %$settings) {
215 my $val = $$settings{$name};
217 my $type = $e->retrieve_config_org_unit_setting_type([
219 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
220 ]) or return $e->die_event;
221 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
223 # If there is no relevant permission, the default assumption will
224 # be, "no, the caller cannot change that value."
225 return $e->die_event unless ($all_allowed ||
226 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
229 $val = OpenSRF::Utils::JSON->perl2JSON($val);
232 $e->update_actor_org_unit_setting($set) or return $e->die_event;
234 $set = Fieldmapper::actor::org_unit_setting->new;
235 $set->org_unit($org_id);
238 $e->create_actor_org_unit_setting($set) or return $e->die_event;
241 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
249 __PACKAGE__->register_method(
250 method => "user_settings",
252 api_name => "open-ils.actor.patron.settings.retrieve",
255 my( $self, $client, $auth, $user_id, $setting ) = @_;
257 my $e = new_editor(authtoken => $auth);
258 return $e->event unless $e->checkauth;
259 $user_id = $e->requestor->id unless defined $user_id;
261 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
262 if($e->requestor->id != $user_id) {
263 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
267 my($e, $user_id, $setting) = @_;
268 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
269 return undef unless $val; # XXX this should really return undef, but needs testing
270 return OpenSRF::Utils::JSON->JSON2perl($val->value);
274 if(ref $setting eq 'ARRAY') {
276 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
279 return get_setting($e, $user_id, $setting);
282 my $s = $e->search_actor_user_setting({usr => $user_id});
283 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
288 __PACKAGE__->register_method(
289 method => "ranged_ou_settings",
290 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
292 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
293 "is implied for retrieving OU settings by the authenticated users' permissions.",
295 {desc => 'Authentication token', type => 'string'},
296 {desc => 'Org unit ID', type => 'number'},
298 return => {desc => 'A hashref of "ranged" settings, event on error'}
301 sub ranged_ou_settings {
302 my( $self, $client, $auth, $org_id ) = @_;
304 my $e = new_editor(authtoken => $auth);
305 return $e->event unless $e->checkauth;
308 my $org_list = $U->get_org_ancestors($org_id);
309 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
310 $org_list = [ reverse @$org_list ];
312 # start at the context org and capture the setting value
313 # without clobbering settings we've already captured
314 for my $this_org_id (@$org_list) {
316 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
318 for my $set (@sets) {
319 my $type = $e->retrieve_config_org_unit_setting_type([
321 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
324 # If there is no relevant permission, the default assumption will
325 # be, "yes, the caller can have that value."
326 if ($type && $type->view_perm) {
327 next if not $e->allowed($type->view_perm->code, $org_id);
330 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
331 unless defined $ranged_settings{$set->name};
335 return \%ranged_settings;
340 __PACKAGE__->register_method(
341 api_name => 'open-ils.actor.ou_setting.ancestor_default',
342 method => 'ou_ancestor_setting',
344 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
345 'This method will make sure that the given user has permission to view that setting, if there is a ' .
346 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
347 'the user lacks the permisssion, undef will be returned.' ,
349 { desc => 'Org unit ID', type => 'number' },
350 { desc => 'setting name', type => 'string' },
351 { desc => 'authtoken (optional)', type => 'string' }
353 return => {desc => 'A value for the org unit setting, or undef'}
357 # ------------------------------------------------------------------
358 # Attempts to find the org setting value for a given org. if not
359 # found at the requested org, searches up the org tree until it
360 # finds a parent that has the requested setting.
361 # when found, returns { org => $id, value => $value }
362 # otherwise, returns NULL
363 # ------------------------------------------------------------------
364 sub ou_ancestor_setting {
365 my( $self, $client, $orgid, $name, $auth ) = @_;
366 # Make sure $auth is set to something if not given.
368 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
371 __PACKAGE__->register_method(
372 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
373 method => 'ou_ancestor_setting_batch',
375 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
376 'This method will make sure that the given user has permission to view that setting, if there is a ' .
377 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
378 'the user lacks the permisssion, undef will be returned.' ,
380 { desc => 'Org unit ID', type => 'number' },
381 { desc => 'setting name list', type => 'array' },
382 { desc => 'authtoken (optional)', type => 'string' }
384 return => {desc => 'A hash with name => value pairs for the org unit settings'}
387 sub ou_ancestor_setting_batch {
388 my( $self, $client, $orgid, $name_list, $auth ) = @_;
390 # splitting the list of settings to fetch values
391 # so that ones that *don't* require view_perm checks
392 # can be fetched in one fell swoop, which is
393 # significantly faster in cases where a large
394 # number of settings need to be fetched.
395 my %perm_check_required = ();
396 my @perm_check_not_required = ();
398 # Note that ->ou_ancestor_setting also can check
399 # to see if the setting has a view_perm, but testing
400 # suggests that the redundant checks do not significantly
401 # increase the time it takes to fetch the values of
402 # permission-controlled settings.
403 my $e = new_editor();
404 my $res = $e->search_config_org_unit_setting_type({
406 view_perm => { "!=" => undef },
408 %perm_check_required = map { $_->name() => 1 } @$res;
409 foreach my $setting (@$name_list) {
410 push @perm_check_not_required, $setting
411 unless exists($perm_check_required{$setting});
415 if (@perm_check_not_required) {
416 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
418 $values{$_} = $U->ou_ancestor_setting(
421 ) for keys(%perm_check_required);
427 __PACKAGE__->register_method(
428 method => "update_patron",
429 api_name => "open-ils.actor.patron.update",
432 Update an existing user, or create a new one. Related objects,
433 like cards, addresses, survey responses, and stat cats,
434 can be updated by attaching them to the user object in their
435 respective fields. For examples, the billing address object
436 may be inserted into the 'billing_address' field, etc. For each
437 attached object, indicate if the object should be created,
438 updated, or deleted using the built-in 'isnew', 'ischanged',
439 and 'isdeleted' fields on the object.
442 { desc => 'Authentication token', type => 'string' },
443 { desc => 'Patron data object', type => 'object' }
445 return => {desc => 'A fleshed user object, event on error'}
450 my( $self, $client, $auth, $patron ) = @_;
452 my $e = new_editor(xact => 1, authtoken => $auth);
453 return $e->event unless $e->checkauth;
455 $logger->info($patron->isnew ? "Creating new patron..." :
456 "Updating Patron: " . $patron->id);
458 my $evt = check_group_perm($e, $e->requestor, $patron);
461 # $new_patron is the patron in progress. $patron is the original patron
462 # passed in with the method. new_patron will change as the components
463 # of patron are added/updated.
467 # unflesh the real items on the patron
468 $patron->card( $patron->card->id ) if(ref($patron->card));
469 $patron->billing_address( $patron->billing_address->id )
470 if(ref($patron->billing_address));
471 $patron->mailing_address( $patron->mailing_address->id )
472 if(ref($patron->mailing_address));
474 # create/update the patron first so we can use his id
476 # $patron is the obj from the client (new data) and $new_patron is the
477 # patron object properly built for db insertion, so we need a third variable
478 # if we want to represent the old patron.
481 my $barred_hook = '';
483 if($patron->isnew()) {
484 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
486 if($U->is_true($patron->barred)) {
487 return $e->die_event unless
488 $e->allowed('BAR_PATRON', $patron->home_ou);
491 $new_patron = $patron;
493 # Did auth checking above already.
494 $old_patron = $e->retrieve_actor_user($patron->id) or
495 return $e->die_event;
497 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
498 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
499 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
501 $barred_hook = $U->is_true($new_patron->barred) ?
502 'au.barred' : 'au.unbarred';
505 # update the password by itself to avoid the password protection magic
506 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
507 modify_migrated_user_password($e, $patron->id, $patron->passwd);
508 $new_patron->passwd(''); # subsequent update will set
509 # actor.usr.passwd to MD5('')
513 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
516 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
519 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
522 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
525 # re-update the patron if anything has happened to him during this process
526 if($new_patron->ischanged()) {
527 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
531 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
534 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
537 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
540 $evt = apply_invalid_addr_penalty($e, $patron);
545 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
547 $tses->request('open-ils.trigger.event.autocreate',
548 'au.create', $new_patron, $new_patron->home_ou);
550 $tses->request('open-ils.trigger.event.autocreate',
551 'au.update', $new_patron, $new_patron->home_ou);
553 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
554 $new_patron, $new_patron->home_ou) if $barred_hook;
557 $e->xact_begin; # $e->rollback is called in new_flesh_user
558 return flesh_user($new_patron->id(), $e);
561 sub apply_invalid_addr_penalty {
565 # grab the invalid address penalty if set
566 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
568 my ($addr_penalty) = grep
569 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
571 # do we enforce invalid address penalty
572 my $enforce = $U->ou_ancestor_setting_value(
573 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
575 my $addrs = $e->search_actor_user_address(
576 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
577 my $addr_count = scalar(@$addrs);
579 if($addr_count == 0 and $addr_penalty) {
581 # regardless of any settings, remove the penalty when the user has no invalid addresses
582 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
585 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
587 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
588 my $depth = $ptype->org_depth;
589 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
590 $ctx_org = $patron->home_ou unless defined $ctx_org;
592 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
593 $penalty->usr($patron->id);
594 $penalty->org_unit($ctx_org);
595 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
597 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
612 "standing_penalties",
622 push @$fields, "home_ou" if $home_ou;
623 return new_flesh_user($id, $fields, $e );
631 # clone and clear stuff that would break the database
635 my $new_patron = $patron->clone;
637 $new_patron->clear_billing_address();
638 $new_patron->clear_mailing_address();
639 $new_patron->clear_addresses();
640 $new_patron->clear_card();
641 $new_patron->clear_cards();
642 $new_patron->clear_id();
643 $new_patron->clear_isnew();
644 $new_patron->clear_ischanged();
645 $new_patron->clear_isdeleted();
646 $new_patron->clear_stat_cat_entries();
647 $new_patron->clear_waiver_entries();
648 $new_patron->clear_permissions();
649 $new_patron->clear_standing_penalties();
660 return (undef, $e->die_event) unless
661 $e->allowed('CREATE_USER', $patron->home_ou);
663 my $ex = $e->search_actor_user(
664 {usrname => $patron->usrname}, {idlist => 1});
665 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
667 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
669 # do a dance to get the password hashed securely
670 my $saved_password = $patron->passwd;
672 $e->create_actor_user($patron) or return (undef, $e->die_event);
673 modify_migrated_user_password($e, $patron->id, $saved_password);
675 my $id = $patron->id; # added by CStoreEditor
677 $logger->info("Successfully created new user [$id] in DB");
678 return ($e->retrieve_actor_user($id), undef);
682 sub check_group_perm {
683 my( $e, $requestor, $patron ) = @_;
686 # first let's see if the requestor has
687 # priveleges to update this user in any way
688 if( ! $patron->isnew ) {
689 my $p = $e->retrieve_actor_user($patron->id);
691 # If we are the requestor (trying to update our own account)
692 # and we are not trying to change our profile, we're good
693 if( $p->id == $requestor->id and
694 $p->profile == $patron->profile ) {
699 $evt = group_perm_failed($e, $requestor, $p);
703 # They are allowed to edit this patron.. can they put the
704 # patron into the group requested?
705 $evt = group_perm_failed($e, $requestor, $patron);
711 sub group_perm_failed {
712 my( $e, $requestor, $patron ) = @_;
716 my $grpid = $patron->profile;
720 $logger->debug("user update looking for group perm for group $grpid");
721 $grp = $e->retrieve_permission_grp_tree($grpid);
723 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
725 $logger->info("user update checking perm $perm on user ".
726 $requestor->id." for update/create on user username=".$patron->usrname);
728 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
734 my( $e, $patron, $noperm) = @_;
736 $logger->info("Updating patron ".$patron->id." in DB");
741 return (undef, $e->die_event)
742 unless $e->allowed('UPDATE_USER', $patron->home_ou);
745 if(!$patron->ident_type) {
746 $patron->clear_ident_type;
747 $patron->clear_ident_value;
750 $evt = verify_last_xact($e, $patron);
751 return (undef, $evt) if $evt;
753 $e->update_actor_user($patron) or return (undef, $e->die_event);
755 # re-fetch the user to pick up the latest last_xact_id value
756 # to avoid collisions.
757 $patron = $e->retrieve_actor_user($patron->id);
762 sub verify_last_xact {
763 my( $e, $patron ) = @_;
764 return undef unless $patron->id and $patron->id > 0;
765 my $p = $e->retrieve_actor_user($patron->id);
766 my $xact = $p->last_xact_id;
767 return undef unless $xact;
768 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
769 return OpenILS::Event->new('XACT_COLLISION')
770 if $xact ne $patron->last_xact_id;
775 sub _check_dup_ident {
776 my( $session, $patron ) = @_;
778 return undef unless $patron->ident_value;
781 ident_type => $patron->ident_type,
782 ident_value => $patron->ident_value,
785 $logger->debug("patron update searching for dup ident values: " .
786 $patron->ident_type . ':' . $patron->ident_value);
788 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
790 my $dups = $session->request(
791 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
794 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
801 sub _add_update_addresses {
805 my $new_patron = shift;
809 my $current_id; # id of the address before creation
811 my $addresses = $patron->addresses();
813 for my $address (@$addresses) {
815 next unless ref $address;
816 $current_id = $address->id();
818 if( $patron->billing_address() and
819 $patron->billing_address() == $current_id ) {
820 $logger->info("setting billing addr to $current_id");
821 $new_patron->billing_address($address->id());
822 $new_patron->ischanged(1);
825 if( $patron->mailing_address() and
826 $patron->mailing_address() == $current_id ) {
827 $new_patron->mailing_address($address->id());
828 $logger->info("setting mailing addr to $current_id");
829 $new_patron->ischanged(1);
833 if($address->isnew()) {
835 $address->usr($new_patron->id());
837 ($address, $evt) = _add_address($e,$address);
838 return (undef, $evt) if $evt;
840 # we need to get the new id
841 if( $patron->billing_address() and
842 $patron->billing_address() == $current_id ) {
843 $new_patron->billing_address($address->id());
844 $logger->info("setting billing addr to $current_id");
845 $new_patron->ischanged(1);
848 if( $patron->mailing_address() and
849 $patron->mailing_address() == $current_id ) {
850 $new_patron->mailing_address($address->id());
851 $logger->info("setting mailing addr to $current_id");
852 $new_patron->ischanged(1);
855 } elsif($address->ischanged() ) {
857 ($address, $evt) = _update_address($e, $address);
858 return (undef, $evt) if $evt;
860 } elsif($address->isdeleted() ) {
862 if( $address->id() == $new_patron->mailing_address() ) {
863 $new_patron->clear_mailing_address();
864 ($new_patron, $evt) = _update_patron($e, $new_patron);
865 return (undef, $evt) if $evt;
868 if( $address->id() == $new_patron->billing_address() ) {
869 $new_patron->clear_billing_address();
870 ($new_patron, $evt) = _update_patron($e, $new_patron);
871 return (undef, $evt) if $evt;
874 $evt = _delete_address($e, $address);
875 return (undef, $evt) if $evt;
879 return ( $new_patron, undef );
883 # adds an address to the db and returns the address with new id
885 my($e, $address) = @_;
886 $address->clear_id();
888 $logger->info("Creating new address at street ".$address->street1);
890 # put the address into the database
891 $e->create_actor_user_address($address) or return (undef, $e->die_event);
892 return ($address, undef);
896 sub _update_address {
897 my( $e, $address ) = @_;
899 $logger->info("Updating address ".$address->id." in the DB");
901 $e->update_actor_user_address($address) or return (undef, $e->die_event);
903 return ($address, undef);
908 sub _add_update_cards {
912 my $new_patron = shift;
916 my $virtual_id; #id of the card before creation
918 my $cards = $patron->cards();
919 for my $card (@$cards) {
921 $card->usr($new_patron->id());
923 if(ref($card) and $card->isnew()) {
925 $virtual_id = $card->id();
926 ( $card, $evt ) = _add_card($e, $card);
927 return (undef, $evt) if $evt;
929 #if(ref($patron->card)) { $patron->card($patron->card->id); }
930 if($patron->card() == $virtual_id) {
931 $new_patron->card($card->id());
932 $new_patron->ischanged(1);
935 } elsif( ref($card) and $card->ischanged() ) {
936 $evt = _update_card($e, $card);
937 return (undef, $evt) if $evt;
941 return ( $new_patron, undef );
945 # adds an card to the db and returns the card with new id
947 my( $e, $card ) = @_;
950 $logger->info("Adding new patron card ".$card->barcode);
952 $e->create_actor_card($card) or return (undef, $e->die_event);
954 return ( $card, undef );
958 # returns event on error. returns undef otherwise
960 my( $e, $card ) = @_;
961 $logger->info("Updating patron card ".$card->id);
963 $e->update_actor_card($card) or return $e->die_event;
968 sub _add_update_waiver_entries {
971 my $new_patron = shift;
974 my $waiver_entries = $patron->waiver_entries();
975 for my $waiver (@$waiver_entries) {
976 next unless ref $waiver;
977 $waiver->usr($new_patron->id());
978 if ($waiver->isnew()) {
979 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
980 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
981 $logger->info("Adding new patron waiver entry");
983 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
984 } elsif ($waiver->ischanged()) {
985 $logger->info("Updating patron waiver entry " . $waiver->id);
986 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
987 } elsif ($waiver->isdeleted()) {
988 $logger->info("Deleting patron waiver entry " . $waiver->id);
989 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
992 return ($new_patron, undef);
996 # returns event on error. returns undef otherwise
997 sub _delete_address {
998 my( $e, $address ) = @_;
1000 $logger->info("Deleting address ".$address->id." from DB");
1002 $e->delete_actor_user_address($address) or return $e->die_event;
1008 sub _add_survey_responses {
1009 my ($e, $patron, $new_patron) = @_;
1011 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1013 my $responses = $patron->survey_responses;
1017 $_->usr($new_patron->id) for (@$responses);
1019 my $evt = $U->simplereq( "open-ils.circ",
1020 "open-ils.circ.survey.submit.user_id", $responses );
1022 return (undef, $evt) if defined($U->event_code($evt));
1026 return ( $new_patron, undef );
1029 sub _clear_badcontact_penalties {
1030 my ($e, $old_patron, $new_patron) = @_;
1032 return ($new_patron, undef) unless $old_patron;
1034 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1036 # This ignores whether the caller of update_patron has any permission
1037 # to remove penalties, but these penalties no longer make sense
1038 # if an email address field (for example) is changed (and the caller must
1039 # have perms to do *that*) so there's no reason not to clear the penalties.
1041 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1043 "+csp" => {"name" => [values(%$PNM)]},
1044 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1046 "join" => {"csp" => {}},
1048 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1050 ]) or return (undef, $e->die_event);
1052 return ($new_patron, undef) unless @$bad_contact_penalties;
1054 my @penalties_to_clear;
1055 my ($field, $penalty_name);
1057 # For each field that might have an associated bad contact penalty,
1058 # check for such penalties and add them to the to-clear list if that
1059 # field has changed.
1060 while (($field, $penalty_name) = each(%$PNM)) {
1061 if ($old_patron->$field ne $new_patron->$field) {
1062 push @penalties_to_clear, grep {
1063 $_->standing_penalty->name eq $penalty_name
1064 } @$bad_contact_penalties;
1068 foreach (@penalties_to_clear) {
1069 # Note that this "archives" penalties, in the terminology of the staff
1070 # client, instead of just deleting them. This may assist reporting,
1071 # or preserving old contact information when it is still potentially
1073 $_->standing_penalty($_->standing_penalty->id); # deflesh
1074 $_->stop_date('now');
1075 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1078 return ($new_patron, undef);
1082 sub _create_stat_maps {
1084 my($e, $patron, $new_patron) = @_;
1086 my $maps = $patron->stat_cat_entries();
1088 for my $map (@$maps) {
1090 my $method = "update_actor_stat_cat_entry_user_map";
1092 if ($map->isdeleted()) {
1093 $method = "delete_actor_stat_cat_entry_user_map";
1095 } elsif ($map->isnew()) {
1096 $method = "create_actor_stat_cat_entry_user_map";
1101 $map->target_usr($new_patron->id);
1103 $logger->info("Updating stat entry with method $method and map $map");
1105 $e->$method($map) or return (undef, $e->die_event);
1108 return ($new_patron, undef);
1111 sub _create_perm_maps {
1113 my($e, $patron, $new_patron) = @_;
1115 my $maps = $patron->permissions;
1117 for my $map (@$maps) {
1119 my $method = "update_permission_usr_perm_map";
1120 if ($map->isdeleted()) {
1121 $method = "delete_permission_usr_perm_map";
1122 } elsif ($map->isnew()) {
1123 $method = "create_permission_usr_perm_map";
1127 $map->usr($new_patron->id);
1129 $logger->info( "Updating permissions with method $method and map $map" );
1131 $e->$method($map) or return (undef, $e->die_event);
1134 return ($new_patron, undef);
1138 __PACKAGE__->register_method(
1139 method => "set_user_work_ous",
1140 api_name => "open-ils.actor.user.work_ous.update",
1143 sub set_user_work_ous {
1149 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1150 return $evt if $evt;
1152 my $session = $apputils->start_db_session();
1153 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1155 for my $map (@$maps) {
1157 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1158 if ($map->isdeleted()) {
1159 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1160 } elsif ($map->isnew()) {
1161 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1165 #warn( "Updating permissions with method $method and session $ses and map $map" );
1166 $logger->info( "Updating work_ou map with method $method and map $map" );
1168 my $stat = $session->request($method, $map)->gather(1);
1169 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1173 $apputils->commit_db_session($session);
1175 return scalar(@$maps);
1179 __PACKAGE__->register_method(
1180 method => "set_user_perms",
1181 api_name => "open-ils.actor.user.permissions.update",
1184 sub set_user_perms {
1190 my $session = $apputils->start_db_session();
1192 my( $user_obj, $evt ) = $U->checkses($ses);
1193 return $evt if $evt;
1194 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1196 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1199 $all = 1 if ($U->is_true($user_obj->super_user()));
1200 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1202 for my $map (@$maps) {
1204 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1205 if ($map->isdeleted()) {
1206 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1207 } elsif ($map->isnew()) {
1208 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1212 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1213 #warn( "Updating permissions with method $method and session $ses and map $map" );
1214 $logger->info( "Updating permissions with method $method and map $map" );
1216 my $stat = $session->request($method, $map)->gather(1);
1217 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1221 $apputils->commit_db_session($session);
1223 return scalar(@$maps);
1227 __PACKAGE__->register_method(
1228 method => "user_retrieve_by_barcode",
1230 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1232 sub user_retrieve_by_barcode {
1233 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1235 my $e = new_editor(authtoken => $auth);
1236 return $e->event unless $e->checkauth;
1238 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1239 or return $e->event;
1241 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1242 return $e->event unless $e->allowed(
1243 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1250 __PACKAGE__->register_method(
1251 method => "get_user_by_id",
1253 api_name => "open-ils.actor.user.retrieve",
1256 sub get_user_by_id {
1257 my ($self, $client, $auth, $id) = @_;
1258 my $e = new_editor(authtoken=>$auth);
1259 return $e->event unless $e->checkauth;
1260 my $user = $e->retrieve_actor_user($id) or return $e->event;
1261 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1266 __PACKAGE__->register_method(
1267 method => "get_org_types",
1268 api_name => "open-ils.actor.org_types.retrieve",
1271 return $U->get_org_types();
1275 __PACKAGE__->register_method(
1276 method => "get_user_ident_types",
1277 api_name => "open-ils.actor.user.ident_types.retrieve",
1280 sub get_user_ident_types {
1281 return $ident_types if $ident_types;
1282 return $ident_types =
1283 new_editor()->retrieve_all_config_identification_type();
1287 __PACKAGE__->register_method(
1288 method => "get_org_unit",
1289 api_name => "open-ils.actor.org_unit.retrieve",
1293 my( $self, $client, $user_session, $org_id ) = @_;
1294 my $e = new_editor(authtoken => $user_session);
1296 return $e->event unless $e->checkauth;
1297 $org_id = $e->requestor->ws_ou;
1299 my $o = $e->retrieve_actor_org_unit($org_id)
1300 or return $e->event;
1304 __PACKAGE__->register_method(
1305 method => "search_org_unit",
1306 api_name => "open-ils.actor.org_unit_list.search",
1309 sub search_org_unit {
1311 my( $self, $client, $field, $value ) = @_;
1313 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1315 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1316 { $field => $value } );
1322 # build the org tree
1324 __PACKAGE__->register_method(
1325 method => "get_org_tree",
1326 api_name => "open-ils.actor.org_tree.retrieve",
1328 note => "Returns the entire org tree structure",
1334 return $U->get_org_tree($client->session->session_locale);
1338 __PACKAGE__->register_method(
1339 method => "get_org_descendants",
1340 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1343 # depth is optional. org_unit is the id
1344 sub get_org_descendants {
1345 my( $self, $client, $org_unit, $depth ) = @_;
1347 if(ref $org_unit eq 'ARRAY') {
1350 for my $i (0..scalar(@$org_unit)-1) {
1351 my $list = $U->simple_scalar_request(
1353 "open-ils.storage.actor.org_unit.descendants.atomic",
1354 $org_unit->[$i], $depth->[$i] );
1355 push(@trees, $U->build_org_tree($list));
1360 my $orglist = $apputils->simple_scalar_request(
1362 "open-ils.storage.actor.org_unit.descendants.atomic",
1363 $org_unit, $depth );
1364 return $U->build_org_tree($orglist);
1369 __PACKAGE__->register_method(
1370 method => "get_org_ancestors",
1371 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1374 # depth is optional. org_unit is the id
1375 sub get_org_ancestors {
1376 my( $self, $client, $org_unit, $depth ) = @_;
1377 my $orglist = $apputils->simple_scalar_request(
1379 "open-ils.storage.actor.org_unit.ancestors.atomic",
1380 $org_unit, $depth );
1381 return $U->build_org_tree($orglist);
1385 __PACKAGE__->register_method(
1386 method => "get_standings",
1387 api_name => "open-ils.actor.standings.retrieve"
1392 return $user_standings if $user_standings;
1393 return $user_standings =
1394 $apputils->simple_scalar_request(
1396 "open-ils.cstore.direct.config.standing.search.atomic",
1397 { id => { "!=" => undef } }
1402 __PACKAGE__->register_method(
1403 method => "get_my_org_path",
1404 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1407 sub get_my_org_path {
1408 my( $self, $client, $auth, $org_id ) = @_;
1409 my $e = new_editor(authtoken=>$auth);
1410 return $e->event unless $e->checkauth;
1411 $org_id = $e->requestor->ws_ou unless defined $org_id;
1413 return $apputils->simple_scalar_request(
1415 "open-ils.storage.actor.org_unit.full_path.atomic",
1420 __PACKAGE__->register_method(
1421 method => "patron_adv_search",
1422 api_name => "open-ils.actor.patron.search.advanced"
1425 __PACKAGE__->register_method(
1426 method => "patron_adv_search",
1427 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1429 # Flush the response stream at most 5 patrons in for UI responsiveness.
1430 max_bundle_count => 5,
1432 desc => q/Returns a stream of fleshed user objects instead of
1433 a pile of identifiers/
1437 sub patron_adv_search {
1438 my( $self, $client, $auth, $search_hash, $search_limit,
1439 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1441 # API params sanity checks.
1442 # Exit early with empty result if no filter exists.
1443 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1444 my $fleshed = ($self->api_name =~ /fleshed/);
1445 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1447 for my $key (keys %$search_hash) {
1448 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1452 return ($fleshed ? undef : []) unless $search_ok;
1454 my $e = new_editor(authtoken=>$auth);
1455 return $e->event unless $e->checkauth;
1456 return $e->event unless $e->allowed('VIEW_USER');
1458 # depth boundary outside of which patrons must opt-in, default to 0
1459 my $opt_boundary = 0;
1460 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1462 if (not defined $search_ou) {
1463 my $depth = $U->ou_ancestor_setting_value(
1464 $e->requestor->ws_ou,
1465 'circ.patron_edit.duplicate_patron_check_depth'
1468 if (defined $depth) {
1469 $search_ou = $U->org_unit_ancestor_at_depth(
1470 $e->requestor->ws_ou, $depth
1475 my $ids = $U->storagereq(
1476 "open-ils.storage.actor.user.crazy_search", $search_hash,
1477 $search_limit, $search_sort, $include_inactive,
1478 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1480 return $ids unless $self->api_name =~ /fleshed/;
1482 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1488 # A migrated (main) password has the form:
1489 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1490 sub modify_migrated_user_password {
1491 my ($e, $user_id, $passwd) = @_;
1493 # new password gets a new salt
1494 my $new_salt = $e->json_query({
1495 from => ['actor.create_salt', 'main']})->[0];
1496 $new_salt = $new_salt->{'actor.create_salt'};
1503 md5_hex($new_salt . md5_hex($passwd)),
1511 __PACKAGE__->register_method(
1512 method => "update_passwd",
1513 api_name => "open-ils.actor.user.password.update",
1515 desc => "Update the operator's password",
1517 { desc => 'Authentication token', type => 'string' },
1518 { desc => 'New password', type => 'string' },
1519 { desc => 'Current password', type => 'string' }
1521 return => {desc => '1 on success, Event on error or incorrect current password'}
1525 __PACKAGE__->register_method(
1526 method => "update_passwd",
1527 api_name => "open-ils.actor.user.username.update",
1529 desc => "Update the operator's username",
1531 { desc => 'Authentication token', type => 'string' },
1532 { desc => 'New username', type => 'string' },
1533 { desc => 'Current password', type => 'string' }
1535 return => {desc => '1 on success, Event on error or incorrect current password'}
1539 __PACKAGE__->register_method(
1540 method => "update_passwd",
1541 api_name => "open-ils.actor.user.email.update",
1543 desc => "Update the operator's email address",
1545 { desc => 'Authentication token', type => 'string' },
1546 { desc => 'New email address', type => 'string' },
1547 { desc => 'Current password', type => 'string' }
1549 return => {desc => '1 on success, Event on error or incorrect current password'}
1554 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1555 my $e = new_editor(xact=>1, authtoken=>$auth);
1556 return $e->die_event unless $e->checkauth;
1558 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1559 or return $e->die_event;
1560 my $api = $self->api_name;
1562 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1564 return new OpenILS::Event('INCORRECT_PASSWORD');
1567 if( $api =~ /password/o ) {
1568 # NOTE: with access to the plain text password we could crypt
1569 # the password without the extra MD5 pre-hashing. Other changes
1570 # would be required. Noting here for future reference.
1571 modify_migrated_user_password($e, $db_user->id, $new_val);
1572 $db_user->passwd('');
1576 # if we don't clear the password, the user will be updated with
1577 # a hashed version of the hashed version of their password
1578 $db_user->clear_passwd;
1580 if( $api =~ /username/o ) {
1582 # make sure no one else has this username
1583 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1586 return new OpenILS::Event('USERNAME_EXISTS');
1588 $db_user->usrname($new_val);
1590 } elsif( $api =~ /email/o ) {
1591 $db_user->email($new_val);
1595 $e->update_actor_user($db_user) or return $e->die_event;
1598 # update the cached user to pick up these changes
1599 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1605 __PACKAGE__->register_method(
1606 method => "check_user_perms",
1607 api_name => "open-ils.actor.user.perm.check",
1608 notes => <<" NOTES");
1609 Takes a login session, user id, an org id, and an array of perm type strings. For each
1610 perm type, if the user does *not* have the given permission it is added
1611 to a list which is returned from the method. If all permissions
1612 are allowed, an empty list is returned
1613 if the logged in user does not match 'user_id', then the logged in user must
1614 have VIEW_PERMISSION priveleges.
1617 sub check_user_perms {
1618 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1620 my( $staff, $evt ) = $apputils->checkses($login_session);
1621 return $evt if $evt;
1623 if($staff->id ne $user_id) {
1624 if( $evt = $apputils->check_perms(
1625 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1631 for my $perm (@$perm_types) {
1632 if($apputils->check_perms($user_id, $org_id, $perm)) {
1633 push @not_allowed, $perm;
1637 return \@not_allowed
1640 __PACKAGE__->register_method(
1641 method => "check_user_perms2",
1642 api_name => "open-ils.actor.user.perm.check.multi_org",
1644 Checks the permissions on a list of perms and orgs for a user
1645 @param authtoken The login session key
1646 @param user_id The id of the user to check
1647 @param orgs The array of org ids
1648 @param perms The array of permission names
1649 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1650 if the logged in user does not match 'user_id', then the logged in user must
1651 have VIEW_PERMISSION priveleges.
1654 sub check_user_perms2 {
1655 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1657 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1658 $authtoken, $user_id, 'VIEW_PERMISSION' );
1659 return $evt if $evt;
1662 for my $org (@$orgs) {
1663 for my $perm (@$perms) {
1664 if($apputils->check_perms($user_id, $org, $perm)) {
1665 push @not_allowed, [ $org, $perm ];
1670 return \@not_allowed
1674 __PACKAGE__->register_method(
1675 method => 'check_user_perms3',
1676 api_name => 'open-ils.actor.user.perm.highest_org',
1678 Returns the highest org unit id at which a user has a given permission
1679 If the requestor does not match the target user, the requestor must have
1680 'VIEW_PERMISSION' rights at the home org unit of the target user
1681 @param authtoken The login session key
1682 @param userid The id of the user in question
1683 @param perm The permission to check
1684 @return The org unit highest in the org tree within which the user has
1685 the requested permission
1688 sub check_user_perms3 {
1689 my($self, $client, $authtoken, $user_id, $perm) = @_;
1690 my $e = new_editor(authtoken=>$authtoken);
1691 return $e->event unless $e->checkauth;
1693 my $tree = $U->get_org_tree();
1695 unless($e->requestor->id == $user_id) {
1696 my $user = $e->retrieve_actor_user($user_id)
1697 or return $e->event;
1698 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1699 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1702 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1705 __PACKAGE__->register_method(
1706 method => 'user_has_work_perm_at',
1707 api_name => 'open-ils.actor.user.has_work_perm_at',
1711 Returns a set of org unit IDs which represent the highest orgs in
1712 the org tree where the user has the requested permission. The
1713 purpose of this method is to return the smallest set of org units
1714 which represent the full expanse of the user's ability to perform
1715 the requested action. The user whose perms this method should
1716 check is implied by the authtoken. /,
1718 {desc => 'authtoken', type => 'string'},
1719 {desc => 'permission name', type => 'string'},
1720 {desc => q/user id, optional. If present, check perms for
1721 this user instead of the logged in user/, type => 'number'},
1723 return => {desc => 'An array of org IDs'}
1727 sub user_has_work_perm_at {
1728 my($self, $conn, $auth, $perm, $user_id) = @_;
1729 my $e = new_editor(authtoken=>$auth);
1730 return $e->event unless $e->checkauth;
1731 if(defined $user_id) {
1732 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1733 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1735 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1738 __PACKAGE__->register_method(
1739 method => 'user_has_work_perm_at_batch',
1740 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1744 sub user_has_work_perm_at_batch {
1745 my($self, $conn, $auth, $perms, $user_id) = @_;
1746 my $e = new_editor(authtoken=>$auth);
1747 return $e->event unless $e->checkauth;
1748 if(defined $user_id) {
1749 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1750 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1753 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1759 __PACKAGE__->register_method(
1760 method => 'check_user_perms4',
1761 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1763 Returns the highest org unit id at which a user has a given permission
1764 If the requestor does not match the target user, the requestor must have
1765 'VIEW_PERMISSION' rights at the home org unit of the target user
1766 @param authtoken The login session key
1767 @param userid The id of the user in question
1768 @param perms An array of perm names to check
1769 @return An array of orgId's representing the org unit
1770 highest in the org tree within which the user has the requested permission
1771 The arrah of orgId's has matches the order of the perms array
1774 sub check_user_perms4 {
1775 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1777 my( $staff, $target, $org, $evt );
1779 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1780 $authtoken, $userid, 'VIEW_PERMISSION' );
1781 return $evt if $evt;
1784 return [] unless ref($perms);
1785 my $tree = $U->get_org_tree();
1787 for my $p (@$perms) {
1788 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1794 __PACKAGE__->register_method(
1795 method => "user_fines_summary",
1796 api_name => "open-ils.actor.user.fines.summary",
1799 desc => 'Returns a short summary of the users total open fines, ' .
1800 'excluding voided fines Params are login_session, user_id' ,
1802 {desc => 'Authentication token', type => 'string'},
1803 {desc => 'User ID', type => 'string'} # number?
1806 desc => "a 'mous' object, event on error",
1811 sub user_fines_summary {
1812 my( $self, $client, $auth, $user_id ) = @_;
1814 my $e = new_editor(authtoken=>$auth);
1815 return $e->event unless $e->checkauth;
1817 if( $user_id ne $e->requestor->id ) {
1818 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1819 return $e->event unless
1820 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1823 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1827 __PACKAGE__->register_method(
1828 method => "user_opac_vitals",
1829 api_name => "open-ils.actor.user.opac.vital_stats",
1833 desc => 'Returns a short summary of the users vital stats, including ' .
1834 'identification information, accumulated balance, number of holds, ' .
1835 'and current open circulation stats' ,
1837 {desc => 'Authentication token', type => 'string'},
1838 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1841 desc => "An object with four properties: user, fines, checkouts and holds."
1846 sub user_opac_vitals {
1847 my( $self, $client, $auth, $user_id ) = @_;
1849 my $e = new_editor(authtoken=>$auth);
1850 return $e->event unless $e->checkauth;
1852 $user_id ||= $e->requestor->id;
1854 my $user = $e->retrieve_actor_user( $user_id );
1857 ->method_lookup('open-ils.actor.user.fines.summary')
1858 ->run($auth => $user_id);
1859 return $fines if (defined($U->event_code($fines)));
1862 $fines = new Fieldmapper::money::open_user_summary ();
1863 $fines->balance_owed(0.00);
1864 $fines->total_owed(0.00);
1865 $fines->total_paid(0.00);
1866 $fines->usr($user_id);
1870 ->method_lookup('open-ils.actor.user.hold_requests.count')
1871 ->run($auth => $user_id);
1872 return $holds if (defined($U->event_code($holds)));
1875 ->method_lookup('open-ils.actor.user.checked_out.count')
1876 ->run($auth => $user_id);
1877 return $out if (defined($U->event_code($out)));
1879 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1881 my $unread_msgs = $e->search_actor_usr_message([
1882 {usr => $user_id, read_date => undef, deleted => 'f'},
1888 first_given_name => $user->first_given_name,
1889 second_given_name => $user->second_given_name,
1890 family_name => $user->family_name,
1891 alias => $user->alias,
1892 usrname => $user->usrname
1894 fines => $fines->to_bare_hash,
1897 messages => { unread => scalar(@$unread_msgs) }
1902 ##### a small consolidation of related method registrations
1903 my $common_params = [
1904 { desc => 'Authentication token', type => 'string' },
1905 { desc => 'User ID', type => 'string' },
1906 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1907 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1910 'open-ils.actor.user.transactions' => '',
1911 'open-ils.actor.user.transactions.fleshed' => '',
1912 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1913 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1914 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1915 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1918 foreach (keys %methods) {
1920 method => "user_transactions",
1923 desc => 'For a given user, retrieve a list of '
1924 . (/\.fleshed/ ? 'fleshed ' : '')
1925 . 'transactions' . $methods{$_}
1926 . ' optionally limited to transactions of a given type.',
1927 params => $common_params,
1929 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1930 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1934 $args{authoritative} = 1;
1935 __PACKAGE__->register_method(%args);
1938 # Now for the counts
1940 'open-ils.actor.user.transactions.count' => '',
1941 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1942 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1945 foreach (keys %methods) {
1947 method => "user_transactions",
1950 desc => 'For a given user, retrieve a count of open '
1951 . 'transactions' . $methods{$_}
1952 . ' optionally limited to transactions of a given type.',
1953 params => $common_params,
1954 return => { desc => "Integer count of transactions, or event on error" }
1957 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1958 __PACKAGE__->register_method(%args);
1961 __PACKAGE__->register_method(
1962 method => "user_transactions",
1963 api_name => "open-ils.actor.user.transactions.have_balance.total",
1966 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1967 . ' optionally limited to transactions of a given type.',
1968 params => $common_params,
1969 return => { desc => "Decimal balance value, or event on error" }
1974 sub user_transactions {
1975 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1978 my $e = new_editor(authtoken => $auth);
1979 return $e->event unless $e->checkauth;
1981 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1983 return $e->event unless
1984 $e->requestor->id == $user_id or
1985 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1987 my $api = $self->api_name();
1989 my $filter = ($api =~ /have_balance/o) ?
1990 { 'balance_owed' => { '<>' => 0 } }:
1991 { 'total_owed' => { '>' => 0 } };
1993 my $method = 'open-ils.actor.user.transactions.history.still_open';
1994 $method = "$method.authoritative" if $api =~ /authoritative/;
1995 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1997 if($api =~ /total/o) {
1999 $total += $_->balance_owed for @$trans;
2003 ($api =~ /count/o ) and return scalar @$trans;
2004 ($api !~ /fleshed/o) and return $trans;
2007 for my $t (@$trans) {
2009 if( $t->xact_type ne 'circulation' ) {
2010 push @resp, {transaction => $t};
2014 my $circ_data = flesh_circ($e, $t->id);
2015 push @resp, {transaction => $t, %$circ_data};
2022 __PACKAGE__->register_method(
2023 method => "user_transaction_retrieve",
2024 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2027 notes => "Returns a fleshed transaction record"
2030 __PACKAGE__->register_method(
2031 method => "user_transaction_retrieve",
2032 api_name => "open-ils.actor.user.transaction.retrieve",
2035 notes => "Returns a transaction record"
2038 sub user_transaction_retrieve {
2039 my($self, $client, $auth, $bill_id) = @_;
2041 my $e = new_editor(authtoken => $auth);
2042 return $e->event unless $e->checkauth;
2044 my $trans = $e->retrieve_money_billable_transaction_summary(
2045 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2047 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2049 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2051 return $trans unless $self->api_name =~ /flesh/;
2052 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2054 my $circ_data = flesh_circ($e, $trans->id, 1);
2056 return {transaction => $trans, %$circ_data};
2061 my $circ_id = shift;
2062 my $flesh_copy = shift;
2064 my $circ = $e->retrieve_action_circulation([
2068 circ => ['target_copy'],
2069 acp => ['call_number'],
2076 my $copy = $circ->target_copy;
2078 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2079 $mods = new Fieldmapper::metabib::virtual_record;
2080 $mods->doc_id(OILS_PRECAT_RECORD);
2081 $mods->title($copy->dummy_title);
2082 $mods->author($copy->dummy_author);
2085 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2089 $circ->target_copy($circ->target_copy->id);
2090 $copy->call_number($copy->call_number->id);
2092 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2096 __PACKAGE__->register_method(
2097 method => "hold_request_count",
2098 api_name => "open-ils.actor.user.hold_requests.count",
2102 Returns hold ready vs. total counts.
2103 If a context org unit is provided, a third value
2104 is returned with key 'behind_desk', which reports
2105 how many holds are ready at the pickup library
2106 with the behind_desk flag set to true.
2110 sub hold_request_count {
2111 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2112 my $e = new_editor(authtoken => $authtoken);
2113 return $e->event unless $e->checkauth;
2115 $user_id = $e->requestor->id unless defined $user_id;
2117 if($e->requestor->id ne $user_id) {
2118 my $user = $e->retrieve_actor_user($user_id);
2119 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2122 my $holds = $e->json_query({
2123 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2127 fulfillment_time => {"=" => undef },
2128 cancel_time => undef,
2133 $_->{current_shelf_lib} and # avoid undef warnings
2134 $_->{pickup_lib} eq $_->{current_shelf_lib}
2138 total => scalar(@$holds),
2139 ready => scalar(@ready)
2143 # count of holds ready at pickup lib with behind_desk true.
2144 $resp->{behind_desk} = scalar(
2146 $_->{pickup_lib} == $ctx_org and
2147 $U->is_true($_->{behind_desk})
2155 __PACKAGE__->register_method(
2156 method => "checked_out",
2157 api_name => "open-ils.actor.user.checked_out",
2161 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2162 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2163 . "(i.e., outstanding balance or some other pending action on the circ). "
2164 . "The .count method also includes a 'total' field which sums all open circs.",
2166 { desc => 'Authentication Token', type => 'string'},
2167 { desc => 'User ID', type => 'string'},
2170 desc => 'Returns event on error, or an object with ID lists, like: '
2171 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2176 __PACKAGE__->register_method(
2177 method => "checked_out",
2178 api_name => "open-ils.actor.user.checked_out.count",
2181 signature => q/@see open-ils.actor.user.checked_out/
2185 my( $self, $conn, $auth, $userid ) = @_;
2187 my $e = new_editor(authtoken=>$auth);
2188 return $e->event unless $e->checkauth;
2190 if( $userid ne $e->requestor->id ) {
2191 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2192 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2194 # see if there is a friend link allowing circ.view perms
2195 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2196 $e, $userid, $e->requestor->id, 'circ.view');
2197 return $e->event unless $allowed;
2201 my $count = $self->api_name =~ /count/;
2202 return _checked_out( $count, $e, $userid );
2206 my( $iscount, $e, $userid ) = @_;
2212 claims_returned => [],
2215 my $meth = 'retrieve_action_open_circ_';
2223 claims_returned => 0,
2230 my $data = $e->$meth($userid);
2234 $result{$_} += $data->$_() for (keys %result);
2235 $result{total} += $data->$_() for (keys %result);
2237 for my $k (keys %result) {
2238 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2248 __PACKAGE__->register_method(
2249 method => "checked_in_with_fines",
2250 api_name => "open-ils.actor.user.checked_in_with_fines",
2253 signature => q/@see open-ils.actor.user.checked_out/
2256 sub checked_in_with_fines {
2257 my( $self, $conn, $auth, $userid ) = @_;
2259 my $e = new_editor(authtoken=>$auth);
2260 return $e->event unless $e->checkauth;
2262 if( $userid ne $e->requestor->id ) {
2263 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2266 # money is owed on these items and they are checked in
2267 my $open = $e->search_action_circulation(
2270 xact_finish => undef,
2271 checkin_time => { "!=" => undef },
2276 my( @lost, @cr, @lo );
2277 for my $c (@$open) {
2278 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2279 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2280 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2285 claims_returned => \@cr,
2286 long_overdue => \@lo
2292 my ($api, $desc, $auth) = @_;
2293 $desc = $desc ? (" " . $desc) : '';
2294 my $ids = ($api =~ /ids$/) ? 1 : 0;
2297 method => "user_transaction_history",
2298 api_name => "open-ils.actor.user.transactions.$api",
2300 desc => "For a given User ID, returns a list of billable transaction" .
2301 ($ids ? " id" : '') .
2302 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2303 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2305 {desc => 'Authentication token', type => 'string'},
2306 {desc => 'User ID', type => 'number'},
2307 {desc => 'Transaction type (optional)', type => 'number'},
2308 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2311 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2315 $auth and push @sig, (authoritative => 1);
2319 my %auth_hist_methods = (
2321 'history.have_charge' => 'that have an initial charge',
2322 'history.still_open' => 'that are not finished',
2323 'history.have_balance' => 'that have a balance',
2324 'history.have_bill' => 'that have billings',
2325 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2326 'history.have_payment' => 'that have at least 1 payment',
2329 foreach (keys %auth_hist_methods) {
2330 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2331 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2332 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2335 sub user_transaction_history {
2336 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2340 my $e = new_editor(authtoken=>$auth);
2341 return $e->die_event unless $e->checkauth;
2343 if ($e->requestor->id ne $userid) {
2344 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2347 my $api = $self->api_name;
2348 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2350 if(defined($type)) {
2351 $filter->{'xact_type'} = $type;
2354 if($api =~ /have_bill_or_payment/o) {
2356 # transactions that have a non-zero sum across all billings or at least 1 payment
2357 $filter->{'-or'} = {
2358 'balance_owed' => { '<>' => 0 },
2359 'last_payment_ts' => { '<>' => undef }
2362 } elsif($api =~ /have_payment/) {
2364 $filter->{last_payment_ts} ||= {'<>' => undef};
2366 } elsif( $api =~ /have_balance/o) {
2368 # transactions that have a non-zero overall balance
2369 $filter->{'balance_owed'} = { '<>' => 0 };
2371 } elsif( $api =~ /have_charge/o) {
2373 # transactions that have at least 1 billing, regardless of whether it was voided
2374 $filter->{'last_billing_ts'} = { '<>' => undef };
2376 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2378 # transactions that have non-zero sum across all billings. This will exclude
2379 # xacts where all billings have been voided
2380 $filter->{'total_owed'} = { '<>' => 0 };
2383 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2384 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2385 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2387 my $mbts = $e->search_money_billable_transaction_summary(
2388 [ { usr => $userid, @xact_finish, %$filter },
2393 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2394 return $mbts unless $api =~ /fleshed/;
2397 for my $t (@$mbts) {
2399 if( $t->xact_type ne 'circulation' ) {
2400 push @resp, {transaction => $t};
2404 my $circ_data = flesh_circ($e, $t->id);
2405 push @resp, {transaction => $t, %$circ_data};
2413 __PACKAGE__->register_method(
2414 method => "user_perms",
2415 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2417 notes => "Returns a list of permissions"
2421 my( $self, $client, $authtoken, $user ) = @_;
2423 my( $staff, $evt ) = $apputils->checkses($authtoken);
2424 return $evt if $evt;
2426 $user ||= $staff->id;
2428 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2432 return $apputils->simple_scalar_request(
2434 "open-ils.storage.permission.user_perms.atomic",
2438 __PACKAGE__->register_method(
2439 method => "retrieve_perms",
2440 api_name => "open-ils.actor.permissions.retrieve",
2441 notes => "Returns a list of permissions"
2443 sub retrieve_perms {
2444 my( $self, $client ) = @_;
2445 return $apputils->simple_scalar_request(
2447 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2448 { id => { '!=' => undef } }
2452 __PACKAGE__->register_method(
2453 method => "retrieve_groups",
2454 api_name => "open-ils.actor.groups.retrieve",
2455 notes => "Returns a list of user groups"
2457 sub retrieve_groups {
2458 my( $self, $client ) = @_;
2459 return new_editor()->retrieve_all_permission_grp_tree();
2462 __PACKAGE__->register_method(
2463 method => "retrieve_org_address",
2464 api_name => "open-ils.actor.org_unit.address.retrieve",
2465 notes => <<' NOTES');
2466 Returns an org_unit address by ID
2467 @param An org_address ID
2469 sub retrieve_org_address {
2470 my( $self, $client, $id ) = @_;
2471 return $apputils->simple_scalar_request(
2473 "open-ils.cstore.direct.actor.org_address.retrieve",
2478 __PACKAGE__->register_method(
2479 method => "retrieve_groups_tree",
2480 api_name => "open-ils.actor.groups.tree.retrieve",
2481 notes => "Returns a list of user groups"
2484 sub retrieve_groups_tree {
2485 my( $self, $client ) = @_;
2486 return new_editor()->search_permission_grp_tree(
2491 flesh_fields => { pgt => ["children"] },
2492 order_by => { pgt => 'name'}
2499 __PACKAGE__->register_method(
2500 method => "add_user_to_groups",
2501 api_name => "open-ils.actor.user.set_groups",
2502 notes => "Adds a user to one or more permission groups"
2505 sub add_user_to_groups {
2506 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2508 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2509 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2510 return $evt if $evt;
2512 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2513 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2514 return $evt if $evt;
2516 $apputils->simplereq(
2518 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2520 for my $group (@$groups) {
2521 my $link = Fieldmapper::permission::usr_grp_map->new;
2523 $link->usr($userid);
2525 my $id = $apputils->simplereq(
2527 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2533 __PACKAGE__->register_method(
2534 method => "get_user_perm_groups",
2535 api_name => "open-ils.actor.user.get_groups",
2536 notes => "Retrieve a user's permission groups."
2540 sub get_user_perm_groups {
2541 my( $self, $client, $authtoken, $userid ) = @_;
2543 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2544 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2545 return $evt if $evt;
2547 return $apputils->simplereq(
2549 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2553 __PACKAGE__->register_method(
2554 method => "get_user_work_ous",
2555 api_name => "open-ils.actor.user.get_work_ous",
2556 notes => "Retrieve a user's work org units."
2559 __PACKAGE__->register_method(
2560 method => "get_user_work_ous",
2561 api_name => "open-ils.actor.user.get_work_ous.ids",
2562 notes => "Retrieve a user's work org units."
2565 sub get_user_work_ous {
2566 my( $self, $client, $auth, $userid ) = @_;
2567 my $e = new_editor(authtoken=>$auth);
2568 return $e->event unless $e->checkauth;
2569 $userid ||= $e->requestor->id;
2571 if($e->requestor->id != $userid) {
2572 my $user = $e->retrieve_actor_user($userid)
2573 or return $e->event;
2574 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2577 return $e->search_permission_usr_work_ou_map({usr => $userid})
2578 unless $self->api_name =~ /.ids$/;
2580 # client just wants a list of org IDs
2581 return $U->get_user_work_ou_ids($e, $userid);
2586 __PACKAGE__->register_method(
2587 method => 'register_workstation',
2588 api_name => 'open-ils.actor.workstation.register.override',
2589 signature => q/@see open-ils.actor.workstation.register/
2592 __PACKAGE__->register_method(
2593 method => 'register_workstation',
2594 api_name => 'open-ils.actor.workstation.register',
2596 Registers a new workstion in the system
2597 @param authtoken The login session key
2598 @param name The name of the workstation id
2599 @param owner The org unit that owns this workstation
2600 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2601 if the name is already in use.
2605 sub register_workstation {
2606 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2608 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2609 return $e->die_event unless $e->checkauth;
2610 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2611 my $existing = $e->search_actor_workstation({name => $name})->[0];
2612 $oargs = { all => 1 } unless defined $oargs;
2616 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2617 # workstation with the given name exists.
2619 if($owner ne $existing->owning_lib) {
2620 # if necessary, update the owning_lib of the workstation
2622 $logger->info("changing owning lib of workstation ".$existing->id.
2623 " from ".$existing->owning_lib." to $owner");
2624 return $e->die_event unless
2625 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2627 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2629 $existing->owning_lib($owner);
2630 return $e->die_event unless $e->update_actor_workstation($existing);
2636 "attempt to register an existing workstation. returning existing ID");
2639 return $existing->id;
2642 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2646 my $ws = Fieldmapper::actor::workstation->new;
2647 $ws->owning_lib($owner);
2649 $e->create_actor_workstation($ws) or return $e->die_event;
2651 return $ws->id; # note: editor sets the id on the new object for us
2654 __PACKAGE__->register_method(
2655 method => 'workstation_list',
2656 api_name => 'open-ils.actor.workstation.list',
2658 Returns a list of workstations registered at the given location
2659 @param authtoken The login session key
2660 @param ids A list of org_unit.id's for the workstation owners
2664 sub workstation_list {
2665 my( $self, $conn, $authtoken, @orgs ) = @_;
2667 my $e = new_editor(authtoken=>$authtoken);
2668 return $e->event unless $e->checkauth;
2673 unless $e->allowed('REGISTER_WORKSTATION', $o);
2674 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2680 __PACKAGE__->register_method(
2681 method => 'fetch_patron_note',
2682 api_name => 'open-ils.actor.note.retrieve.all',
2685 Returns a list of notes for a given user
2686 Requestor must have VIEW_USER permission if pub==false and
2687 @param authtoken The login session key
2688 @param args Hash of params including
2689 patronid : the patron's id
2690 pub : true if retrieving only public notes
2694 sub fetch_patron_note {
2695 my( $self, $conn, $authtoken, $args ) = @_;
2696 my $patronid = $$args{patronid};
2698 my($reqr, $evt) = $U->checkses($authtoken);
2699 return $evt if $evt;
2702 ($patron, $evt) = $U->fetch_user($patronid);
2703 return $evt if $evt;
2706 if( $patronid ne $reqr->id ) {
2707 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2708 return $evt if $evt;
2710 return $U->cstorereq(
2711 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2712 { usr => $patronid, pub => 't' } );
2715 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2716 return $evt if $evt;
2718 return $U->cstorereq(
2719 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2722 __PACKAGE__->register_method(
2723 method => 'create_user_note',
2724 api_name => 'open-ils.actor.note.create',
2726 Creates a new note for the given user
2727 @param authtoken The login session key
2728 @param note The note object
2731 sub create_user_note {
2732 my( $self, $conn, $authtoken, $note ) = @_;
2733 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2734 return $e->die_event unless $e->checkauth;
2736 my $user = $e->retrieve_actor_user($note->usr)
2737 or return $e->die_event;
2739 return $e->die_event unless
2740 $e->allowed('UPDATE_USER',$user->home_ou);
2742 $note->creator($e->requestor->id);
2743 $e->create_actor_usr_note($note) or return $e->die_event;
2749 __PACKAGE__->register_method(
2750 method => 'delete_user_note',
2751 api_name => 'open-ils.actor.note.delete',
2753 Deletes a note for the given user
2754 @param authtoken The login session key
2755 @param noteid The note id
2758 sub delete_user_note {
2759 my( $self, $conn, $authtoken, $noteid ) = @_;
2761 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2762 return $e->die_event unless $e->checkauth;
2763 my $note = $e->retrieve_actor_usr_note($noteid)
2764 or return $e->die_event;
2765 my $user = $e->retrieve_actor_user($note->usr)
2766 or return $e->die_event;
2767 return $e->die_event unless
2768 $e->allowed('UPDATE_USER', $user->home_ou);
2770 $e->delete_actor_usr_note($note) or return $e->die_event;
2776 __PACKAGE__->register_method(
2777 method => 'update_user_note',
2778 api_name => 'open-ils.actor.note.update',
2780 @param authtoken The login session key
2781 @param note The note
2785 sub update_user_note {
2786 my( $self, $conn, $auth, $note ) = @_;
2787 my $e = new_editor(authtoken=>$auth, xact=>1);
2788 return $e->die_event unless $e->checkauth;
2789 my $patron = $e->retrieve_actor_user($note->usr)
2790 or return $e->die_event;
2791 return $e->die_event unless
2792 $e->allowed('UPDATE_USER', $patron->home_ou);
2793 $e->update_actor_user_note($note)
2794 or return $e->die_event;
2799 __PACKAGE__->register_method(
2800 method => 'fetch_patron_messages',
2801 api_name => 'open-ils.actor.message.retrieve',
2804 Returns a list of notes for a given user, not
2805 including ones marked deleted
2806 @param authtoken The login session key
2807 @param patronid patron ID
2808 @param options hash containing optional limit and offset
2812 sub fetch_patron_messages {
2813 my( $self, $conn, $auth, $patronid, $options ) = @_;
2817 my $e = new_editor(authtoken => $auth);
2818 return $e->die_event unless $e->checkauth;
2820 if ($e->requestor->id ne $patronid) {
2821 return $e->die_event unless $e->allowed('VIEW_USER');
2824 my $select_clause = { usr => $patronid };
2825 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2826 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2827 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2829 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2834 __PACKAGE__->register_method(
2835 method => 'usrname_exists',
2836 api_name => 'open-ils.actor.username.exists',
2838 desc => 'Check if a username is already taken (by an undeleted patron)',
2840 {desc => 'Authentication token', type => 'string'},
2841 {desc => 'Username', type => 'string'}
2844 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2849 sub usrname_exists {
2850 my( $self, $conn, $auth, $usrname ) = @_;
2851 my $e = new_editor(authtoken=>$auth);
2852 return $e->event unless $e->checkauth;
2853 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2854 return $$a[0] if $a and @$a;
2858 __PACKAGE__->register_method(
2859 method => 'barcode_exists',
2860 api_name => 'open-ils.actor.barcode.exists',
2862 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2865 sub barcode_exists {
2866 my( $self, $conn, $auth, $barcode ) = @_;
2867 my $e = new_editor(authtoken=>$auth);
2868 return $e->event unless $e->checkauth;
2869 my $card = $e->search_actor_card({barcode => $barcode});
2875 #return undef unless @$card;
2876 #return $card->[0]->usr;
2880 __PACKAGE__->register_method(
2881 method => 'retrieve_net_levels',
2882 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2885 sub retrieve_net_levels {
2886 my( $self, $conn, $auth ) = @_;
2887 my $e = new_editor(authtoken=>$auth);
2888 return $e->event unless $e->checkauth;
2889 return $e->retrieve_all_config_net_access_level();
2892 # Retain the old typo API name just in case
2893 __PACKAGE__->register_method(
2894 method => 'fetch_org_by_shortname',
2895 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2897 __PACKAGE__->register_method(
2898 method => 'fetch_org_by_shortname',
2899 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2901 sub fetch_org_by_shortname {
2902 my( $self, $conn, $sname ) = @_;
2903 my $e = new_editor();
2904 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2905 return $e->event unless $org;
2910 __PACKAGE__->register_method(
2911 method => 'session_home_lib',
2912 api_name => 'open-ils.actor.session.home_lib',
2915 sub session_home_lib {
2916 my( $self, $conn, $auth ) = @_;
2917 my $e = new_editor(authtoken=>$auth);
2918 return undef unless $e->checkauth;
2919 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2920 return $org->shortname;
2923 __PACKAGE__->register_method(
2924 method => 'session_safe_token',
2925 api_name => 'open-ils.actor.session.safe_token',
2927 Returns a hashed session ID that is safe for export to the world.
2928 This safe token will expire after 1 hour of non-use.
2929 @param auth Active authentication token
2933 sub session_safe_token {
2934 my( $self, $conn, $auth ) = @_;
2935 my $e = new_editor(authtoken=>$auth);
2936 return undef unless $e->checkauth;
2938 my $safe_token = md5_hex($auth);
2940 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2942 # add more user fields as needed
2944 "safe-token-user-$safe_token", {
2945 id => $e->requestor->id,
2946 home_ou_shortname => $e->retrieve_actor_org_unit(
2947 $e->requestor->home_ou)->shortname,
2956 __PACKAGE__->register_method(
2957 method => 'safe_token_home_lib',
2958 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2960 Returns the home library shortname from the session
2961 asscociated with a safe token from generated by
2962 open-ils.actor.session.safe_token.
2963 @param safe_token Active safe token
2964 @param who Optional user activity "ewho" value
2968 sub safe_token_home_lib {
2969 my( $self, $conn, $safe_token, $who ) = @_;
2970 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2972 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2973 return unless $blob;
2975 $U->log_user_activity($blob->{id}, $who, 'verify');
2976 return $blob->{home_ou_shortname};
2980 __PACKAGE__->register_method(
2981 method => "update_penalties",
2982 api_name => "open-ils.actor.user.penalties.update"
2985 sub update_penalties {
2986 my($self, $conn, $auth, $user_id) = @_;
2987 my $e = new_editor(authtoken=>$auth, xact => 1);
2988 return $e->die_event unless $e->checkauth;
2989 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2990 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2991 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2992 return $evt if $evt;
2998 __PACKAGE__->register_method(
2999 method => "apply_penalty",
3000 api_name => "open-ils.actor.user.penalty.apply"
3004 my($self, $conn, $auth, $penalty) = @_;
3006 my $e = new_editor(authtoken=>$auth, xact => 1);
3007 return $e->die_event unless $e->checkauth;
3009 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3010 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3012 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3015 (defined $ptype->org_depth) ?
3016 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
3019 $penalty->org_unit($ctx_org);
3020 $penalty->staff($e->requestor->id);
3021 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3024 return $penalty->id;
3027 __PACKAGE__->register_method(
3028 method => "remove_penalty",
3029 api_name => "open-ils.actor.user.penalty.remove"
3032 sub remove_penalty {
3033 my($self, $conn, $auth, $penalty) = @_;
3034 my $e = new_editor(authtoken=>$auth, xact => 1);
3035 return $e->die_event unless $e->checkauth;
3036 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3037 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3039 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3044 __PACKAGE__->register_method(
3045 method => "update_penalty_note",
3046 api_name => "open-ils.actor.user.penalty.note.update"
3049 sub update_penalty_note {
3050 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3051 my $e = new_editor(authtoken=>$auth, xact => 1);
3052 return $e->die_event unless $e->checkauth;
3053 for my $penalty_id (@$penalty_ids) {
3054 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3055 if (! $penalty ) { return $e->die_event; }
3056 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3057 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3059 $penalty->note( $note ); $penalty->ischanged( 1 );
3061 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3067 __PACKAGE__->register_method(
3068 method => "ranged_penalty_thresholds",
3069 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3073 sub ranged_penalty_thresholds {
3074 my($self, $conn, $auth, $context_org) = @_;
3075 my $e = new_editor(authtoken=>$auth);
3076 return $e->event unless $e->checkauth;
3077 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3078 my $list = $e->search_permission_grp_penalty_threshold([
3079 {org_unit => $U->get_org_ancestors($context_org)},
3080 {order_by => {pgpt => 'id'}}
3082 $conn->respond($_) for @$list;
3088 __PACKAGE__->register_method(
3089 method => "user_retrieve_fleshed_by_id",
3091 api_name => "open-ils.actor.user.fleshed.retrieve",
3094 sub user_retrieve_fleshed_by_id {
3095 my( $self, $client, $auth, $user_id, $fields ) = @_;
3096 my $e = new_editor(authtoken => $auth);
3097 return $e->event unless $e->checkauth;
3099 if( $e->requestor->id != $user_id ) {
3100 return $e->event unless $e->allowed('VIEW_USER');
3107 "standing_penalties",
3115 return new_flesh_user($user_id, $fields, $e);
3119 sub new_flesh_user {
3122 my $fields = shift || [];
3125 my $fetch_penalties = 0;
3126 if(grep {$_ eq 'standing_penalties'} @$fields) {
3127 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3128 $fetch_penalties = 1;
3131 my $fetch_usr_act = 0;
3132 if(grep {$_ eq 'usr_activity'} @$fields) {
3133 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3137 my $user = $e->retrieve_actor_user(
3142 "flesh_fields" => { "au" => $fields }
3145 ) or return $e->die_event;
3148 if( grep { $_ eq 'addresses' } @$fields ) {
3150 $user->addresses([]) unless @{$user->addresses};
3151 # don't expose "replaced" addresses by default
3152 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3154 if( ref $user->billing_address ) {
3155 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3156 push( @{$user->addresses}, $user->billing_address );
3160 if( ref $user->mailing_address ) {
3161 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3162 push( @{$user->addresses}, $user->mailing_address );
3167 if($fetch_penalties) {
3168 # grab the user penalties ranged for this location
3169 $user->standing_penalties(
3170 $e->search_actor_user_standing_penalty([
3173 {stop_date => undef},
3174 {stop_date => {'>' => 'now'}}
3176 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3179 flesh_fields => {ausp => ['standing_penalty']}
3185 # retrieve the most recent usr_activity entry
3186 if ($fetch_usr_act) {
3188 # max number to return for simple patron fleshing
3189 my $limit = $U->ou_ancestor_setting_value(
3190 $e->requestor->ws_ou,
3191 'circ.patron.usr_activity_retrieve.max');
3195 flesh_fields => {auact => ['etype']},
3196 order_by => {auact => 'event_time DESC'},
3199 # 0 == none, <0 == return all
3200 $limit = 1 unless defined $limit;
3201 $opts->{limit} = $limit if $limit > 0;
3203 $user->usr_activity(
3205 [] : # skip the DB call
3206 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3211 $user->clear_passwd();
3218 __PACKAGE__->register_method(
3219 method => "user_retrieve_parts",
3220 api_name => "open-ils.actor.user.retrieve.parts",
3223 sub user_retrieve_parts {
3224 my( $self, $client, $auth, $user_id, $fields ) = @_;
3225 my $e = new_editor(authtoken => $auth);
3226 return $e->event unless $e->checkauth;
3227 $user_id ||= $e->requestor->id;
3228 if( $e->requestor->id != $user_id ) {
3229 return $e->event unless $e->allowed('VIEW_USER');
3232 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3233 push(@resp, $user->$_()) for(@$fields);
3239 __PACKAGE__->register_method(
3240 method => 'user_opt_in_enabled',
3241 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3242 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3245 sub user_opt_in_enabled {
3246 my($self, $conn) = @_;
3247 my $sc = OpenSRF::Utils::SettingsClient->new;
3248 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3253 __PACKAGE__->register_method(
3254 method => 'user_opt_in_at_org',
3255 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3257 @param $auth The auth token
3258 @param user_id The ID of the user to test
3259 @return 1 if the user has opted in at the specified org,
3260 2 if opt-in is disallowed for the user's home org,
3261 event on error, and 0 otherwise. /
3263 sub user_opt_in_at_org {
3264 my($self, $conn, $auth, $user_id) = @_;
3266 # see if we even need to enforce the opt-in value
3267 return 1 unless user_opt_in_enabled($self);
3269 my $e = new_editor(authtoken => $auth);
3270 return $e->event unless $e->checkauth;
3272 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3273 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3275 my $ws_org = $e->requestor->ws_ou;
3276 # user is automatically opted-in if they are from the local org
3277 return 1 if $user->home_ou eq $ws_org;
3279 # get the boundary setting
3280 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3282 # auto opt in if user falls within the opt boundary
3283 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3285 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3287 # check whether opt-in is restricted at the user's home library
3288 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3289 if ($opt_restrict_depth) {
3290 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3291 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3293 # opt-in is disallowed unless the workstation org is within the home
3294 # library's opt-in scope
3295 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3298 my $vals = $e->search_actor_usr_org_unit_opt_in(
3299 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3305 __PACKAGE__->register_method(
3306 method => 'create_user_opt_in_at_org',
3307 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3309 @param $auth The auth token
3310 @param user_id The ID of the user to test
3311 @return The ID of the newly created object, event on error./
3314 sub create_user_opt_in_at_org {
3315 my($self, $conn, $auth, $user_id, $org_id) = @_;
3317 my $e = new_editor(authtoken => $auth, xact=>1);
3318 return $e->die_event unless $e->checkauth;
3320 # if a specific org unit wasn't passed in, get one based on the defaults;
3322 my $wsou = $e->requestor->ws_ou;
3323 # get the default opt depth
3324 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3325 # get the org unit at that depth
3326 my $org = $e->json_query({
3327 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3328 $org_id = $org->{id};
3331 # fall back to the workstation OU, the pre-opt-in-boundary way
3332 $org_id = $e->requestor->ws_ou;
3335 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3336 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3338 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3340 $opt_in->org_unit($org_id);
3341 $opt_in->usr($user_id);
3342 $opt_in->staff($e->requestor->id);
3343 $opt_in->opt_in_ts('now');
3344 $opt_in->opt_in_ws($e->requestor->wsid);
3346 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3347 or return $e->die_event;
3355 __PACKAGE__->register_method (
3356 method => 'retrieve_org_hours',
3357 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3359 Returns the hours of operation for a specified org unit
3360 @param authtoken The login session key
3361 @param org_id The org_unit ID
3365 sub retrieve_org_hours {
3366 my($self, $conn, $auth, $org_id) = @_;
3367 my $e = new_editor(authtoken => $auth);
3368 return $e->die_event unless $e->checkauth;
3369 $org_id ||= $e->requestor->ws_ou;
3370 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3374 __PACKAGE__->register_method (
3375 method => 'verify_user_password',
3376 api_name => 'open-ils.actor.verify_user_password',
3378 Given a barcode or username and the MD5 encoded password,
3379 returns 1 if the password is correct. Returns 0 otherwise.
3383 sub verify_user_password {
3384 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3385 my $e = new_editor(authtoken => $auth);
3386 return $e->die_event unless $e->checkauth;
3388 my $user_by_barcode;
3389 my $user_by_username;
3391 my $card = $e->search_actor_card([
3392 {barcode => $barcode},
3393 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3394 $user_by_barcode = $card->usr;
3395 $user = $user_by_barcode;
3398 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3399 $user = $user_by_username;
3401 return 0 if (!$user || $U->is_true($user->deleted));
3402 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3403 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3404 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3407 __PACKAGE__->register_method (
3408 method => 'retrieve_usr_id_via_barcode_or_usrname',
3409 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3411 Given a barcode or username returns the id for the user or
3416 sub retrieve_usr_id_via_barcode_or_usrname {
3417 my($self, $conn, $auth, $barcode, $username) = @_;
3418 my $e = new_editor(authtoken => $auth);
3419 return $e->die_event unless $e->checkauth;
3420 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3422 my $user_by_barcode;
3423 my $user_by_username;
3424 $logger->info("$id_as_barcode is the ID as BARCODE");
3426 my $card = $e->search_actor_card([
3427 {barcode => $barcode},
3428 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3429 if ($id_as_barcode =~ /^t/i) {
3431 $user = $e->retrieve_actor_user($barcode);
3432 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3434 $user_by_barcode = $card->usr;
3435 $user = $user_by_barcode;
3438 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3439 $user_by_barcode = $card->usr;
3440 $user = $user_by_barcode;
3445 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3447 $user = $user_by_username;
3449 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3450 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3451 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3456 __PACKAGE__->register_method (
3457 method => 'merge_users',
3458 api_name => 'open-ils.actor.user.merge',
3461 Given a list of source users and destination user, transfer all data from the source
3462 to the dest user and delete the source user. All user related data is
3463 transferred, including circulations, holds, bookbags, etc.
3469 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3470 my $e = new_editor(xact => 1, authtoken => $auth);
3471 return $e->die_event unless $e->checkauth;
3473 # disallow the merge if any subordinate accounts are in collections
3474 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3475 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3477 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3478 if $master_id == $e->requestor->id;
3480 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3481 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3482 return $evt if $evt;
3484 my $del_addrs = ($U->ou_ancestor_setting_value(
3485 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3486 my $del_cards = ($U->ou_ancestor_setting_value(
3487 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3488 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3489 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3491 for my $src_id (@$user_ids) {
3493 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3494 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3495 return $evt if $evt;
3497 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3498 if $src_id == $e->requestor->id;
3500 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3501 if($src_user->home_ou ne $master_user->home_ou) {
3502 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3505 return $e->die_event unless
3506 $e->json_query({from => [
3521 __PACKAGE__->register_method (
3522 method => 'approve_user_address',
3523 api_name => 'open-ils.actor.user.pending_address.approve',
3530 sub approve_user_address {
3531 my($self, $conn, $auth, $addr) = @_;
3532 my $e = new_editor(xact => 1, authtoken => $auth);
3533 return $e->die_event unless $e->checkauth;
3535 # if the caller passes an address object, assume they want to
3536 # update it first before approving it
3537 $e->update_actor_user_address($addr) or return $e->die_event;
3539 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3541 my $user = $e->retrieve_actor_user($addr->usr);
3542 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3543 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3544 or return $e->die_event;
3546 return [values %$result]->[0];
3550 __PACKAGE__->register_method (
3551 method => 'retrieve_friends',
3552 api_name => 'open-ils.actor.friends.retrieve',
3555 returns { confirmed: [], pending_out: [], pending_in: []}
3556 pending_out are users I'm requesting friendship with
3557 pending_in are users requesting friendship with me
3562 sub retrieve_friends {
3563 my($self, $conn, $auth, $user_id, $options) = @_;
3564 my $e = new_editor(authtoken => $auth);
3565 return $e->event unless $e->checkauth;
3566 $user_id ||= $e->requestor->id;
3568 if($user_id != $e->requestor->id) {
3569 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3570 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3573 return OpenILS::Application::Actor::Friends->retrieve_friends(
3574 $e, $user_id, $options);
3579 __PACKAGE__->register_method (
3580 method => 'apply_friend_perms',
3581 api_name => 'open-ils.actor.friends.perms.apply',
3587 sub apply_friend_perms {
3588 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3589 my $e = new_editor(authtoken => $auth, xact => 1);
3590 return $e->die_event unless $e->checkauth;
3592 if($user_id != $e->requestor->id) {
3593 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3594 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3597 for my $perm (@perms) {
3599 OpenILS::Application::Actor::Friends->apply_friend_perm(
3600 $e, $user_id, $delegate_id, $perm);
3601 return $evt if $evt;
3609 __PACKAGE__->register_method (
3610 method => 'update_user_pending_address',
3611 api_name => 'open-ils.actor.user.address.pending.cud'
3614 sub update_user_pending_address {
3615 my($self, $conn, $auth, $addr) = @_;
3616 my $e = new_editor(authtoken => $auth, xact => 1);
3617 return $e->die_event unless $e->checkauth;
3619 if($addr->usr != $e->requestor->id) {
3620 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3621 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3625 $e->create_actor_user_address($addr) or return $e->die_event;
3626 } elsif($addr->isdeleted) {
3627 $e->delete_actor_user_address($addr) or return $e->die_event;
3629 $e->update_actor_user_address($addr) or return $e->die_event;
3637 __PACKAGE__->register_method (
3638 method => 'user_events',
3639 api_name => 'open-ils.actor.user.events.circ',
3642 __PACKAGE__->register_method (
3643 method => 'user_events',
3644 api_name => 'open-ils.actor.user.events.ahr',
3649 my($self, $conn, $auth, $user_id, $filters) = @_;
3650 my $e = new_editor(authtoken => $auth);
3651 return $e->event unless $e->checkauth;
3653 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3654 my $user_field = 'usr';
3657 $filters->{target} = {
3658 select => { $obj_type => ['id'] },
3660 where => {usr => $user_id}
3663 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3664 if($e->requestor->id != $user_id) {
3665 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3668 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3669 my $req = $ses->request('open-ils.trigger.events_by_target',
3670 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3672 while(my $resp = $req->recv) {
3673 my $val = $resp->content;
3674 my $tgt = $val->target;
3676 if($obj_type eq 'circ') {
3677 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3679 } elsif($obj_type eq 'ahr') {
3680 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3681 if $tgt->current_copy;
3684 $conn->respond($val) if $val;
3690 __PACKAGE__->register_method (
3691 method => 'copy_events',
3692 api_name => 'open-ils.actor.copy.events.circ',
3695 __PACKAGE__->register_method (
3696 method => 'copy_events',
3697 api_name => 'open-ils.actor.copy.events.ahr',
3702 my($self, $conn, $auth, $copy_id, $filters) = @_;
3703 my $e = new_editor(authtoken => $auth);
3704 return $e->event unless $e->checkauth;
3706 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3708 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3710 my $copy_field = 'target_copy';
3711 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3714 $filters->{target} = {
3715 select => { $obj_type => ['id'] },
3717 where => {$copy_field => $copy_id}
3721 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3722 my $req = $ses->request('open-ils.trigger.events_by_target',
3723 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3725 while(my $resp = $req->recv) {
3726 my $val = $resp->content;
3727 my $tgt = $val->target;
3729 my $user = $e->retrieve_actor_user($tgt->usr);
3730 if($e->requestor->id != $user->id) {
3731 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3734 $tgt->$copy_field($copy);
3737 $conn->respond($val) if $val;
3744 __PACKAGE__->register_method (
3745 method => 'get_itemsout_notices',
3746 api_name => 'open-ils.actor.user.itemsout.notices',
3751 sub get_itemsout_notices{
3752 my( $self, $conn, $auth, $circId, $patronId) = @_;
3754 my $e = new_editor(authtoken => $auth);
3755 return $e->event unless $e->checkauth;
3757 my $requestorId = $e->requestor->id;
3759 if( $patronId ne $requestorId ){
3760 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3761 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3764 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3765 #my $req = $ses->request('open-ils.trigger.events_by_target',
3766 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3767 # ^ Above removed in favor of faster json_query.
3770 # select complete_time
3771 # from action_trigger.event atev
3772 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3773 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3774 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3777 my $ctx_loc = $e->requestor->ws_ou;
3778 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value($ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3780 select => { atev => ["complete_time"] },
3783 atevdef => { field => "id",fkey => "event_def", join => { ath => { field => "key", fkey => "hook" }} }
3786 where => {"+ath" => { key => "checkout.due" },"+atevdef" => { active => 't' },"+atev" => { target => $circId, state => 'complete' }}
3789 if ($exclude_courtesy_notices){
3790 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3793 my %resblob = ( numNotices => 0, lastDt => undef );
3795 my $res = $e->json_query($query);
3796 for my $ndate (@$res) {
3797 $resblob{numNotices}++;
3798 if( !defined $resblob{lastDt}){
3799 $resblob{lastDt} = $$ndate{complete_time};
3802 if ($resblob{lastDt} lt $$ndate{complete_time}){
3803 $resblob{lastDt} = $$ndate{complete_time};
3807 $conn->respond(\%resblob);
3811 __PACKAGE__->register_method (
3812 method => 'update_events',
3813 api_name => 'open-ils.actor.user.event.cancel.batch',
3816 __PACKAGE__->register_method (
3817 method => 'update_events',
3818 api_name => 'open-ils.actor.user.event.reset.batch',
3823 my($self, $conn, $auth, $event_ids) = @_;
3824 my $e = new_editor(xact => 1, authtoken => $auth);
3825 return $e->die_event unless $e->checkauth;
3828 for my $id (@$event_ids) {
3830 # do a little dance to determine what user we are ultimately affecting
3831 my $event = $e->retrieve_action_trigger_event([
3834 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3836 ]) or return $e->die_event;
3839 if($event->event_def->hook->core_type eq 'circ') {
3840 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3841 } elsif($event->event_def->hook->core_type eq 'ahr') {
3842 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3847 my $user = $e->retrieve_actor_user($user_id);
3848 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3850 if($self->api_name =~ /cancel/) {
3851 $event->state('invalid');
3852 } elsif($self->api_name =~ /reset/) {
3853 $event->clear_start_time;
3854 $event->clear_update_time;
3855 $event->state('pending');
3858 $e->update_action_trigger_event($event) or return $e->die_event;
3859 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3863 return {complete => 1};
3867 __PACKAGE__->register_method (
3868 method => 'really_delete_user',
3869 api_name => 'open-ils.actor.user.delete.override',
3870 signature => q/@see open-ils.actor.user.delete/
3873 __PACKAGE__->register_method (
3874 method => 'really_delete_user',
3875 api_name => 'open-ils.actor.user.delete',
3877 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3878 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3879 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3880 dest_usr_id is only required when deleting a user that performs staff functions.
3884 sub really_delete_user {
3885 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3886 my $e = new_editor(authtoken => $auth, xact => 1);
3887 return $e->die_event unless $e->checkauth;
3888 $oargs = { all => 1 } unless defined $oargs;
3890 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3891 my $open_bills = $e->json_query({
3892 select => { mbts => ['id'] },
3895 xact_finish => { '=' => undef },
3896 usr => { '=' => $user_id },
3898 }) or return $e->die_event;
3900 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3902 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3904 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3905 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3906 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3908 # No deleting yourself - UI is supposed to stop you first, though.
3909 return $e->die_event unless $e->requestor->id != $user->id;
3910 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3911 # Check if you are allowed to mess with this patron permission group at all
3912 my $evt = group_perm_failed($e, $e->requestor, $user);
3913 return $e->die_event($evt) if $evt;
3914 my $stat = $e->json_query(
3915 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3916 or return $e->die_event;
3922 __PACKAGE__->register_method (
3923 method => 'user_payments',
3924 api_name => 'open-ils.actor.user.payments.retrieve',
3927 Returns all payments for a given user. Default order is newest payments first.
3928 @param auth Authentication token
3929 @param user_id The user ID
3930 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3935 my($self, $conn, $auth, $user_id, $filters) = @_;
3938 my $e = new_editor(authtoken => $auth);
3939 return $e->die_event unless $e->checkauth;
3941 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3942 return $e->event unless
3943 $e->requestor->id == $user_id or
3944 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3946 # Find all payments for all transactions for user $user_id
3948 select => {mp => ['id']},
3953 select => {mbt => ['id']},
3955 where => {usr => $user_id}
3960 { # by default, order newest payments first
3962 field => 'payment_ts',
3965 # secondary sort in ID as a tie-breaker, since payments created
3966 # within the same transaction will have identical payment_ts's
3973 for (qw/order_by limit offset/) {
3974 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3977 if(defined $filters->{where}) {
3978 foreach (keys %{$filters->{where}}) {
3979 # don't allow the caller to expand the result set to other users
3980 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3984 my $payment_ids = $e->json_query($query);
3985 for my $pid (@$payment_ids) {
3986 my $pay = $e->retrieve_money_payment([
3991 mbt => ['summary', 'circulation', 'grocery'],
3992 circ => ['target_copy'],
3993 acp => ['call_number'],
4001 xact_type => $pay->xact->summary->xact_type,
4002 last_billing_type => $pay->xact->summary->last_billing_type,
4005 if($pay->xact->summary->xact_type eq 'circulation') {
4006 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4007 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4010 $pay->xact($pay->xact->id); # de-flesh
4011 $conn->respond($resp);
4019 __PACKAGE__->register_method (
4020 method => 'negative_balance_users',
4021 api_name => 'open-ils.actor.users.negative_balance',
4024 Returns all users that have an overall negative balance
4025 @param auth Authentication token
4026 @param org_id The context org unit as an ID or list of IDs. This will be the home
4027 library of the user. If no org_unit is specified, no org unit filter is applied
4031 sub negative_balance_users {
4032 my($self, $conn, $auth, $org_id) = @_;
4034 my $e = new_editor(authtoken => $auth);
4035 return $e->die_event unless $e->checkauth;
4036 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4040 mous => ['usr', 'balance_owed'],
4043 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4044 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4061 where => {'+mous' => {balance_owed => {'<' => 0}}}
4064 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4066 my $list = $e->json_query($query, {timeout => 600});
4068 for my $data (@$list) {
4070 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4071 balance_owed => $data->{balance_owed},
4072 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4079 __PACKAGE__->register_method(
4080 method => "request_password_reset",
4081 api_name => "open-ils.actor.patron.password_reset.request",
4083 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4084 "method for changing a user's password. The UUID token is distributed via A/T " .
4085 "templates (i.e. email to the user).",
4087 { desc => 'user_id_type', type => 'string' },
4088 { desc => 'user_id', type => 'string' },
4089 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4091 return => {desc => '1 on success, Event on error'}
4094 sub request_password_reset {
4095 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4097 # Check to see if password reset requests are already being throttled:
4098 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4100 my $e = new_editor(xact => 1);
4103 # Get the user, if any, depending on the input value
4104 if ($user_id_type eq 'username') {
4105 $user = $e->search_actor_user({usrname => $user_id})->[0];
4108 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4110 } elsif ($user_id_type eq 'barcode') {
4111 my $card = $e->search_actor_card([
4112 {barcode => $user_id},
4113 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4116 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4121 # If the user doesn't have an email address, we can't help them
4122 if (!$user->email) {
4124 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4127 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4128 if ($email_must_match) {
4129 if (lc($user->email) ne lc($email)) {
4130 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4134 _reset_password_request($conn, $e, $user);
4137 # Once we have the user, we can issue the password reset request
4138 # XXX Add a wrapper method that accepts barcode + email input
4139 sub _reset_password_request {
4140 my ($conn, $e, $user) = @_;
4142 # 1. Get throttle threshold and time-to-live from OU_settings
4143 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4144 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4146 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4148 # 2. Get time of last request and number of active requests (num_active)
4149 my $active_requests = $e->json_query({
4155 transform => 'COUNT'
4158 column => 'request_time',
4164 has_been_reset => { '=' => 'f' },
4165 request_time => { '>' => $threshold_time }
4169 # Guard against no active requests
4170 if ($active_requests->[0]->{'request_time'}) {
4171 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4172 my $now = DateTime::Format::ISO8601->new();
4174 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4175 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4176 ($last_request->add_duration('1 minute') > $now)) {
4177 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4179 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4183 # TODO Check to see if the user is in a password-reset-restricted group
4185 # Otherwise, go ahead and try to get the user.
4187 # Check the number of active requests for this user
4188 $active_requests = $e->json_query({
4194 transform => 'COUNT'
4199 usr => { '=' => $user->id },
4200 has_been_reset => { '=' => 'f' },
4201 request_time => { '>' => $threshold_time }
4205 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4207 # if less than or equal to per-user threshold, proceed; otherwise, return event
4208 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4209 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4211 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4214 # Create the aupr object and insert into the database
4215 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4216 my $uuid = create_uuid_as_string(UUID_V4);
4217 $reset_request->uuid($uuid);
4218 $reset_request->usr($user->id);
4220 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4223 # Create an event to notify user of the URL to reset their password
4225 # Can we stuff this in the user_data param for trigger autocreate?
4226 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4228 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4229 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4232 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4237 __PACKAGE__->register_method(
4238 method => "commit_password_reset",
4239 api_name => "open-ils.actor.patron.password_reset.commit",
4241 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4242 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4243 "with the supplied password.",
4245 { desc => 'uuid', type => 'string' },
4246 { desc => 'password', type => 'string' },
4248 return => {desc => '1 on success, Event on error'}
4251 sub commit_password_reset {
4252 my($self, $conn, $uuid, $password) = @_;
4254 # Check to see if password reset requests are already being throttled:
4255 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4256 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4257 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4259 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4262 my $e = new_editor(xact => 1);
4264 my $aupr = $e->search_actor_usr_password_reset({
4271 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4273 my $user_id = $aupr->[0]->usr;
4274 my $user = $e->retrieve_actor_user($user_id);
4276 # Ensure we're still within the TTL for the request
4277 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4278 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4279 if ($threshold < DateTime->now(time_zone => 'local')) {
4281 $logger->info("Password reset request needed to be submitted before $threshold");
4282 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4285 # Check complexity of password against OU-defined regex
4286 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4290 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4291 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4292 $is_strong = check_password_strength_custom($password, $pw_regex);
4294 $is_strong = check_password_strength_default($password);
4299 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4302 # All is well; update the password
4303 modify_migrated_user_password($e, $user->id, $password);
4305 # And flag that this password reset request has been honoured
4306 $aupr->[0]->has_been_reset('t');
4307 $e->update_actor_usr_password_reset($aupr->[0]);
4313 sub check_password_strength_default {
4314 my $password = shift;
4315 # Use the default set of checks
4316 if ( (length($password) < 7) or
4317 ($password !~ m/.*\d+.*/) or
4318 ($password !~ m/.*[A-Za-z]+.*/)
4325 sub check_password_strength_custom {
4326 my ($password, $pw_regex) = @_;
4328 $pw_regex = qr/$pw_regex/;
4329 if ($password !~ /$pw_regex/) {
4337 __PACKAGE__->register_method(
4338 method => "event_def_opt_in_settings",
4339 api_name => "open-ils.actor.event_def.opt_in.settings",
4342 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4344 { desc => 'Authentication token', type => 'string'},
4346 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4351 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4358 sub event_def_opt_in_settings {
4359 my($self, $conn, $auth, $org_id) = @_;
4360 my $e = new_editor(authtoken => $auth);
4361 return $e->event unless $e->checkauth;
4363 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4364 return $e->event unless
4365 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4367 $org_id = $e->requestor->home_ou;
4370 # find all config.user_setting_type's related to event_defs for the requested org unit
4371 my $types = $e->json_query({
4372 select => {cust => ['name']},
4373 from => {atevdef => 'cust'},
4376 owner => $U->get_org_ancestors($org_id), # context org plus parents
4383 $conn->respond($_) for
4384 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4391 __PACKAGE__->register_method(
4392 method => "user_circ_history",
4393 api_name => "open-ils.actor.history.circ",
4397 desc => 'Returns user circ history objects for the calling user',
4399 { desc => 'Authentication token', type => 'string'},
4400 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4403 desc => q/Stream of 'auch' circ history objects/,
4409 __PACKAGE__->register_method(
4410 method => "user_circ_history",
4411 api_name => "open-ils.actor.history.circ.clear",
4414 desc => 'Delete all user circ history entries for the calling user',
4416 { desc => 'Authentication token', type => 'string'},
4417 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4420 desc => q/1 on success, event on error/,
4426 __PACKAGE__->register_method(
4427 method => "user_circ_history",
4428 api_name => "open-ils.actor.history.circ.print",
4431 desc => q/Returns printable output for the caller's circ history objects/,
4433 { desc => 'Authentication token', type => 'string'},
4434 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4437 desc => q/An action_trigger.event object or error event./,
4443 __PACKAGE__->register_method(
4444 method => "user_circ_history",
4445 api_name => "open-ils.actor.history.circ.email",
4448 desc => q/Emails the caller's circ history/,
4450 { desc => 'Authentication token', type => 'string'},
4451 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4452 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4455 desc => q/undef, or event on error/
4460 sub user_circ_history {
4461 my ($self, $conn, $auth, $options) = @_;
4464 my $for_print = ($self->api_name =~ /print/);
4465 my $for_email = ($self->api_name =~ /email/);
4466 my $for_clear = ($self->api_name =~ /clear/);
4468 # No perm check is performed. Caller may only access his/her own
4469 # circ history entries.
4470 my $e = new_editor(authtoken => $auth);
4471 return $e->event unless $e->checkauth;
4474 if (!$for_clear) { # clear deletes all
4475 $limits{offset} = $options->{offset} if defined $options->{offset};
4476 $limits{limit} = $options->{limit} if defined $options->{limit};
4479 my %circ_id_filter = $options->{circ_ids} ?
4480 (id => $options->{circ_ids}) : ();
4482 my $circs = $e->search_action_user_circ_history([
4483 { usr => $e->requestor->id,
4486 { # order newest to oldest by default
4487 order_by => {auch => 'xact_start DESC'},
4490 {substream => 1} # could be a large list
4494 return $U->fire_object_event(undef,
4495 'circ.format.history.print', $circs, $e->requestor->home_ou);
4498 $e->xact_begin if $for_clear;
4499 $conn->respond_complete(1) if $for_email; # no sense in waiting
4501 for my $circ (@$circs) {
4504 # events will be fired from action_trigger_runner
4505 $U->create_events_for_hook('circ.format.history.email',
4506 $circ, $e->editor->home_ou, undef, undef, 1);
4508 } elsif ($for_clear) {
4510 $e->delete_action_user_circ_history($circ)
4511 or return $e->die_event;
4514 $conn->respond($circ);
4527 __PACKAGE__->register_method(
4528 method => "user_visible_holds",
4529 api_name => "open-ils.actor.history.hold.visible",
4532 desc => 'Returns the set of opt-in visible holds',
4534 { desc => 'Authentication token', type => 'string'},
4535 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4536 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4539 desc => q/An object with 1 field: "hold"/,
4545 __PACKAGE__->register_method(
4546 method => "user_visible_holds",
4547 api_name => "open-ils.actor.history.hold.visible.print",
4550 desc => 'Returns printable output for the set of opt-in visible holds',
4552 { desc => 'Authentication token', type => 'string'},
4553 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4554 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4557 desc => q/An action_trigger.event object or error event./,
4563 __PACKAGE__->register_method(
4564 method => "user_visible_holds",
4565 api_name => "open-ils.actor.history.hold.visible.email",
4568 desc => 'Emails the set of opt-in visible holds to the requestor',
4570 { desc => 'Authentication token', type => 'string'},
4571 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4572 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4575 desc => q/undef, or event on error/
4580 sub user_visible_holds {
4581 my($self, $conn, $auth, $user_id, $options) = @_;
4584 my $for_print = ($self->api_name =~ /print/);
4585 my $for_email = ($self->api_name =~ /email/);
4586 my $e = new_editor(authtoken => $auth);
4587 return $e->event unless $e->checkauth;
4589 $user_id ||= $e->requestor->id;
4591 $options->{limit} ||= 50;
4592 $options->{offset} ||= 0;
4594 if($user_id != $e->requestor->id) {
4595 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4596 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4597 return $e->event unless $e->allowed($perm, $user->home_ou);
4600 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4602 my $data = $e->json_query({
4603 from => [$db_func, $user_id],
4604 limit => $$options{limit},
4605 offset => $$options{offset}
4607 # TODO: I only want IDs. code below didn't get me there
4608 # {"select":{"au":[{"column":"id", "result_field":"id",
4609 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4614 return undef unless @$data;
4618 # collect the batch of objects
4622 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4623 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4627 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4628 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4631 } elsif ($for_email) {
4633 $conn->respond_complete(1) if $for_email; # no sense in waiting
4641 my $hold = $e->retrieve_action_hold_request($id);
4642 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4643 # events will be fired from action_trigger_runner
4647 my $circ = $e->retrieve_action_circulation($id);
4648 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4649 # events will be fired from action_trigger_runner
4653 } else { # just give me the data please
4661 my $hold = $e->retrieve_action_hold_request($id);
4662 $conn->respond({hold => $hold});
4666 my $circ = $e->retrieve_action_circulation($id);
4669 summary => $U->create_circ_chain_summary($e, $id)
4678 __PACKAGE__->register_method(
4679 method => "user_saved_search_cud",
4680 api_name => "open-ils.actor.user.saved_search.cud",
4683 desc => 'Create/Update/Delete Access to user saved searches',
4685 { desc => 'Authentication token', type => 'string' },
4686 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4689 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4695 __PACKAGE__->register_method(
4696 method => "user_saved_search_cud",
4697 api_name => "open-ils.actor.user.saved_search.retrieve",
4700 desc => 'Retrieve a saved search object',
4702 { desc => 'Authentication token', type => 'string' },
4703 { desc => 'Saved Search ID', type => 'number' }
4706 desc => q/The saved search object, Event on error/,
4712 sub user_saved_search_cud {
4713 my( $self, $client, $auth, $search ) = @_;
4714 my $e = new_editor( authtoken=>$auth );
4715 return $e->die_event unless $e->checkauth;
4717 my $o_search; # prior version of the object, if any
4718 my $res; # to be returned
4720 # branch on the operation type
4722 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4724 # Get the old version, to check ownership
4725 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4726 or return $e->die_event;
4728 # You can't read somebody else's search
4729 return OpenILS::Event->new('BAD_PARAMS')
4730 unless $o_search->owner == $e->requestor->id;
4736 $e->xact_begin; # start an editor transaction
4738 if( $search->isnew ) { # Create
4740 # You can't create a search for somebody else
4741 return OpenILS::Event->new('BAD_PARAMS')
4742 unless $search->owner == $e->requestor->id;
4744 $e->create_actor_usr_saved_search( $search )
4745 or return $e->die_event;
4749 } elsif( $search->ischanged ) { # Update
4751 # You can't change ownership of a search
4752 return OpenILS::Event->new('BAD_PARAMS')
4753 unless $search->owner == $e->requestor->id;
4755 # Get the old version, to check ownership
4756 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4757 or return $e->die_event;
4759 # You can't update somebody else's search
4760 return OpenILS::Event->new('BAD_PARAMS')
4761 unless $o_search->owner == $e->requestor->id;
4764 $e->update_actor_usr_saved_search( $search )
4765 or return $e->die_event;
4769 } elsif( $search->isdeleted ) { # Delete
4771 # Get the old version, to check ownership
4772 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4773 or return $e->die_event;
4775 # You can't delete somebody else's search
4776 return OpenILS::Event->new('BAD_PARAMS')
4777 unless $o_search->owner == $e->requestor->id;
4780 $e->delete_actor_usr_saved_search( $o_search )
4781 or return $e->die_event;
4792 __PACKAGE__->register_method(
4793 method => "get_barcodes",
4794 api_name => "open-ils.actor.get_barcodes"
4798 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4799 my $e = new_editor(authtoken => $auth);
4800 return $e->event unless $e->checkauth;
4801 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4803 my $db_result = $e->json_query(
4805 'evergreen.get_barcodes',
4806 $org_id, $context, $barcode,
4810 if($context =~ /actor/) {
4811 my $filter_result = ();
4813 foreach my $result (@$db_result) {
4814 if($result->{type} eq 'actor') {
4815 if($e->requestor->id != $result->{id}) {
4816 $patron = $e->retrieve_actor_user($result->{id});
4818 push(@$filter_result, $e->event);
4821 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4822 push(@$filter_result, $result);
4825 push(@$filter_result, $e->event);
4829 push(@$filter_result, $result);
4833 push(@$filter_result, $result);
4836 return $filter_result;
4842 __PACKAGE__->register_method(
4843 method => 'address_alert_test',
4844 api_name => 'open-ils.actor.address_alert.test',
4846 desc => "Tests a set of address fields to determine if they match with an address_alert",
4848 {desc => 'Authentication token', type => 'string'},
4849 {desc => 'Org Unit', type => 'number'},
4850 {desc => 'Fields', type => 'hash'},
4852 return => {desc => 'List of matching address_alerts'}
4856 sub address_alert_test {
4857 my ($self, $client, $auth, $org_unit, $fields) = @_;
4858 return [] unless $fields and grep {$_} values %$fields;
4860 my $e = new_editor(authtoken => $auth);
4861 return $e->event unless $e->checkauth;
4862 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4863 $org_unit ||= $e->requestor->ws_ou;
4865 my $alerts = $e->json_query({
4867 'actor.address_alert_matches',
4875 $$fields{post_code},
4876 $$fields{mailing_address},
4877 $$fields{billing_address}
4881 # map the json_query hashes to real objects
4883 map {$e->retrieve_actor_address_alert($_)}
4884 (map {$_->{id}} @$alerts)
4888 __PACKAGE__->register_method(
4889 method => "mark_users_contact_invalid",
4890 api_name => "open-ils.actor.invalidate.email",
4892 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",
4894 {desc => "Authentication token", type => "string"},
4895 {desc => "Patron ID (optional if Email address specified)", type => "number"},
4896 {desc => "Additional note text (optional)", type => "string"},
4897 {desc => "penalty org unit ID (optional)", type => "number"},
4898 {desc => "Email address (optional)", type => "string"}
4900 return => {desc => "Event describing success or failure", type => "object"}
4904 __PACKAGE__->register_method(
4905 method => "mark_users_contact_invalid",
4906 api_name => "open-ils.actor.invalidate.day_phone",
4908 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",
4910 {desc => "Authentication token", type => "string"},
4911 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4912 {desc => "Additional note text (optional)", type => "string"},
4913 {desc => "penalty org unit ID (optional)", type => "number"},
4914 {desc => "Phone Number (optional)", type => "string"}
4916 return => {desc => "Event describing success or failure", type => "object"}
4920 __PACKAGE__->register_method(
4921 method => "mark_users_contact_invalid",
4922 api_name => "open-ils.actor.invalidate.evening_phone",
4924 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",
4926 {desc => "Authentication token", type => "string"},
4927 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4928 {desc => "Additional note text (optional)", type => "string"},
4929 {desc => "penalty org unit ID (optional)", type => "number"},
4930 {desc => "Phone Number (optional)", type => "string"}
4932 return => {desc => "Event describing success or failure", type => "object"}
4936 __PACKAGE__->register_method(
4937 method => "mark_users_contact_invalid",
4938 api_name => "open-ils.actor.invalidate.other_phone",
4940 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",
4942 {desc => "Authentication token", type => "string"},
4943 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4944 {desc => "Additional note text (optional)", type => "string"},
4945 {desc => "penalty org unit ID (optional, default to top of org tree)",
4947 {desc => "Phone Number (optional)", type => "string"}
4949 return => {desc => "Event describing success or failure", type => "object"}
4953 sub mark_users_contact_invalid {
4954 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
4956 # This method invalidates an email address or a phone_number which
4957 # removes the bad email address or phone number, copying its contents
4958 # to a patron note, and institutes a standing penalty for "bad email"
4959 # or "bad phone number" which is cleared when the user is saved or
4960 # optionally only when the user is saved with an email address or
4961 # phone number (or staff manually delete the penalty).
4963 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4965 my $e = new_editor(authtoken => $auth, xact => 1);
4966 return $e->die_event unless $e->checkauth;
4969 if (defined $patron_id && $patron_id ne "") {
4970 $howfind = {usr => $patron_id};
4971 } elsif (defined $contact && $contact ne "") {
4972 $howfind = {$contact_type => $contact};
4974 # Error out if no patron id set or no contact is set.
4975 return OpenILS::Event->new('BAD_PARAMS');
4978 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4979 $e, $contact_type, $howfind,
4980 $addl_note, $penalty_ou, $e->requestor->id
4984 # Putting the following method in open-ils.actor is a bad fit, except in that
4985 # it serves an interface that lives under 'actor' in the templates directory,
4986 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4988 __PACKAGE__->register_method(
4989 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4990 method => "get_all_at_reactors_in_use",
4995 { name => 'authtoken', type => 'string' }
4998 desc => 'list of reactor names', type => 'array'
5003 sub get_all_at_reactors_in_use {
5004 my ($self, $conn, $auth) = @_;
5006 my $e = new_editor(authtoken => $auth);
5007 $e->checkauth or return $e->die_event;
5008 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5010 my $reactors = $e->json_query({
5012 atevdef => [{column => "reactor", transform => "distinct"}]
5014 from => {atevdef => {}}
5017 return $e->die_event unless ref $reactors eq "ARRAY";
5020 return [ map { $_->{reactor} } @$reactors ];
5023 __PACKAGE__->register_method(
5024 method => "filter_group_entry_crud",
5025 api_name => "open-ils.actor.filter_group_entry.crud",
5028 Provides CRUD access to filter group entry objects. These are not full accessible
5029 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5030 are not accessible via PCRUD (because they have no fields against which to link perms)
5033 {desc => "Authentication token", type => "string"},
5034 {desc => "Entry ID / Entry Object", type => "number"},
5035 {desc => "Additional note text (optional)", type => "string"},
5036 {desc => "penalty org unit ID (optional, default to top of org tree)",
5040 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5046 sub filter_group_entry_crud {
5047 my ($self, $conn, $auth, $arg) = @_;
5049 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5050 my $e = new_editor(authtoken => $auth, xact => 1);
5051 return $e->die_event unless $e->checkauth;
5057 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5058 or return $e->die_event;
5060 return $e->die_event unless $e->allowed(
5061 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5063 my $query = $arg->query;
5064 $query = $e->create_actor_search_query($query) or return $e->die_event;
5065 $arg->query($query->id);
5066 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5067 $entry->query($query);
5072 } elsif ($arg->ischanged) {
5074 my $entry = $e->retrieve_actor_search_filter_group_entry([
5077 flesh_fields => {asfge => ['grp']}
5079 ]) or return $e->die_event;
5081 return $e->die_event unless $e->allowed(
5082 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5084 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5085 $arg->query($arg->query->id);
5086 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5087 $arg->query($query);
5092 } elsif ($arg->isdeleted) {
5094 my $entry = $e->retrieve_actor_search_filter_group_entry([
5097 flesh_fields => {asfge => ['grp', 'query']}
5099 ]) or return $e->die_event;
5101 return $e->die_event unless $e->allowed(
5102 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5104 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5105 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5118 my $entry = $e->retrieve_actor_search_filter_group_entry([
5121 flesh_fields => {asfge => ['grp', 'query']}
5123 ]) or return $e->die_event;
5125 return $e->die_event unless $e->allowed(
5126 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5127 $entry->grp->owner);
5130 $entry->grp($entry->grp->id); # for consistency