1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenILS::Utils::DateTime qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Carousel;
30 use OpenILS::Application::Actor::Container;
31 use OpenILS::Application::Actor::ClosedDates;
32 use OpenILS::Application::Actor::UserGroups;
33 use OpenILS::Application::Actor::Friends;
34 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Application::Actor::Settings;
37 use OpenILS::Utils::CStoreEditor qw/:funcs/;
38 use OpenILS::Utils::Penalty;
39 use OpenILS::Utils::BadContact;
40 use List::Util qw/max reduce/;
42 use UUID::Tiny qw/:std/;
45 OpenILS::Application::Actor::Container->initialize();
46 OpenILS::Application::Actor::UserGroups->initialize();
47 OpenILS::Application::Actor::ClosedDates->initialize();
50 my $apputils = "OpenILS::Application::AppUtils";
53 sub _d { warn "Patron:\n" . Dumper(shift()); }
56 my $set_user_settings;
60 #__PACKAGE__->register_method(
61 # method => "allowed_test",
62 # api_name => "open-ils.actor.allowed_test",
65 # my($self, $conn, $auth, $orgid, $permcode) = @_;
66 # my $e = new_editor(authtoken => $auth);
67 # return $e->die_event unless $e->checkauth;
71 # permcode => $permcode,
72 # result => $e->allowed($permcode, $orgid)
76 __PACKAGE__->register_method(
77 method => "update_user_setting",
78 api_name => "open-ils.actor.patron.settings.update",
80 sub update_user_setting {
81 my($self, $conn, $auth, $user_id, $settings) = @_;
82 my $e = new_editor(xact => 1, authtoken => $auth);
83 return $e->die_event unless $e->checkauth;
85 $user_id = $e->requestor->id unless defined $user_id;
87 unless($e->requestor->id == $user_id) {
88 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
89 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
92 for my $name (keys %$settings) {
93 my $val = $$settings{$name};
94 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
97 $val = OpenSRF::Utils::JSON->perl2JSON($val);
100 $e->update_actor_user_setting($set) or return $e->die_event;
102 $set = Fieldmapper::actor::user_setting->new;
106 $e->create_actor_user_setting($set) or return $e->die_event;
109 $e->delete_actor_user_setting($set) or return $e->die_event;
118 __PACKAGE__->register_method(
119 method => "update_privacy_waiver",
120 api_name => "open-ils.actor.patron.privacy_waiver.update",
122 desc => "Replaces any existing privacy waiver entries for the patron with the supplied values.",
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'User ID', type => 'number'},
126 {desc => 'Arrayref of privacy waiver entries', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
131 sub update_privacy_waiver {
132 my($self, $conn, $auth, $user_id, $waiver) = @_;
133 my $e = new_editor(xact => 1, authtoken => $auth);
134 return $e->die_event unless $e->checkauth;
136 $user_id = $e->requestor->id unless defined $user_id;
138 unless($e->requestor->id == $user_id) {
139 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
140 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
143 foreach my $w (@$waiver) {
144 $w->{usr} = $user_id unless $w->{usr};
145 if ($w->{id} && $w->{id} ne 'new') {
146 my $existing_rows = $e->search_actor_usr_privacy_waiver({usr => $user_id, id => $w->{id}});
147 if ($existing_rows) {
148 my $existing = $existing_rows->[0];
149 # delete existing if name is empty
150 if (!$w->{name} or $w->{name} =~ /^\s*$/) {
151 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
153 # delete existing if none of the boxes were checked
154 } elsif (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}) {
155 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
157 # otherwise, update existing waiver entry
159 $existing->name($w->{name});
160 $existing->place_holds($w->{place_holds});
161 $existing->pickup_holds($w->{pickup_holds});
162 $existing->checkout_items($w->{checkout_items});
163 $existing->view_history($w->{view_history});
164 $e->update_actor_usr_privacy_waiver($existing) or return $e->die_event;
167 $logger->warn("No privacy waiver entry found for user $user_id with ID " . $w->{id});
171 # ignore new entries with empty name or with no boxes checked
172 next if (!$w->{name} or $w->{name} =~ /^\s*$/);
173 next if (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history});
174 my $new = Fieldmapper::actor::usr_privacy_waiver->new;
175 $new->usr($w->{usr});
176 $new->name($w->{name});
177 $new->place_holds($w->{place_holds});
178 $new->pickup_holds($w->{pickup_holds});
179 $new->checkout_items($w->{checkout_items});
180 $new->view_history($w->{view_history});
181 $e->create_actor_usr_privacy_waiver($new) or return $e->die_event;
190 __PACKAGE__->register_method(
191 method => "set_ou_settings",
192 api_name => "open-ils.actor.org_unit.settings.update",
194 desc => "Updates the value for a given org unit setting. The permission to update " .
195 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
196 "permission specified in the update_perm column of the config.org_unit_setting_type " .
197 "table's row corresponding to the setting being changed." ,
199 {desc => 'Authentication token', type => 'string'},
200 {desc => 'Org unit ID', type => 'number'},
201 {desc => 'Hash of setting name-value pairs', type => 'object'}
203 return => {desc => '1 on success, Event on error'}
207 sub set_ou_settings {
208 my( $self, $client, $auth, $org_id, $settings ) = @_;
210 my $e = new_editor(authtoken => $auth, xact => 1);
211 return $e->die_event unless $e->checkauth;
213 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
215 for my $name (keys %$settings) {
216 my $val = $$settings{$name};
218 my $type = $e->retrieve_config_org_unit_setting_type([
220 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
221 ]) or return $e->die_event;
222 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
224 # If there is no relevant permission, the default assumption will
225 # be, "no, the caller cannot change that value."
226 return $e->die_event unless ($all_allowed ||
227 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
230 $val = OpenSRF::Utils::JSON->perl2JSON($val);
233 $e->update_actor_org_unit_setting($set) or return $e->die_event;
235 $set = Fieldmapper::actor::org_unit_setting->new;
236 $set->org_unit($org_id);
239 $e->create_actor_org_unit_setting($set) or return $e->die_event;
242 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
250 __PACKAGE__->register_method(
251 method => "user_settings",
253 api_name => "open-ils.actor.patron.settings.retrieve",
256 my( $self, $client, $auth, $user_id, $setting ) = @_;
258 my $e = new_editor(authtoken => $auth);
259 return $e->event unless $e->checkauth;
260 $user_id = $e->requestor->id unless defined $user_id;
262 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
263 if($e->requestor->id != $user_id) {
264 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
268 my($e, $user_id, $setting) = @_;
269 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
270 return undef unless $val; # XXX this should really return undef, but needs testing
271 return OpenSRF::Utils::JSON->JSON2perl($val->value);
275 if(ref $setting eq 'ARRAY') {
277 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
280 return get_setting($e, $user_id, $setting);
283 my $s = $e->search_actor_user_setting({usr => $user_id});
284 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
289 __PACKAGE__->register_method(
290 method => "ranged_ou_settings",
291 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
293 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
294 "is implied for retrieving OU settings by the authenticated users' permissions.",
296 {desc => 'Authentication token', type => 'string'},
297 {desc => 'Org unit ID', type => 'number'},
299 return => {desc => 'A hashref of "ranged" settings, event on error'}
302 sub ranged_ou_settings {
303 my( $self, $client, $auth, $org_id ) = @_;
305 my $e = new_editor(authtoken => $auth);
306 return $e->event unless $e->checkauth;
309 my $org_list = $U->get_org_ancestors($org_id);
310 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
311 $org_list = [ reverse @$org_list ];
313 # start at the context org and capture the setting value
314 # without clobbering settings we've already captured
315 for my $this_org_id (@$org_list) {
317 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
319 for my $set (@sets) {
320 my $type = $e->retrieve_config_org_unit_setting_type([
322 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
325 # If there is no relevant permission, the default assumption will
326 # be, "yes, the caller can have that value."
327 if ($type && $type->view_perm) {
328 next if not $e->allowed($type->view_perm->code, $org_id);
331 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
332 unless defined $ranged_settings{$set->name};
336 return \%ranged_settings;
341 __PACKAGE__->register_method(
342 api_name => 'open-ils.actor.ou_setting.ancestor_default',
343 method => 'ou_ancestor_setting',
345 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
346 'This method will make sure that the given user has permission to view that setting, if there is a ' .
347 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
348 'the user lacks the permisssion, undef will be returned.' ,
350 { desc => 'Org unit ID', type => 'number' },
351 { desc => 'setting name', type => 'string' },
352 { desc => 'authtoken (optional)', type => 'string' }
354 return => {desc => 'A value for the org unit setting, or undef'}
358 # ------------------------------------------------------------------
359 # Attempts to find the org setting value for a given org. if not
360 # found at the requested org, searches up the org tree until it
361 # finds a parent that has the requested setting.
362 # when found, returns { org => $id, value => $value }
363 # otherwise, returns NULL
364 # ------------------------------------------------------------------
365 sub ou_ancestor_setting {
366 my( $self, $client, $orgid, $name, $auth ) = @_;
367 # Make sure $auth is set to something if not given.
369 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
372 __PACKAGE__->register_method(
373 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
374 method => 'ou_ancestor_setting_batch',
376 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
377 'This method will make sure that the given user has permission to view that setting, if there is a ' .
378 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
379 'the user lacks the permisssion, undef will be returned.' ,
381 { desc => 'Org unit ID', type => 'number' },
382 { desc => 'setting name list', type => 'array' },
383 { desc => 'authtoken (optional)', type => 'string' }
385 return => {desc => 'A hash with name => value pairs for the org unit settings'}
388 sub ou_ancestor_setting_batch {
389 my( $self, $client, $orgid, $name_list, $auth ) = @_;
391 # splitting the list of settings to fetch values
392 # so that ones that *don't* require view_perm checks
393 # can be fetched in one fell swoop, which is
394 # significantly faster in cases where a large
395 # number of settings need to be fetched.
396 my %perm_check_required = ();
397 my @perm_check_not_required = ();
399 # Note that ->ou_ancestor_setting also can check
400 # to see if the setting has a view_perm, but testing
401 # suggests that the redundant checks do not significantly
402 # increase the time it takes to fetch the values of
403 # permission-controlled settings.
404 my $e = new_editor();
405 my $res = $e->search_config_org_unit_setting_type({
407 view_perm => { "!=" => undef },
409 %perm_check_required = map { $_->name() => 1 } @$res;
410 foreach my $setting (@$name_list) {
411 push @perm_check_not_required, $setting
412 unless exists($perm_check_required{$setting});
416 if (@perm_check_not_required) {
417 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
419 $values{$_} = $U->ou_ancestor_setting(
422 ) for keys(%perm_check_required);
428 __PACKAGE__->register_method(
429 method => "update_patron",
430 api_name => "open-ils.actor.patron.update",
433 Update an existing user, or create a new one. Related objects,
434 like cards, addresses, survey responses, and stat cats,
435 can be updated by attaching them to the user object in their
436 respective fields. For examples, the billing address object
437 may be inserted into the 'billing_address' field, etc. For each
438 attached object, indicate if the object should be created,
439 updated, or deleted using the built-in 'isnew', 'ischanged',
440 and 'isdeleted' fields on the object.
443 { desc => 'Authentication token', type => 'string' },
444 { desc => 'Patron data object', type => 'object' }
446 return => {desc => 'A fleshed user object, event on error'}
451 my( $self, $client, $auth, $patron ) = @_;
453 my $e = new_editor(xact => 1, authtoken => $auth);
454 return $e->event unless $e->checkauth;
456 $logger->info($patron->isnew ? "Creating new patron..." :
457 "Updating Patron: " . $patron->id);
459 my $evt = check_group_perm($e, $e->requestor, $patron);
462 # $new_patron is the patron in progress. $patron is the original patron
463 # passed in with the method. new_patron will change as the components
464 # of patron are added/updated.
468 # unflesh the real items on the patron
469 $patron->card( $patron->card->id ) if(ref($patron->card));
470 $patron->billing_address( $patron->billing_address->id )
471 if(ref($patron->billing_address));
472 $patron->mailing_address( $patron->mailing_address->id )
473 if(ref($patron->mailing_address));
475 # create/update the patron first so we can use his id
477 # $patron is the obj from the client (new data) and $new_patron is the
478 # patron object properly built for db insertion, so we need a third variable
479 # if we want to represent the old patron.
482 my $barred_hook = '';
485 if($patron->isnew()) {
486 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
488 if($U->is_true($patron->barred)) {
489 return $e->die_event unless
490 $e->allowed('BAR_PATRON', $patron->home_ou);
493 $new_patron = $patron;
495 # Did auth checking above already.
496 $old_patron = $e->retrieve_actor_user($patron->id) or
497 return $e->die_event;
499 $renew_hook = 'au.renewed' if ($old_patron->expire_date ne $new_patron->expire_date);
501 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
502 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
503 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
505 $barred_hook = $U->is_true($new_patron->barred) ?
506 'au.barred' : 'au.unbarred';
509 # update the password by itself to avoid the password protection magic
510 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
511 modify_migrated_user_password($e, $patron->id, $patron->passwd);
512 $new_patron->passwd(''); # subsequent update will set
513 # actor.usr.passwd to MD5('')
517 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
520 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
523 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
526 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
529 # re-update the patron if anything has happened to him during this process
530 if($new_patron->ischanged()) {
531 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
535 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
538 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
541 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
544 $evt = apply_invalid_addr_penalty($e, $patron);
549 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
551 $tses->request('open-ils.trigger.event.autocreate',
552 'au.created', $new_patron, $new_patron->home_ou);
554 $tses->request('open-ils.trigger.event.autocreate',
555 'au.updated', $new_patron, $new_patron->home_ou);
557 $tses->request('open-ils.trigger.event.autocreate', $renew_hook,
558 $new_patron, $new_patron->home_ou) if $renew_hook;
560 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
561 $new_patron, $new_patron->home_ou) if $barred_hook;
564 $e->xact_begin; # $e->rollback is called in new_flesh_user
565 return flesh_user($new_patron->id(), $e);
568 sub apply_invalid_addr_penalty {
572 # grab the invalid address penalty if set
573 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
575 my ($addr_penalty) = grep
576 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
578 # do we enforce invalid address penalty
579 my $enforce = $U->ou_ancestor_setting_value(
580 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
582 my $addrs = $e->search_actor_user_address(
583 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
584 my $addr_count = scalar(@$addrs);
586 if($addr_count == 0 and $addr_penalty) {
588 # regardless of any settings, remove the penalty when the user has no invalid addresses
589 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
592 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
594 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
595 my $depth = $ptype->org_depth;
596 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
597 $ctx_org = $patron->home_ou unless defined $ctx_org;
599 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
600 $penalty->usr($patron->id);
601 $penalty->org_unit($ctx_org);
602 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
604 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
619 "standing_penalties",
629 push @$fields, "home_ou" if $home_ou;
630 return new_flesh_user($id, $fields, $e );
638 # clone and clear stuff that would break the database
642 my $new_patron = $patron->clone;
644 $new_patron->clear_billing_address();
645 $new_patron->clear_mailing_address();
646 $new_patron->clear_addresses();
647 $new_patron->clear_card();
648 $new_patron->clear_cards();
649 $new_patron->clear_id();
650 $new_patron->clear_isnew();
651 $new_patron->clear_ischanged();
652 $new_patron->clear_isdeleted();
653 $new_patron->clear_stat_cat_entries();
654 $new_patron->clear_waiver_entries();
655 $new_patron->clear_permissions();
656 $new_patron->clear_standing_penalties();
667 return (undef, $e->die_event) unless
668 $e->allowed('CREATE_USER', $patron->home_ou);
670 my $ex = $e->search_actor_user(
671 {usrname => $patron->usrname}, {idlist => 1});
672 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
674 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
676 # do a dance to get the password hashed securely
677 my $saved_password = $patron->passwd;
679 $e->create_actor_user($patron) or return (undef, $e->die_event);
680 modify_migrated_user_password($e, $patron->id, $saved_password);
682 my $id = $patron->id; # added by CStoreEditor
684 $logger->info("Successfully created new user [$id] in DB");
685 return ($e->retrieve_actor_user($id), undef);
689 sub check_group_perm {
690 my( $e, $requestor, $patron ) = @_;
693 # first let's see if the requestor has
694 # priveleges to update this user in any way
695 if( ! $patron->isnew ) {
696 my $p = $e->retrieve_actor_user($patron->id);
698 # If we are the requestor (trying to update our own account)
699 # and we are not trying to change our profile, we're good
700 if( $p->id == $requestor->id and
701 $p->profile == $patron->profile ) {
706 $evt = group_perm_failed($e, $requestor, $p);
710 # They are allowed to edit this patron.. can they put the
711 # patron into the group requested?
712 $evt = group_perm_failed($e, $requestor, $patron);
718 sub group_perm_failed {
719 my( $e, $requestor, $patron ) = @_;
723 my $grpid = $patron->profile;
727 $logger->debug("user update looking for group perm for group $grpid");
728 $grp = $e->retrieve_permission_grp_tree($grpid);
730 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
732 $logger->info("user update checking perm $perm on user ".
733 $requestor->id." for update/create on user username=".$patron->usrname);
735 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
741 my( $e, $patron, $noperm) = @_;
743 $logger->info("Updating patron ".$patron->id." in DB");
748 return (undef, $e->die_event)
749 unless $e->allowed('UPDATE_USER', $patron->home_ou);
752 if(!$patron->ident_type) {
753 $patron->clear_ident_type;
754 $patron->clear_ident_value;
757 $evt = verify_last_xact($e, $patron);
758 return (undef, $evt) if $evt;
760 $e->update_actor_user($patron) or return (undef, $e->die_event);
762 # re-fetch the user to pick up the latest last_xact_id value
763 # to avoid collisions.
764 $patron = $e->retrieve_actor_user($patron->id);
769 sub verify_last_xact {
770 my( $e, $patron ) = @_;
771 return undef unless $patron->id and $patron->id > 0;
772 my $p = $e->retrieve_actor_user($patron->id);
773 my $xact = $p->last_xact_id;
774 return undef unless $xact;
775 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
776 return OpenILS::Event->new('XACT_COLLISION')
777 if $xact ne $patron->last_xact_id;
782 sub _check_dup_ident {
783 my( $session, $patron ) = @_;
785 return undef unless $patron->ident_value;
788 ident_type => $patron->ident_type,
789 ident_value => $patron->ident_value,
792 $logger->debug("patron update searching for dup ident values: " .
793 $patron->ident_type . ':' . $patron->ident_value);
795 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
797 my $dups = $session->request(
798 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
801 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
808 sub _add_update_addresses {
812 my $new_patron = shift;
816 my $current_id; # id of the address before creation
818 my $addresses = $patron->addresses();
820 for my $address (@$addresses) {
822 next unless ref $address;
823 $current_id = $address->id();
825 if( $patron->billing_address() and
826 $patron->billing_address() == $current_id ) {
827 $logger->info("setting billing addr to $current_id");
828 $new_patron->billing_address($address->id());
829 $new_patron->ischanged(1);
832 if( $patron->mailing_address() and
833 $patron->mailing_address() == $current_id ) {
834 $new_patron->mailing_address($address->id());
835 $logger->info("setting mailing addr to $current_id");
836 $new_patron->ischanged(1);
840 if($address->isnew()) {
842 $address->usr($new_patron->id());
844 ($address, $evt) = _add_address($e,$address);
845 return (undef, $evt) if $evt;
847 # we need to get the new id
848 if( $patron->billing_address() and
849 $patron->billing_address() == $current_id ) {
850 $new_patron->billing_address($address->id());
851 $logger->info("setting billing addr to $current_id");
852 $new_patron->ischanged(1);
855 if( $patron->mailing_address() and
856 $patron->mailing_address() == $current_id ) {
857 $new_patron->mailing_address($address->id());
858 $logger->info("setting mailing addr to $current_id");
859 $new_patron->ischanged(1);
862 } elsif($address->ischanged() ) {
864 ($address, $evt) = _update_address($e, $address);
865 return (undef, $evt) if $evt;
867 } elsif($address->isdeleted() ) {
869 if( $address->id() == $new_patron->mailing_address() ) {
870 $new_patron->clear_mailing_address();
871 ($new_patron, $evt) = _update_patron($e, $new_patron);
872 return (undef, $evt) if $evt;
875 if( $address->id() == $new_patron->billing_address() ) {
876 $new_patron->clear_billing_address();
877 ($new_patron, $evt) = _update_patron($e, $new_patron);
878 return (undef, $evt) if $evt;
881 $evt = _delete_address($e, $address);
882 return (undef, $evt) if $evt;
886 return ( $new_patron, undef );
890 # adds an address to the db and returns the address with new id
892 my($e, $address) = @_;
893 $address->clear_id();
895 $logger->info("Creating new address at street ".$address->street1);
897 # put the address into the database
898 $e->create_actor_user_address($address) or return (undef, $e->die_event);
899 return ($address, undef);
903 sub _update_address {
904 my( $e, $address ) = @_;
906 $logger->info("Updating address ".$address->id." in the DB");
908 $e->update_actor_user_address($address) or return (undef, $e->die_event);
910 return ($address, undef);
915 sub _add_update_cards {
919 my $new_patron = shift;
923 my $virtual_id; #id of the card before creation
925 my $card_changed = 0;
926 my $cards = $patron->cards();
927 for my $card (@$cards) {
929 $card->usr($new_patron->id());
931 if(ref($card) and $card->isnew()) {
933 $virtual_id = $card->id();
934 ( $card, $evt ) = _add_card($e, $card);
935 return (undef, $evt) if $evt;
937 #if(ref($patron->card)) { $patron->card($patron->card->id); }
938 if($patron->card() == $virtual_id) {
939 $new_patron->card($card->id());
940 $new_patron->ischanged(1);
944 } elsif( ref($card) and $card->ischanged() ) {
945 $evt = _update_card($e, $card);
946 return (undef, $evt) if $evt;
951 $U->create_events_for_hook('au.barcode_changed', $new_patron, $e->requestor->ws_ou)
954 return ( $new_patron, undef );
958 # adds an card to the db and returns the card with new id
960 my( $e, $card ) = @_;
963 $logger->info("Adding new patron card ".$card->barcode);
965 $e->create_actor_card($card) or return (undef, $e->die_event);
967 return ( $card, undef );
971 # returns event on error. returns undef otherwise
973 my( $e, $card ) = @_;
974 $logger->info("Updating patron card ".$card->id);
976 $e->update_actor_card($card) or return $e->die_event;
981 sub _add_update_waiver_entries {
984 my $new_patron = shift;
987 my $waiver_entries = $patron->waiver_entries();
988 for my $waiver (@$waiver_entries) {
989 next unless ref $waiver;
990 $waiver->usr($new_patron->id());
991 if ($waiver->isnew()) {
992 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
993 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
994 $logger->info("Adding new patron waiver entry");
996 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
997 } elsif ($waiver->ischanged()) {
998 $logger->info("Updating patron waiver entry " . $waiver->id);
999 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1000 } elsif ($waiver->isdeleted()) {
1001 $logger->info("Deleting patron waiver entry " . $waiver->id);
1002 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1005 return ($new_patron, undef);
1009 # returns event on error. returns undef otherwise
1010 sub _delete_address {
1011 my( $e, $address ) = @_;
1013 $logger->info("Deleting address ".$address->id." from DB");
1015 $e->delete_actor_user_address($address) or return $e->die_event;
1021 sub _add_survey_responses {
1022 my ($e, $patron, $new_patron) = @_;
1024 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1026 my $responses = $patron->survey_responses;
1030 $_->usr($new_patron->id) for (@$responses);
1032 my $evt = $U->simplereq( "open-ils.circ",
1033 "open-ils.circ.survey.submit.user_id", $responses );
1035 return (undef, $evt) if defined($U->event_code($evt));
1039 return ( $new_patron, undef );
1042 sub _clear_badcontact_penalties {
1043 my ($e, $old_patron, $new_patron) = @_;
1045 return ($new_patron, undef) unless $old_patron;
1047 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1049 # This ignores whether the caller of update_patron has any permission
1050 # to remove penalties, but these penalties no longer make sense
1051 # if an email address field (for example) is changed (and the caller must
1052 # have perms to do *that*) so there's no reason not to clear the penalties.
1054 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1056 "+csp" => {"name" => [values(%$PNM)]},
1057 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1059 "join" => {"csp" => {}},
1061 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1063 ]) or return (undef, $e->die_event);
1065 return ($new_patron, undef) unless @$bad_contact_penalties;
1067 my @penalties_to_clear;
1068 my ($field, $penalty_name);
1070 # For each field that might have an associated bad contact penalty,
1071 # check for such penalties and add them to the to-clear list if that
1072 # field has changed.
1073 while (($field, $penalty_name) = each(%$PNM)) {
1074 if ($old_patron->$field ne $new_patron->$field) {
1075 push @penalties_to_clear, grep {
1076 $_->standing_penalty->name eq $penalty_name
1077 } @$bad_contact_penalties;
1081 foreach (@penalties_to_clear) {
1082 # Note that this "archives" penalties, in the terminology of the staff
1083 # client, instead of just deleting them. This may assist reporting,
1084 # or preserving old contact information when it is still potentially
1086 $_->standing_penalty($_->standing_penalty->id); # deflesh
1087 $_->stop_date('now');
1088 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1091 return ($new_patron, undef);
1095 sub _create_stat_maps {
1097 my($e, $patron, $new_patron) = @_;
1099 my $maps = $patron->stat_cat_entries();
1101 for my $map (@$maps) {
1103 my $method = "update_actor_stat_cat_entry_user_map";
1105 if ($map->isdeleted()) {
1106 $method = "delete_actor_stat_cat_entry_user_map";
1108 } elsif ($map->isnew()) {
1109 $method = "create_actor_stat_cat_entry_user_map";
1114 $map->target_usr($new_patron->id);
1116 $logger->info("Updating stat entry with method $method and map $map");
1118 $e->$method($map) or return (undef, $e->die_event);
1121 return ($new_patron, undef);
1124 sub _create_perm_maps {
1126 my($e, $patron, $new_patron) = @_;
1128 my $maps = $patron->permissions;
1130 for my $map (@$maps) {
1132 my $method = "update_permission_usr_perm_map";
1133 if ($map->isdeleted()) {
1134 $method = "delete_permission_usr_perm_map";
1135 } elsif ($map->isnew()) {
1136 $method = "create_permission_usr_perm_map";
1140 $map->usr($new_patron->id);
1142 $logger->info( "Updating permissions with method $method and map $map" );
1144 $e->$method($map) or return (undef, $e->die_event);
1147 return ($new_patron, undef);
1151 __PACKAGE__->register_method(
1152 method => "set_user_work_ous",
1153 api_name => "open-ils.actor.user.work_ous.update",
1156 sub set_user_work_ous {
1162 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1163 return $evt if $evt;
1165 my $session = $apputils->start_db_session();
1166 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1168 for my $map (@$maps) {
1170 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1171 if ($map->isdeleted()) {
1172 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1173 } elsif ($map->isnew()) {
1174 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1178 #warn( "Updating permissions with method $method and session $ses and map $map" );
1179 $logger->info( "Updating work_ou map with method $method and map $map" );
1181 my $stat = $session->request($method, $map)->gather(1);
1182 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1186 $apputils->commit_db_session($session);
1188 return scalar(@$maps);
1192 __PACKAGE__->register_method(
1193 method => "set_user_perms",
1194 api_name => "open-ils.actor.user.permissions.update",
1197 sub set_user_perms {
1203 my $session = $apputils->start_db_session();
1205 my( $user_obj, $evt ) = $U->checkses($ses);
1206 return $evt if $evt;
1207 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1209 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1212 $all = 1 if ($U->is_true($user_obj->super_user()));
1213 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1215 for my $map (@$maps) {
1217 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1218 if ($map->isdeleted()) {
1219 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1220 } elsif ($map->isnew()) {
1221 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1225 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1226 #warn( "Updating permissions with method $method and session $ses and map $map" );
1227 $logger->info( "Updating permissions with method $method and map $map" );
1229 my $stat = $session->request($method, $map)->gather(1);
1230 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1234 $apputils->commit_db_session($session);
1236 return scalar(@$maps);
1240 __PACKAGE__->register_method(
1241 method => "user_retrieve_by_barcode",
1243 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1245 sub user_retrieve_by_barcode {
1246 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1248 my $e = new_editor(authtoken => $auth);
1249 return $e->event unless $e->checkauth;
1251 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1252 or return $e->event;
1254 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1255 return $e->event unless $e->allowed(
1256 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1263 __PACKAGE__->register_method(
1264 method => "get_user_by_id",
1266 api_name => "open-ils.actor.user.retrieve",
1269 sub get_user_by_id {
1270 my ($self, $client, $auth, $id) = @_;
1271 my $e = new_editor(authtoken=>$auth);
1272 return $e->event unless $e->checkauth;
1273 my $user = $e->retrieve_actor_user($id) or return $e->event;
1274 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1279 __PACKAGE__->register_method(
1280 method => "get_org_types",
1281 api_name => "open-ils.actor.org_types.retrieve",
1284 return $U->get_org_types();
1288 __PACKAGE__->register_method(
1289 method => "get_user_ident_types",
1290 api_name => "open-ils.actor.user.ident_types.retrieve",
1293 sub get_user_ident_types {
1294 return $ident_types if $ident_types;
1295 return $ident_types =
1296 new_editor()->retrieve_all_config_identification_type();
1300 __PACKAGE__->register_method(
1301 method => "get_org_unit",
1302 api_name => "open-ils.actor.org_unit.retrieve",
1306 my( $self, $client, $user_session, $org_id ) = @_;
1307 my $e = new_editor(authtoken => $user_session);
1309 return $e->event unless $e->checkauth;
1310 $org_id = $e->requestor->ws_ou;
1312 my $o = $e->retrieve_actor_org_unit($org_id)
1313 or return $e->event;
1317 __PACKAGE__->register_method(
1318 method => "search_org_unit",
1319 api_name => "open-ils.actor.org_unit_list.search",
1322 sub search_org_unit {
1324 my( $self, $client, $field, $value ) = @_;
1326 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1328 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1329 { $field => $value } );
1335 # build the org tree
1337 __PACKAGE__->register_method(
1338 method => "get_org_tree",
1339 api_name => "open-ils.actor.org_tree.retrieve",
1341 note => "Returns the entire org tree structure",
1347 return $U->get_org_tree($client->session->session_locale);
1351 __PACKAGE__->register_method(
1352 method => "get_org_descendants",
1353 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1356 # depth is optional. org_unit is the id
1357 sub get_org_descendants {
1358 my( $self, $client, $org_unit, $depth ) = @_;
1360 if(ref $org_unit eq 'ARRAY') {
1363 for my $i (0..scalar(@$org_unit)-1) {
1364 my $list = $U->simple_scalar_request(
1366 "open-ils.storage.actor.org_unit.descendants.atomic",
1367 $org_unit->[$i], $depth->[$i] );
1368 push(@trees, $U->build_org_tree($list));
1373 my $orglist = $apputils->simple_scalar_request(
1375 "open-ils.storage.actor.org_unit.descendants.atomic",
1376 $org_unit, $depth );
1377 return $U->build_org_tree($orglist);
1382 __PACKAGE__->register_method(
1383 method => "get_org_ancestors",
1384 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1387 # depth is optional. org_unit is the id
1388 sub get_org_ancestors {
1389 my( $self, $client, $org_unit, $depth ) = @_;
1390 my $orglist = $apputils->simple_scalar_request(
1392 "open-ils.storage.actor.org_unit.ancestors.atomic",
1393 $org_unit, $depth );
1394 return $U->build_org_tree($orglist);
1398 __PACKAGE__->register_method(
1399 method => "get_standings",
1400 api_name => "open-ils.actor.standings.retrieve"
1405 return $user_standings if $user_standings;
1406 return $user_standings =
1407 $apputils->simple_scalar_request(
1409 "open-ils.cstore.direct.config.standing.search.atomic",
1410 { id => { "!=" => undef } }
1415 __PACKAGE__->register_method(
1416 method => "get_my_org_path",
1417 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1420 sub get_my_org_path {
1421 my( $self, $client, $auth, $org_id ) = @_;
1422 my $e = new_editor(authtoken=>$auth);
1423 return $e->event unless $e->checkauth;
1424 $org_id = $e->requestor->ws_ou unless defined $org_id;
1426 return $apputils->simple_scalar_request(
1428 "open-ils.storage.actor.org_unit.full_path.atomic",
1433 __PACKAGE__->register_method(
1434 method => "patron_adv_search",
1435 api_name => "open-ils.actor.patron.search.advanced"
1438 __PACKAGE__->register_method(
1439 method => "patron_adv_search",
1440 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1442 # Flush the response stream at most 5 patrons in for UI responsiveness.
1443 max_bundle_count => 5,
1445 desc => q/Returns a stream of fleshed user objects instead of
1446 a pile of identifiers/
1450 sub patron_adv_search {
1451 my( $self, $client, $auth, $search_hash, $search_limit,
1452 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1454 # API params sanity checks.
1455 # Exit early with empty result if no filter exists.
1456 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1457 my $fleshed = ($self->api_name =~ /fleshed/);
1458 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1460 for my $key (keys %$search_hash) {
1461 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1465 return ($fleshed ? undef : []) unless $search_ok;
1467 my $e = new_editor(authtoken=>$auth);
1468 return $e->event unless $e->checkauth;
1469 return $e->event unless $e->allowed('VIEW_USER');
1471 # depth boundary outside of which patrons must opt-in, default to 0
1472 my $opt_boundary = 0;
1473 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1475 if (not defined $search_ou) {
1476 my $depth = $U->ou_ancestor_setting_value(
1477 $e->requestor->ws_ou,
1478 'circ.patron_edit.duplicate_patron_check_depth'
1481 if (defined $depth) {
1482 $search_ou = $U->org_unit_ancestor_at_depth(
1483 $e->requestor->ws_ou, $depth
1488 my $ids = $U->storagereq(
1489 "open-ils.storage.actor.user.crazy_search", $search_hash,
1490 $search_limit, $search_sort, $include_inactive,
1491 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1493 return $ids unless $self->api_name =~ /fleshed/;
1495 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1501 # A migrated (main) password has the form:
1502 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1503 sub modify_migrated_user_password {
1504 my ($e, $user_id, $passwd) = @_;
1506 # new password gets a new salt
1507 my $new_salt = $e->json_query({
1508 from => ['actor.create_salt', 'main']})->[0];
1509 $new_salt = $new_salt->{'actor.create_salt'};
1516 md5_hex($new_salt . md5_hex($passwd)),
1524 __PACKAGE__->register_method(
1525 method => "update_passwd",
1526 api_name => "open-ils.actor.user.password.update",
1528 desc => "Update the operator's password",
1530 { desc => 'Authentication token', type => 'string' },
1531 { desc => 'New password', type => 'string' },
1532 { desc => 'Current password', type => 'string' }
1534 return => {desc => '1 on success, Event on error or incorrect current password'}
1538 __PACKAGE__->register_method(
1539 method => "update_passwd",
1540 api_name => "open-ils.actor.user.username.update",
1542 desc => "Update the operator's username",
1544 { desc => 'Authentication token', type => 'string' },
1545 { desc => 'New username', type => 'string' },
1546 { desc => 'Current password', type => 'string' }
1548 return => {desc => '1 on success, Event on error or incorrect current password'}
1552 __PACKAGE__->register_method(
1553 method => "update_passwd",
1554 api_name => "open-ils.actor.user.email.update",
1556 desc => "Update the operator's email address",
1558 { desc => 'Authentication token', type => 'string' },
1559 { desc => 'New email address', type => 'string' },
1560 { desc => 'Current password', type => 'string' }
1562 return => {desc => '1 on success, Event on error or incorrect current password'}
1567 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1568 my $e = new_editor(xact=>1, authtoken=>$auth);
1569 return $e->die_event unless $e->checkauth;
1571 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1572 or return $e->die_event;
1573 my $api = $self->api_name;
1575 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1577 return new OpenILS::Event('INCORRECT_PASSWORD');
1581 if( $api =~ /password/o ) {
1582 # NOTE: with access to the plain text password we could crypt
1583 # the password without the extra MD5 pre-hashing. Other changes
1584 # would be required. Noting here for future reference.
1585 modify_migrated_user_password($e, $db_user->id, $new_val);
1586 $db_user->passwd('');
1590 # if we don't clear the password, the user will be updated with
1591 # a hashed version of the hashed version of their password
1592 $db_user->clear_passwd;
1594 if( $api =~ /username/o ) {
1596 # make sure no one else has this username
1597 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1600 return new OpenILS::Event('USERNAME_EXISTS');
1602 $db_user->usrname($new_val);
1605 } elsif( $api =~ /email/o ) {
1606 $db_user->email($new_val);
1611 $e->update_actor_user($db_user) or return $e->die_event;
1614 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1617 # update the cached user to pick up these changes
1618 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1624 __PACKAGE__->register_method(
1625 method => "check_user_perms",
1626 api_name => "open-ils.actor.user.perm.check",
1627 notes => <<" NOTES");
1628 Takes a login session, user id, an org id, and an array of perm type strings. For each
1629 perm type, if the user does *not* have the given permission it is added
1630 to a list which is returned from the method. If all permissions
1631 are allowed, an empty list is returned
1632 if the logged in user does not match 'user_id', then the logged in user must
1633 have VIEW_PERMISSION priveleges.
1636 sub check_user_perms {
1637 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1639 my( $staff, $evt ) = $apputils->checkses($login_session);
1640 return $evt if $evt;
1642 if($staff->id ne $user_id) {
1643 if( $evt = $apputils->check_perms(
1644 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1650 for my $perm (@$perm_types) {
1651 if($apputils->check_perms($user_id, $org_id, $perm)) {
1652 push @not_allowed, $perm;
1656 return \@not_allowed
1659 __PACKAGE__->register_method(
1660 method => "check_user_perms2",
1661 api_name => "open-ils.actor.user.perm.check.multi_org",
1663 Checks the permissions on a list of perms and orgs for a user
1664 @param authtoken The login session key
1665 @param user_id The id of the user to check
1666 @param orgs The array of org ids
1667 @param perms The array of permission names
1668 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1669 if the logged in user does not match 'user_id', then the logged in user must
1670 have VIEW_PERMISSION priveleges.
1673 sub check_user_perms2 {
1674 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1676 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1677 $authtoken, $user_id, 'VIEW_PERMISSION' );
1678 return $evt if $evt;
1681 for my $org (@$orgs) {
1682 for my $perm (@$perms) {
1683 if($apputils->check_perms($user_id, $org, $perm)) {
1684 push @not_allowed, [ $org, $perm ];
1689 return \@not_allowed
1693 __PACKAGE__->register_method(
1694 method => 'check_user_perms3',
1695 api_name => 'open-ils.actor.user.perm.highest_org',
1697 Returns the highest org unit id at which a user has a given permission
1698 If the requestor does not match the target user, the requestor must have
1699 'VIEW_PERMISSION' rights at the home org unit of the target user
1700 @param authtoken The login session key
1701 @param userid The id of the user in question
1702 @param perm The permission to check
1703 @return The org unit highest in the org tree within which the user has
1704 the requested permission
1707 sub check_user_perms3 {
1708 my($self, $client, $authtoken, $user_id, $perm) = @_;
1709 my $e = new_editor(authtoken=>$authtoken);
1710 return $e->event unless $e->checkauth;
1712 my $tree = $U->get_org_tree();
1714 unless($e->requestor->id == $user_id) {
1715 my $user = $e->retrieve_actor_user($user_id)
1716 or return $e->event;
1717 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1718 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1721 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1724 __PACKAGE__->register_method(
1725 method => 'user_has_work_perm_at',
1726 api_name => 'open-ils.actor.user.has_work_perm_at',
1730 Returns a set of org unit IDs which represent the highest orgs in
1731 the org tree where the user has the requested permission. The
1732 purpose of this method is to return the smallest set of org units
1733 which represent the full expanse of the user's ability to perform
1734 the requested action. The user whose perms this method should
1735 check is implied by the authtoken. /,
1737 {desc => 'authtoken', type => 'string'},
1738 {desc => 'permission name', type => 'string'},
1739 {desc => q/user id, optional. If present, check perms for
1740 this user instead of the logged in user/, type => 'number'},
1742 return => {desc => 'An array of org IDs'}
1746 sub user_has_work_perm_at {
1747 my($self, $conn, $auth, $perm, $user_id) = @_;
1748 my $e = new_editor(authtoken=>$auth);
1749 return $e->event unless $e->checkauth;
1750 if(defined $user_id) {
1751 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1752 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1754 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1757 __PACKAGE__->register_method(
1758 method => 'user_has_work_perm_at_batch',
1759 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1763 sub user_has_work_perm_at_batch {
1764 my($self, $conn, $auth, $perms, $user_id) = @_;
1765 my $e = new_editor(authtoken=>$auth);
1766 return $e->event unless $e->checkauth;
1767 if(defined $user_id) {
1768 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1769 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1772 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1778 __PACKAGE__->register_method(
1779 method => 'check_user_perms4',
1780 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1782 Returns the highest org unit id at which a user has a given permission
1783 If the requestor does not match the target user, the requestor must have
1784 'VIEW_PERMISSION' rights at the home org unit of the target user
1785 @param authtoken The login session key
1786 @param userid The id of the user in question
1787 @param perms An array of perm names to check
1788 @return An array of orgId's representing the org unit
1789 highest in the org tree within which the user has the requested permission
1790 The arrah of orgId's has matches the order of the perms array
1793 sub check_user_perms4 {
1794 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1796 my( $staff, $target, $org, $evt );
1798 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1799 $authtoken, $userid, 'VIEW_PERMISSION' );
1800 return $evt if $evt;
1803 return [] unless ref($perms);
1804 my $tree = $U->get_org_tree();
1806 for my $p (@$perms) {
1807 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1813 __PACKAGE__->register_method(
1814 method => "user_fines_summary",
1815 api_name => "open-ils.actor.user.fines.summary",
1818 desc => 'Returns a short summary of the users total open fines, ' .
1819 'excluding voided fines Params are login_session, user_id' ,
1821 {desc => 'Authentication token', type => 'string'},
1822 {desc => 'User ID', type => 'string'} # number?
1825 desc => "a 'mous' object, event on error",
1830 sub user_fines_summary {
1831 my( $self, $client, $auth, $user_id ) = @_;
1833 my $e = new_editor(authtoken=>$auth);
1834 return $e->event unless $e->checkauth;
1836 if( $user_id ne $e->requestor->id ) {
1837 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1838 return $e->event unless
1839 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1842 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1846 __PACKAGE__->register_method(
1847 method => "user_opac_vitals",
1848 api_name => "open-ils.actor.user.opac.vital_stats",
1852 desc => 'Returns a short summary of the users vital stats, including ' .
1853 'identification information, accumulated balance, number of holds, ' .
1854 'and current open circulation stats' ,
1856 {desc => 'Authentication token', type => 'string'},
1857 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1860 desc => "An object with four properties: user, fines, checkouts and holds."
1865 sub user_opac_vitals {
1866 my( $self, $client, $auth, $user_id ) = @_;
1868 my $e = new_editor(authtoken=>$auth);
1869 return $e->event unless $e->checkauth;
1871 $user_id ||= $e->requestor->id;
1873 my $user = $e->retrieve_actor_user( $user_id );
1876 ->method_lookup('open-ils.actor.user.fines.summary')
1877 ->run($auth => $user_id);
1878 return $fines if (defined($U->event_code($fines)));
1881 $fines = new Fieldmapper::money::open_user_summary ();
1882 $fines->balance_owed(0.00);
1883 $fines->total_owed(0.00);
1884 $fines->total_paid(0.00);
1885 $fines->usr($user_id);
1889 ->method_lookup('open-ils.actor.user.hold_requests.count')
1890 ->run($auth => $user_id);
1891 return $holds if (defined($U->event_code($holds)));
1894 ->method_lookup('open-ils.actor.user.checked_out.count')
1895 ->run($auth => $user_id);
1896 return $out if (defined($U->event_code($out)));
1898 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1900 my $unread_msgs = $e->search_actor_usr_message([
1901 {usr => $user_id, read_date => undef, deleted => 'f'},
1907 first_given_name => $user->first_given_name,
1908 second_given_name => $user->second_given_name,
1909 family_name => $user->family_name,
1910 alias => $user->alias,
1911 usrname => $user->usrname
1913 fines => $fines->to_bare_hash,
1916 messages => { unread => scalar(@$unread_msgs) }
1921 ##### a small consolidation of related method registrations
1922 my $common_params = [
1923 { desc => 'Authentication token', type => 'string' },
1924 { desc => 'User ID', type => 'string' },
1925 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1926 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1929 'open-ils.actor.user.transactions' => '',
1930 'open-ils.actor.user.transactions.fleshed' => '',
1931 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1932 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1933 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1934 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1937 foreach (keys %methods) {
1939 method => "user_transactions",
1942 desc => 'For a given user, retrieve a list of '
1943 . (/\.fleshed/ ? 'fleshed ' : '')
1944 . 'transactions' . $methods{$_}
1945 . ' optionally limited to transactions of a given type.',
1946 params => $common_params,
1948 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1949 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1953 $args{authoritative} = 1;
1954 __PACKAGE__->register_method(%args);
1957 # Now for the counts
1959 'open-ils.actor.user.transactions.count' => '',
1960 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1961 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1964 foreach (keys %methods) {
1966 method => "user_transactions",
1969 desc => 'For a given user, retrieve a count of open '
1970 . 'transactions' . $methods{$_}
1971 . ' optionally limited to transactions of a given type.',
1972 params => $common_params,
1973 return => { desc => "Integer count of transactions, or event on error" }
1976 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1977 __PACKAGE__->register_method(%args);
1980 __PACKAGE__->register_method(
1981 method => "user_transactions",
1982 api_name => "open-ils.actor.user.transactions.have_balance.total",
1985 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1986 . ' optionally limited to transactions of a given type.',
1987 params => $common_params,
1988 return => { desc => "Decimal balance value, or event on error" }
1993 sub user_transactions {
1994 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1997 my $e = new_editor(authtoken => $auth);
1998 return $e->event unless $e->checkauth;
2000 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2002 return $e->event unless
2003 $e->requestor->id == $user_id or
2004 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2006 my $api = $self->api_name();
2008 my $filter = ($api =~ /have_balance/o) ?
2009 { 'balance_owed' => { '<>' => 0 } }:
2010 { 'total_owed' => { '>' => 0 } };
2012 my $method = 'open-ils.actor.user.transactions.history.still_open';
2013 $method = "$method.authoritative" if $api =~ /authoritative/;
2014 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2016 if($api =~ /total/o) {
2018 $total += $_->balance_owed for @$trans;
2022 ($api =~ /count/o ) and return scalar @$trans;
2023 ($api !~ /fleshed/o) and return $trans;
2026 for my $t (@$trans) {
2028 if( $t->xact_type ne 'circulation' ) {
2029 push @resp, {transaction => $t};
2033 my $circ_data = flesh_circ($e, $t->id);
2034 push @resp, {transaction => $t, %$circ_data};
2041 __PACKAGE__->register_method(
2042 method => "user_transaction_retrieve",
2043 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2046 notes => "Returns a fleshed transaction record"
2049 __PACKAGE__->register_method(
2050 method => "user_transaction_retrieve",
2051 api_name => "open-ils.actor.user.transaction.retrieve",
2054 notes => "Returns a transaction record"
2057 sub user_transaction_retrieve {
2058 my($self, $client, $auth, $bill_id) = @_;
2060 my $e = new_editor(authtoken => $auth);
2061 return $e->event unless $e->checkauth;
2063 my $trans = $e->retrieve_money_billable_transaction_summary(
2064 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2066 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2068 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2070 return $trans unless $self->api_name =~ /flesh/;
2071 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2073 my $circ_data = flesh_circ($e, $trans->id, 1);
2075 return {transaction => $trans, %$circ_data};
2080 my $circ_id = shift;
2081 my $flesh_copy = shift;
2083 my $circ = $e->retrieve_action_circulation([
2087 circ => ['target_copy'],
2088 acp => ['call_number'],
2095 my $copy = $circ->target_copy;
2097 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2098 $mods = new Fieldmapper::metabib::virtual_record;
2099 $mods->doc_id(OILS_PRECAT_RECORD);
2100 $mods->title($copy->dummy_title);
2101 $mods->author($copy->dummy_author);
2104 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2108 $circ->target_copy($circ->target_copy->id);
2109 $copy->call_number($copy->call_number->id);
2111 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2115 __PACKAGE__->register_method(
2116 method => "hold_request_count",
2117 api_name => "open-ils.actor.user.hold_requests.count",
2121 Returns hold ready vs. total counts.
2122 If a context org unit is provided, a third value
2123 is returned with key 'behind_desk', which reports
2124 how many holds are ready at the pickup library
2125 with the behind_desk flag set to true.
2129 sub hold_request_count {
2130 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2131 my $e = new_editor(authtoken => $authtoken);
2132 return $e->event unless $e->checkauth;
2134 $user_id = $e->requestor->id unless defined $user_id;
2136 if($e->requestor->id ne $user_id) {
2137 my $user = $e->retrieve_actor_user($user_id);
2138 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2141 my $holds = $e->json_query({
2142 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2146 fulfillment_time => {"=" => undef },
2147 cancel_time => undef,
2152 $_->{current_shelf_lib} and # avoid undef warnings
2153 $_->{pickup_lib} eq $_->{current_shelf_lib}
2157 total => scalar(@$holds),
2158 ready => scalar(@ready)
2162 # count of holds ready at pickup lib with behind_desk true.
2163 $resp->{behind_desk} = scalar(
2165 $_->{pickup_lib} == $ctx_org and
2166 $U->is_true($_->{behind_desk})
2174 __PACKAGE__->register_method(
2175 method => "checked_out",
2176 api_name => "open-ils.actor.user.checked_out",
2180 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2181 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2182 . "(i.e., outstanding balance or some other pending action on the circ). "
2183 . "The .count method also includes a 'total' field which sums all open circs.",
2185 { desc => 'Authentication Token', type => 'string'},
2186 { desc => 'User ID', type => 'string'},
2189 desc => 'Returns event on error, or an object with ID lists, like: '
2190 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2195 __PACKAGE__->register_method(
2196 method => "checked_out",
2197 api_name => "open-ils.actor.user.checked_out.count",
2200 signature => q/@see open-ils.actor.user.checked_out/
2204 my( $self, $conn, $auth, $userid ) = @_;
2206 my $e = new_editor(authtoken=>$auth);
2207 return $e->event unless $e->checkauth;
2209 if( $userid ne $e->requestor->id ) {
2210 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2211 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2213 # see if there is a friend link allowing circ.view perms
2214 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2215 $e, $userid, $e->requestor->id, 'circ.view');
2216 return $e->event unless $allowed;
2220 my $count = $self->api_name =~ /count/;
2221 return _checked_out( $count, $e, $userid );
2225 my( $iscount, $e, $userid ) = @_;
2231 claims_returned => [],
2234 my $meth = 'retrieve_action_open_circ_';
2242 claims_returned => 0,
2249 my $data = $e->$meth($userid);
2253 $result{$_} += $data->$_() for (keys %result);
2254 $result{total} += $data->$_() for (keys %result);
2256 for my $k (keys %result) {
2257 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2267 __PACKAGE__->register_method(
2268 method => "checked_in_with_fines",
2269 api_name => "open-ils.actor.user.checked_in_with_fines",
2272 signature => q/@see open-ils.actor.user.checked_out/
2275 sub checked_in_with_fines {
2276 my( $self, $conn, $auth, $userid ) = @_;
2278 my $e = new_editor(authtoken=>$auth);
2279 return $e->event unless $e->checkauth;
2281 if( $userid ne $e->requestor->id ) {
2282 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2285 # money is owed on these items and they are checked in
2286 my $open = $e->search_action_circulation(
2289 xact_finish => undef,
2290 checkin_time => { "!=" => undef },
2295 my( @lost, @cr, @lo );
2296 for my $c (@$open) {
2297 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2298 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2299 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2304 claims_returned => \@cr,
2305 long_overdue => \@lo
2311 my ($api, $desc, $auth) = @_;
2312 $desc = $desc ? (" " . $desc) : '';
2313 my $ids = ($api =~ /ids$/) ? 1 : 0;
2316 method => "user_transaction_history",
2317 api_name => "open-ils.actor.user.transactions.$api",
2319 desc => "For a given User ID, returns a list of billable transaction" .
2320 ($ids ? " id" : '') .
2321 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2322 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2324 {desc => 'Authentication token', type => 'string'},
2325 {desc => 'User ID', type => 'number'},
2326 {desc => 'Transaction type (optional)', type => 'number'},
2327 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2330 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2334 $auth and push @sig, (authoritative => 1);
2338 my %auth_hist_methods = (
2340 'history.have_charge' => 'that have an initial charge',
2341 'history.still_open' => 'that are not finished',
2342 'history.have_balance' => 'that have a balance',
2343 'history.have_bill' => 'that have billings',
2344 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2345 'history.have_payment' => 'that have at least 1 payment',
2348 foreach (keys %auth_hist_methods) {
2349 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2350 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2351 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2354 sub user_transaction_history {
2355 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2359 my $e = new_editor(authtoken=>$auth);
2360 return $e->die_event unless $e->checkauth;
2362 if ($e->requestor->id ne $userid) {
2363 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2366 my $api = $self->api_name;
2367 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2369 if(defined($type)) {
2370 $filter->{'xact_type'} = $type;
2373 if($api =~ /have_bill_or_payment/o) {
2375 # transactions that have a non-zero sum across all billings or at least 1 payment
2376 $filter->{'-or'} = {
2377 'balance_owed' => { '<>' => 0 },
2378 'last_payment_ts' => { '<>' => undef }
2381 } elsif($api =~ /have_payment/) {
2383 $filter->{last_payment_ts} ||= {'<>' => undef};
2385 } elsif( $api =~ /have_balance/o) {
2387 # transactions that have a non-zero overall balance
2388 $filter->{'balance_owed'} = { '<>' => 0 };
2390 } elsif( $api =~ /have_charge/o) {
2392 # transactions that have at least 1 billing, regardless of whether it was voided
2393 $filter->{'last_billing_ts'} = { '<>' => undef };
2395 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2397 # transactions that have non-zero sum across all billings. This will exclude
2398 # xacts where all billings have been voided
2399 $filter->{'total_owed'} = { '<>' => 0 };
2402 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2403 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2404 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2406 my $mbts = $e->search_money_billable_transaction_summary(
2407 [ { usr => $userid, @xact_finish, %$filter },
2412 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2413 return $mbts unless $api =~ /fleshed/;
2416 for my $t (@$mbts) {
2418 if( $t->xact_type ne 'circulation' ) {
2419 push @resp, {transaction => $t};
2423 my $circ_data = flesh_circ($e, $t->id);
2424 push @resp, {transaction => $t, %$circ_data};
2432 __PACKAGE__->register_method(
2433 method => "user_perms",
2434 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2436 notes => "Returns a list of permissions"
2440 my( $self, $client, $authtoken, $user ) = @_;
2442 my( $staff, $evt ) = $apputils->checkses($authtoken);
2443 return $evt if $evt;
2445 $user ||= $staff->id;
2447 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2451 return $apputils->simple_scalar_request(
2453 "open-ils.storage.permission.user_perms.atomic",
2457 __PACKAGE__->register_method(
2458 method => "retrieve_perms",
2459 api_name => "open-ils.actor.permissions.retrieve",
2460 notes => "Returns a list of permissions"
2462 sub retrieve_perms {
2463 my( $self, $client ) = @_;
2464 return $apputils->simple_scalar_request(
2466 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2467 { id => { '!=' => undef } }
2471 __PACKAGE__->register_method(
2472 method => "retrieve_groups",
2473 api_name => "open-ils.actor.groups.retrieve",
2474 notes => "Returns a list of user groups"
2476 sub retrieve_groups {
2477 my( $self, $client ) = @_;
2478 return new_editor()->retrieve_all_permission_grp_tree();
2481 __PACKAGE__->register_method(
2482 method => "retrieve_org_address",
2483 api_name => "open-ils.actor.org_unit.address.retrieve",
2484 notes => <<' NOTES');
2485 Returns an org_unit address by ID
2486 @param An org_address ID
2488 sub retrieve_org_address {
2489 my( $self, $client, $id ) = @_;
2490 return $apputils->simple_scalar_request(
2492 "open-ils.cstore.direct.actor.org_address.retrieve",
2497 __PACKAGE__->register_method(
2498 method => "retrieve_groups_tree",
2499 api_name => "open-ils.actor.groups.tree.retrieve",
2500 notes => "Returns a list of user groups"
2503 sub retrieve_groups_tree {
2504 my( $self, $client ) = @_;
2505 return new_editor()->search_permission_grp_tree(
2510 flesh_fields => { pgt => ["children"] },
2511 order_by => { pgt => 'name'}
2518 __PACKAGE__->register_method(
2519 method => "add_user_to_groups",
2520 api_name => "open-ils.actor.user.set_groups",
2521 notes => "Adds a user to one or more permission groups"
2524 sub add_user_to_groups {
2525 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2527 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2528 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2529 return $evt if $evt;
2531 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2532 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2533 return $evt if $evt;
2535 $apputils->simplereq(
2537 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2539 for my $group (@$groups) {
2540 my $link = Fieldmapper::permission::usr_grp_map->new;
2542 $link->usr($userid);
2544 my $id = $apputils->simplereq(
2546 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2552 __PACKAGE__->register_method(
2553 method => "get_user_perm_groups",
2554 api_name => "open-ils.actor.user.get_groups",
2555 notes => "Retrieve a user's permission groups."
2559 sub get_user_perm_groups {
2560 my( $self, $client, $authtoken, $userid ) = @_;
2562 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2563 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2564 return $evt if $evt;
2566 return $apputils->simplereq(
2568 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2572 __PACKAGE__->register_method(
2573 method => "get_user_work_ous",
2574 api_name => "open-ils.actor.user.get_work_ous",
2575 notes => "Retrieve a user's work org units."
2578 __PACKAGE__->register_method(
2579 method => "get_user_work_ous",
2580 api_name => "open-ils.actor.user.get_work_ous.ids",
2581 notes => "Retrieve a user's work org units."
2584 sub get_user_work_ous {
2585 my( $self, $client, $auth, $userid ) = @_;
2586 my $e = new_editor(authtoken=>$auth);
2587 return $e->event unless $e->checkauth;
2588 $userid ||= $e->requestor->id;
2590 if($e->requestor->id != $userid) {
2591 my $user = $e->retrieve_actor_user($userid)
2592 or return $e->event;
2593 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2596 return $e->search_permission_usr_work_ou_map({usr => $userid})
2597 unless $self->api_name =~ /.ids$/;
2599 # client just wants a list of org IDs
2600 return $U->get_user_work_ou_ids($e, $userid);
2605 __PACKAGE__->register_method(
2606 method => 'register_workstation',
2607 api_name => 'open-ils.actor.workstation.register.override',
2608 signature => q/@see open-ils.actor.workstation.register/
2611 __PACKAGE__->register_method(
2612 method => 'register_workstation',
2613 api_name => 'open-ils.actor.workstation.register',
2615 Registers a new workstion in the system
2616 @param authtoken The login session key
2617 @param name The name of the workstation id
2618 @param owner The org unit that owns this workstation
2619 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2620 if the name is already in use.
2624 sub register_workstation {
2625 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2627 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2628 return $e->die_event unless $e->checkauth;
2629 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2630 my $existing = $e->search_actor_workstation({name => $name})->[0];
2631 $oargs = { all => 1 } unless defined $oargs;
2635 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2636 # workstation with the given name exists.
2638 if($owner ne $existing->owning_lib) {
2639 # if necessary, update the owning_lib of the workstation
2641 $logger->info("changing owning lib of workstation ".$existing->id.
2642 " from ".$existing->owning_lib." to $owner");
2643 return $e->die_event unless
2644 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2646 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2648 $existing->owning_lib($owner);
2649 return $e->die_event unless $e->update_actor_workstation($existing);
2655 "attempt to register an existing workstation. returning existing ID");
2658 return $existing->id;
2661 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2665 my $ws = Fieldmapper::actor::workstation->new;
2666 $ws->owning_lib($owner);
2668 $e->create_actor_workstation($ws) or return $e->die_event;
2670 return $ws->id; # note: editor sets the id on the new object for us
2673 __PACKAGE__->register_method(
2674 method => 'workstation_list',
2675 api_name => 'open-ils.actor.workstation.list',
2677 Returns a list of workstations registered at the given location
2678 @param authtoken The login session key
2679 @param ids A list of org_unit.id's for the workstation owners
2683 sub workstation_list {
2684 my( $self, $conn, $authtoken, @orgs ) = @_;
2686 my $e = new_editor(authtoken=>$authtoken);
2687 return $e->event unless $e->checkauth;
2692 unless $e->allowed('REGISTER_WORKSTATION', $o);
2693 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2699 __PACKAGE__->register_method(
2700 method => 'fetch_patron_note',
2701 api_name => 'open-ils.actor.note.retrieve.all',
2704 Returns a list of notes for a given user
2705 Requestor must have VIEW_USER permission if pub==false and
2706 @param authtoken The login session key
2707 @param args Hash of params including
2708 patronid : the patron's id
2709 pub : true if retrieving only public notes
2713 sub fetch_patron_note {
2714 my( $self, $conn, $authtoken, $args ) = @_;
2715 my $patronid = $$args{patronid};
2717 my($reqr, $evt) = $U->checkses($authtoken);
2718 return $evt if $evt;
2721 ($patron, $evt) = $U->fetch_user($patronid);
2722 return $evt if $evt;
2725 if( $patronid ne $reqr->id ) {
2726 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2727 return $evt if $evt;
2729 return $U->cstorereq(
2730 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2731 { usr => $patronid, pub => 't' } );
2734 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2735 return $evt if $evt;
2737 return $U->cstorereq(
2738 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2741 __PACKAGE__->register_method(
2742 method => 'create_user_note',
2743 api_name => 'open-ils.actor.note.create',
2745 Creates a new note for the given user
2746 @param authtoken The login session key
2747 @param note The note object
2750 sub create_user_note {
2751 my( $self, $conn, $authtoken, $note ) = @_;
2752 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2753 return $e->die_event unless $e->checkauth;
2755 my $user = $e->retrieve_actor_user($note->usr)
2756 or return $e->die_event;
2758 return $e->die_event unless
2759 $e->allowed('UPDATE_USER',$user->home_ou);
2761 $note->creator($e->requestor->id);
2762 $e->create_actor_usr_note($note) or return $e->die_event;
2768 __PACKAGE__->register_method(
2769 method => 'delete_user_note',
2770 api_name => 'open-ils.actor.note.delete',
2772 Deletes a note for the given user
2773 @param authtoken The login session key
2774 @param noteid The note id
2777 sub delete_user_note {
2778 my( $self, $conn, $authtoken, $noteid ) = @_;
2780 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2781 return $e->die_event unless $e->checkauth;
2782 my $note = $e->retrieve_actor_usr_note($noteid)
2783 or return $e->die_event;
2784 my $user = $e->retrieve_actor_user($note->usr)
2785 or return $e->die_event;
2786 return $e->die_event unless
2787 $e->allowed('UPDATE_USER', $user->home_ou);
2789 $e->delete_actor_usr_note($note) or return $e->die_event;
2795 __PACKAGE__->register_method(
2796 method => 'update_user_note',
2797 api_name => 'open-ils.actor.note.update',
2799 @param authtoken The login session key
2800 @param note The note
2804 sub update_user_note {
2805 my( $self, $conn, $auth, $note ) = @_;
2806 my $e = new_editor(authtoken=>$auth, xact=>1);
2807 return $e->die_event unless $e->checkauth;
2808 my $patron = $e->retrieve_actor_user($note->usr)
2809 or return $e->die_event;
2810 return $e->die_event unless
2811 $e->allowed('UPDATE_USER', $patron->home_ou);
2812 $e->update_actor_user_note($note)
2813 or return $e->die_event;
2818 __PACKAGE__->register_method(
2819 method => 'fetch_patron_messages',
2820 api_name => 'open-ils.actor.message.retrieve',
2823 Returns a list of notes for a given user, not
2824 including ones marked deleted
2825 @param authtoken The login session key
2826 @param patronid patron ID
2827 @param options hash containing optional limit and offset
2831 sub fetch_patron_messages {
2832 my( $self, $conn, $auth, $patronid, $options ) = @_;
2836 my $e = new_editor(authtoken => $auth);
2837 return $e->die_event unless $e->checkauth;
2839 if ($e->requestor->id ne $patronid) {
2840 return $e->die_event unless $e->allowed('VIEW_USER');
2843 my $select_clause = { usr => $patronid };
2844 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2845 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2846 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2848 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2853 __PACKAGE__->register_method(
2854 method => 'usrname_exists',
2855 api_name => 'open-ils.actor.username.exists',
2857 desc => 'Check if a username is already taken (by an undeleted patron)',
2859 {desc => 'Authentication token', type => 'string'},
2860 {desc => 'Username', type => 'string'}
2863 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2868 sub usrname_exists {
2869 my( $self, $conn, $auth, $usrname ) = @_;
2870 my $e = new_editor(authtoken=>$auth);
2871 return $e->event unless $e->checkauth;
2872 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2873 return $$a[0] if $a and @$a;
2877 __PACKAGE__->register_method(
2878 method => 'barcode_exists',
2879 api_name => 'open-ils.actor.barcode.exists',
2881 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2884 sub barcode_exists {
2885 my( $self, $conn, $auth, $barcode ) = @_;
2886 my $e = new_editor(authtoken=>$auth);
2887 return $e->event unless $e->checkauth;
2888 my $card = $e->search_actor_card({barcode => $barcode});
2894 #return undef unless @$card;
2895 #return $card->[0]->usr;
2899 __PACKAGE__->register_method(
2900 method => 'retrieve_net_levels',
2901 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2904 sub retrieve_net_levels {
2905 my( $self, $conn, $auth ) = @_;
2906 my $e = new_editor(authtoken=>$auth);
2907 return $e->event unless $e->checkauth;
2908 return $e->retrieve_all_config_net_access_level();
2911 # Retain the old typo API name just in case
2912 __PACKAGE__->register_method(
2913 method => 'fetch_org_by_shortname',
2914 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2916 __PACKAGE__->register_method(
2917 method => 'fetch_org_by_shortname',
2918 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2920 sub fetch_org_by_shortname {
2921 my( $self, $conn, $sname ) = @_;
2922 my $e = new_editor();
2923 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2924 return $e->event unless $org;
2929 __PACKAGE__->register_method(
2930 method => 'session_home_lib',
2931 api_name => 'open-ils.actor.session.home_lib',
2934 sub session_home_lib {
2935 my( $self, $conn, $auth ) = @_;
2936 my $e = new_editor(authtoken=>$auth);
2937 return undef unless $e->checkauth;
2938 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2939 return $org->shortname;
2942 __PACKAGE__->register_method(
2943 method => 'session_safe_token',
2944 api_name => 'open-ils.actor.session.safe_token',
2946 Returns a hashed session ID that is safe for export to the world.
2947 This safe token will expire after 1 hour of non-use.
2948 @param auth Active authentication token
2952 sub session_safe_token {
2953 my( $self, $conn, $auth ) = @_;
2954 my $e = new_editor(authtoken=>$auth);
2955 return undef unless $e->checkauth;
2957 my $safe_token = md5_hex($auth);
2959 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2961 # add more user fields as needed
2963 "safe-token-user-$safe_token", {
2964 id => $e->requestor->id,
2965 home_ou_shortname => $e->retrieve_actor_org_unit(
2966 $e->requestor->home_ou)->shortname,
2975 __PACKAGE__->register_method(
2976 method => 'safe_token_home_lib',
2977 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2979 Returns the home library shortname from the session
2980 asscociated with a safe token from generated by
2981 open-ils.actor.session.safe_token.
2982 @param safe_token Active safe token
2983 @param who Optional user activity "ewho" value
2987 sub safe_token_home_lib {
2988 my( $self, $conn, $safe_token, $who ) = @_;
2989 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2991 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2992 return unless $blob;
2994 $U->log_user_activity($blob->{id}, $who, 'verify');
2995 return $blob->{home_ou_shortname};
2999 __PACKAGE__->register_method(
3000 method => "update_penalties",
3001 api_name => "open-ils.actor.user.penalties.update"
3004 sub update_penalties {
3005 my($self, $conn, $auth, $user_id) = @_;
3006 my $e = new_editor(authtoken=>$auth, xact => 1);
3007 return $e->die_event unless $e->checkauth;
3008 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3009 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3010 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3011 return $evt if $evt;
3017 __PACKAGE__->register_method(
3018 method => "apply_penalty",
3019 api_name => "open-ils.actor.user.penalty.apply"
3023 my($self, $conn, $auth, $penalty) = @_;
3025 my $e = new_editor(authtoken=>$auth, xact => 1);
3026 return $e->die_event unless $e->checkauth;
3028 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3029 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3031 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3034 (defined $ptype->org_depth) ?
3035 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
3038 $penalty->org_unit($ctx_org);
3039 $penalty->staff($e->requestor->id);
3040 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3043 return $penalty->id;
3046 __PACKAGE__->register_method(
3047 method => "remove_penalty",
3048 api_name => "open-ils.actor.user.penalty.remove"
3051 sub remove_penalty {
3052 my($self, $conn, $auth, $penalty) = @_;
3053 my $e = new_editor(authtoken=>$auth, xact => 1);
3054 return $e->die_event unless $e->checkauth;
3055 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3056 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3058 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3063 __PACKAGE__->register_method(
3064 method => "update_penalty_note",
3065 api_name => "open-ils.actor.user.penalty.note.update"
3068 sub update_penalty_note {
3069 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3070 my $e = new_editor(authtoken=>$auth, xact => 1);
3071 return $e->die_event unless $e->checkauth;
3072 for my $penalty_id (@$penalty_ids) {
3073 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3074 if (! $penalty ) { return $e->die_event; }
3075 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3076 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3078 $penalty->note( $note ); $penalty->ischanged( 1 );
3080 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3086 __PACKAGE__->register_method(
3087 method => "ranged_penalty_thresholds",
3088 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3092 sub ranged_penalty_thresholds {
3093 my($self, $conn, $auth, $context_org) = @_;
3094 my $e = new_editor(authtoken=>$auth);
3095 return $e->event unless $e->checkauth;
3096 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3097 my $list = $e->search_permission_grp_penalty_threshold([
3098 {org_unit => $U->get_org_ancestors($context_org)},
3099 {order_by => {pgpt => 'id'}}
3101 $conn->respond($_) for @$list;
3107 __PACKAGE__->register_method(
3108 method => "user_retrieve_fleshed_by_id",
3110 api_name => "open-ils.actor.user.fleshed.retrieve",
3113 sub user_retrieve_fleshed_by_id {
3114 my( $self, $client, $auth, $user_id, $fields ) = @_;
3115 my $e = new_editor(authtoken => $auth);
3116 return $e->event unless $e->checkauth;
3118 if( $e->requestor->id != $user_id ) {
3119 return $e->event unless $e->allowed('VIEW_USER');
3126 "standing_penalties",
3134 return new_flesh_user($user_id, $fields, $e);
3138 sub new_flesh_user {
3141 my $fields = shift || [];
3144 my $fetch_penalties = 0;
3145 if(grep {$_ eq 'standing_penalties'} @$fields) {
3146 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3147 $fetch_penalties = 1;
3150 my $fetch_usr_act = 0;
3151 if(grep {$_ eq 'usr_activity'} @$fields) {
3152 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3156 my $user = $e->retrieve_actor_user(
3161 "flesh_fields" => { "au" => $fields }
3164 ) or return $e->die_event;
3167 if( grep { $_ eq 'addresses' } @$fields ) {
3169 $user->addresses([]) unless @{$user->addresses};
3170 # don't expose "replaced" addresses by default
3171 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3173 if( ref $user->billing_address ) {
3174 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3175 push( @{$user->addresses}, $user->billing_address );
3179 if( ref $user->mailing_address ) {
3180 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3181 push( @{$user->addresses}, $user->mailing_address );
3186 if($fetch_penalties) {
3187 # grab the user penalties ranged for this location
3188 $user->standing_penalties(
3189 $e->search_actor_user_standing_penalty([
3192 {stop_date => undef},
3193 {stop_date => {'>' => 'now'}}
3195 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3198 flesh_fields => {ausp => ['standing_penalty']}
3204 # retrieve the most recent usr_activity entry
3205 if ($fetch_usr_act) {
3207 # max number to return for simple patron fleshing
3208 my $limit = $U->ou_ancestor_setting_value(
3209 $e->requestor->ws_ou,
3210 'circ.patron.usr_activity_retrieve.max');
3214 flesh_fields => {auact => ['etype']},
3215 order_by => {auact => 'event_time DESC'},
3218 # 0 == none, <0 == return all
3219 $limit = 1 unless defined $limit;
3220 $opts->{limit} = $limit if $limit > 0;
3222 $user->usr_activity(
3224 [] : # skip the DB call
3225 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3230 $user->clear_passwd();
3237 __PACKAGE__->register_method(
3238 method => "user_retrieve_parts",
3239 api_name => "open-ils.actor.user.retrieve.parts",
3242 sub user_retrieve_parts {
3243 my( $self, $client, $auth, $user_id, $fields ) = @_;
3244 my $e = new_editor(authtoken => $auth);
3245 return $e->event unless $e->checkauth;
3246 $user_id ||= $e->requestor->id;
3247 if( $e->requestor->id != $user_id ) {
3248 return $e->event unless $e->allowed('VIEW_USER');
3251 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3252 push(@resp, $user->$_()) for(@$fields);
3258 __PACKAGE__->register_method(
3259 method => 'user_opt_in_enabled',
3260 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3261 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3264 sub user_opt_in_enabled {
3265 my($self, $conn) = @_;
3266 my $sc = OpenSRF::Utils::SettingsClient->new;
3267 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3272 __PACKAGE__->register_method(
3273 method => 'user_opt_in_at_org',
3274 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3276 @param $auth The auth token
3277 @param user_id The ID of the user to test
3278 @return 1 if the user has opted in at the specified org,
3279 2 if opt-in is disallowed for the user's home org,
3280 event on error, and 0 otherwise. /
3282 sub user_opt_in_at_org {
3283 my($self, $conn, $auth, $user_id) = @_;
3285 # see if we even need to enforce the opt-in value
3286 return 1 unless user_opt_in_enabled($self);
3288 my $e = new_editor(authtoken => $auth);
3289 return $e->event unless $e->checkauth;
3291 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3292 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3294 my $ws_org = $e->requestor->ws_ou;
3295 # user is automatically opted-in if they are from the local org
3296 return 1 if $user->home_ou eq $ws_org;
3298 # get the boundary setting
3299 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3301 # auto opt in if user falls within the opt boundary
3302 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3304 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3306 # check whether opt-in is restricted at the user's home library
3307 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3308 if ($opt_restrict_depth) {
3309 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3310 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3312 # opt-in is disallowed unless the workstation org is within the home
3313 # library's opt-in scope
3314 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3317 my $vals = $e->search_actor_usr_org_unit_opt_in(
3318 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3324 __PACKAGE__->register_method(
3325 method => 'create_user_opt_in_at_org',
3326 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3328 @param $auth The auth token
3329 @param user_id The ID of the user to test
3330 @return The ID of the newly created object, event on error./
3333 sub create_user_opt_in_at_org {
3334 my($self, $conn, $auth, $user_id, $org_id) = @_;
3336 my $e = new_editor(authtoken => $auth, xact=>1);
3337 return $e->die_event unless $e->checkauth;
3339 # if a specific org unit wasn't passed in, get one based on the defaults;
3341 my $wsou = $e->requestor->ws_ou;
3342 # get the default opt depth
3343 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3344 # get the org unit at that depth
3345 my $org = $e->json_query({
3346 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3347 $org_id = $org->{id};
3350 # fall back to the workstation OU, the pre-opt-in-boundary way
3351 $org_id = $e->requestor->ws_ou;
3354 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3355 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3357 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3359 $opt_in->org_unit($org_id);
3360 $opt_in->usr($user_id);
3361 $opt_in->staff($e->requestor->id);
3362 $opt_in->opt_in_ts('now');
3363 $opt_in->opt_in_ws($e->requestor->wsid);
3365 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3366 or return $e->die_event;
3374 __PACKAGE__->register_method (
3375 method => 'retrieve_org_hours',
3376 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3378 Returns the hours of operation for a specified org unit
3379 @param authtoken The login session key
3380 @param org_id The org_unit ID
3384 sub retrieve_org_hours {
3385 my($self, $conn, $auth, $org_id) = @_;
3386 my $e = new_editor(authtoken => $auth);
3387 return $e->die_event unless $e->checkauth;
3388 $org_id ||= $e->requestor->ws_ou;
3389 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3393 __PACKAGE__->register_method (
3394 method => 'verify_user_password',
3395 api_name => 'open-ils.actor.verify_user_password',
3397 Given a barcode or username and the MD5 encoded password,
3398 returns 1 if the password is correct. Returns 0 otherwise.
3402 sub verify_user_password {
3403 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3404 my $e = new_editor(authtoken => $auth);
3405 return $e->die_event unless $e->checkauth;
3407 my $user_by_barcode;
3408 my $user_by_username;
3410 my $card = $e->search_actor_card([
3411 {barcode => $barcode},
3412 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3413 $user_by_barcode = $card->usr;
3414 $user = $user_by_barcode;
3417 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3418 $user = $user_by_username;
3420 return 0 if (!$user || $U->is_true($user->deleted));
3421 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3422 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3423 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3426 __PACKAGE__->register_method (
3427 method => 'retrieve_usr_id_via_barcode_or_usrname',
3428 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3430 Given a barcode or username returns the id for the user or
3435 sub retrieve_usr_id_via_barcode_or_usrname {
3436 my($self, $conn, $auth, $barcode, $username) = @_;
3437 my $e = new_editor(authtoken => $auth);
3438 return $e->die_event unless $e->checkauth;
3439 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3441 my $user_by_barcode;
3442 my $user_by_username;
3443 $logger->info("$id_as_barcode is the ID as BARCODE");
3445 my $card = $e->search_actor_card([
3446 {barcode => $barcode},
3447 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3448 if ($id_as_barcode =~ /^t/i) {
3450 $user = $e->retrieve_actor_user($barcode);
3451 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3453 $user_by_barcode = $card->usr;
3454 $user = $user_by_barcode;
3457 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3458 $user_by_barcode = $card->usr;
3459 $user = $user_by_barcode;
3464 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3466 $user = $user_by_username;
3468 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3469 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3470 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3475 __PACKAGE__->register_method (
3476 method => 'merge_users',
3477 api_name => 'open-ils.actor.user.merge',
3480 Given a list of source users and destination user, transfer all data from the source
3481 to the dest user and delete the source user. All user related data is
3482 transferred, including circulations, holds, bookbags, etc.
3488 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3489 my $e = new_editor(xact => 1, authtoken => $auth);
3490 return $e->die_event unless $e->checkauth;
3492 # disallow the merge if any subordinate accounts are in collections
3493 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3494 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3496 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3497 if $master_id == $e->requestor->id;
3499 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3500 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3501 return $evt if $evt;
3503 my $del_addrs = ($U->ou_ancestor_setting_value(
3504 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3505 my $del_cards = ($U->ou_ancestor_setting_value(
3506 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3507 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3508 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3510 for my $src_id (@$user_ids) {
3512 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3513 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3514 return $evt if $evt;
3516 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3517 if $src_id == $e->requestor->id;
3519 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3520 if($src_user->home_ou ne $master_user->home_ou) {
3521 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3524 return $e->die_event unless
3525 $e->json_query({from => [
3540 __PACKAGE__->register_method (
3541 method => 'approve_user_address',
3542 api_name => 'open-ils.actor.user.pending_address.approve',
3549 sub approve_user_address {
3550 my($self, $conn, $auth, $addr) = @_;
3551 my $e = new_editor(xact => 1, authtoken => $auth);
3552 return $e->die_event unless $e->checkauth;
3554 # if the caller passes an address object, assume they want to
3555 # update it first before approving it
3556 $e->update_actor_user_address($addr) or return $e->die_event;
3558 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3560 my $user = $e->retrieve_actor_user($addr->usr);
3561 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3562 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3563 or return $e->die_event;
3565 return [values %$result]->[0];
3569 __PACKAGE__->register_method (
3570 method => 'retrieve_friends',
3571 api_name => 'open-ils.actor.friends.retrieve',
3574 returns { confirmed: [], pending_out: [], pending_in: []}
3575 pending_out are users I'm requesting friendship with
3576 pending_in are users requesting friendship with me
3581 sub retrieve_friends {
3582 my($self, $conn, $auth, $user_id, $options) = @_;
3583 my $e = new_editor(authtoken => $auth);
3584 return $e->event unless $e->checkauth;
3585 $user_id ||= $e->requestor->id;
3587 if($user_id != $e->requestor->id) {
3588 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3589 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3592 return OpenILS::Application::Actor::Friends->retrieve_friends(
3593 $e, $user_id, $options);
3598 __PACKAGE__->register_method (
3599 method => 'apply_friend_perms',
3600 api_name => 'open-ils.actor.friends.perms.apply',
3606 sub apply_friend_perms {
3607 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3608 my $e = new_editor(authtoken => $auth, xact => 1);
3609 return $e->die_event unless $e->checkauth;
3611 if($user_id != $e->requestor->id) {
3612 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3613 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3616 for my $perm (@perms) {
3618 OpenILS::Application::Actor::Friends->apply_friend_perm(
3619 $e, $user_id, $delegate_id, $perm);
3620 return $evt if $evt;
3628 __PACKAGE__->register_method (
3629 method => 'update_user_pending_address',
3630 api_name => 'open-ils.actor.user.address.pending.cud'
3633 sub update_user_pending_address {
3634 my($self, $conn, $auth, $addr) = @_;
3635 my $e = new_editor(authtoken => $auth, xact => 1);
3636 return $e->die_event unless $e->checkauth;
3638 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3639 if($addr->usr != $e->requestor->id) {
3640 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3644 $e->create_actor_user_address($addr) or return $e->die_event;
3645 } elsif($addr->isdeleted) {
3646 $e->delete_actor_user_address($addr) or return $e->die_event;
3648 $e->update_actor_user_address($addr) or return $e->die_event;
3652 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3658 __PACKAGE__->register_method (
3659 method => 'user_events',
3660 api_name => 'open-ils.actor.user.events.circ',
3663 __PACKAGE__->register_method (
3664 method => 'user_events',
3665 api_name => 'open-ils.actor.user.events.ahr',
3670 my($self, $conn, $auth, $user_id, $filters) = @_;
3671 my $e = new_editor(authtoken => $auth);
3672 return $e->event unless $e->checkauth;
3674 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3675 my $user_field = 'usr';
3678 $filters->{target} = {
3679 select => { $obj_type => ['id'] },
3681 where => {usr => $user_id}
3684 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3685 if($e->requestor->id != $user_id) {
3686 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3689 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3690 my $req = $ses->request('open-ils.trigger.events_by_target',
3691 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3693 while(my $resp = $req->recv) {
3694 my $val = $resp->content;
3695 my $tgt = $val->target;
3697 if($obj_type eq 'circ') {
3698 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3700 } elsif($obj_type eq 'ahr') {
3701 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3702 if $tgt->current_copy;
3705 $conn->respond($val) if $val;
3711 __PACKAGE__->register_method (
3712 method => 'copy_events',
3713 api_name => 'open-ils.actor.copy.events.circ',
3716 __PACKAGE__->register_method (
3717 method => 'copy_events',
3718 api_name => 'open-ils.actor.copy.events.ahr',
3723 my($self, $conn, $auth, $copy_id, $filters) = @_;
3724 my $e = new_editor(authtoken => $auth);
3725 return $e->event unless $e->checkauth;
3727 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3729 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3731 my $copy_field = 'target_copy';
3732 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3735 $filters->{target} = {
3736 select => { $obj_type => ['id'] },
3738 where => {$copy_field => $copy_id}
3742 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3743 my $req = $ses->request('open-ils.trigger.events_by_target',
3744 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3746 while(my $resp = $req->recv) {
3747 my $val = $resp->content;
3748 my $tgt = $val->target;
3750 my $user = $e->retrieve_actor_user($tgt->usr);
3751 if($e->requestor->id != $user->id) {
3752 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3755 $tgt->$copy_field($copy);
3758 $conn->respond($val) if $val;
3765 __PACKAGE__->register_method (
3766 method => 'get_itemsout_notices',
3767 api_name => 'open-ils.actor.user.itemsout.notices',
3771 desc => q/Summary counts of circulat notices/,
3773 {desc => 'authtoken', type => 'string'},
3774 {desc => 'circulation identifiers', type => 'array of numbers'}
3776 return => q/Stream of summary objects/
3780 sub get_itemsout_notices {
3781 my ($self, $client, $auth, $circ_ids) = @_;
3783 my $e = new_editor(authtoken => $auth);
3784 return $e->event unless $e->checkauth;
3786 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3788 for my $circ_id (@$circ_ids) {
3789 my $resp = get_itemsout_notices_impl($e, $circ_id);
3791 if ($U->is_event($resp)) {
3792 $client->respond($resp);
3796 $client->respond({circ_id => $circ_id, %$resp});
3804 sub get_itemsout_notices_impl {
3805 my ($e, $circId) = @_;
3807 my $requestorId = $e->requestor->id;
3809 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3811 my $patronId = $circ->usr;
3813 if( $patronId ne $requestorId ){
3814 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3815 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3818 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3819 #my $req = $ses->request('open-ils.trigger.events_by_target',
3820 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3821 # ^ Above removed in favor of faster json_query.
3824 # select complete_time
3825 # from action_trigger.event atev
3826 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3827 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3828 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3831 my $ctx_loc = $e->requestor->ws_ou;
3832 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3833 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3836 select => { atev => ["complete_time"] },
3839 atevdef => { field => "id",fkey => "event_def"}
3843 "+atevdef" => { active => 't', hook => 'checkout.due' },
3844 "+atev" => { target => $circId, state => 'complete' }
3848 if ($exclude_courtesy_notices){
3849 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3852 my %resblob = ( numNotices => 0, lastDt => undef );
3854 my $res = $e->json_query($query);
3855 for my $ndate (@$res) {
3856 $resblob{numNotices}++;
3857 if( !defined $resblob{lastDt}){
3858 $resblob{lastDt} = $$ndate{complete_time};
3861 if ($resblob{lastDt} lt $$ndate{complete_time}){
3862 $resblob{lastDt} = $$ndate{complete_time};
3869 __PACKAGE__->register_method (
3870 method => 'update_events',
3871 api_name => 'open-ils.actor.user.event.cancel.batch',
3874 __PACKAGE__->register_method (
3875 method => 'update_events',
3876 api_name => 'open-ils.actor.user.event.reset.batch',
3881 my($self, $conn, $auth, $event_ids) = @_;
3882 my $e = new_editor(xact => 1, authtoken => $auth);
3883 return $e->die_event unless $e->checkauth;
3886 for my $id (@$event_ids) {
3888 # do a little dance to determine what user we are ultimately affecting
3889 my $event = $e->retrieve_action_trigger_event([
3892 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3894 ]) or return $e->die_event;
3897 if($event->event_def->hook->core_type eq 'circ') {
3898 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3899 } elsif($event->event_def->hook->core_type eq 'ahr') {
3900 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3905 my $user = $e->retrieve_actor_user($user_id);
3906 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3908 if($self->api_name =~ /cancel/) {
3909 $event->state('invalid');
3910 } elsif($self->api_name =~ /reset/) {
3911 $event->clear_start_time;
3912 $event->clear_update_time;
3913 $event->state('pending');
3916 $e->update_action_trigger_event($event) or return $e->die_event;
3917 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3921 return {complete => 1};
3925 __PACKAGE__->register_method (
3926 method => 'really_delete_user',
3927 api_name => 'open-ils.actor.user.delete.override',
3928 signature => q/@see open-ils.actor.user.delete/
3931 __PACKAGE__->register_method (
3932 method => 'really_delete_user',
3933 api_name => 'open-ils.actor.user.delete',
3935 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3936 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3937 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3938 dest_usr_id is only required when deleting a user that performs staff functions.
3942 sub really_delete_user {
3943 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3944 my $e = new_editor(authtoken => $auth, xact => 1);
3945 return $e->die_event unless $e->checkauth;
3946 $oargs = { all => 1 } unless defined $oargs;
3948 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3949 my $open_bills = $e->json_query({
3950 select => { mbts => ['id'] },
3953 xact_finish => { '=' => undef },
3954 usr => { '=' => $user_id },
3956 }) or return $e->die_event;
3958 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3960 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3962 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3963 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3964 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3966 # No deleting yourself - UI is supposed to stop you first, though.
3967 return $e->die_event unless $e->requestor->id != $user->id;
3968 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3969 # Check if you are allowed to mess with this patron permission group at all
3970 my $evt = group_perm_failed($e, $e->requestor, $user);
3971 return $e->die_event($evt) if $evt;
3972 my $stat = $e->json_query(
3973 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3974 or return $e->die_event;
3980 __PACKAGE__->register_method (
3981 method => 'user_payments',
3982 api_name => 'open-ils.actor.user.payments.retrieve',
3985 Returns all payments for a given user. Default order is newest payments first.
3986 @param auth Authentication token
3987 @param user_id The user ID
3988 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3993 my($self, $conn, $auth, $user_id, $filters) = @_;
3996 my $e = new_editor(authtoken => $auth);
3997 return $e->die_event unless $e->checkauth;
3999 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4000 return $e->event unless
4001 $e->requestor->id == $user_id or
4002 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4004 # Find all payments for all transactions for user $user_id
4006 select => {mp => ['id']},
4011 select => {mbt => ['id']},
4013 where => {usr => $user_id}
4018 { # by default, order newest payments first
4020 field => 'payment_ts',
4023 # secondary sort in ID as a tie-breaker, since payments created
4024 # within the same transaction will have identical payment_ts's
4031 for (qw/order_by limit offset/) {
4032 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4035 if(defined $filters->{where}) {
4036 foreach (keys %{$filters->{where}}) {
4037 # don't allow the caller to expand the result set to other users
4038 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4042 my $payment_ids = $e->json_query($query);
4043 for my $pid (@$payment_ids) {
4044 my $pay = $e->retrieve_money_payment([
4049 mbt => ['summary', 'circulation', 'grocery'],
4050 circ => ['target_copy'],
4051 acp => ['call_number'],
4059 xact_type => $pay->xact->summary->xact_type,
4060 last_billing_type => $pay->xact->summary->last_billing_type,
4063 if($pay->xact->summary->xact_type eq 'circulation') {
4064 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4065 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4068 $pay->xact($pay->xact->id); # de-flesh
4069 $conn->respond($resp);
4077 __PACKAGE__->register_method (
4078 method => 'negative_balance_users',
4079 api_name => 'open-ils.actor.users.negative_balance',
4082 Returns all users that have an overall negative balance
4083 @param auth Authentication token
4084 @param org_id The context org unit as an ID or list of IDs. This will be the home
4085 library of the user. If no org_unit is specified, no org unit filter is applied
4089 sub negative_balance_users {
4090 my($self, $conn, $auth, $org_id) = @_;
4092 my $e = new_editor(authtoken => $auth);
4093 return $e->die_event unless $e->checkauth;
4094 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4098 mous => ['usr', 'balance_owed'],
4101 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4102 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4119 where => {'+mous' => {balance_owed => {'<' => 0}}}
4122 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4124 my $list = $e->json_query($query, {timeout => 600});
4126 for my $data (@$list) {
4128 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4129 balance_owed => $data->{balance_owed},
4130 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4137 __PACKAGE__->register_method(
4138 method => "request_password_reset",
4139 api_name => "open-ils.actor.patron.password_reset.request",
4141 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4142 "method for changing a user's password. The UUID token is distributed via A/T " .
4143 "templates (i.e. email to the user).",
4145 { desc => 'user_id_type', type => 'string' },
4146 { desc => 'user_id', type => 'string' },
4147 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4149 return => {desc => '1 on success, Event on error'}
4152 sub request_password_reset {
4153 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4155 # Check to see if password reset requests are already being throttled:
4156 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4158 my $e = new_editor(xact => 1);
4161 # Get the user, if any, depending on the input value
4162 if ($user_id_type eq 'username') {
4163 $user = $e->search_actor_user({usrname => $user_id})->[0];
4166 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4168 } elsif ($user_id_type eq 'barcode') {
4169 my $card = $e->search_actor_card([
4170 {barcode => $user_id},
4171 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4174 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4179 # If the user doesn't have an email address, we can't help them
4180 if (!$user->email) {
4182 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4185 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4186 if ($email_must_match) {
4187 if (lc($user->email) ne lc($email)) {
4188 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4192 _reset_password_request($conn, $e, $user);
4195 # Once we have the user, we can issue the password reset request
4196 # XXX Add a wrapper method that accepts barcode + email input
4197 sub _reset_password_request {
4198 my ($conn, $e, $user) = @_;
4200 # 1. Get throttle threshold and time-to-live from OU_settings
4201 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4202 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4204 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4206 # 2. Get time of last request and number of active requests (num_active)
4207 my $active_requests = $e->json_query({
4213 transform => 'COUNT'
4216 column => 'request_time',
4222 has_been_reset => { '=' => 'f' },
4223 request_time => { '>' => $threshold_time }
4227 # Guard against no active requests
4228 if ($active_requests->[0]->{'request_time'}) {
4229 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4230 my $now = DateTime::Format::ISO8601->new();
4232 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4233 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4234 ($last_request->add_duration('1 minute') > $now)) {
4235 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4237 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4241 # TODO Check to see if the user is in a password-reset-restricted group
4243 # Otherwise, go ahead and try to get the user.
4245 # Check the number of active requests for this user
4246 $active_requests = $e->json_query({
4252 transform => 'COUNT'
4257 usr => { '=' => $user->id },
4258 has_been_reset => { '=' => 'f' },
4259 request_time => { '>' => $threshold_time }
4263 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4265 # if less than or equal to per-user threshold, proceed; otherwise, return event
4266 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4267 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4269 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4272 # Create the aupr object and insert into the database
4273 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4274 my $uuid = create_uuid_as_string(UUID_V4);
4275 $reset_request->uuid($uuid);
4276 $reset_request->usr($user->id);
4278 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4281 # Create an event to notify user of the URL to reset their password
4283 # Can we stuff this in the user_data param for trigger autocreate?
4284 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4286 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4287 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4290 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4295 __PACKAGE__->register_method(
4296 method => "commit_password_reset",
4297 api_name => "open-ils.actor.patron.password_reset.commit",
4299 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4300 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4301 "with the supplied password.",
4303 { desc => 'uuid', type => 'string' },
4304 { desc => 'password', type => 'string' },
4306 return => {desc => '1 on success, Event on error'}
4309 sub commit_password_reset {
4310 my($self, $conn, $uuid, $password) = @_;
4312 # Check to see if password reset requests are already being throttled:
4313 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4314 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4315 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4317 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4320 my $e = new_editor(xact => 1);
4322 my $aupr = $e->search_actor_usr_password_reset({
4329 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4331 my $user_id = $aupr->[0]->usr;
4332 my $user = $e->retrieve_actor_user($user_id);
4334 # Ensure we're still within the TTL for the request
4335 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4336 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4337 if ($threshold < DateTime->now(time_zone => 'local')) {
4339 $logger->info("Password reset request needed to be submitted before $threshold");
4340 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4343 # Check complexity of password against OU-defined regex
4344 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4348 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4349 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4350 $is_strong = check_password_strength_custom($password, $pw_regex);
4352 $is_strong = check_password_strength_default($password);
4357 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4360 # All is well; update the password
4361 modify_migrated_user_password($e, $user->id, $password);
4363 # And flag that this password reset request has been honoured
4364 $aupr->[0]->has_been_reset('t');
4365 $e->update_actor_usr_password_reset($aupr->[0]);
4371 sub check_password_strength_default {
4372 my $password = shift;
4373 # Use the default set of checks
4374 if ( (length($password) < 7) or
4375 ($password !~ m/.*\d+.*/) or
4376 ($password !~ m/.*[A-Za-z]+.*/)
4383 sub check_password_strength_custom {
4384 my ($password, $pw_regex) = @_;
4386 $pw_regex = qr/$pw_regex/;
4387 if ($password !~ /$pw_regex/) {
4393 __PACKAGE__->register_method(
4394 method => "fire_test_notification",
4395 api_name => "open-ils.actor.event.test_notification"
4398 sub fire_test_notification {
4399 my($self, $conn, $auth, $args) = @_;
4400 my $e = new_editor(authtoken => $auth);
4401 return $e->event unless $e->checkauth;
4402 if ($e->requestor->id != $$args{target}) {
4403 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4404 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4407 my $event_hook = $$args{hook} or return $e->event;
4408 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4410 my $usr = $e->retrieve_actor_user($$args{target});
4411 return $e->event unless $usr;
4413 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4417 __PACKAGE__->register_method(
4418 method => "event_def_opt_in_settings",
4419 api_name => "open-ils.actor.event_def.opt_in.settings",
4422 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4424 { desc => 'Authentication token', type => 'string'},
4426 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4431 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4438 sub event_def_opt_in_settings {
4439 my($self, $conn, $auth, $org_id) = @_;
4440 my $e = new_editor(authtoken => $auth);
4441 return $e->event unless $e->checkauth;
4443 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4444 return $e->event unless
4445 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4447 $org_id = $e->requestor->home_ou;
4450 # find all config.user_setting_type's related to event_defs for the requested org unit
4451 my $types = $e->json_query({
4452 select => {cust => ['name']},
4453 from => {atevdef => 'cust'},
4456 owner => $U->get_org_ancestors($org_id), # context org plus parents
4463 $conn->respond($_) for
4464 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4471 __PACKAGE__->register_method(
4472 method => "user_circ_history",
4473 api_name => "open-ils.actor.history.circ",
4477 desc => 'Returns user circ history objects for the calling user',
4479 { desc => 'Authentication token', type => 'string'},
4480 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4483 desc => q/Stream of 'auch' circ history objects/,
4489 __PACKAGE__->register_method(
4490 method => "user_circ_history",
4491 api_name => "open-ils.actor.history.circ.clear",
4494 desc => 'Delete all user circ history entries for the calling user',
4496 { desc => 'Authentication token', type => 'string'},
4497 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4500 desc => q/1 on success, event on error/,
4506 __PACKAGE__->register_method(
4507 method => "user_circ_history",
4508 api_name => "open-ils.actor.history.circ.print",
4511 desc => q/Returns printable output for the caller's circ history objects/,
4513 { desc => 'Authentication token', type => 'string'},
4514 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4517 desc => q/An action_trigger.event object or error event./,
4523 __PACKAGE__->register_method(
4524 method => "user_circ_history",
4525 api_name => "open-ils.actor.history.circ.email",
4528 desc => q/Emails the caller's circ history/,
4530 { desc => 'Authentication token', type => 'string'},
4531 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4532 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4535 desc => q/undef, or event on error/
4540 sub user_circ_history {
4541 my ($self, $conn, $auth, $options) = @_;
4544 my $for_print = ($self->api_name =~ /print/);
4545 my $for_email = ($self->api_name =~ /email/);
4546 my $for_clear = ($self->api_name =~ /clear/);
4548 # No perm check is performed. Caller may only access his/her own
4549 # circ history entries.
4550 my $e = new_editor(authtoken => $auth);
4551 return $e->event unless $e->checkauth;
4554 if (!$for_clear) { # clear deletes all
4555 $limits{offset} = $options->{offset} if defined $options->{offset};
4556 $limits{limit} = $options->{limit} if defined $options->{limit};
4559 my %circ_id_filter = $options->{circ_ids} ?
4560 (id => $options->{circ_ids}) : ();
4562 my $circs = $e->search_action_user_circ_history([
4563 { usr => $e->requestor->id,
4566 { # order newest to oldest by default
4567 order_by => {auch => 'xact_start DESC'},
4570 {substream => 1} # could be a large list
4574 return $U->fire_object_event(undef,
4575 'circ.format.history.print', $circs, $e->requestor->home_ou);
4578 $e->xact_begin if $for_clear;
4579 $conn->respond_complete(1) if $for_email; # no sense in waiting
4581 for my $circ (@$circs) {
4584 # events will be fired from action_trigger_runner
4585 $U->create_events_for_hook('circ.format.history.email',
4586 $circ, $e->editor->home_ou, undef, undef, 1);
4588 } elsif ($for_clear) {
4590 $e->delete_action_user_circ_history($circ)
4591 or return $e->die_event;
4594 $conn->respond($circ);
4607 __PACKAGE__->register_method(
4608 method => "user_visible_holds",
4609 api_name => "open-ils.actor.history.hold.visible",
4612 desc => 'Returns the set of opt-in visible holds',
4614 { desc => 'Authentication token', type => 'string'},
4615 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4616 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4619 desc => q/An object with 1 field: "hold"/,
4625 __PACKAGE__->register_method(
4626 method => "user_visible_holds",
4627 api_name => "open-ils.actor.history.hold.visible.print",
4630 desc => 'Returns printable output for the set of opt-in visible holds',
4632 { desc => 'Authentication token', type => 'string'},
4633 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4634 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4637 desc => q/An action_trigger.event object or error event./,
4643 __PACKAGE__->register_method(
4644 method => "user_visible_holds",
4645 api_name => "open-ils.actor.history.hold.visible.email",
4648 desc => 'Emails the set of opt-in visible holds to the requestor',
4650 { desc => 'Authentication token', type => 'string'},
4651 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4652 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4655 desc => q/undef, or event on error/
4660 sub user_visible_holds {
4661 my($self, $conn, $auth, $user_id, $options) = @_;
4664 my $for_print = ($self->api_name =~ /print/);
4665 my $for_email = ($self->api_name =~ /email/);
4666 my $e = new_editor(authtoken => $auth);
4667 return $e->event unless $e->checkauth;
4669 $user_id ||= $e->requestor->id;
4671 $options->{limit} ||= 50;
4672 $options->{offset} ||= 0;
4674 if($user_id != $e->requestor->id) {
4675 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4676 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4677 return $e->event unless $e->allowed($perm, $user->home_ou);
4680 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4682 my $data = $e->json_query({
4683 from => [$db_func, $user_id],
4684 limit => $$options{limit},
4685 offset => $$options{offset}
4687 # TODO: I only want IDs. code below didn't get me there
4688 # {"select":{"au":[{"column":"id", "result_field":"id",
4689 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4694 return undef unless @$data;
4698 # collect the batch of objects
4702 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4703 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4707 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4708 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4711 } elsif ($for_email) {
4713 $conn->respond_complete(1) if $for_email; # no sense in waiting
4721 my $hold = $e->retrieve_action_hold_request($id);
4722 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4723 # events will be fired from action_trigger_runner
4727 my $circ = $e->retrieve_action_circulation($id);
4728 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4729 # events will be fired from action_trigger_runner
4733 } else { # just give me the data please
4741 my $hold = $e->retrieve_action_hold_request($id);
4742 $conn->respond({hold => $hold});
4746 my $circ = $e->retrieve_action_circulation($id);
4749 summary => $U->create_circ_chain_summary($e, $id)
4758 __PACKAGE__->register_method(
4759 method => "user_saved_search_cud",
4760 api_name => "open-ils.actor.user.saved_search.cud",
4763 desc => 'Create/Update/Delete Access to user saved searches',
4765 { desc => 'Authentication token', type => 'string' },
4766 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4769 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4775 __PACKAGE__->register_method(
4776 method => "user_saved_search_cud",
4777 api_name => "open-ils.actor.user.saved_search.retrieve",
4780 desc => 'Retrieve a saved search object',
4782 { desc => 'Authentication token', type => 'string' },
4783 { desc => 'Saved Search ID', type => 'number' }
4786 desc => q/The saved search object, Event on error/,
4792 sub user_saved_search_cud {
4793 my( $self, $client, $auth, $search ) = @_;
4794 my $e = new_editor( authtoken=>$auth );
4795 return $e->die_event unless $e->checkauth;
4797 my $o_search; # prior version of the object, if any
4798 my $res; # to be returned
4800 # branch on the operation type
4802 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4804 # Get the old version, to check ownership
4805 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4806 or return $e->die_event;
4808 # You can't read somebody else's search
4809 return OpenILS::Event->new('BAD_PARAMS')
4810 unless $o_search->owner == $e->requestor->id;
4816 $e->xact_begin; # start an editor transaction
4818 if( $search->isnew ) { # Create
4820 # You can't create a search for somebody else
4821 return OpenILS::Event->new('BAD_PARAMS')
4822 unless $search->owner == $e->requestor->id;
4824 $e->create_actor_usr_saved_search( $search )
4825 or return $e->die_event;
4829 } elsif( $search->ischanged ) { # Update
4831 # You can't change ownership of a search
4832 return OpenILS::Event->new('BAD_PARAMS')
4833 unless $search->owner == $e->requestor->id;
4835 # Get the old version, to check ownership
4836 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4837 or return $e->die_event;
4839 # You can't update somebody else's search
4840 return OpenILS::Event->new('BAD_PARAMS')
4841 unless $o_search->owner == $e->requestor->id;
4844 $e->update_actor_usr_saved_search( $search )
4845 or return $e->die_event;
4849 } elsif( $search->isdeleted ) { # Delete
4851 # Get the old version, to check ownership
4852 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4853 or return $e->die_event;
4855 # You can't delete somebody else's search
4856 return OpenILS::Event->new('BAD_PARAMS')
4857 unless $o_search->owner == $e->requestor->id;
4860 $e->delete_actor_usr_saved_search( $o_search )
4861 or return $e->die_event;
4872 __PACKAGE__->register_method(
4873 method => "get_barcodes",
4874 api_name => "open-ils.actor.get_barcodes"
4878 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4879 my $e = new_editor(authtoken => $auth);
4880 return $e->event unless $e->checkauth;
4881 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4883 my $db_result = $e->json_query(
4885 'evergreen.get_barcodes',
4886 $org_id, $context, $barcode,
4890 if($context =~ /actor/) {
4891 my $filter_result = ();
4893 foreach my $result (@$db_result) {
4894 if($result->{type} eq 'actor') {
4895 if($e->requestor->id != $result->{id}) {
4896 $patron = $e->retrieve_actor_user($result->{id});
4898 push(@$filter_result, $e->event);
4901 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4902 push(@$filter_result, $result);
4905 push(@$filter_result, $e->event);
4909 push(@$filter_result, $result);
4913 push(@$filter_result, $result);
4916 return $filter_result;
4922 __PACKAGE__->register_method(
4923 method => 'address_alert_test',
4924 api_name => 'open-ils.actor.address_alert.test',
4926 desc => "Tests a set of address fields to determine if they match with an address_alert",
4928 {desc => 'Authentication token', type => 'string'},
4929 {desc => 'Org Unit', type => 'number'},
4930 {desc => 'Fields', type => 'hash'},
4932 return => {desc => 'List of matching address_alerts'}
4936 sub address_alert_test {
4937 my ($self, $client, $auth, $org_unit, $fields) = @_;
4938 return [] unless $fields and grep {$_} values %$fields;
4940 my $e = new_editor(authtoken => $auth);
4941 return $e->event unless $e->checkauth;
4942 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4943 $org_unit ||= $e->requestor->ws_ou;
4945 my $alerts = $e->json_query({
4947 'actor.address_alert_matches',
4955 $$fields{post_code},
4956 $$fields{mailing_address},
4957 $$fields{billing_address}
4961 # map the json_query hashes to real objects
4963 map {$e->retrieve_actor_address_alert($_)}
4964 (map {$_->{id}} @$alerts)
4968 __PACKAGE__->register_method(
4969 method => "mark_users_contact_invalid",
4970 api_name => "open-ils.actor.invalidate.email",
4972 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",
4974 {desc => "Authentication token", type => "string"},
4975 {desc => "Patron ID (optional if Email address specified)", type => "number"},
4976 {desc => "Additional note text (optional)", type => "string"},
4977 {desc => "penalty org unit ID (optional)", type => "number"},
4978 {desc => "Email address (optional)", type => "string"}
4980 return => {desc => "Event describing success or failure", type => "object"}
4984 __PACKAGE__->register_method(
4985 method => "mark_users_contact_invalid",
4986 api_name => "open-ils.actor.invalidate.day_phone",
4988 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",
4990 {desc => "Authentication token", type => "string"},
4991 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4992 {desc => "Additional note text (optional)", type => "string"},
4993 {desc => "penalty org unit ID (optional)", type => "number"},
4994 {desc => "Phone Number (optional)", type => "string"}
4996 return => {desc => "Event describing success or failure", type => "object"}
5000 __PACKAGE__->register_method(
5001 method => "mark_users_contact_invalid",
5002 api_name => "open-ils.actor.invalidate.evening_phone",
5004 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",
5006 {desc => "Authentication token", type => "string"},
5007 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5008 {desc => "Additional note text (optional)", type => "string"},
5009 {desc => "penalty org unit ID (optional)", type => "number"},
5010 {desc => "Phone Number (optional)", type => "string"}
5012 return => {desc => "Event describing success or failure", type => "object"}
5016 __PACKAGE__->register_method(
5017 method => "mark_users_contact_invalid",
5018 api_name => "open-ils.actor.invalidate.other_phone",
5020 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",
5022 {desc => "Authentication token", type => "string"},
5023 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5024 {desc => "Additional note text (optional)", type => "string"},
5025 {desc => "penalty org unit ID (optional, default to top of org tree)",
5027 {desc => "Phone Number (optional)", type => "string"}
5029 return => {desc => "Event describing success or failure", type => "object"}
5033 sub mark_users_contact_invalid {
5034 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5036 # This method invalidates an email address or a phone_number which
5037 # removes the bad email address or phone number, copying its contents
5038 # to a patron note, and institutes a standing penalty for "bad email"
5039 # or "bad phone number" which is cleared when the user is saved or
5040 # optionally only when the user is saved with an email address or
5041 # phone number (or staff manually delete the penalty).
5043 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5045 my $e = new_editor(authtoken => $auth, xact => 1);
5046 return $e->die_event unless $e->checkauth;
5049 if (defined $patron_id && $patron_id ne "") {
5050 $howfind = {usr => $patron_id};
5051 } elsif (defined $contact && $contact ne "") {
5052 $howfind = {$contact_type => $contact};
5054 # Error out if no patron id set or no contact is set.
5055 return OpenILS::Event->new('BAD_PARAMS');
5058 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5059 $e, $contact_type, $howfind,
5060 $addl_note, $penalty_ou, $e->requestor->id
5064 # Putting the following method in open-ils.actor is a bad fit, except in that
5065 # it serves an interface that lives under 'actor' in the templates directory,
5066 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5068 __PACKAGE__->register_method(
5069 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5070 method => "get_all_at_reactors_in_use",
5075 { name => 'authtoken', type => 'string' }
5078 desc => 'list of reactor names', type => 'array'
5083 sub get_all_at_reactors_in_use {
5084 my ($self, $conn, $auth) = @_;
5086 my $e = new_editor(authtoken => $auth);
5087 $e->checkauth or return $e->die_event;
5088 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5090 my $reactors = $e->json_query({
5092 atevdef => [{column => "reactor", transform => "distinct"}]
5094 from => {atevdef => {}}
5097 return $e->die_event unless ref $reactors eq "ARRAY";
5100 return [ map { $_->{reactor} } @$reactors ];
5103 __PACKAGE__->register_method(
5104 method => "filter_group_entry_crud",
5105 api_name => "open-ils.actor.filter_group_entry.crud",
5108 Provides CRUD access to filter group entry objects. These are not full accessible
5109 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5110 are not accessible via PCRUD (because they have no fields against which to link perms)
5113 {desc => "Authentication token", type => "string"},
5114 {desc => "Entry ID / Entry Object", type => "number"},
5115 {desc => "Additional note text (optional)", type => "string"},
5116 {desc => "penalty org unit ID (optional, default to top of org tree)",
5120 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5126 sub filter_group_entry_crud {
5127 my ($self, $conn, $auth, $arg) = @_;
5129 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5130 my $e = new_editor(authtoken => $auth, xact => 1);
5131 return $e->die_event unless $e->checkauth;
5137 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5138 or return $e->die_event;
5140 return $e->die_event unless $e->allowed(
5141 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5143 my $query = $arg->query;
5144 $query = $e->create_actor_search_query($query) or return $e->die_event;
5145 $arg->query($query->id);
5146 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5147 $entry->query($query);
5152 } elsif ($arg->ischanged) {
5154 my $entry = $e->retrieve_actor_search_filter_group_entry([
5157 flesh_fields => {asfge => ['grp']}
5159 ]) or return $e->die_event;
5161 return $e->die_event unless $e->allowed(
5162 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5164 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5165 $arg->query($arg->query->id);
5166 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5167 $arg->query($query);
5172 } elsif ($arg->isdeleted) {
5174 my $entry = $e->retrieve_actor_search_filter_group_entry([
5177 flesh_fields => {asfge => ['grp', 'query']}
5179 ]) or return $e->die_event;
5181 return $e->die_event unless $e->allowed(
5182 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5184 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5185 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5198 my $entry = $e->retrieve_actor_search_filter_group_entry([
5201 flesh_fields => {asfge => ['grp', 'query']}
5203 ]) or return $e->die_event;
5205 return $e->die_event unless $e->allowed(
5206 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5207 $entry->grp->owner);
5210 $entry->grp($entry->grp->id); # for consistency