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",
1432 __PACKAGE__->register_method(
1433 method => "retrieve_coordinates",
1434 api_name => "open-ils.actor.geo.retrieve_coordinates",
1437 {desc => 'Authentication token', type => 'string' },
1438 {type => 'number', desc => 'Context Organizational Unit'},
1439 {type => 'string', desc => 'Address to look-up as a text string'}
1441 return => { desc => 'Hash/object containing latitude and longitude for the provided address.'}
1445 sub retrieve_coordinates {
1446 my( $self, $client, $auth, $org_id, $addr_string ) = @_;
1447 my $e = new_editor(authtoken=>$auth);
1448 return $e->event unless $e->checkauth;
1449 $org_id = $e->requestor->ws_ou unless defined $org_id;
1451 return $apputils->simple_scalar_request(
1453 "open-ils.geo.retrieve_coordinates",
1454 $org_id, $addr_string );
1457 __PACKAGE__->register_method(
1458 method => "patron_adv_search",
1459 api_name => "open-ils.actor.patron.search.advanced"
1462 __PACKAGE__->register_method(
1463 method => "patron_adv_search",
1464 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1466 # Flush the response stream at most 5 patrons in for UI responsiveness.
1467 max_bundle_count => 5,
1469 desc => q/Returns a stream of fleshed user objects instead of
1470 a pile of identifiers/
1474 sub patron_adv_search {
1475 my( $self, $client, $auth, $search_hash, $search_limit,
1476 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1478 # API params sanity checks.
1479 # Exit early with empty result if no filter exists.
1480 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1481 my $fleshed = ($self->api_name =~ /fleshed/);
1482 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1484 for my $key (keys %$search_hash) {
1485 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1489 return ($fleshed ? undef : []) unless $search_ok;
1491 my $e = new_editor(authtoken=>$auth);
1492 return $e->event unless $e->checkauth;
1493 return $e->event unless $e->allowed('VIEW_USER');
1495 # depth boundary outside of which patrons must opt-in, default to 0
1496 my $opt_boundary = 0;
1497 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1499 if (not defined $search_ou) {
1500 my $depth = $U->ou_ancestor_setting_value(
1501 $e->requestor->ws_ou,
1502 'circ.patron_edit.duplicate_patron_check_depth'
1505 if (defined $depth) {
1506 $search_ou = $U->org_unit_ancestor_at_depth(
1507 $e->requestor->ws_ou, $depth
1512 my $ids = $U->storagereq(
1513 "open-ils.storage.actor.user.crazy_search", $search_hash,
1514 $search_limit, $search_sort, $include_inactive,
1515 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1517 return $ids unless $self->api_name =~ /fleshed/;
1519 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1525 # A migrated (main) password has the form:
1526 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1527 sub modify_migrated_user_password {
1528 my ($e, $user_id, $passwd) = @_;
1530 # new password gets a new salt
1531 my $new_salt = $e->json_query({
1532 from => ['actor.create_salt', 'main']})->[0];
1533 $new_salt = $new_salt->{'actor.create_salt'};
1540 md5_hex($new_salt . md5_hex($passwd)),
1548 __PACKAGE__->register_method(
1549 method => "update_passwd",
1550 api_name => "open-ils.actor.user.password.update",
1552 desc => "Update the operator's password",
1554 { desc => 'Authentication token', type => 'string' },
1555 { desc => 'New password', type => 'string' },
1556 { desc => 'Current password', type => 'string' }
1558 return => {desc => '1 on success, Event on error or incorrect current password'}
1562 __PACKAGE__->register_method(
1563 method => "update_passwd",
1564 api_name => "open-ils.actor.user.username.update",
1566 desc => "Update the operator's username",
1568 { desc => 'Authentication token', type => 'string' },
1569 { desc => 'New username', type => 'string' },
1570 { desc => 'Current password', type => 'string' }
1572 return => {desc => '1 on success, Event on error or incorrect current password'}
1576 __PACKAGE__->register_method(
1577 method => "update_passwd",
1578 api_name => "open-ils.actor.user.email.update",
1580 desc => "Update the operator's email address",
1582 { desc => 'Authentication token', type => 'string' },
1583 { desc => 'New email address', type => 'string' },
1584 { desc => 'Current password', type => 'string' }
1586 return => {desc => '1 on success, Event on error or incorrect current password'}
1591 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1592 my $e = new_editor(xact=>1, authtoken=>$auth);
1593 return $e->die_event unless $e->checkauth;
1595 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1596 or return $e->die_event;
1597 my $api = $self->api_name;
1599 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1601 return new OpenILS::Event('INCORRECT_PASSWORD');
1605 if( $api =~ /password/o ) {
1606 # NOTE: with access to the plain text password we could crypt
1607 # the password without the extra MD5 pre-hashing. Other changes
1608 # would be required. Noting here for future reference.
1609 modify_migrated_user_password($e, $db_user->id, $new_val);
1610 $db_user->passwd('');
1614 # if we don't clear the password, the user will be updated with
1615 # a hashed version of the hashed version of their password
1616 $db_user->clear_passwd;
1618 if( $api =~ /username/o ) {
1620 # make sure no one else has this username
1621 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1624 return new OpenILS::Event('USERNAME_EXISTS');
1626 $db_user->usrname($new_val);
1629 } elsif( $api =~ /email/o ) {
1630 $db_user->email($new_val);
1635 $e->update_actor_user($db_user) or return $e->die_event;
1638 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1641 # update the cached user to pick up these changes
1642 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1648 __PACKAGE__->register_method(
1649 method => "check_user_perms",
1650 api_name => "open-ils.actor.user.perm.check",
1651 notes => <<" NOTES");
1652 Takes a login session, user id, an org id, and an array of perm type strings. For each
1653 perm type, if the user does *not* have the given permission it is added
1654 to a list which is returned from the method. If all permissions
1655 are allowed, an empty list is returned
1656 if the logged in user does not match 'user_id', then the logged in user must
1657 have VIEW_PERMISSION priveleges.
1660 sub check_user_perms {
1661 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1663 my( $staff, $evt ) = $apputils->checkses($login_session);
1664 return $evt if $evt;
1666 if($staff->id ne $user_id) {
1667 if( $evt = $apputils->check_perms(
1668 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1674 for my $perm (@$perm_types) {
1675 if($apputils->check_perms($user_id, $org_id, $perm)) {
1676 push @not_allowed, $perm;
1680 return \@not_allowed
1683 __PACKAGE__->register_method(
1684 method => "check_user_perms2",
1685 api_name => "open-ils.actor.user.perm.check.multi_org",
1687 Checks the permissions on a list of perms and orgs for a user
1688 @param authtoken The login session key
1689 @param user_id The id of the user to check
1690 @param orgs The array of org ids
1691 @param perms The array of permission names
1692 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1693 if the logged in user does not match 'user_id', then the logged in user must
1694 have VIEW_PERMISSION priveleges.
1697 sub check_user_perms2 {
1698 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1700 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1701 $authtoken, $user_id, 'VIEW_PERMISSION' );
1702 return $evt if $evt;
1705 for my $org (@$orgs) {
1706 for my $perm (@$perms) {
1707 if($apputils->check_perms($user_id, $org, $perm)) {
1708 push @not_allowed, [ $org, $perm ];
1713 return \@not_allowed
1717 __PACKAGE__->register_method(
1718 method => 'check_user_perms3',
1719 api_name => 'open-ils.actor.user.perm.highest_org',
1721 Returns the highest org unit id at which a user has a given permission
1722 If the requestor does not match the target user, the requestor must have
1723 'VIEW_PERMISSION' rights at the home org unit of the target user
1724 @param authtoken The login session key
1725 @param userid The id of the user in question
1726 @param perm The permission to check
1727 @return The org unit highest in the org tree within which the user has
1728 the requested permission
1731 sub check_user_perms3 {
1732 my($self, $client, $authtoken, $user_id, $perm) = @_;
1733 my $e = new_editor(authtoken=>$authtoken);
1734 return $e->event unless $e->checkauth;
1736 my $tree = $U->get_org_tree();
1738 unless($e->requestor->id == $user_id) {
1739 my $user = $e->retrieve_actor_user($user_id)
1740 or return $e->event;
1741 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1742 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1745 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1748 __PACKAGE__->register_method(
1749 method => 'user_has_work_perm_at',
1750 api_name => 'open-ils.actor.user.has_work_perm_at',
1754 Returns a set of org unit IDs which represent the highest orgs in
1755 the org tree where the user has the requested permission. The
1756 purpose of this method is to return the smallest set of org units
1757 which represent the full expanse of the user's ability to perform
1758 the requested action. The user whose perms this method should
1759 check is implied by the authtoken. /,
1761 {desc => 'authtoken', type => 'string'},
1762 {desc => 'permission name', type => 'string'},
1763 {desc => q/user id, optional. If present, check perms for
1764 this user instead of the logged in user/, type => 'number'},
1766 return => {desc => 'An array of org IDs'}
1770 sub user_has_work_perm_at {
1771 my($self, $conn, $auth, $perm, $user_id) = @_;
1772 my $e = new_editor(authtoken=>$auth);
1773 return $e->event unless $e->checkauth;
1774 if(defined $user_id) {
1775 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1776 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1778 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1781 __PACKAGE__->register_method(
1782 method => 'user_has_work_perm_at_batch',
1783 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1787 sub user_has_work_perm_at_batch {
1788 my($self, $conn, $auth, $perms, $user_id) = @_;
1789 my $e = new_editor(authtoken=>$auth);
1790 return $e->event unless $e->checkauth;
1791 if(defined $user_id) {
1792 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1793 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1796 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1802 __PACKAGE__->register_method(
1803 method => 'check_user_perms4',
1804 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1806 Returns the highest org unit id at which a user has a given permission
1807 If the requestor does not match the target user, the requestor must have
1808 'VIEW_PERMISSION' rights at the home org unit of the target user
1809 @param authtoken The login session key
1810 @param userid The id of the user in question
1811 @param perms An array of perm names to check
1812 @return An array of orgId's representing the org unit
1813 highest in the org tree within which the user has the requested permission
1814 The arrah of orgId's has matches the order of the perms array
1817 sub check_user_perms4 {
1818 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1820 my( $staff, $target, $org, $evt );
1822 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1823 $authtoken, $userid, 'VIEW_PERMISSION' );
1824 return $evt if $evt;
1827 return [] unless ref($perms);
1828 my $tree = $U->get_org_tree();
1830 for my $p (@$perms) {
1831 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1837 __PACKAGE__->register_method(
1838 method => "user_fines_summary",
1839 api_name => "open-ils.actor.user.fines.summary",
1842 desc => 'Returns a short summary of the users total open fines, ' .
1843 'excluding voided fines Params are login_session, user_id' ,
1845 {desc => 'Authentication token', type => 'string'},
1846 {desc => 'User ID', type => 'string'} # number?
1849 desc => "a 'mous' object, event on error",
1854 sub user_fines_summary {
1855 my( $self, $client, $auth, $user_id ) = @_;
1857 my $e = new_editor(authtoken=>$auth);
1858 return $e->event unless $e->checkauth;
1860 if( $user_id ne $e->requestor->id ) {
1861 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1862 return $e->event unless
1863 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1866 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1870 __PACKAGE__->register_method(
1871 method => "user_opac_vitals",
1872 api_name => "open-ils.actor.user.opac.vital_stats",
1876 desc => 'Returns a short summary of the users vital stats, including ' .
1877 'identification information, accumulated balance, number of holds, ' .
1878 'and current open circulation stats' ,
1880 {desc => 'Authentication token', type => 'string'},
1881 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1884 desc => "An object with four properties: user, fines, checkouts and holds."
1889 sub user_opac_vitals {
1890 my( $self, $client, $auth, $user_id ) = @_;
1892 my $e = new_editor(authtoken=>$auth);
1893 return $e->event unless $e->checkauth;
1895 $user_id ||= $e->requestor->id;
1897 my $user = $e->retrieve_actor_user( $user_id );
1900 ->method_lookup('open-ils.actor.user.fines.summary')
1901 ->run($auth => $user_id);
1902 return $fines if (defined($U->event_code($fines)));
1905 $fines = new Fieldmapper::money::open_user_summary ();
1906 $fines->balance_owed(0.00);
1907 $fines->total_owed(0.00);
1908 $fines->total_paid(0.00);
1909 $fines->usr($user_id);
1913 ->method_lookup('open-ils.actor.user.hold_requests.count')
1914 ->run($auth => $user_id);
1915 return $holds if (defined($U->event_code($holds)));
1918 ->method_lookup('open-ils.actor.user.checked_out.count')
1919 ->run($auth => $user_id);
1920 return $out if (defined($U->event_code($out)));
1922 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1924 my $unread_msgs = $e->search_actor_usr_message([
1925 {usr => $user_id, read_date => undef, deleted => 'f'},
1931 first_given_name => $user->first_given_name,
1932 second_given_name => $user->second_given_name,
1933 family_name => $user->family_name,
1934 alias => $user->alias,
1935 usrname => $user->usrname
1937 fines => $fines->to_bare_hash,
1940 messages => { unread => scalar(@$unread_msgs) }
1945 ##### a small consolidation of related method registrations
1946 my $common_params = [
1947 { desc => 'Authentication token', type => 'string' },
1948 { desc => 'User ID', type => 'string' },
1949 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1950 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1953 'open-ils.actor.user.transactions' => '',
1954 'open-ils.actor.user.transactions.fleshed' => '',
1955 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1956 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1957 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1958 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1961 foreach (keys %methods) {
1963 method => "user_transactions",
1966 desc => 'For a given user, retrieve a list of '
1967 . (/\.fleshed/ ? 'fleshed ' : '')
1968 . 'transactions' . $methods{$_}
1969 . ' optionally limited to transactions of a given type.',
1970 params => $common_params,
1972 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1973 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1977 $args{authoritative} = 1;
1978 __PACKAGE__->register_method(%args);
1981 # Now for the counts
1983 'open-ils.actor.user.transactions.count' => '',
1984 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1985 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1988 foreach (keys %methods) {
1990 method => "user_transactions",
1993 desc => 'For a given user, retrieve a count of open '
1994 . 'transactions' . $methods{$_}
1995 . ' optionally limited to transactions of a given type.',
1996 params => $common_params,
1997 return => { desc => "Integer count of transactions, or event on error" }
2000 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2001 __PACKAGE__->register_method(%args);
2004 __PACKAGE__->register_method(
2005 method => "user_transactions",
2006 api_name => "open-ils.actor.user.transactions.have_balance.total",
2009 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2010 . ' optionally limited to transactions of a given type.',
2011 params => $common_params,
2012 return => { desc => "Decimal balance value, or event on error" }
2017 sub user_transactions {
2018 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2021 my $e = new_editor(authtoken => $auth);
2022 return $e->event unless $e->checkauth;
2024 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2026 return $e->event unless
2027 $e->requestor->id == $user_id or
2028 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2030 my $api = $self->api_name();
2032 my $filter = ($api =~ /have_balance/o) ?
2033 { 'balance_owed' => { '<>' => 0 } }:
2034 { 'total_owed' => { '>' => 0 } };
2036 my $method = 'open-ils.actor.user.transactions.history.still_open';
2037 $method = "$method.authoritative" if $api =~ /authoritative/;
2038 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2040 if($api =~ /total/o) {
2042 $total += $_->balance_owed for @$trans;
2046 ($api =~ /count/o ) and return scalar @$trans;
2047 ($api !~ /fleshed/o) and return $trans;
2050 for my $t (@$trans) {
2052 if( $t->xact_type ne 'circulation' ) {
2053 push @resp, {transaction => $t};
2057 my $circ_data = flesh_circ($e, $t->id);
2058 push @resp, {transaction => $t, %$circ_data};
2065 __PACKAGE__->register_method(
2066 method => "user_transaction_retrieve",
2067 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2070 notes => "Returns a fleshed transaction record"
2073 __PACKAGE__->register_method(
2074 method => "user_transaction_retrieve",
2075 api_name => "open-ils.actor.user.transaction.retrieve",
2078 notes => "Returns a transaction record"
2081 sub user_transaction_retrieve {
2082 my($self, $client, $auth, $bill_id) = @_;
2084 my $e = new_editor(authtoken => $auth);
2085 return $e->event unless $e->checkauth;
2087 my $trans = $e->retrieve_money_billable_transaction_summary(
2088 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2090 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2092 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2094 return $trans unless $self->api_name =~ /flesh/;
2095 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2097 my $circ_data = flesh_circ($e, $trans->id, 1);
2099 return {transaction => $trans, %$circ_data};
2104 my $circ_id = shift;
2105 my $flesh_copy = shift;
2107 my $circ = $e->retrieve_action_circulation([
2111 circ => ['target_copy'],
2112 acp => ['call_number'],
2119 my $copy = $circ->target_copy;
2121 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2122 $mods = new Fieldmapper::metabib::virtual_record;
2123 $mods->doc_id(OILS_PRECAT_RECORD);
2124 $mods->title($copy->dummy_title);
2125 $mods->author($copy->dummy_author);
2128 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2132 $circ->target_copy($circ->target_copy->id);
2133 $copy->call_number($copy->call_number->id);
2135 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2139 __PACKAGE__->register_method(
2140 method => "hold_request_count",
2141 api_name => "open-ils.actor.user.hold_requests.count",
2145 Returns hold ready vs. total counts.
2146 If a context org unit is provided, a third value
2147 is returned with key 'behind_desk', which reports
2148 how many holds are ready at the pickup library
2149 with the behind_desk flag set to true.
2153 sub hold_request_count {
2154 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2155 my $e = new_editor(authtoken => $authtoken);
2156 return $e->event unless $e->checkauth;
2158 $user_id = $e->requestor->id unless defined $user_id;
2160 if($e->requestor->id ne $user_id) {
2161 my $user = $e->retrieve_actor_user($user_id);
2162 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2165 my $holds = $e->json_query({
2166 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2170 fulfillment_time => {"=" => undef },
2171 cancel_time => undef,
2176 $_->{current_shelf_lib} and # avoid undef warnings
2177 $_->{pickup_lib} eq $_->{current_shelf_lib}
2181 total => scalar(@$holds),
2182 ready => scalar(@ready)
2186 # count of holds ready at pickup lib with behind_desk true.
2187 $resp->{behind_desk} = scalar(
2189 $_->{pickup_lib} == $ctx_org and
2190 $U->is_true($_->{behind_desk})
2198 __PACKAGE__->register_method(
2199 method => "checked_out",
2200 api_name => "open-ils.actor.user.checked_out",
2204 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2205 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2206 . "(i.e., outstanding balance or some other pending action on the circ). "
2207 . "The .count method also includes a 'total' field which sums all open circs.",
2209 { desc => 'Authentication Token', type => 'string'},
2210 { desc => 'User ID', type => 'string'},
2213 desc => 'Returns event on error, or an object with ID lists, like: '
2214 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2219 __PACKAGE__->register_method(
2220 method => "checked_out",
2221 api_name => "open-ils.actor.user.checked_out.count",
2224 signature => q/@see open-ils.actor.user.checked_out/
2228 my( $self, $conn, $auth, $userid ) = @_;
2230 my $e = new_editor(authtoken=>$auth);
2231 return $e->event unless $e->checkauth;
2233 if( $userid ne $e->requestor->id ) {
2234 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2235 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2237 # see if there is a friend link allowing circ.view perms
2238 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2239 $e, $userid, $e->requestor->id, 'circ.view');
2240 return $e->event unless $allowed;
2244 my $count = $self->api_name =~ /count/;
2245 return _checked_out( $count, $e, $userid );
2249 my( $iscount, $e, $userid ) = @_;
2255 claims_returned => [],
2258 my $meth = 'retrieve_action_open_circ_';
2266 claims_returned => 0,
2273 my $data = $e->$meth($userid);
2277 $result{$_} += $data->$_() for (keys %result);
2278 $result{total} += $data->$_() for (keys %result);
2280 for my $k (keys %result) {
2281 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2291 __PACKAGE__->register_method(
2292 method => "checked_in_with_fines",
2293 api_name => "open-ils.actor.user.checked_in_with_fines",
2296 signature => q/@see open-ils.actor.user.checked_out/
2299 sub checked_in_with_fines {
2300 my( $self, $conn, $auth, $userid ) = @_;
2302 my $e = new_editor(authtoken=>$auth);
2303 return $e->event unless $e->checkauth;
2305 if( $userid ne $e->requestor->id ) {
2306 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2309 # money is owed on these items and they are checked in
2310 my $open = $e->search_action_circulation(
2313 xact_finish => undef,
2314 checkin_time => { "!=" => undef },
2319 my( @lost, @cr, @lo );
2320 for my $c (@$open) {
2321 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2322 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2323 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2328 claims_returned => \@cr,
2329 long_overdue => \@lo
2335 my ($api, $desc, $auth) = @_;
2336 $desc = $desc ? (" " . $desc) : '';
2337 my $ids = ($api =~ /ids$/) ? 1 : 0;
2340 method => "user_transaction_history",
2341 api_name => "open-ils.actor.user.transactions.$api",
2343 desc => "For a given User ID, returns a list of billable transaction" .
2344 ($ids ? " id" : '') .
2345 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2346 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2348 {desc => 'Authentication token', type => 'string'},
2349 {desc => 'User ID', type => 'number'},
2350 {desc => 'Transaction type (optional)', type => 'number'},
2351 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2354 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2358 $auth and push @sig, (authoritative => 1);
2362 my %auth_hist_methods = (
2364 'history.have_charge' => 'that have an initial charge',
2365 'history.still_open' => 'that are not finished',
2366 'history.have_balance' => 'that have a balance',
2367 'history.have_bill' => 'that have billings',
2368 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2369 'history.have_payment' => 'that have at least 1 payment',
2372 foreach (keys %auth_hist_methods) {
2373 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2374 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2375 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2378 sub user_transaction_history {
2379 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2383 my $e = new_editor(authtoken=>$auth);
2384 return $e->die_event unless $e->checkauth;
2386 if ($e->requestor->id ne $userid) {
2387 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2390 my $api = $self->api_name;
2391 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2393 if(defined($type)) {
2394 $filter->{'xact_type'} = $type;
2397 if($api =~ /have_bill_or_payment/o) {
2399 # transactions that have a non-zero sum across all billings or at least 1 payment
2400 $filter->{'-or'} = {
2401 'balance_owed' => { '<>' => 0 },
2402 'last_payment_ts' => { '<>' => undef }
2405 } elsif($api =~ /have_payment/) {
2407 $filter->{last_payment_ts} ||= {'<>' => undef};
2409 } elsif( $api =~ /have_balance/o) {
2411 # transactions that have a non-zero overall balance
2412 $filter->{'balance_owed'} = { '<>' => 0 };
2414 } elsif( $api =~ /have_charge/o) {
2416 # transactions that have at least 1 billing, regardless of whether it was voided
2417 $filter->{'last_billing_ts'} = { '<>' => undef };
2419 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2421 # transactions that have non-zero sum across all billings. This will exclude
2422 # xacts where all billings have been voided
2423 $filter->{'total_owed'} = { '<>' => 0 };
2426 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2427 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2428 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2430 my $mbts = $e->search_money_billable_transaction_summary(
2431 [ { usr => $userid, @xact_finish, %$filter },
2436 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2437 return $mbts unless $api =~ /fleshed/;
2440 for my $t (@$mbts) {
2442 if( $t->xact_type ne 'circulation' ) {
2443 push @resp, {transaction => $t};
2447 my $circ_data = flesh_circ($e, $t->id);
2448 push @resp, {transaction => $t, %$circ_data};
2456 __PACKAGE__->register_method(
2457 method => "user_perms",
2458 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2460 notes => "Returns a list of permissions"
2464 my( $self, $client, $authtoken, $user ) = @_;
2466 my( $staff, $evt ) = $apputils->checkses($authtoken);
2467 return $evt if $evt;
2469 $user ||= $staff->id;
2471 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2475 return $apputils->simple_scalar_request(
2477 "open-ils.storage.permission.user_perms.atomic",
2481 __PACKAGE__->register_method(
2482 method => "retrieve_perms",
2483 api_name => "open-ils.actor.permissions.retrieve",
2484 notes => "Returns a list of permissions"
2486 sub retrieve_perms {
2487 my( $self, $client ) = @_;
2488 return $apputils->simple_scalar_request(
2490 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2491 { id => { '!=' => undef } }
2495 __PACKAGE__->register_method(
2496 method => "retrieve_groups",
2497 api_name => "open-ils.actor.groups.retrieve",
2498 notes => "Returns a list of user groups"
2500 sub retrieve_groups {
2501 my( $self, $client ) = @_;
2502 return new_editor()->retrieve_all_permission_grp_tree();
2505 __PACKAGE__->register_method(
2506 method => "retrieve_org_address",
2507 api_name => "open-ils.actor.org_unit.address.retrieve",
2508 notes => <<' NOTES');
2509 Returns an org_unit address by ID
2510 @param An org_address ID
2512 sub retrieve_org_address {
2513 my( $self, $client, $id ) = @_;
2514 return $apputils->simple_scalar_request(
2516 "open-ils.cstore.direct.actor.org_address.retrieve",
2521 __PACKAGE__->register_method(
2522 method => "retrieve_groups_tree",
2523 api_name => "open-ils.actor.groups.tree.retrieve",
2524 notes => "Returns a list of user groups"
2527 sub retrieve_groups_tree {
2528 my( $self, $client ) = @_;
2529 return new_editor()->search_permission_grp_tree(
2534 flesh_fields => { pgt => ["children"] },
2535 order_by => { pgt => 'name'}
2542 __PACKAGE__->register_method(
2543 method => "add_user_to_groups",
2544 api_name => "open-ils.actor.user.set_groups",
2545 notes => "Adds a user to one or more permission groups"
2548 sub add_user_to_groups {
2549 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2551 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2552 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2553 return $evt if $evt;
2555 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2556 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2557 return $evt if $evt;
2559 $apputils->simplereq(
2561 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2563 for my $group (@$groups) {
2564 my $link = Fieldmapper::permission::usr_grp_map->new;
2566 $link->usr($userid);
2568 my $id = $apputils->simplereq(
2570 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2576 __PACKAGE__->register_method(
2577 method => "get_user_perm_groups",
2578 api_name => "open-ils.actor.user.get_groups",
2579 notes => "Retrieve a user's permission groups."
2583 sub get_user_perm_groups {
2584 my( $self, $client, $authtoken, $userid ) = @_;
2586 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2587 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2588 return $evt if $evt;
2590 return $apputils->simplereq(
2592 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2596 __PACKAGE__->register_method(
2597 method => "get_user_work_ous",
2598 api_name => "open-ils.actor.user.get_work_ous",
2599 notes => "Retrieve a user's work org units."
2602 __PACKAGE__->register_method(
2603 method => "get_user_work_ous",
2604 api_name => "open-ils.actor.user.get_work_ous.ids",
2605 notes => "Retrieve a user's work org units."
2608 sub get_user_work_ous {
2609 my( $self, $client, $auth, $userid ) = @_;
2610 my $e = new_editor(authtoken=>$auth);
2611 return $e->event unless $e->checkauth;
2612 $userid ||= $e->requestor->id;
2614 if($e->requestor->id != $userid) {
2615 my $user = $e->retrieve_actor_user($userid)
2616 or return $e->event;
2617 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2620 return $e->search_permission_usr_work_ou_map({usr => $userid})
2621 unless $self->api_name =~ /.ids$/;
2623 # client just wants a list of org IDs
2624 return $U->get_user_work_ou_ids($e, $userid);
2629 __PACKAGE__->register_method(
2630 method => 'register_workstation',
2631 api_name => 'open-ils.actor.workstation.register.override',
2632 signature => q/@see open-ils.actor.workstation.register/
2635 __PACKAGE__->register_method(
2636 method => 'register_workstation',
2637 api_name => 'open-ils.actor.workstation.register',
2639 Registers a new workstion in the system
2640 @param authtoken The login session key
2641 @param name The name of the workstation id
2642 @param owner The org unit that owns this workstation
2643 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2644 if the name is already in use.
2648 sub register_workstation {
2649 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2651 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2652 return $e->die_event unless $e->checkauth;
2653 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2654 my $existing = $e->search_actor_workstation({name => $name})->[0];
2655 $oargs = { all => 1 } unless defined $oargs;
2659 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2660 # workstation with the given name exists.
2662 if($owner ne $existing->owning_lib) {
2663 # if necessary, update the owning_lib of the workstation
2665 $logger->info("changing owning lib of workstation ".$existing->id.
2666 " from ".$existing->owning_lib." to $owner");
2667 return $e->die_event unless
2668 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2670 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2672 $existing->owning_lib($owner);
2673 return $e->die_event unless $e->update_actor_workstation($existing);
2679 "attempt to register an existing workstation. returning existing ID");
2682 return $existing->id;
2685 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2689 my $ws = Fieldmapper::actor::workstation->new;
2690 $ws->owning_lib($owner);
2692 $e->create_actor_workstation($ws) or return $e->die_event;
2694 return $ws->id; # note: editor sets the id on the new object for us
2697 __PACKAGE__->register_method(
2698 method => 'workstation_list',
2699 api_name => 'open-ils.actor.workstation.list',
2701 Returns a list of workstations registered at the given location
2702 @param authtoken The login session key
2703 @param ids A list of org_unit.id's for the workstation owners
2707 sub workstation_list {
2708 my( $self, $conn, $authtoken, @orgs ) = @_;
2710 my $e = new_editor(authtoken=>$authtoken);
2711 return $e->event unless $e->checkauth;
2716 unless $e->allowed('REGISTER_WORKSTATION', $o);
2717 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2723 __PACKAGE__->register_method(
2724 method => 'fetch_patron_note',
2725 api_name => 'open-ils.actor.note.retrieve.all',
2728 Returns a list of notes for a given user
2729 Requestor must have VIEW_USER permission if pub==false and
2730 @param authtoken The login session key
2731 @param args Hash of params including
2732 patronid : the patron's id
2733 pub : true if retrieving only public notes
2737 sub fetch_patron_note {
2738 my( $self, $conn, $authtoken, $args ) = @_;
2739 my $patronid = $$args{patronid};
2741 my($reqr, $evt) = $U->checkses($authtoken);
2742 return $evt if $evt;
2745 ($patron, $evt) = $U->fetch_user($patronid);
2746 return $evt if $evt;
2749 if( $patronid ne $reqr->id ) {
2750 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2751 return $evt if $evt;
2753 return $U->cstorereq(
2754 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2755 { usr => $patronid, pub => 't' } );
2758 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2759 return $evt if $evt;
2761 return $U->cstorereq(
2762 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2765 __PACKAGE__->register_method(
2766 method => 'create_user_note',
2767 api_name => 'open-ils.actor.note.create',
2769 Creates a new note for the given user
2770 @param authtoken The login session key
2771 @param note The note object
2774 sub create_user_note {
2775 my( $self, $conn, $authtoken, $note ) = @_;
2776 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2777 return $e->die_event unless $e->checkauth;
2779 my $user = $e->retrieve_actor_user($note->usr)
2780 or return $e->die_event;
2782 return $e->die_event unless
2783 $e->allowed('UPDATE_USER',$user->home_ou);
2785 $note->creator($e->requestor->id);
2786 $e->create_actor_usr_note($note) or return $e->die_event;
2792 __PACKAGE__->register_method(
2793 method => 'delete_user_note',
2794 api_name => 'open-ils.actor.note.delete',
2796 Deletes a note for the given user
2797 @param authtoken The login session key
2798 @param noteid The note id
2801 sub delete_user_note {
2802 my( $self, $conn, $authtoken, $noteid ) = @_;
2804 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2805 return $e->die_event unless $e->checkauth;
2806 my $note = $e->retrieve_actor_usr_note($noteid)
2807 or return $e->die_event;
2808 my $user = $e->retrieve_actor_user($note->usr)
2809 or return $e->die_event;
2810 return $e->die_event unless
2811 $e->allowed('UPDATE_USER', $user->home_ou);
2813 $e->delete_actor_usr_note($note) or return $e->die_event;
2819 __PACKAGE__->register_method(
2820 method => 'update_user_note',
2821 api_name => 'open-ils.actor.note.update',
2823 @param authtoken The login session key
2824 @param note The note
2828 sub update_user_note {
2829 my( $self, $conn, $auth, $note ) = @_;
2830 my $e = new_editor(authtoken=>$auth, xact=>1);
2831 return $e->die_event unless $e->checkauth;
2832 my $patron = $e->retrieve_actor_user($note->usr)
2833 or return $e->die_event;
2834 return $e->die_event unless
2835 $e->allowed('UPDATE_USER', $patron->home_ou);
2836 $e->update_actor_user_note($note)
2837 or return $e->die_event;
2842 __PACKAGE__->register_method(
2843 method => 'fetch_patron_messages',
2844 api_name => 'open-ils.actor.message.retrieve',
2847 Returns a list of notes for a given user, not
2848 including ones marked deleted
2849 @param authtoken The login session key
2850 @param patronid patron ID
2851 @param options hash containing optional limit and offset
2855 sub fetch_patron_messages {
2856 my( $self, $conn, $auth, $patronid, $options ) = @_;
2860 my $e = new_editor(authtoken => $auth);
2861 return $e->die_event unless $e->checkauth;
2863 if ($e->requestor->id ne $patronid) {
2864 return $e->die_event unless $e->allowed('VIEW_USER');
2867 my $select_clause = { usr => $patronid };
2868 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2869 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2870 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2872 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2877 __PACKAGE__->register_method(
2878 method => 'usrname_exists',
2879 api_name => 'open-ils.actor.username.exists',
2881 desc => 'Check if a username is already taken (by an undeleted patron)',
2883 {desc => 'Authentication token', type => 'string'},
2884 {desc => 'Username', type => 'string'}
2887 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2892 sub usrname_exists {
2893 my( $self, $conn, $auth, $usrname ) = @_;
2894 my $e = new_editor(authtoken=>$auth);
2895 return $e->event unless $e->checkauth;
2896 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2897 return $$a[0] if $a and @$a;
2901 __PACKAGE__->register_method(
2902 method => 'barcode_exists',
2903 api_name => 'open-ils.actor.barcode.exists',
2905 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2908 sub barcode_exists {
2909 my( $self, $conn, $auth, $barcode ) = @_;
2910 my $e = new_editor(authtoken=>$auth);
2911 return $e->event unless $e->checkauth;
2912 my $card = $e->search_actor_card({barcode => $barcode});
2918 #return undef unless @$card;
2919 #return $card->[0]->usr;
2923 __PACKAGE__->register_method(
2924 method => 'retrieve_net_levels',
2925 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2928 sub retrieve_net_levels {
2929 my( $self, $conn, $auth ) = @_;
2930 my $e = new_editor(authtoken=>$auth);
2931 return $e->event unless $e->checkauth;
2932 return $e->retrieve_all_config_net_access_level();
2935 # Retain the old typo API name just in case
2936 __PACKAGE__->register_method(
2937 method => 'fetch_org_by_shortname',
2938 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2940 __PACKAGE__->register_method(
2941 method => 'fetch_org_by_shortname',
2942 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2944 sub fetch_org_by_shortname {
2945 my( $self, $conn, $sname ) = @_;
2946 my $e = new_editor();
2947 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2948 return $e->event unless $org;
2953 __PACKAGE__->register_method(
2954 method => 'session_home_lib',
2955 api_name => 'open-ils.actor.session.home_lib',
2958 sub session_home_lib {
2959 my( $self, $conn, $auth ) = @_;
2960 my $e = new_editor(authtoken=>$auth);
2961 return undef unless $e->checkauth;
2962 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2963 return $org->shortname;
2966 __PACKAGE__->register_method(
2967 method => 'session_safe_token',
2968 api_name => 'open-ils.actor.session.safe_token',
2970 Returns a hashed session ID that is safe for export to the world.
2971 This safe token will expire after 1 hour of non-use.
2972 @param auth Active authentication token
2976 sub session_safe_token {
2977 my( $self, $conn, $auth ) = @_;
2978 my $e = new_editor(authtoken=>$auth);
2979 return undef unless $e->checkauth;
2981 my $safe_token = md5_hex($auth);
2983 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2985 # add more user fields as needed
2987 "safe-token-user-$safe_token", {
2988 id => $e->requestor->id,
2989 home_ou_shortname => $e->retrieve_actor_org_unit(
2990 $e->requestor->home_ou)->shortname,
2999 __PACKAGE__->register_method(
3000 method => 'safe_token_home_lib',
3001 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
3003 Returns the home library shortname from the session
3004 asscociated with a safe token from generated by
3005 open-ils.actor.session.safe_token.
3006 @param safe_token Active safe token
3007 @param who Optional user activity "ewho" value
3011 sub safe_token_home_lib {
3012 my( $self, $conn, $safe_token, $who ) = @_;
3013 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3015 my $blob = $cache->get_cache("safe-token-user-$safe_token");
3016 return unless $blob;
3018 $U->log_user_activity($blob->{id}, $who, 'verify');
3019 return $blob->{home_ou_shortname};
3023 __PACKAGE__->register_method(
3024 method => "update_penalties",
3025 api_name => "open-ils.actor.user.penalties.update"
3028 sub update_penalties {
3029 my($self, $conn, $auth, $user_id) = @_;
3030 my $e = new_editor(authtoken=>$auth, xact => 1);
3031 return $e->die_event unless $e->checkauth;
3032 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3033 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3034 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3035 return $evt if $evt;
3041 __PACKAGE__->register_method(
3042 method => "apply_penalty",
3043 api_name => "open-ils.actor.user.penalty.apply"
3047 my($self, $conn, $auth, $penalty) = @_;
3049 my $e = new_editor(authtoken=>$auth, xact => 1);
3050 return $e->die_event unless $e->checkauth;
3052 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3053 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3055 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3058 (defined $ptype->org_depth) ?
3059 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
3062 $penalty->org_unit($ctx_org);
3063 $penalty->staff($e->requestor->id);
3064 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3067 return $penalty->id;
3070 __PACKAGE__->register_method(
3071 method => "remove_penalty",
3072 api_name => "open-ils.actor.user.penalty.remove"
3075 sub remove_penalty {
3076 my($self, $conn, $auth, $penalty) = @_;
3077 my $e = new_editor(authtoken=>$auth, xact => 1);
3078 return $e->die_event unless $e->checkauth;
3079 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3080 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3082 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3087 __PACKAGE__->register_method(
3088 method => "update_penalty_note",
3089 api_name => "open-ils.actor.user.penalty.note.update"
3092 sub update_penalty_note {
3093 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3094 my $e = new_editor(authtoken=>$auth, xact => 1);
3095 return $e->die_event unless $e->checkauth;
3096 for my $penalty_id (@$penalty_ids) {
3097 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3098 if (! $penalty ) { return $e->die_event; }
3099 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3100 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3102 $penalty->note( $note ); $penalty->ischanged( 1 );
3104 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3110 __PACKAGE__->register_method(
3111 method => "ranged_penalty_thresholds",
3112 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3116 sub ranged_penalty_thresholds {
3117 my($self, $conn, $auth, $context_org) = @_;
3118 my $e = new_editor(authtoken=>$auth);
3119 return $e->event unless $e->checkauth;
3120 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3121 my $list = $e->search_permission_grp_penalty_threshold([
3122 {org_unit => $U->get_org_ancestors($context_org)},
3123 {order_by => {pgpt => 'id'}}
3125 $conn->respond($_) for @$list;
3131 __PACKAGE__->register_method(
3132 method => "user_retrieve_fleshed_by_id",
3134 api_name => "open-ils.actor.user.fleshed.retrieve",
3137 sub user_retrieve_fleshed_by_id {
3138 my( $self, $client, $auth, $user_id, $fields ) = @_;
3139 my $e = new_editor(authtoken => $auth);
3140 return $e->event unless $e->checkauth;
3142 if( $e->requestor->id != $user_id ) {
3143 return $e->event unless $e->allowed('VIEW_USER');
3150 "standing_penalties",
3158 return new_flesh_user($user_id, $fields, $e);
3162 sub new_flesh_user {
3165 my $fields = shift || [];
3168 my $fetch_penalties = 0;
3169 if(grep {$_ eq 'standing_penalties'} @$fields) {
3170 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3171 $fetch_penalties = 1;
3174 my $fetch_usr_act = 0;
3175 if(grep {$_ eq 'usr_activity'} @$fields) {
3176 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3180 my $user = $e->retrieve_actor_user(
3185 "flesh_fields" => { "au" => $fields }
3188 ) or return $e->die_event;
3191 if( grep { $_ eq 'addresses' } @$fields ) {
3193 $user->addresses([]) unless @{$user->addresses};
3194 # don't expose "replaced" addresses by default
3195 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3197 if( ref $user->billing_address ) {
3198 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3199 push( @{$user->addresses}, $user->billing_address );
3203 if( ref $user->mailing_address ) {
3204 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3205 push( @{$user->addresses}, $user->mailing_address );
3210 if($fetch_penalties) {
3211 # grab the user penalties ranged for this location
3212 $user->standing_penalties(
3213 $e->search_actor_user_standing_penalty([
3216 {stop_date => undef},
3217 {stop_date => {'>' => 'now'}}
3219 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3222 flesh_fields => {ausp => ['standing_penalty']}
3228 # retrieve the most recent usr_activity entry
3229 if ($fetch_usr_act) {
3231 # max number to return for simple patron fleshing
3232 my $limit = $U->ou_ancestor_setting_value(
3233 $e->requestor->ws_ou,
3234 'circ.patron.usr_activity_retrieve.max');
3238 flesh_fields => {auact => ['etype']},
3239 order_by => {auact => 'event_time DESC'},
3242 # 0 == none, <0 == return all
3243 $limit = 1 unless defined $limit;
3244 $opts->{limit} = $limit if $limit > 0;
3246 $user->usr_activity(
3248 [] : # skip the DB call
3249 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3254 $user->clear_passwd();
3261 __PACKAGE__->register_method(
3262 method => "user_retrieve_parts",
3263 api_name => "open-ils.actor.user.retrieve.parts",
3266 sub user_retrieve_parts {
3267 my( $self, $client, $auth, $user_id, $fields ) = @_;
3268 my $e = new_editor(authtoken => $auth);
3269 return $e->event unless $e->checkauth;
3270 $user_id ||= $e->requestor->id;
3271 if( $e->requestor->id != $user_id ) {
3272 return $e->event unless $e->allowed('VIEW_USER');
3275 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3276 push(@resp, $user->$_()) for(@$fields);
3282 __PACKAGE__->register_method(
3283 method => 'user_opt_in_enabled',
3284 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3285 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3288 sub user_opt_in_enabled {
3289 my($self, $conn) = @_;
3290 my $sc = OpenSRF::Utils::SettingsClient->new;
3291 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3296 __PACKAGE__->register_method(
3297 method => 'user_opt_in_at_org',
3298 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3300 @param $auth The auth token
3301 @param user_id The ID of the user to test
3302 @return 1 if the user has opted in at the specified org,
3303 2 if opt-in is disallowed for the user's home org,
3304 event on error, and 0 otherwise. /
3306 sub user_opt_in_at_org {
3307 my($self, $conn, $auth, $user_id) = @_;
3309 # see if we even need to enforce the opt-in value
3310 return 1 unless user_opt_in_enabled($self);
3312 my $e = new_editor(authtoken => $auth);
3313 return $e->event unless $e->checkauth;
3315 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3316 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3318 my $ws_org = $e->requestor->ws_ou;
3319 # user is automatically opted-in if they are from the local org
3320 return 1 if $user->home_ou eq $ws_org;
3322 # get the boundary setting
3323 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3325 # auto opt in if user falls within the opt boundary
3326 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3328 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3330 # check whether opt-in is restricted at the user's home library
3331 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3332 if ($opt_restrict_depth) {
3333 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3334 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3336 # opt-in is disallowed unless the workstation org is within the home
3337 # library's opt-in scope
3338 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3341 my $vals = $e->search_actor_usr_org_unit_opt_in(
3342 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3348 __PACKAGE__->register_method(
3349 method => 'create_user_opt_in_at_org',
3350 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3352 @param $auth The auth token
3353 @param user_id The ID of the user to test
3354 @return The ID of the newly created object, event on error./
3357 sub create_user_opt_in_at_org {
3358 my($self, $conn, $auth, $user_id, $org_id) = @_;
3360 my $e = new_editor(authtoken => $auth, xact=>1);
3361 return $e->die_event unless $e->checkauth;
3363 # if a specific org unit wasn't passed in, get one based on the defaults;
3365 my $wsou = $e->requestor->ws_ou;
3366 # get the default opt depth
3367 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3368 # get the org unit at that depth
3369 my $org = $e->json_query({
3370 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3371 $org_id = $org->{id};
3374 # fall back to the workstation OU, the pre-opt-in-boundary way
3375 $org_id = $e->requestor->ws_ou;
3378 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3379 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3381 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3383 $opt_in->org_unit($org_id);
3384 $opt_in->usr($user_id);
3385 $opt_in->staff($e->requestor->id);
3386 $opt_in->opt_in_ts('now');
3387 $opt_in->opt_in_ws($e->requestor->wsid);
3389 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3390 or return $e->die_event;
3398 __PACKAGE__->register_method (
3399 method => 'retrieve_org_hours',
3400 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3402 Returns the hours of operation for a specified org unit
3403 @param authtoken The login session key
3404 @param org_id The org_unit ID
3408 sub retrieve_org_hours {
3409 my($self, $conn, $auth, $org_id) = @_;
3410 my $e = new_editor(authtoken => $auth);
3411 return $e->die_event unless $e->checkauth;
3412 $org_id ||= $e->requestor->ws_ou;
3413 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3417 __PACKAGE__->register_method (
3418 method => 'verify_user_password',
3419 api_name => 'open-ils.actor.verify_user_password',
3421 Given a barcode or username and the MD5 encoded password,
3422 returns 1 if the password is correct. Returns 0 otherwise.
3426 sub verify_user_password {
3427 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3428 my $e = new_editor(authtoken => $auth);
3429 return $e->die_event unless $e->checkauth;
3431 my $user_by_barcode;
3432 my $user_by_username;
3434 my $card = $e->search_actor_card([
3435 {barcode => $barcode},
3436 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3437 $user_by_barcode = $card->usr;
3438 $user = $user_by_barcode;
3441 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3442 $user = $user_by_username;
3444 return 0 if (!$user || $U->is_true($user->deleted));
3445 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3446 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3447 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3450 __PACKAGE__->register_method (
3451 method => 'retrieve_usr_id_via_barcode_or_usrname',
3452 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3454 Given a barcode or username returns the id for the user or
3459 sub retrieve_usr_id_via_barcode_or_usrname {
3460 my($self, $conn, $auth, $barcode, $username) = @_;
3461 my $e = new_editor(authtoken => $auth);
3462 return $e->die_event unless $e->checkauth;
3463 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3465 my $user_by_barcode;
3466 my $user_by_username;
3467 $logger->info("$id_as_barcode is the ID as BARCODE");
3469 my $card = $e->search_actor_card([
3470 {barcode => $barcode},
3471 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3472 if ($id_as_barcode =~ /^t/i) {
3474 $user = $e->retrieve_actor_user($barcode);
3475 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3477 $user_by_barcode = $card->usr;
3478 $user = $user_by_barcode;
3481 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3482 $user_by_barcode = $card->usr;
3483 $user = $user_by_barcode;
3488 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3490 $user = $user_by_username;
3492 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3493 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3494 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3499 __PACKAGE__->register_method (
3500 method => 'merge_users',
3501 api_name => 'open-ils.actor.user.merge',
3504 Given a list of source users and destination user, transfer all data from the source
3505 to the dest user and delete the source user. All user related data is
3506 transferred, including circulations, holds, bookbags, etc.
3512 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3513 my $e = new_editor(xact => 1, authtoken => $auth);
3514 return $e->die_event unless $e->checkauth;
3516 # disallow the merge if any subordinate accounts are in collections
3517 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3518 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3520 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3521 if $master_id == $e->requestor->id;
3523 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3524 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3525 return $evt if $evt;
3527 my $del_addrs = ($U->ou_ancestor_setting_value(
3528 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3529 my $del_cards = ($U->ou_ancestor_setting_value(
3530 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3531 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3532 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3534 for my $src_id (@$user_ids) {
3536 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3537 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3538 return $evt if $evt;
3540 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3541 if $src_id == $e->requestor->id;
3543 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3544 if($src_user->home_ou ne $master_user->home_ou) {
3545 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3548 return $e->die_event unless
3549 $e->json_query({from => [
3564 __PACKAGE__->register_method (
3565 method => 'approve_user_address',
3566 api_name => 'open-ils.actor.user.pending_address.approve',
3573 sub approve_user_address {
3574 my($self, $conn, $auth, $addr) = @_;
3575 my $e = new_editor(xact => 1, authtoken => $auth);
3576 return $e->die_event unless $e->checkauth;
3578 # if the caller passes an address object, assume they want to
3579 # update it first before approving it
3580 $e->update_actor_user_address($addr) or return $e->die_event;
3582 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3584 my $user = $e->retrieve_actor_user($addr->usr);
3585 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3586 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3587 or return $e->die_event;
3589 return [values %$result]->[0];
3593 __PACKAGE__->register_method (
3594 method => 'retrieve_friends',
3595 api_name => 'open-ils.actor.friends.retrieve',
3598 returns { confirmed: [], pending_out: [], pending_in: []}
3599 pending_out are users I'm requesting friendship with
3600 pending_in are users requesting friendship with me
3605 sub retrieve_friends {
3606 my($self, $conn, $auth, $user_id, $options) = @_;
3607 my $e = new_editor(authtoken => $auth);
3608 return $e->event unless $e->checkauth;
3609 $user_id ||= $e->requestor->id;
3611 if($user_id != $e->requestor->id) {
3612 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3613 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3616 return OpenILS::Application::Actor::Friends->retrieve_friends(
3617 $e, $user_id, $options);
3622 __PACKAGE__->register_method (
3623 method => 'apply_friend_perms',
3624 api_name => 'open-ils.actor.friends.perms.apply',
3630 sub apply_friend_perms {
3631 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3632 my $e = new_editor(authtoken => $auth, xact => 1);
3633 return $e->die_event unless $e->checkauth;
3635 if($user_id != $e->requestor->id) {
3636 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3637 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3640 for my $perm (@perms) {
3642 OpenILS::Application::Actor::Friends->apply_friend_perm(
3643 $e, $user_id, $delegate_id, $perm);
3644 return $evt if $evt;
3652 __PACKAGE__->register_method (
3653 method => 'update_user_pending_address',
3654 api_name => 'open-ils.actor.user.address.pending.cud'
3657 sub update_user_pending_address {
3658 my($self, $conn, $auth, $addr) = @_;
3659 my $e = new_editor(authtoken => $auth, xact => 1);
3660 return $e->die_event unless $e->checkauth;
3662 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3663 if($addr->usr != $e->requestor->id) {
3664 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3668 $e->create_actor_user_address($addr) or return $e->die_event;
3669 } elsif($addr->isdeleted) {
3670 $e->delete_actor_user_address($addr) or return $e->die_event;
3672 $e->update_actor_user_address($addr) or return $e->die_event;
3676 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3682 __PACKAGE__->register_method (
3683 method => 'user_events',
3684 api_name => 'open-ils.actor.user.events.circ',
3687 __PACKAGE__->register_method (
3688 method => 'user_events',
3689 api_name => 'open-ils.actor.user.events.ahr',
3694 my($self, $conn, $auth, $user_id, $filters) = @_;
3695 my $e = new_editor(authtoken => $auth);
3696 return $e->event unless $e->checkauth;
3698 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3699 my $user_field = 'usr';
3702 $filters->{target} = {
3703 select => { $obj_type => ['id'] },
3705 where => {usr => $user_id}
3708 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3709 if($e->requestor->id != $user_id) {
3710 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3713 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3714 my $req = $ses->request('open-ils.trigger.events_by_target',
3715 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3717 while(my $resp = $req->recv) {
3718 my $val = $resp->content;
3719 my $tgt = $val->target;
3721 if($obj_type eq 'circ') {
3722 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3724 } elsif($obj_type eq 'ahr') {
3725 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3726 if $tgt->current_copy;
3729 $conn->respond($val) if $val;
3735 __PACKAGE__->register_method (
3736 method => 'copy_events',
3737 api_name => 'open-ils.actor.copy.events.circ',
3740 __PACKAGE__->register_method (
3741 method => 'copy_events',
3742 api_name => 'open-ils.actor.copy.events.ahr',
3747 my($self, $conn, $auth, $copy_id, $filters) = @_;
3748 my $e = new_editor(authtoken => $auth);
3749 return $e->event unless $e->checkauth;
3751 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3753 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3755 my $copy_field = 'target_copy';
3756 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3759 $filters->{target} = {
3760 select => { $obj_type => ['id'] },
3762 where => {$copy_field => $copy_id}
3766 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3767 my $req = $ses->request('open-ils.trigger.events_by_target',
3768 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3770 while(my $resp = $req->recv) {
3771 my $val = $resp->content;
3772 my $tgt = $val->target;
3774 my $user = $e->retrieve_actor_user($tgt->usr);
3775 if($e->requestor->id != $user->id) {
3776 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3779 $tgt->$copy_field($copy);
3782 $conn->respond($val) if $val;
3789 __PACKAGE__->register_method (
3790 method => 'get_itemsout_notices',
3791 api_name => 'open-ils.actor.user.itemsout.notices',
3795 desc => q/Summary counts of circulat notices/,
3797 {desc => 'authtoken', type => 'string'},
3798 {desc => 'circulation identifiers', type => 'array of numbers'}
3800 return => q/Stream of summary objects/
3804 sub get_itemsout_notices {
3805 my ($self, $client, $auth, $circ_ids) = @_;
3807 my $e = new_editor(authtoken => $auth);
3808 return $e->event unless $e->checkauth;
3810 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3812 for my $circ_id (@$circ_ids) {
3813 my $resp = get_itemsout_notices_impl($e, $circ_id);
3815 if ($U->is_event($resp)) {
3816 $client->respond($resp);
3820 $client->respond({circ_id => $circ_id, %$resp});
3828 sub get_itemsout_notices_impl {
3829 my ($e, $circId) = @_;
3831 my $requestorId = $e->requestor->id;
3833 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3835 my $patronId = $circ->usr;
3837 if( $patronId ne $requestorId ){
3838 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3839 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3842 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3843 #my $req = $ses->request('open-ils.trigger.events_by_target',
3844 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3845 # ^ Above removed in favor of faster json_query.
3848 # select complete_time
3849 # from action_trigger.event atev
3850 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3851 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3852 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3855 my $ctx_loc = $e->requestor->ws_ou;
3856 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3857 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3860 select => { atev => ["complete_time"] },
3863 atevdef => { field => "id",fkey => "event_def"}
3867 "+atevdef" => { active => 't', hook => 'checkout.due' },
3868 "+atev" => { target => $circId, state => 'complete' }
3872 if ($exclude_courtesy_notices){
3873 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3876 my %resblob = ( numNotices => 0, lastDt => undef );
3878 my $res = $e->json_query($query);
3879 for my $ndate (@$res) {
3880 $resblob{numNotices}++;
3881 if( !defined $resblob{lastDt}){
3882 $resblob{lastDt} = $$ndate{complete_time};
3885 if ($resblob{lastDt} lt $$ndate{complete_time}){
3886 $resblob{lastDt} = $$ndate{complete_time};
3893 __PACKAGE__->register_method (
3894 method => 'update_events',
3895 api_name => 'open-ils.actor.user.event.cancel.batch',
3898 __PACKAGE__->register_method (
3899 method => 'update_events',
3900 api_name => 'open-ils.actor.user.event.reset.batch',
3905 my($self, $conn, $auth, $event_ids) = @_;
3906 my $e = new_editor(xact => 1, authtoken => $auth);
3907 return $e->die_event unless $e->checkauth;
3910 for my $id (@$event_ids) {
3912 # do a little dance to determine what user we are ultimately affecting
3913 my $event = $e->retrieve_action_trigger_event([
3916 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3918 ]) or return $e->die_event;
3921 if($event->event_def->hook->core_type eq 'circ') {
3922 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3923 } elsif($event->event_def->hook->core_type eq 'ahr') {
3924 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3929 my $user = $e->retrieve_actor_user($user_id);
3930 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3932 if($self->api_name =~ /cancel/) {
3933 $event->state('invalid');
3934 } elsif($self->api_name =~ /reset/) {
3935 $event->clear_start_time;
3936 $event->clear_update_time;
3937 $event->state('pending');
3940 $e->update_action_trigger_event($event) or return $e->die_event;
3941 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3945 return {complete => 1};
3949 __PACKAGE__->register_method (
3950 method => 'really_delete_user',
3951 api_name => 'open-ils.actor.user.delete.override',
3952 signature => q/@see open-ils.actor.user.delete/
3955 __PACKAGE__->register_method (
3956 method => 'really_delete_user',
3957 api_name => 'open-ils.actor.user.delete',
3959 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3960 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3961 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3962 dest_usr_id is only required when deleting a user that performs staff functions.
3966 sub really_delete_user {
3967 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3968 my $e = new_editor(authtoken => $auth, xact => 1);
3969 return $e->die_event unless $e->checkauth;
3970 $oargs = { all => 1 } unless defined $oargs;
3972 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3973 my $open_bills = $e->json_query({
3974 select => { mbts => ['id'] },
3977 xact_finish => { '=' => undef },
3978 usr => { '=' => $user_id },
3980 }) or return $e->die_event;
3982 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3984 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3986 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3987 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3988 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3990 # No deleting yourself - UI is supposed to stop you first, though.
3991 return $e->die_event unless $e->requestor->id != $user->id;
3992 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3993 # Check if you are allowed to mess with this patron permission group at all
3994 my $evt = group_perm_failed($e, $e->requestor, $user);
3995 return $e->die_event($evt) if $evt;
3996 my $stat = $e->json_query(
3997 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3998 or return $e->die_event;
4004 __PACKAGE__->register_method (
4005 method => 'user_payments',
4006 api_name => 'open-ils.actor.user.payments.retrieve',
4009 Returns all payments for a given user. Default order is newest payments first.
4010 @param auth Authentication token
4011 @param user_id The user ID
4012 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4017 my($self, $conn, $auth, $user_id, $filters) = @_;
4020 my $e = new_editor(authtoken => $auth);
4021 return $e->die_event unless $e->checkauth;
4023 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4024 return $e->event unless
4025 $e->requestor->id == $user_id or
4026 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4028 # Find all payments for all transactions for user $user_id
4030 select => {mp => ['id']},
4035 select => {mbt => ['id']},
4037 where => {usr => $user_id}
4042 { # by default, order newest payments first
4044 field => 'payment_ts',
4047 # secondary sort in ID as a tie-breaker, since payments created
4048 # within the same transaction will have identical payment_ts's
4055 for (qw/order_by limit offset/) {
4056 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4059 if(defined $filters->{where}) {
4060 foreach (keys %{$filters->{where}}) {
4061 # don't allow the caller to expand the result set to other users
4062 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4066 my $payment_ids = $e->json_query($query);
4067 for my $pid (@$payment_ids) {
4068 my $pay = $e->retrieve_money_payment([
4073 mbt => ['summary', 'circulation', 'grocery'],
4074 circ => ['target_copy'],
4075 acp => ['call_number'],
4083 xact_type => $pay->xact->summary->xact_type,
4084 last_billing_type => $pay->xact->summary->last_billing_type,
4087 if($pay->xact->summary->xact_type eq 'circulation') {
4088 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4089 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4092 $pay->xact($pay->xact->id); # de-flesh
4093 $conn->respond($resp);
4101 __PACKAGE__->register_method (
4102 method => 'negative_balance_users',
4103 api_name => 'open-ils.actor.users.negative_balance',
4106 Returns all users that have an overall negative balance
4107 @param auth Authentication token
4108 @param org_id The context org unit as an ID or list of IDs. This will be the home
4109 library of the user. If no org_unit is specified, no org unit filter is applied
4113 sub negative_balance_users {
4114 my($self, $conn, $auth, $org_id) = @_;
4116 my $e = new_editor(authtoken => $auth);
4117 return $e->die_event unless $e->checkauth;
4118 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4122 mous => ['usr', 'balance_owed'],
4125 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4126 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4143 where => {'+mous' => {balance_owed => {'<' => 0}}}
4146 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4148 my $list = $e->json_query($query, {timeout => 600});
4150 for my $data (@$list) {
4152 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4153 balance_owed => $data->{balance_owed},
4154 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4161 __PACKAGE__->register_method(
4162 method => "request_password_reset",
4163 api_name => "open-ils.actor.patron.password_reset.request",
4165 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4166 "method for changing a user's password. The UUID token is distributed via A/T " .
4167 "templates (i.e. email to the user).",
4169 { desc => 'user_id_type', type => 'string' },
4170 { desc => 'user_id', type => 'string' },
4171 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4173 return => {desc => '1 on success, Event on error'}
4176 sub request_password_reset {
4177 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4179 # Check to see if password reset requests are already being throttled:
4180 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4182 my $e = new_editor(xact => 1);
4185 # Get the user, if any, depending on the input value
4186 if ($user_id_type eq 'username') {
4187 $user = $e->search_actor_user({usrname => $user_id})->[0];
4190 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4192 } elsif ($user_id_type eq 'barcode') {
4193 my $card = $e->search_actor_card([
4194 {barcode => $user_id},
4195 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4198 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4203 # If the user doesn't have an email address, we can't help them
4204 if (!$user->email) {
4206 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4209 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4210 if ($email_must_match) {
4211 if (lc($user->email) ne lc($email)) {
4212 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4216 _reset_password_request($conn, $e, $user);
4219 # Once we have the user, we can issue the password reset request
4220 # XXX Add a wrapper method that accepts barcode + email input
4221 sub _reset_password_request {
4222 my ($conn, $e, $user) = @_;
4224 # 1. Get throttle threshold and time-to-live from OU_settings
4225 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4226 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4228 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4230 # 2. Get time of last request and number of active requests (num_active)
4231 my $active_requests = $e->json_query({
4237 transform => 'COUNT'
4240 column => 'request_time',
4246 has_been_reset => { '=' => 'f' },
4247 request_time => { '>' => $threshold_time }
4251 # Guard against no active requests
4252 if ($active_requests->[0]->{'request_time'}) {
4253 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4254 my $now = DateTime::Format::ISO8601->new();
4256 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4257 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4258 ($last_request->add_duration('1 minute') > $now)) {
4259 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4261 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4265 # TODO Check to see if the user is in a password-reset-restricted group
4267 # Otherwise, go ahead and try to get the user.
4269 # Check the number of active requests for this user
4270 $active_requests = $e->json_query({
4276 transform => 'COUNT'
4281 usr => { '=' => $user->id },
4282 has_been_reset => { '=' => 'f' },
4283 request_time => { '>' => $threshold_time }
4287 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4289 # if less than or equal to per-user threshold, proceed; otherwise, return event
4290 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4291 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4293 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4296 # Create the aupr object and insert into the database
4297 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4298 my $uuid = create_uuid_as_string(UUID_V4);
4299 $reset_request->uuid($uuid);
4300 $reset_request->usr($user->id);
4302 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4305 # Create an event to notify user of the URL to reset their password
4307 # Can we stuff this in the user_data param for trigger autocreate?
4308 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4310 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4311 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4314 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4319 __PACKAGE__->register_method(
4320 method => "commit_password_reset",
4321 api_name => "open-ils.actor.patron.password_reset.commit",
4323 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4324 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4325 "with the supplied password.",
4327 { desc => 'uuid', type => 'string' },
4328 { desc => 'password', type => 'string' },
4330 return => {desc => '1 on success, Event on error'}
4333 sub commit_password_reset {
4334 my($self, $conn, $uuid, $password) = @_;
4336 # Check to see if password reset requests are already being throttled:
4337 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4338 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4339 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4341 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4344 my $e = new_editor(xact => 1);
4346 my $aupr = $e->search_actor_usr_password_reset({
4353 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4355 my $user_id = $aupr->[0]->usr;
4356 my $user = $e->retrieve_actor_user($user_id);
4358 # Ensure we're still within the TTL for the request
4359 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4360 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4361 if ($threshold < DateTime->now(time_zone => 'local')) {
4363 $logger->info("Password reset request needed to be submitted before $threshold");
4364 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4367 # Check complexity of password against OU-defined regex
4368 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4372 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4373 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4374 $is_strong = check_password_strength_custom($password, $pw_regex);
4376 $is_strong = check_password_strength_default($password);
4381 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4384 # All is well; update the password
4385 modify_migrated_user_password($e, $user->id, $password);
4387 # And flag that this password reset request has been honoured
4388 $aupr->[0]->has_been_reset('t');
4389 $e->update_actor_usr_password_reset($aupr->[0]);
4395 sub check_password_strength_default {
4396 my $password = shift;
4397 # Use the default set of checks
4398 if ( (length($password) < 7) or
4399 ($password !~ m/.*\d+.*/) or
4400 ($password !~ m/.*[A-Za-z]+.*/)
4407 sub check_password_strength_custom {
4408 my ($password, $pw_regex) = @_;
4410 $pw_regex = qr/$pw_regex/;
4411 if ($password !~ /$pw_regex/) {
4417 __PACKAGE__->register_method(
4418 method => "fire_test_notification",
4419 api_name => "open-ils.actor.event.test_notification"
4422 sub fire_test_notification {
4423 my($self, $conn, $auth, $args) = @_;
4424 my $e = new_editor(authtoken => $auth);
4425 return $e->event unless $e->checkauth;
4426 if ($e->requestor->id != $$args{target}) {
4427 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4428 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4431 my $event_hook = $$args{hook} or return $e->event;
4432 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4434 my $usr = $e->retrieve_actor_user($$args{target});
4435 return $e->event unless $usr;
4437 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4441 __PACKAGE__->register_method(
4442 method => "event_def_opt_in_settings",
4443 api_name => "open-ils.actor.event_def.opt_in.settings",
4446 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4448 { desc => 'Authentication token', type => 'string'},
4450 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4455 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4462 sub event_def_opt_in_settings {
4463 my($self, $conn, $auth, $org_id) = @_;
4464 my $e = new_editor(authtoken => $auth);
4465 return $e->event unless $e->checkauth;
4467 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4468 return $e->event unless
4469 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4471 $org_id = $e->requestor->home_ou;
4474 # find all config.user_setting_type's related to event_defs for the requested org unit
4475 my $types = $e->json_query({
4476 select => {cust => ['name']},
4477 from => {atevdef => 'cust'},
4480 owner => $U->get_org_ancestors($org_id), # context org plus parents
4487 $conn->respond($_) for
4488 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4495 __PACKAGE__->register_method(
4496 method => "user_circ_history",
4497 api_name => "open-ils.actor.history.circ",
4501 desc => 'Returns user circ history objects for the calling user',
4503 { desc => 'Authentication token', type => 'string'},
4504 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4507 desc => q/Stream of 'auch' circ history objects/,
4513 __PACKAGE__->register_method(
4514 method => "user_circ_history",
4515 api_name => "open-ils.actor.history.circ.clear",
4518 desc => 'Delete all user circ history entries for the calling user',
4520 { desc => 'Authentication token', type => 'string'},
4521 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4524 desc => q/1 on success, event on error/,
4530 __PACKAGE__->register_method(
4531 method => "user_circ_history",
4532 api_name => "open-ils.actor.history.circ.print",
4535 desc => q/Returns printable output for the caller's circ history objects/,
4537 { desc => 'Authentication token', type => 'string'},
4538 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4541 desc => q/An action_trigger.event object or error event./,
4547 __PACKAGE__->register_method(
4548 method => "user_circ_history",
4549 api_name => "open-ils.actor.history.circ.email",
4552 desc => q/Emails the caller's circ history/,
4554 { desc => 'Authentication token', type => 'string'},
4555 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4556 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4559 desc => q/undef, or event on error/
4564 sub user_circ_history {
4565 my ($self, $conn, $auth, $options) = @_;
4568 my $for_print = ($self->api_name =~ /print/);
4569 my $for_email = ($self->api_name =~ /email/);
4570 my $for_clear = ($self->api_name =~ /clear/);
4572 # No perm check is performed. Caller may only access his/her own
4573 # circ history entries.
4574 my $e = new_editor(authtoken => $auth);
4575 return $e->event unless $e->checkauth;
4578 if (!$for_clear) { # clear deletes all
4579 $limits{offset} = $options->{offset} if defined $options->{offset};
4580 $limits{limit} = $options->{limit} if defined $options->{limit};
4583 my %circ_id_filter = $options->{circ_ids} ?
4584 (id => $options->{circ_ids}) : ();
4586 my $circs = $e->search_action_user_circ_history([
4587 { usr => $e->requestor->id,
4590 { # order newest to oldest by default
4591 order_by => {auch => 'xact_start DESC'},
4594 {substream => 1} # could be a large list
4598 return $U->fire_object_event(undef,
4599 'circ.format.history.print', $circs, $e->requestor->home_ou);
4602 $e->xact_begin if $for_clear;
4603 $conn->respond_complete(1) if $for_email; # no sense in waiting
4605 for my $circ (@$circs) {
4608 # events will be fired from action_trigger_runner
4609 $U->create_events_for_hook('circ.format.history.email',
4610 $circ, $e->editor->home_ou, undef, undef, 1);
4612 } elsif ($for_clear) {
4614 $e->delete_action_user_circ_history($circ)
4615 or return $e->die_event;
4618 $conn->respond($circ);
4631 __PACKAGE__->register_method(
4632 method => "user_visible_holds",
4633 api_name => "open-ils.actor.history.hold.visible",
4636 desc => 'Returns the set of opt-in visible holds',
4638 { desc => 'Authentication token', type => 'string'},
4639 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4640 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4643 desc => q/An object with 1 field: "hold"/,
4649 __PACKAGE__->register_method(
4650 method => "user_visible_holds",
4651 api_name => "open-ils.actor.history.hold.visible.print",
4654 desc => 'Returns printable output for the set of opt-in visible holds',
4656 { desc => 'Authentication token', type => 'string'},
4657 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4658 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4661 desc => q/An action_trigger.event object or error event./,
4667 __PACKAGE__->register_method(
4668 method => "user_visible_holds",
4669 api_name => "open-ils.actor.history.hold.visible.email",
4672 desc => 'Emails the set of opt-in visible holds to the requestor',
4674 { desc => 'Authentication token', type => 'string'},
4675 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4676 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4679 desc => q/undef, or event on error/
4684 sub user_visible_holds {
4685 my($self, $conn, $auth, $user_id, $options) = @_;
4688 my $for_print = ($self->api_name =~ /print/);
4689 my $for_email = ($self->api_name =~ /email/);
4690 my $e = new_editor(authtoken => $auth);
4691 return $e->event unless $e->checkauth;
4693 $user_id ||= $e->requestor->id;
4695 $options->{limit} ||= 50;
4696 $options->{offset} ||= 0;
4698 if($user_id != $e->requestor->id) {
4699 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4700 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4701 return $e->event unless $e->allowed($perm, $user->home_ou);
4704 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4706 my $data = $e->json_query({
4707 from => [$db_func, $user_id],
4708 limit => $$options{limit},
4709 offset => $$options{offset}
4711 # TODO: I only want IDs. code below didn't get me there
4712 # {"select":{"au":[{"column":"id", "result_field":"id",
4713 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4718 return undef unless @$data;
4722 # collect the batch of objects
4726 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4727 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4731 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4732 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4735 } elsif ($for_email) {
4737 $conn->respond_complete(1) if $for_email; # no sense in waiting
4745 my $hold = $e->retrieve_action_hold_request($id);
4746 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4747 # events will be fired from action_trigger_runner
4751 my $circ = $e->retrieve_action_circulation($id);
4752 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4753 # events will be fired from action_trigger_runner
4757 } else { # just give me the data please
4765 my $hold = $e->retrieve_action_hold_request($id);
4766 $conn->respond({hold => $hold});
4770 my $circ = $e->retrieve_action_circulation($id);
4773 summary => $U->create_circ_chain_summary($e, $id)
4782 __PACKAGE__->register_method(
4783 method => "user_saved_search_cud",
4784 api_name => "open-ils.actor.user.saved_search.cud",
4787 desc => 'Create/Update/Delete Access to user saved searches',
4789 { desc => 'Authentication token', type => 'string' },
4790 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4793 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4799 __PACKAGE__->register_method(
4800 method => "user_saved_search_cud",
4801 api_name => "open-ils.actor.user.saved_search.retrieve",
4804 desc => 'Retrieve a saved search object',
4806 { desc => 'Authentication token', type => 'string' },
4807 { desc => 'Saved Search ID', type => 'number' }
4810 desc => q/The saved search object, Event on error/,
4816 sub user_saved_search_cud {
4817 my( $self, $client, $auth, $search ) = @_;
4818 my $e = new_editor( authtoken=>$auth );
4819 return $e->die_event unless $e->checkauth;
4821 my $o_search; # prior version of the object, if any
4822 my $res; # to be returned
4824 # branch on the operation type
4826 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4828 # Get the old version, to check ownership
4829 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4830 or return $e->die_event;
4832 # You can't read somebody else's search
4833 return OpenILS::Event->new('BAD_PARAMS')
4834 unless $o_search->owner == $e->requestor->id;
4840 $e->xact_begin; # start an editor transaction
4842 if( $search->isnew ) { # Create
4844 # You can't create a search for somebody else
4845 return OpenILS::Event->new('BAD_PARAMS')
4846 unless $search->owner == $e->requestor->id;
4848 $e->create_actor_usr_saved_search( $search )
4849 or return $e->die_event;
4853 } elsif( $search->ischanged ) { # Update
4855 # You can't change ownership of a search
4856 return OpenILS::Event->new('BAD_PARAMS')
4857 unless $search->owner == $e->requestor->id;
4859 # Get the old version, to check ownership
4860 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4861 or return $e->die_event;
4863 # You can't update somebody else's search
4864 return OpenILS::Event->new('BAD_PARAMS')
4865 unless $o_search->owner == $e->requestor->id;
4868 $e->update_actor_usr_saved_search( $search )
4869 or return $e->die_event;
4873 } elsif( $search->isdeleted ) { # Delete
4875 # Get the old version, to check ownership
4876 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4877 or return $e->die_event;
4879 # You can't delete somebody else's search
4880 return OpenILS::Event->new('BAD_PARAMS')
4881 unless $o_search->owner == $e->requestor->id;
4884 $e->delete_actor_usr_saved_search( $o_search )
4885 or return $e->die_event;
4896 __PACKAGE__->register_method(
4897 method => "get_barcodes",
4898 api_name => "open-ils.actor.get_barcodes"
4902 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4903 my $e = new_editor(authtoken => $auth);
4904 return $e->event unless $e->checkauth;
4905 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4907 my $db_result = $e->json_query(
4909 'evergreen.get_barcodes',
4910 $org_id, $context, $barcode,
4914 if($context =~ /actor/) {
4915 my $filter_result = ();
4917 foreach my $result (@$db_result) {
4918 if($result->{type} eq 'actor') {
4919 if($e->requestor->id != $result->{id}) {
4920 $patron = $e->retrieve_actor_user($result->{id});
4922 push(@$filter_result, $e->event);
4925 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4926 push(@$filter_result, $result);
4929 push(@$filter_result, $e->event);
4933 push(@$filter_result, $result);
4937 push(@$filter_result, $result);
4940 return $filter_result;
4946 __PACKAGE__->register_method(
4947 method => 'address_alert_test',
4948 api_name => 'open-ils.actor.address_alert.test',
4950 desc => "Tests a set of address fields to determine if they match with an address_alert",
4952 {desc => 'Authentication token', type => 'string'},
4953 {desc => 'Org Unit', type => 'number'},
4954 {desc => 'Fields', type => 'hash'},
4956 return => {desc => 'List of matching address_alerts'}
4960 sub address_alert_test {
4961 my ($self, $client, $auth, $org_unit, $fields) = @_;
4962 return [] unless $fields and grep {$_} values %$fields;
4964 my $e = new_editor(authtoken => $auth);
4965 return $e->event unless $e->checkauth;
4966 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4967 $org_unit ||= $e->requestor->ws_ou;
4969 my $alerts = $e->json_query({
4971 'actor.address_alert_matches',
4979 $$fields{post_code},
4980 $$fields{mailing_address},
4981 $$fields{billing_address}
4985 # map the json_query hashes to real objects
4987 map {$e->retrieve_actor_address_alert($_)}
4988 (map {$_->{id}} @$alerts)
4992 __PACKAGE__->register_method(
4993 method => "mark_users_contact_invalid",
4994 api_name => "open-ils.actor.invalidate.email",
4996 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",
4998 {desc => "Authentication token", type => "string"},
4999 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5000 {desc => "Additional note text (optional)", type => "string"},
5001 {desc => "penalty org unit ID (optional)", type => "number"},
5002 {desc => "Email address (optional)", type => "string"}
5004 return => {desc => "Event describing success or failure", type => "object"}
5008 __PACKAGE__->register_method(
5009 method => "mark_users_contact_invalid",
5010 api_name => "open-ils.actor.invalidate.day_phone",
5012 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",
5014 {desc => "Authentication token", type => "string"},
5015 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5016 {desc => "Additional note text (optional)", type => "string"},
5017 {desc => "penalty org unit ID (optional)", type => "number"},
5018 {desc => "Phone Number (optional)", type => "string"}
5020 return => {desc => "Event describing success or failure", type => "object"}
5024 __PACKAGE__->register_method(
5025 method => "mark_users_contact_invalid",
5026 api_name => "open-ils.actor.invalidate.evening_phone",
5028 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",
5030 {desc => "Authentication token", type => "string"},
5031 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5032 {desc => "Additional note text (optional)", type => "string"},
5033 {desc => "penalty org unit ID (optional)", type => "number"},
5034 {desc => "Phone Number (optional)", type => "string"}
5036 return => {desc => "Event describing success or failure", type => "object"}
5040 __PACKAGE__->register_method(
5041 method => "mark_users_contact_invalid",
5042 api_name => "open-ils.actor.invalidate.other_phone",
5044 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",
5046 {desc => "Authentication token", type => "string"},
5047 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5048 {desc => "Additional note text (optional)", type => "string"},
5049 {desc => "penalty org unit ID (optional, default to top of org tree)",
5051 {desc => "Phone Number (optional)", type => "string"}
5053 return => {desc => "Event describing success or failure", type => "object"}
5057 sub mark_users_contact_invalid {
5058 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5060 # This method invalidates an email address or a phone_number which
5061 # removes the bad email address or phone number, copying its contents
5062 # to a patron note, and institutes a standing penalty for "bad email"
5063 # or "bad phone number" which is cleared when the user is saved or
5064 # optionally only when the user is saved with an email address or
5065 # phone number (or staff manually delete the penalty).
5067 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5069 my $e = new_editor(authtoken => $auth, xact => 1);
5070 return $e->die_event unless $e->checkauth;
5073 if (defined $patron_id && $patron_id ne "") {
5074 $howfind = {usr => $patron_id};
5075 } elsif (defined $contact && $contact ne "") {
5076 $howfind = {$contact_type => $contact};
5078 # Error out if no patron id set or no contact is set.
5079 return OpenILS::Event->new('BAD_PARAMS');
5082 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5083 $e, $contact_type, $howfind,
5084 $addl_note, $penalty_ou, $e->requestor->id
5088 # Putting the following method in open-ils.actor is a bad fit, except in that
5089 # it serves an interface that lives under 'actor' in the templates directory,
5090 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5092 __PACKAGE__->register_method(
5093 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5094 method => "get_all_at_reactors_in_use",
5099 { name => 'authtoken', type => 'string' }
5102 desc => 'list of reactor names', type => 'array'
5107 sub get_all_at_reactors_in_use {
5108 my ($self, $conn, $auth) = @_;
5110 my $e = new_editor(authtoken => $auth);
5111 $e->checkauth or return $e->die_event;
5112 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5114 my $reactors = $e->json_query({
5116 atevdef => [{column => "reactor", transform => "distinct"}]
5118 from => {atevdef => {}}
5121 return $e->die_event unless ref $reactors eq "ARRAY";
5124 return [ map { $_->{reactor} } @$reactors ];
5127 __PACKAGE__->register_method(
5128 method => "filter_group_entry_crud",
5129 api_name => "open-ils.actor.filter_group_entry.crud",
5132 Provides CRUD access to filter group entry objects. These are not full accessible
5133 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5134 are not accessible via PCRUD (because they have no fields against which to link perms)
5137 {desc => "Authentication token", type => "string"},
5138 {desc => "Entry ID / Entry Object", type => "number"},
5139 {desc => "Additional note text (optional)", type => "string"},
5140 {desc => "penalty org unit ID (optional, default to top of org tree)",
5144 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5150 sub filter_group_entry_crud {
5151 my ($self, $conn, $auth, $arg) = @_;
5153 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5154 my $e = new_editor(authtoken => $auth, xact => 1);
5155 return $e->die_event unless $e->checkauth;
5161 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5162 or return $e->die_event;
5164 return $e->die_event unless $e->allowed(
5165 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5167 my $query = $arg->query;
5168 $query = $e->create_actor_search_query($query) or return $e->die_event;
5169 $arg->query($query->id);
5170 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5171 $entry->query($query);
5176 } elsif ($arg->ischanged) {
5178 my $entry = $e->retrieve_actor_search_filter_group_entry([
5181 flesh_fields => {asfge => ['grp']}
5183 ]) or return $e->die_event;
5185 return $e->die_event unless $e->allowed(
5186 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5188 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5189 $arg->query($arg->query->id);
5190 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5191 $arg->query($query);
5196 } elsif ($arg->isdeleted) {
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', $entry->grp->owner);
5208 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5209 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5222 my $entry = $e->retrieve_actor_search_filter_group_entry([
5225 flesh_fields => {asfge => ['grp', 'query']}
5227 ]) or return $e->die_event;
5229 return $e->die_event unless $e->allowed(
5230 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5231 $entry->grp->owner);
5234 $entry->grp($entry->grp->id); # for consistency