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);
492 if(($patron->photo_url)) {
493 return $e->die_event unless
494 $e->allowed('UPDATE_USER_PHOTO_URL', $patron->home_ou);
497 $new_patron = $patron;
499 # Did auth checking above already.
500 $old_patron = $e->retrieve_actor_user($patron->id) or
501 return $e->die_event;
503 $renew_hook = 'au.renewed' if ($old_patron->expire_date ne $new_patron->expire_date);
505 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
506 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
507 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
509 $barred_hook = $U->is_true($new_patron->barred) ?
510 'au.barred' : 'au.unbarred';
513 if($old_patron->photo_url ne $new_patron->photo_url) {
514 my $perm = 'UPDATE_USER_PHOTO_URL';
515 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
518 # update the password by itself to avoid the password protection magic
519 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
520 modify_migrated_user_password($e, $patron->id, $patron->passwd);
521 $new_patron->passwd(''); # subsequent update will set
522 # actor.usr.passwd to MD5('')
526 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
529 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
532 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
535 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
538 # re-update the patron if anything has happened to him during this process
539 if($new_patron->ischanged()) {
540 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
544 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
547 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
550 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
553 $evt = apply_invalid_addr_penalty($e, $patron);
558 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
560 $tses->request('open-ils.trigger.event.autocreate',
561 'au.created', $new_patron, $new_patron->home_ou);
563 $tses->request('open-ils.trigger.event.autocreate',
564 'au.updated', $new_patron, $new_patron->home_ou);
566 $tses->request('open-ils.trigger.event.autocreate', $renew_hook,
567 $new_patron, $new_patron->home_ou) if $renew_hook;
569 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
570 $new_patron, $new_patron->home_ou) if $barred_hook;
573 $e->xact_begin; # $e->rollback is called in new_flesh_user
574 return flesh_user($new_patron->id(), $e);
577 sub apply_invalid_addr_penalty {
581 # grab the invalid address penalty if set
582 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
584 my ($addr_penalty) = grep
585 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
587 # do we enforce invalid address penalty
588 my $enforce = $U->ou_ancestor_setting_value(
589 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
591 my $addrs = $e->search_actor_user_address(
592 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
593 my $addr_count = scalar(@$addrs);
595 if($addr_count == 0 and $addr_penalty) {
597 # regardless of any settings, remove the penalty when the user has no invalid addresses
598 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
601 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
603 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
604 my $depth = $ptype->org_depth;
605 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
606 $ctx_org = $patron->home_ou unless defined $ctx_org;
608 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
609 $penalty->usr($patron->id);
610 $penalty->org_unit($ctx_org);
611 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
613 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
628 "standing_penalties",
638 push @$fields, "home_ou" if $home_ou;
639 return new_flesh_user($id, $fields, $e );
647 # clone and clear stuff that would break the database
651 my $new_patron = $patron->clone;
653 $new_patron->clear_billing_address();
654 $new_patron->clear_mailing_address();
655 $new_patron->clear_addresses();
656 $new_patron->clear_card();
657 $new_patron->clear_cards();
658 $new_patron->clear_id();
659 $new_patron->clear_isnew();
660 $new_patron->clear_ischanged();
661 $new_patron->clear_isdeleted();
662 $new_patron->clear_stat_cat_entries();
663 $new_patron->clear_waiver_entries();
664 $new_patron->clear_permissions();
665 $new_patron->clear_standing_penalties();
676 return (undef, $e->die_event) unless
677 $e->allowed('CREATE_USER', $patron->home_ou);
679 my $ex = $e->search_actor_user(
680 {usrname => $patron->usrname}, {idlist => 1});
681 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
683 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
685 # do a dance to get the password hashed securely
686 my $saved_password = $patron->passwd;
688 $e->create_actor_user($patron) or return (undef, $e->die_event);
689 modify_migrated_user_password($e, $patron->id, $saved_password);
691 my $id = $patron->id; # added by CStoreEditor
693 $logger->info("Successfully created new user [$id] in DB");
694 return ($e->retrieve_actor_user($id), undef);
698 sub check_group_perm {
699 my( $e, $requestor, $patron ) = @_;
702 # first let's see if the requestor has
703 # priveleges to update this user in any way
704 if( ! $patron->isnew ) {
705 my $p = $e->retrieve_actor_user($patron->id);
707 # If we are the requestor (trying to update our own account)
708 # and we are not trying to change our profile, we're good
709 if( $p->id == $requestor->id and
710 $p->profile == $patron->profile ) {
715 $evt = group_perm_failed($e, $requestor, $p);
719 # They are allowed to edit this patron.. can they put the
720 # patron into the group requested?
721 $evt = group_perm_failed($e, $requestor, $patron);
727 sub group_perm_failed {
728 my( $e, $requestor, $patron ) = @_;
732 my $grpid = $patron->profile;
736 $logger->debug("user update looking for group perm for group $grpid");
737 $grp = $e->retrieve_permission_grp_tree($grpid);
739 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
741 $logger->info("user update checking perm $perm on user ".
742 $requestor->id." for update/create on user username=".$patron->usrname);
744 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
750 my( $e, $patron, $noperm) = @_;
752 $logger->info("Updating patron ".$patron->id." in DB");
757 return (undef, $e->die_event)
758 unless $e->allowed('UPDATE_USER', $patron->home_ou);
761 if(!$patron->ident_type) {
762 $patron->clear_ident_type;
763 $patron->clear_ident_value;
766 $evt = verify_last_xact($e, $patron);
767 return (undef, $evt) if $evt;
769 $e->update_actor_user($patron) or return (undef, $e->die_event);
771 # re-fetch the user to pick up the latest last_xact_id value
772 # to avoid collisions.
773 $patron = $e->retrieve_actor_user($patron->id);
778 sub verify_last_xact {
779 my( $e, $patron ) = @_;
780 return undef unless $patron->id and $patron->id > 0;
781 my $p = $e->retrieve_actor_user($patron->id);
782 my $xact = $p->last_xact_id;
783 return undef unless $xact;
784 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
785 return OpenILS::Event->new('XACT_COLLISION')
786 if $xact ne $patron->last_xact_id;
791 sub _check_dup_ident {
792 my( $session, $patron ) = @_;
794 return undef unless $patron->ident_value;
797 ident_type => $patron->ident_type,
798 ident_value => $patron->ident_value,
801 $logger->debug("patron update searching for dup ident values: " .
802 $patron->ident_type . ':' . $patron->ident_value);
804 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
806 my $dups = $session->request(
807 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
810 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
817 sub _add_update_addresses {
821 my $new_patron = shift;
825 my $current_id; # id of the address before creation
827 my $addresses = $patron->addresses();
829 for my $address (@$addresses) {
831 next unless ref $address;
832 $current_id = $address->id();
834 if( $patron->billing_address() and
835 $patron->billing_address() == $current_id ) {
836 $logger->info("setting billing addr to $current_id");
837 $new_patron->billing_address($address->id());
838 $new_patron->ischanged(1);
841 if( $patron->mailing_address() and
842 $patron->mailing_address() == $current_id ) {
843 $new_patron->mailing_address($address->id());
844 $logger->info("setting mailing addr to $current_id");
845 $new_patron->ischanged(1);
849 if($address->isnew()) {
851 $address->usr($new_patron->id());
853 ($address, $evt) = _add_address($e,$address);
854 return (undef, $evt) if $evt;
856 # we need to get the new id
857 if( $patron->billing_address() and
858 $patron->billing_address() == $current_id ) {
859 $new_patron->billing_address($address->id());
860 $logger->info("setting billing addr to $current_id");
861 $new_patron->ischanged(1);
864 if( $patron->mailing_address() and
865 $patron->mailing_address() == $current_id ) {
866 $new_patron->mailing_address($address->id());
867 $logger->info("setting mailing addr to $current_id");
868 $new_patron->ischanged(1);
871 } elsif($address->ischanged() ) {
873 ($address, $evt) = _update_address($e, $address);
874 return (undef, $evt) if $evt;
876 } elsif($address->isdeleted() ) {
878 if( $address->id() == $new_patron->mailing_address() ) {
879 $new_patron->clear_mailing_address();
880 ($new_patron, $evt) = _update_patron($e, $new_patron);
881 return (undef, $evt) if $evt;
884 if( $address->id() == $new_patron->billing_address() ) {
885 $new_patron->clear_billing_address();
886 ($new_patron, $evt) = _update_patron($e, $new_patron);
887 return (undef, $evt) if $evt;
890 $evt = _delete_address($e, $address);
891 return (undef, $evt) if $evt;
895 return ( $new_patron, undef );
899 # adds an address to the db and returns the address with new id
901 my($e, $address) = @_;
902 $address->clear_id();
904 $logger->info("Creating new address at street ".$address->street1);
906 # put the address into the database
907 $e->create_actor_user_address($address) or return (undef, $e->die_event);
908 return ($address, undef);
912 sub _update_address {
913 my( $e, $address ) = @_;
915 $logger->info("Updating address ".$address->id." in the DB");
917 $e->update_actor_user_address($address) or return (undef, $e->die_event);
919 return ($address, undef);
924 sub _add_update_cards {
928 my $new_patron = shift;
932 my $virtual_id; #id of the card before creation
934 my $card_changed = 0;
935 my $cards = $patron->cards();
936 for my $card (@$cards) {
938 $card->usr($new_patron->id());
940 if(ref($card) and $card->isnew()) {
942 $virtual_id = $card->id();
943 ( $card, $evt ) = _add_card($e, $card);
944 return (undef, $evt) if $evt;
946 #if(ref($patron->card)) { $patron->card($patron->card->id); }
947 if($patron->card() == $virtual_id) {
948 $new_patron->card($card->id());
949 $new_patron->ischanged(1);
953 } elsif( ref($card) and $card->ischanged() ) {
954 $evt = _update_card($e, $card);
955 return (undef, $evt) if $evt;
960 $U->create_events_for_hook('au.barcode_changed', $new_patron, $e->requestor->ws_ou)
963 return ( $new_patron, undef );
967 # adds an card to the db and returns the card with new id
969 my( $e, $card ) = @_;
972 $logger->info("Adding new patron card ".$card->barcode);
974 $e->create_actor_card($card) or return (undef, $e->die_event);
976 return ( $card, undef );
980 # returns event on error. returns undef otherwise
982 my( $e, $card ) = @_;
983 $logger->info("Updating patron card ".$card->id);
985 $e->update_actor_card($card) or return $e->die_event;
990 sub _add_update_waiver_entries {
993 my $new_patron = shift;
996 my $waiver_entries = $patron->waiver_entries();
997 for my $waiver (@$waiver_entries) {
998 next unless ref $waiver;
999 $waiver->usr($new_patron->id());
1000 if ($waiver->isnew()) {
1001 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
1002 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
1003 $logger->info("Adding new patron waiver entry");
1004 $waiver->clear_id();
1005 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1006 } elsif ($waiver->ischanged()) {
1007 $logger->info("Updating patron waiver entry " . $waiver->id);
1008 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1009 } elsif ($waiver->isdeleted()) {
1010 $logger->info("Deleting patron waiver entry " . $waiver->id);
1011 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1014 return ($new_patron, undef);
1018 # returns event on error. returns undef otherwise
1019 sub _delete_address {
1020 my( $e, $address ) = @_;
1022 $logger->info("Deleting address ".$address->id." from DB");
1024 $e->delete_actor_user_address($address) or return $e->die_event;
1030 sub _add_survey_responses {
1031 my ($e, $patron, $new_patron) = @_;
1033 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1035 my $responses = $patron->survey_responses;
1039 $_->usr($new_patron->id) for (@$responses);
1041 my $evt = $U->simplereq( "open-ils.circ",
1042 "open-ils.circ.survey.submit.user_id", $responses );
1044 return (undef, $evt) if defined($U->event_code($evt));
1048 return ( $new_patron, undef );
1051 sub _clear_badcontact_penalties {
1052 my ($e, $old_patron, $new_patron) = @_;
1054 return ($new_patron, undef) unless $old_patron;
1056 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1058 # This ignores whether the caller of update_patron has any permission
1059 # to remove penalties, but these penalties no longer make sense
1060 # if an email address field (for example) is changed (and the caller must
1061 # have perms to do *that*) so there's no reason not to clear the penalties.
1063 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1065 "+csp" => {"name" => [values(%$PNM)]},
1066 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1068 "join" => {"csp" => {}},
1070 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1072 ]) or return (undef, $e->die_event);
1074 return ($new_patron, undef) unless @$bad_contact_penalties;
1076 my @penalties_to_clear;
1077 my ($field, $penalty_name);
1079 # For each field that might have an associated bad contact penalty,
1080 # check for such penalties and add them to the to-clear list if that
1081 # field has changed.
1082 while (($field, $penalty_name) = each(%$PNM)) {
1083 if ($old_patron->$field ne $new_patron->$field) {
1084 push @penalties_to_clear, grep {
1085 $_->standing_penalty->name eq $penalty_name
1086 } @$bad_contact_penalties;
1090 foreach (@penalties_to_clear) {
1091 # Note that this "archives" penalties, in the terminology of the staff
1092 # client, instead of just deleting them. This may assist reporting,
1093 # or preserving old contact information when it is still potentially
1095 $_->standing_penalty($_->standing_penalty->id); # deflesh
1096 $_->stop_date('now');
1097 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1100 return ($new_patron, undef);
1104 sub _create_stat_maps {
1106 my($e, $patron, $new_patron) = @_;
1108 my $maps = $patron->stat_cat_entries();
1110 for my $map (@$maps) {
1112 my $method = "update_actor_stat_cat_entry_user_map";
1114 if ($map->isdeleted()) {
1115 $method = "delete_actor_stat_cat_entry_user_map";
1117 } elsif ($map->isnew()) {
1118 $method = "create_actor_stat_cat_entry_user_map";
1123 $map->target_usr($new_patron->id);
1125 $logger->info("Updating stat entry with method $method and map $map");
1127 $e->$method($map) or return (undef, $e->die_event);
1130 return ($new_patron, undef);
1133 sub _create_perm_maps {
1135 my($e, $patron, $new_patron) = @_;
1137 my $maps = $patron->permissions;
1139 for my $map (@$maps) {
1141 my $method = "update_permission_usr_perm_map";
1142 if ($map->isdeleted()) {
1143 $method = "delete_permission_usr_perm_map";
1144 } elsif ($map->isnew()) {
1145 $method = "create_permission_usr_perm_map";
1149 $map->usr($new_patron->id);
1151 $logger->info( "Updating permissions with method $method and map $map" );
1153 $e->$method($map) or return (undef, $e->die_event);
1156 return ($new_patron, undef);
1160 __PACKAGE__->register_method(
1161 method => "set_user_work_ous",
1162 api_name => "open-ils.actor.user.work_ous.update",
1165 sub set_user_work_ous {
1171 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1172 return $evt if $evt;
1174 my $session = $apputils->start_db_session();
1175 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1177 for my $map (@$maps) {
1179 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1180 if ($map->isdeleted()) {
1181 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1182 } elsif ($map->isnew()) {
1183 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1187 #warn( "Updating permissions with method $method and session $ses and map $map" );
1188 $logger->info( "Updating work_ou map with method $method and map $map" );
1190 my $stat = $session->request($method, $map)->gather(1);
1191 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1195 $apputils->commit_db_session($session);
1197 return scalar(@$maps);
1201 __PACKAGE__->register_method(
1202 method => "set_user_perms",
1203 api_name => "open-ils.actor.user.permissions.update",
1206 sub set_user_perms {
1212 my $session = $apputils->start_db_session();
1214 my( $user_obj, $evt ) = $U->checkses($ses);
1215 return $evt if $evt;
1216 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1218 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1221 $all = 1 if ($U->is_true($user_obj->super_user()));
1222 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1224 for my $map (@$maps) {
1226 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1227 if ($map->isdeleted()) {
1228 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1229 } elsif ($map->isnew()) {
1230 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1234 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1235 #warn( "Updating permissions with method $method and session $ses and map $map" );
1236 $logger->info( "Updating permissions with method $method and map $map" );
1238 my $stat = $session->request($method, $map)->gather(1);
1239 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1243 $apputils->commit_db_session($session);
1245 return scalar(@$maps);
1249 __PACKAGE__->register_method(
1250 method => "user_retrieve_by_barcode",
1252 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1254 sub user_retrieve_by_barcode {
1255 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1257 my $e = new_editor(authtoken => $auth);
1258 return $e->event unless $e->checkauth;
1260 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1261 or return $e->event;
1263 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1264 return $e->event unless $e->allowed(
1265 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1272 __PACKAGE__->register_method(
1273 method => "get_user_by_id",
1275 api_name => "open-ils.actor.user.retrieve",
1278 sub get_user_by_id {
1279 my ($self, $client, $auth, $id) = @_;
1280 my $e = new_editor(authtoken=>$auth);
1281 return $e->event unless $e->checkauth;
1282 my $user = $e->retrieve_actor_user($id) or return $e->event;
1283 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1288 __PACKAGE__->register_method(
1289 method => "get_org_types",
1290 api_name => "open-ils.actor.org_types.retrieve",
1293 return $U->get_org_types();
1297 __PACKAGE__->register_method(
1298 method => "get_user_ident_types",
1299 api_name => "open-ils.actor.user.ident_types.retrieve",
1302 sub get_user_ident_types {
1303 return $ident_types if $ident_types;
1304 return $ident_types =
1305 new_editor()->retrieve_all_config_identification_type();
1309 __PACKAGE__->register_method(
1310 method => "get_org_unit",
1311 api_name => "open-ils.actor.org_unit.retrieve",
1315 my( $self, $client, $user_session, $org_id ) = @_;
1316 my $e = new_editor(authtoken => $user_session);
1318 return $e->event unless $e->checkauth;
1319 $org_id = $e->requestor->ws_ou;
1321 my $o = $e->retrieve_actor_org_unit($org_id)
1322 or return $e->event;
1326 __PACKAGE__->register_method(
1327 method => "search_org_unit",
1328 api_name => "open-ils.actor.org_unit_list.search",
1331 sub search_org_unit {
1333 my( $self, $client, $field, $value ) = @_;
1335 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1337 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1338 { $field => $value } );
1344 # build the org tree
1346 __PACKAGE__->register_method(
1347 method => "get_org_tree",
1348 api_name => "open-ils.actor.org_tree.retrieve",
1350 note => "Returns the entire org tree structure",
1356 return $U->get_org_tree($client->session->session_locale);
1360 __PACKAGE__->register_method(
1361 method => "get_org_descendants",
1362 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1365 # depth is optional. org_unit is the id
1366 sub get_org_descendants {
1367 my( $self, $client, $org_unit, $depth ) = @_;
1369 if(ref $org_unit eq 'ARRAY') {
1372 for my $i (0..scalar(@$org_unit)-1) {
1373 my $list = $U->simple_scalar_request(
1375 "open-ils.storage.actor.org_unit.descendants.atomic",
1376 $org_unit->[$i], $depth->[$i] );
1377 push(@trees, $U->build_org_tree($list));
1382 my $orglist = $apputils->simple_scalar_request(
1384 "open-ils.storage.actor.org_unit.descendants.atomic",
1385 $org_unit, $depth );
1386 return $U->build_org_tree($orglist);
1391 __PACKAGE__->register_method(
1392 method => "get_org_ancestors",
1393 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1396 # depth is optional. org_unit is the id
1397 sub get_org_ancestors {
1398 my( $self, $client, $org_unit, $depth ) = @_;
1399 my $orglist = $apputils->simple_scalar_request(
1401 "open-ils.storage.actor.org_unit.ancestors.atomic",
1402 $org_unit, $depth );
1403 return $U->build_org_tree($orglist);
1407 __PACKAGE__->register_method(
1408 method => "get_standings",
1409 api_name => "open-ils.actor.standings.retrieve"
1414 return $user_standings if $user_standings;
1415 return $user_standings =
1416 $apputils->simple_scalar_request(
1418 "open-ils.cstore.direct.config.standing.search.atomic",
1419 { id => { "!=" => undef } }
1424 __PACKAGE__->register_method(
1425 method => "get_my_org_path",
1426 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1429 sub get_my_org_path {
1430 my( $self, $client, $auth, $org_id ) = @_;
1431 my $e = new_editor(authtoken=>$auth);
1432 return $e->event unless $e->checkauth;
1433 $org_id = $e->requestor->ws_ou unless defined $org_id;
1435 return $apputils->simple_scalar_request(
1437 "open-ils.storage.actor.org_unit.full_path.atomic",
1441 __PACKAGE__->register_method(
1442 method => "retrieve_coordinates",
1443 api_name => "open-ils.actor.geo.retrieve_coordinates",
1446 {desc => 'Authentication token', type => 'string' },
1447 {type => 'number', desc => 'Context Organizational Unit'},
1448 {type => 'string', desc => 'Address to look-up as a text string'}
1450 return => { desc => 'Hash/object containing latitude and longitude for the provided address.'}
1454 sub retrieve_coordinates {
1455 my( $self, $client, $auth, $org_id, $addr_string ) = @_;
1456 my $e = new_editor(authtoken=>$auth);
1457 return $e->event unless $e->checkauth;
1458 $org_id = $e->requestor->ws_ou unless defined $org_id;
1460 return $apputils->simple_scalar_request(
1462 "open-ils.geo.retrieve_coordinates",
1463 $org_id, $addr_string );
1466 __PACKAGE__->register_method(
1467 method => "patron_adv_search",
1468 api_name => "open-ils.actor.patron.search.advanced"
1471 __PACKAGE__->register_method(
1472 method => "patron_adv_search",
1473 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1475 # Flush the response stream at most 5 patrons in for UI responsiveness.
1476 max_bundle_count => 5,
1478 desc => q/Returns a stream of fleshed user objects instead of
1479 a pile of identifiers/
1483 sub patron_adv_search {
1484 my( $self, $client, $auth, $search_hash, $search_limit,
1485 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1487 # API params sanity checks.
1488 # Exit early with empty result if no filter exists.
1489 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1490 my $fleshed = ($self->api_name =~ /fleshed/);
1491 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1493 for my $key (keys %$search_hash) {
1494 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1498 return ($fleshed ? undef : []) unless $search_ok;
1500 my $e = new_editor(authtoken=>$auth);
1501 return $e->event unless $e->checkauth;
1502 return $e->event unless $e->allowed('VIEW_USER');
1504 # depth boundary outside of which patrons must opt-in, default to 0
1505 my $opt_boundary = 0;
1506 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1508 if (not defined $search_ou) {
1509 my $depth = $U->ou_ancestor_setting_value(
1510 $e->requestor->ws_ou,
1511 'circ.patron_edit.duplicate_patron_check_depth'
1514 if (defined $depth) {
1515 $search_ou = $U->org_unit_ancestor_at_depth(
1516 $e->requestor->ws_ou, $depth
1521 my $ids = $U->storagereq(
1522 "open-ils.storage.actor.user.crazy_search", $search_hash,
1523 $search_limit, $search_sort, $include_inactive,
1524 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1526 return $ids unless $self->api_name =~ /fleshed/;
1528 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1534 # A migrated (main) password has the form:
1535 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1536 sub modify_migrated_user_password {
1537 my ($e, $user_id, $passwd) = @_;
1539 # new password gets a new salt
1540 my $new_salt = $e->json_query({
1541 from => ['actor.create_salt', 'main']})->[0];
1542 $new_salt = $new_salt->{'actor.create_salt'};
1549 md5_hex($new_salt . md5_hex($passwd)),
1557 __PACKAGE__->register_method(
1558 method => "update_passwd",
1559 api_name => "open-ils.actor.user.password.update",
1561 desc => "Update the operator's password",
1563 { desc => 'Authentication token', type => 'string' },
1564 { desc => 'New password', type => 'string' },
1565 { desc => 'Current password', type => 'string' }
1567 return => {desc => '1 on success, Event on error or incorrect current password'}
1571 __PACKAGE__->register_method(
1572 method => "update_passwd",
1573 api_name => "open-ils.actor.user.username.update",
1575 desc => "Update the operator's username",
1577 { desc => 'Authentication token', type => 'string' },
1578 { desc => 'New username', type => 'string' },
1579 { desc => 'Current password', type => 'string' }
1581 return => {desc => '1 on success, Event on error or incorrect current password'}
1585 __PACKAGE__->register_method(
1586 method => "update_passwd",
1587 api_name => "open-ils.actor.user.email.update",
1589 desc => "Update the operator's email address",
1591 { desc => 'Authentication token', type => 'string' },
1592 { desc => 'New email address', type => 'string' },
1593 { desc => 'Current password', type => 'string' }
1595 return => {desc => '1 on success, Event on error or incorrect current password'}
1600 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1601 my $e = new_editor(xact=>1, authtoken=>$auth);
1602 return $e->die_event unless $e->checkauth;
1604 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1605 or return $e->die_event;
1606 my $api = $self->api_name;
1608 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1610 return new OpenILS::Event('INCORRECT_PASSWORD');
1614 if( $api =~ /password/o ) {
1615 # NOTE: with access to the plain text password we could crypt
1616 # the password without the extra MD5 pre-hashing. Other changes
1617 # would be required. Noting here for future reference.
1618 modify_migrated_user_password($e, $db_user->id, $new_val);
1619 $db_user->passwd('');
1623 # if we don't clear the password, the user will be updated with
1624 # a hashed version of the hashed version of their password
1625 $db_user->clear_passwd;
1627 if( $api =~ /username/o ) {
1629 # make sure no one else has this username
1630 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1633 return new OpenILS::Event('USERNAME_EXISTS');
1635 $db_user->usrname($new_val);
1638 } elsif( $api =~ /email/o ) {
1639 $db_user->email($new_val);
1644 $e->update_actor_user($db_user) or return $e->die_event;
1647 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1650 # update the cached user to pick up these changes
1651 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1657 __PACKAGE__->register_method(
1658 method => "check_user_perms",
1659 api_name => "open-ils.actor.user.perm.check",
1660 notes => <<" NOTES");
1661 Takes a login session, user id, an org id, and an array of perm type strings. For each
1662 perm type, if the user does *not* have the given permission it is added
1663 to a list which is returned from the method. If all permissions
1664 are allowed, an empty list is returned
1665 if the logged in user does not match 'user_id', then the logged in user must
1666 have VIEW_PERMISSION priveleges.
1669 sub check_user_perms {
1670 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1672 my( $staff, $evt ) = $apputils->checkses($login_session);
1673 return $evt if $evt;
1675 if($staff->id ne $user_id) {
1676 if( $evt = $apputils->check_perms(
1677 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1683 for my $perm (@$perm_types) {
1684 if($apputils->check_perms($user_id, $org_id, $perm)) {
1685 push @not_allowed, $perm;
1689 return \@not_allowed
1692 __PACKAGE__->register_method(
1693 method => "check_user_perms2",
1694 api_name => "open-ils.actor.user.perm.check.multi_org",
1696 Checks the permissions on a list of perms and orgs for a user
1697 @param authtoken The login session key
1698 @param user_id The id of the user to check
1699 @param orgs The array of org ids
1700 @param perms The array of permission names
1701 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1702 if the logged in user does not match 'user_id', then the logged in user must
1703 have VIEW_PERMISSION priveleges.
1706 sub check_user_perms2 {
1707 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1709 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1710 $authtoken, $user_id, 'VIEW_PERMISSION' );
1711 return $evt if $evt;
1714 for my $org (@$orgs) {
1715 for my $perm (@$perms) {
1716 if($apputils->check_perms($user_id, $org, $perm)) {
1717 push @not_allowed, [ $org, $perm ];
1722 return \@not_allowed
1726 __PACKAGE__->register_method(
1727 method => 'check_user_perms3',
1728 api_name => 'open-ils.actor.user.perm.highest_org',
1730 Returns the highest org unit id at which a user has a given permission
1731 If the requestor does not match the target user, the requestor must have
1732 'VIEW_PERMISSION' rights at the home org unit of the target user
1733 @param authtoken The login session key
1734 @param userid The id of the user in question
1735 @param perm The permission to check
1736 @return The org unit highest in the org tree within which the user has
1737 the requested permission
1740 sub check_user_perms3 {
1741 my($self, $client, $authtoken, $user_id, $perm) = @_;
1742 my $e = new_editor(authtoken=>$authtoken);
1743 return $e->event unless $e->checkauth;
1745 my $tree = $U->get_org_tree();
1747 unless($e->requestor->id == $user_id) {
1748 my $user = $e->retrieve_actor_user($user_id)
1749 or return $e->event;
1750 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1751 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1754 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1757 __PACKAGE__->register_method(
1758 method => 'user_has_work_perm_at',
1759 api_name => 'open-ils.actor.user.has_work_perm_at',
1763 Returns a set of org unit IDs which represent the highest orgs in
1764 the org tree where the user has the requested permission. The
1765 purpose of this method is to return the smallest set of org units
1766 which represent the full expanse of the user's ability to perform
1767 the requested action. The user whose perms this method should
1768 check is implied by the authtoken. /,
1770 {desc => 'authtoken', type => 'string'},
1771 {desc => 'permission name', type => 'string'},
1772 {desc => q/user id, optional. If present, check perms for
1773 this user instead of the logged in user/, type => 'number'},
1775 return => {desc => 'An array of org IDs'}
1779 sub user_has_work_perm_at {
1780 my($self, $conn, $auth, $perm, $user_id) = @_;
1781 my $e = new_editor(authtoken=>$auth);
1782 return $e->event unless $e->checkauth;
1783 if(defined $user_id) {
1784 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1785 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1787 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1790 __PACKAGE__->register_method(
1791 method => 'user_has_work_perm_at_batch',
1792 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1796 sub user_has_work_perm_at_batch {
1797 my($self, $conn, $auth, $perms, $user_id) = @_;
1798 my $e = new_editor(authtoken=>$auth);
1799 return $e->event unless $e->checkauth;
1800 if(defined $user_id) {
1801 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1802 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1805 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1811 __PACKAGE__->register_method(
1812 method => 'check_user_perms4',
1813 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1815 Returns the highest org unit id at which a user has a given permission
1816 If the requestor does not match the target user, the requestor must have
1817 'VIEW_PERMISSION' rights at the home org unit of the target user
1818 @param authtoken The login session key
1819 @param userid The id of the user in question
1820 @param perms An array of perm names to check
1821 @return An array of orgId's representing the org unit
1822 highest in the org tree within which the user has the requested permission
1823 The arrah of orgId's has matches the order of the perms array
1826 sub check_user_perms4 {
1827 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1829 my( $staff, $target, $org, $evt );
1831 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1832 $authtoken, $userid, 'VIEW_PERMISSION' );
1833 return $evt if $evt;
1836 return [] unless ref($perms);
1837 my $tree = $U->get_org_tree();
1839 for my $p (@$perms) {
1840 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1846 __PACKAGE__->register_method(
1847 method => "user_fines_summary",
1848 api_name => "open-ils.actor.user.fines.summary",
1851 desc => 'Returns a short summary of the users total open fines, ' .
1852 'excluding voided fines Params are login_session, user_id' ,
1854 {desc => 'Authentication token', type => 'string'},
1855 {desc => 'User ID', type => 'string'} # number?
1858 desc => "a 'mous' object, event on error",
1863 sub user_fines_summary {
1864 my( $self, $client, $auth, $user_id ) = @_;
1866 my $e = new_editor(authtoken=>$auth);
1867 return $e->event unless $e->checkauth;
1869 if( $user_id ne $e->requestor->id ) {
1870 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1871 return $e->event unless
1872 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1875 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1879 __PACKAGE__->register_method(
1880 method => "user_opac_vitals",
1881 api_name => "open-ils.actor.user.opac.vital_stats",
1885 desc => 'Returns a short summary of the users vital stats, including ' .
1886 'identification information, accumulated balance, number of holds, ' .
1887 'and current open circulation stats' ,
1889 {desc => 'Authentication token', type => 'string'},
1890 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1893 desc => "An object with four properties: user, fines, checkouts and holds."
1898 sub user_opac_vitals {
1899 my( $self, $client, $auth, $user_id ) = @_;
1901 my $e = new_editor(authtoken=>$auth);
1902 return $e->event unless $e->checkauth;
1904 $user_id ||= $e->requestor->id;
1906 my $user = $e->retrieve_actor_user( $user_id );
1909 ->method_lookup('open-ils.actor.user.fines.summary')
1910 ->run($auth => $user_id);
1911 return $fines if (defined($U->event_code($fines)));
1914 $fines = new Fieldmapper::money::open_user_summary ();
1915 $fines->balance_owed(0.00);
1916 $fines->total_owed(0.00);
1917 $fines->total_paid(0.00);
1918 $fines->usr($user_id);
1922 ->method_lookup('open-ils.actor.user.hold_requests.count')
1923 ->run($auth => $user_id);
1924 return $holds if (defined($U->event_code($holds)));
1927 ->method_lookup('open-ils.actor.user.checked_out.count')
1928 ->run($auth => $user_id);
1929 return $out if (defined($U->event_code($out)));
1931 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1933 my $unread_msgs = $e->search_actor_usr_message([
1934 {usr => $user_id, read_date => undef, deleted => 'f'},
1940 first_given_name => $user->first_given_name,
1941 second_given_name => $user->second_given_name,
1942 family_name => $user->family_name,
1943 alias => $user->alias,
1944 usrname => $user->usrname
1946 fines => $fines->to_bare_hash,
1949 messages => { unread => scalar(@$unread_msgs) }
1954 ##### a small consolidation of related method registrations
1955 my $common_params = [
1956 { desc => 'Authentication token', type => 'string' },
1957 { desc => 'User ID', type => 'string' },
1958 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1959 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1962 'open-ils.actor.user.transactions' => '',
1963 'open-ils.actor.user.transactions.fleshed' => '',
1964 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1965 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1966 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1967 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1970 foreach (keys %methods) {
1972 method => "user_transactions",
1975 desc => 'For a given user, retrieve a list of '
1976 . (/\.fleshed/ ? 'fleshed ' : '')
1977 . 'transactions' . $methods{$_}
1978 . ' optionally limited to transactions of a given type.',
1979 params => $common_params,
1981 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1982 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1986 $args{authoritative} = 1;
1987 __PACKAGE__->register_method(%args);
1990 # Now for the counts
1992 'open-ils.actor.user.transactions.count' => '',
1993 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1994 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1997 foreach (keys %methods) {
1999 method => "user_transactions",
2002 desc => 'For a given user, retrieve a count of open '
2003 . 'transactions' . $methods{$_}
2004 . ' optionally limited to transactions of a given type.',
2005 params => $common_params,
2006 return => { desc => "Integer count of transactions, or event on error" }
2009 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2010 __PACKAGE__->register_method(%args);
2013 __PACKAGE__->register_method(
2014 method => "user_transactions",
2015 api_name => "open-ils.actor.user.transactions.have_balance.total",
2018 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2019 . ' optionally limited to transactions of a given type.',
2020 params => $common_params,
2021 return => { desc => "Decimal balance value, or event on error" }
2026 sub user_transactions {
2027 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2030 my $e = new_editor(authtoken => $auth);
2031 return $e->event unless $e->checkauth;
2033 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2035 return $e->event unless
2036 $e->requestor->id == $user_id or
2037 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2039 my $api = $self->api_name();
2041 my $filter = ($api =~ /have_balance/o) ?
2042 { 'balance_owed' => { '<>' => 0 } }:
2043 { 'total_owed' => { '>' => 0 } };
2045 my $method = 'open-ils.actor.user.transactions.history.still_open';
2046 $method = "$method.authoritative" if $api =~ /authoritative/;
2047 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2049 if($api =~ /total/o) {
2051 $total += $_->balance_owed for @$trans;
2055 ($api =~ /count/o ) and return scalar @$trans;
2056 ($api !~ /fleshed/o) and return $trans;
2059 for my $t (@$trans) {
2061 if( $t->xact_type ne 'circulation' ) {
2062 push @resp, {transaction => $t};
2066 my $circ_data = flesh_circ($e, $t->id);
2067 push @resp, {transaction => $t, %$circ_data};
2074 __PACKAGE__->register_method(
2075 method => "user_transaction_retrieve",
2076 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2079 notes => "Returns a fleshed transaction record"
2082 __PACKAGE__->register_method(
2083 method => "user_transaction_retrieve",
2084 api_name => "open-ils.actor.user.transaction.retrieve",
2087 notes => "Returns a transaction record"
2090 sub user_transaction_retrieve {
2091 my($self, $client, $auth, $bill_id) = @_;
2093 my $e = new_editor(authtoken => $auth);
2094 return $e->event unless $e->checkauth;
2096 my $trans = $e->retrieve_money_billable_transaction_summary(
2097 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2099 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2101 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2103 return $trans unless $self->api_name =~ /flesh/;
2104 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2106 my $circ_data = flesh_circ($e, $trans->id, 1);
2108 return {transaction => $trans, %$circ_data};
2113 my $circ_id = shift;
2114 my $flesh_copy = shift;
2116 my $circ = $e->retrieve_action_circulation([
2120 circ => ['target_copy'],
2121 acp => ['call_number'],
2128 my $copy = $circ->target_copy;
2130 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2131 $mods = new Fieldmapper::metabib::virtual_record;
2132 $mods->doc_id(OILS_PRECAT_RECORD);
2133 $mods->title($copy->dummy_title);
2134 $mods->author($copy->dummy_author);
2137 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2141 $circ->target_copy($circ->target_copy->id);
2142 $copy->call_number($copy->call_number->id);
2144 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2148 __PACKAGE__->register_method(
2149 method => "hold_request_count",
2150 api_name => "open-ils.actor.user.hold_requests.count",
2154 Returns hold ready vs. total counts.
2155 If a context org unit is provided, a third value
2156 is returned with key 'behind_desk', which reports
2157 how many holds are ready at the pickup library
2158 with the behind_desk flag set to true.
2162 sub hold_request_count {
2163 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2164 my $e = new_editor(authtoken => $authtoken);
2165 return $e->event unless $e->checkauth;
2167 $user_id = $e->requestor->id unless defined $user_id;
2169 if($e->requestor->id ne $user_id) {
2170 my $user = $e->retrieve_actor_user($user_id);
2171 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2174 my $holds = $e->json_query({
2175 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2179 fulfillment_time => {"=" => undef },
2180 cancel_time => undef,
2185 $_->{current_shelf_lib} and # avoid undef warnings
2186 $_->{pickup_lib} eq $_->{current_shelf_lib}
2190 total => scalar(@$holds),
2191 ready => scalar(@ready)
2195 # count of holds ready at pickup lib with behind_desk true.
2196 $resp->{behind_desk} = scalar(
2198 $_->{pickup_lib} == $ctx_org and
2199 $U->is_true($_->{behind_desk})
2207 __PACKAGE__->register_method(
2208 method => "checked_out",
2209 api_name => "open-ils.actor.user.checked_out",
2213 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2214 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2215 . "(i.e., outstanding balance or some other pending action on the circ). "
2216 . "The .count method also includes a 'total' field which sums all open circs.",
2218 { desc => 'Authentication Token', type => 'string'},
2219 { desc => 'User ID', type => 'string'},
2222 desc => 'Returns event on error, or an object with ID lists, like: '
2223 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2228 __PACKAGE__->register_method(
2229 method => "checked_out",
2230 api_name => "open-ils.actor.user.checked_out.count",
2233 signature => q/@see open-ils.actor.user.checked_out/
2237 my( $self, $conn, $auth, $userid ) = @_;
2239 my $e = new_editor(authtoken=>$auth);
2240 return $e->event unless $e->checkauth;
2242 if( $userid ne $e->requestor->id ) {
2243 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2244 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2246 # see if there is a friend link allowing circ.view perms
2247 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2248 $e, $userid, $e->requestor->id, 'circ.view');
2249 return $e->event unless $allowed;
2253 my $count = $self->api_name =~ /count/;
2254 return _checked_out( $count, $e, $userid );
2258 my( $iscount, $e, $userid ) = @_;
2264 claims_returned => [],
2267 my $meth = 'retrieve_action_open_circ_';
2275 claims_returned => 0,
2282 my $data = $e->$meth($userid);
2286 $result{$_} += $data->$_() for (keys %result);
2287 $result{total} += $data->$_() for (keys %result);
2289 for my $k (keys %result) {
2290 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2300 __PACKAGE__->register_method(
2301 method => "checked_in_with_fines",
2302 api_name => "open-ils.actor.user.checked_in_with_fines",
2305 signature => q/@see open-ils.actor.user.checked_out/
2308 sub checked_in_with_fines {
2309 my( $self, $conn, $auth, $userid ) = @_;
2311 my $e = new_editor(authtoken=>$auth);
2312 return $e->event unless $e->checkauth;
2314 if( $userid ne $e->requestor->id ) {
2315 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2318 # money is owed on these items and they are checked in
2319 my $open = $e->search_action_circulation(
2322 xact_finish => undef,
2323 checkin_time => { "!=" => undef },
2328 my( @lost, @cr, @lo );
2329 for my $c (@$open) {
2330 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2331 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2332 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2337 claims_returned => \@cr,
2338 long_overdue => \@lo
2344 my ($api, $desc, $auth) = @_;
2345 $desc = $desc ? (" " . $desc) : '';
2346 my $ids = ($api =~ /ids$/) ? 1 : 0;
2349 method => "user_transaction_history",
2350 api_name => "open-ils.actor.user.transactions.$api",
2352 desc => "For a given User ID, returns a list of billable transaction" .
2353 ($ids ? " id" : '') .
2354 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2355 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2357 {desc => 'Authentication token', type => 'string'},
2358 {desc => 'User ID', type => 'number'},
2359 {desc => 'Transaction type (optional)', type => 'number'},
2360 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2363 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2367 $auth and push @sig, (authoritative => 1);
2371 my %auth_hist_methods = (
2373 'history.have_charge' => 'that have an initial charge',
2374 'history.still_open' => 'that are not finished',
2375 'history.have_balance' => 'that have a balance',
2376 'history.have_bill' => 'that have billings',
2377 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2378 'history.have_payment' => 'that have at least 1 payment',
2381 foreach (keys %auth_hist_methods) {
2382 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2383 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2384 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2387 sub user_transaction_history {
2388 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2392 my $e = new_editor(authtoken=>$auth);
2393 return $e->die_event unless $e->checkauth;
2395 if ($e->requestor->id ne $userid) {
2396 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2399 my $api = $self->api_name;
2400 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2402 if(defined($type)) {
2403 $filter->{'xact_type'} = $type;
2406 if($api =~ /have_bill_or_payment/o) {
2408 # transactions that have a non-zero sum across all billings or at least 1 payment
2409 $filter->{'-or'} = {
2410 'balance_owed' => { '<>' => 0 },
2411 'last_payment_ts' => { '<>' => undef }
2414 } elsif($api =~ /have_payment/) {
2416 $filter->{last_payment_ts} ||= {'<>' => undef};
2418 } elsif( $api =~ /have_balance/o) {
2420 # transactions that have a non-zero overall balance
2421 $filter->{'balance_owed'} = { '<>' => 0 };
2423 } elsif( $api =~ /have_charge/o) {
2425 # transactions that have at least 1 billing, regardless of whether it was voided
2426 $filter->{'last_billing_ts'} = { '<>' => undef };
2428 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2430 # transactions that have non-zero sum across all billings. This will exclude
2431 # xacts where all billings have been voided
2432 $filter->{'total_owed'} = { '<>' => 0 };
2435 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2436 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2437 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2439 my $mbts = $e->search_money_billable_transaction_summary(
2440 [ { usr => $userid, @xact_finish, %$filter },
2445 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2446 return $mbts unless $api =~ /fleshed/;
2449 for my $t (@$mbts) {
2451 if( $t->xact_type ne 'circulation' ) {
2452 push @resp, {transaction => $t};
2456 my $circ_data = flesh_circ($e, $t->id);
2457 push @resp, {transaction => $t, %$circ_data};
2465 __PACKAGE__->register_method(
2466 method => "user_perms",
2467 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2469 notes => "Returns a list of permissions"
2473 my( $self, $client, $authtoken, $user ) = @_;
2475 my( $staff, $evt ) = $apputils->checkses($authtoken);
2476 return $evt if $evt;
2478 $user ||= $staff->id;
2480 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2484 return $apputils->simple_scalar_request(
2486 "open-ils.storage.permission.user_perms.atomic",
2490 __PACKAGE__->register_method(
2491 method => "retrieve_perms",
2492 api_name => "open-ils.actor.permissions.retrieve",
2493 notes => "Returns a list of permissions"
2495 sub retrieve_perms {
2496 my( $self, $client ) = @_;
2497 return $apputils->simple_scalar_request(
2499 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2500 { id => { '!=' => undef } }
2504 __PACKAGE__->register_method(
2505 method => "retrieve_groups",
2506 api_name => "open-ils.actor.groups.retrieve",
2507 notes => "Returns a list of user groups"
2509 sub retrieve_groups {
2510 my( $self, $client ) = @_;
2511 return new_editor()->retrieve_all_permission_grp_tree();
2514 __PACKAGE__->register_method(
2515 method => "retrieve_org_address",
2516 api_name => "open-ils.actor.org_unit.address.retrieve",
2517 notes => <<' NOTES');
2518 Returns an org_unit address by ID
2519 @param An org_address ID
2521 sub retrieve_org_address {
2522 my( $self, $client, $id ) = @_;
2523 return $apputils->simple_scalar_request(
2525 "open-ils.cstore.direct.actor.org_address.retrieve",
2530 __PACKAGE__->register_method(
2531 method => "retrieve_groups_tree",
2532 api_name => "open-ils.actor.groups.tree.retrieve",
2533 notes => "Returns a list of user groups"
2536 sub retrieve_groups_tree {
2537 my( $self, $client ) = @_;
2538 return new_editor()->search_permission_grp_tree(
2543 flesh_fields => { pgt => ["children"] },
2544 order_by => { pgt => 'name'}
2551 __PACKAGE__->register_method(
2552 method => "add_user_to_groups",
2553 api_name => "open-ils.actor.user.set_groups",
2554 notes => "Adds a user to one or more permission groups"
2557 sub add_user_to_groups {
2558 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2560 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2561 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2562 return $evt if $evt;
2564 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2565 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2566 return $evt if $evt;
2568 $apputils->simplereq(
2570 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2572 for my $group (@$groups) {
2573 my $link = Fieldmapper::permission::usr_grp_map->new;
2575 $link->usr($userid);
2577 my $id = $apputils->simplereq(
2579 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2585 __PACKAGE__->register_method(
2586 method => "get_user_perm_groups",
2587 api_name => "open-ils.actor.user.get_groups",
2588 notes => "Retrieve a user's permission groups."
2592 sub get_user_perm_groups {
2593 my( $self, $client, $authtoken, $userid ) = @_;
2595 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2596 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2597 return $evt if $evt;
2599 return $apputils->simplereq(
2601 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2605 __PACKAGE__->register_method(
2606 method => "get_user_work_ous",
2607 api_name => "open-ils.actor.user.get_work_ous",
2608 notes => "Retrieve a user's work org units."
2611 __PACKAGE__->register_method(
2612 method => "get_user_work_ous",
2613 api_name => "open-ils.actor.user.get_work_ous.ids",
2614 notes => "Retrieve a user's work org units."
2617 sub get_user_work_ous {
2618 my( $self, $client, $auth, $userid ) = @_;
2619 my $e = new_editor(authtoken=>$auth);
2620 return $e->event unless $e->checkauth;
2621 $userid ||= $e->requestor->id;
2623 if($e->requestor->id != $userid) {
2624 my $user = $e->retrieve_actor_user($userid)
2625 or return $e->event;
2626 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2629 return $e->search_permission_usr_work_ou_map({usr => $userid})
2630 unless $self->api_name =~ /.ids$/;
2632 # client just wants a list of org IDs
2633 return $U->get_user_work_ou_ids($e, $userid);
2638 __PACKAGE__->register_method(
2639 method => 'register_workstation',
2640 api_name => 'open-ils.actor.workstation.register.override',
2641 signature => q/@see open-ils.actor.workstation.register/
2644 __PACKAGE__->register_method(
2645 method => 'register_workstation',
2646 api_name => 'open-ils.actor.workstation.register',
2648 Registers a new workstion in the system
2649 @param authtoken The login session key
2650 @param name The name of the workstation id
2651 @param owner The org unit that owns this workstation
2652 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2653 if the name is already in use.
2657 sub register_workstation {
2658 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2660 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2661 return $e->die_event unless $e->checkauth;
2662 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2663 my $existing = $e->search_actor_workstation({name => $name})->[0];
2664 $oargs = { all => 1 } unless defined $oargs;
2668 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2669 # workstation with the given name exists.
2671 if($owner ne $existing->owning_lib) {
2672 # if necessary, update the owning_lib of the workstation
2674 $logger->info("changing owning lib of workstation ".$existing->id.
2675 " from ".$existing->owning_lib." to $owner");
2676 return $e->die_event unless
2677 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2679 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2681 $existing->owning_lib($owner);
2682 return $e->die_event unless $e->update_actor_workstation($existing);
2688 "attempt to register an existing workstation. returning existing ID");
2691 return $existing->id;
2694 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2698 my $ws = Fieldmapper::actor::workstation->new;
2699 $ws->owning_lib($owner);
2701 $e->create_actor_workstation($ws) or return $e->die_event;
2703 return $ws->id; # note: editor sets the id on the new object for us
2706 __PACKAGE__->register_method(
2707 method => 'workstation_list',
2708 api_name => 'open-ils.actor.workstation.list',
2710 Returns a list of workstations registered at the given location
2711 @param authtoken The login session key
2712 @param ids A list of org_unit.id's for the workstation owners
2716 sub workstation_list {
2717 my( $self, $conn, $authtoken, @orgs ) = @_;
2719 my $e = new_editor(authtoken=>$authtoken);
2720 return $e->event unless $e->checkauth;
2725 unless $e->allowed('REGISTER_WORKSTATION', $o);
2726 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2732 __PACKAGE__->register_method(
2733 method => 'fetch_patron_note',
2734 api_name => 'open-ils.actor.note.retrieve.all',
2737 Returns a list of notes for a given user
2738 Requestor must have VIEW_USER permission if pub==false and
2739 @param authtoken The login session key
2740 @param args Hash of params including
2741 patronid : the patron's id
2742 pub : true if retrieving only public notes
2746 sub fetch_patron_note {
2747 my( $self, $conn, $authtoken, $args ) = @_;
2748 my $patronid = $$args{patronid};
2750 my($reqr, $evt) = $U->checkses($authtoken);
2751 return $evt if $evt;
2754 ($patron, $evt) = $U->fetch_user($patronid);
2755 return $evt if $evt;
2758 if( $patronid ne $reqr->id ) {
2759 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2760 return $evt if $evt;
2762 return $U->cstorereq(
2763 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2764 { usr => $patronid, pub => 't' } );
2767 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2768 return $evt if $evt;
2770 return $U->cstorereq(
2771 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2774 __PACKAGE__->register_method(
2775 method => 'create_user_note',
2776 api_name => 'open-ils.actor.note.create',
2778 Creates a new note for the given user
2779 @param authtoken The login session key
2780 @param note The note object
2783 sub create_user_note {
2784 my( $self, $conn, $authtoken, $note ) = @_;
2785 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2786 return $e->die_event unless $e->checkauth;
2788 my $user = $e->retrieve_actor_user($note->usr)
2789 or return $e->die_event;
2791 return $e->die_event unless
2792 $e->allowed('UPDATE_USER',$user->home_ou);
2794 $note->creator($e->requestor->id);
2795 $e->create_actor_usr_note($note) or return $e->die_event;
2801 __PACKAGE__->register_method(
2802 method => 'delete_user_note',
2803 api_name => 'open-ils.actor.note.delete',
2805 Deletes a note for the given user
2806 @param authtoken The login session key
2807 @param noteid The note id
2810 sub delete_user_note {
2811 my( $self, $conn, $authtoken, $noteid ) = @_;
2813 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2814 return $e->die_event unless $e->checkauth;
2815 my $note = $e->retrieve_actor_usr_note($noteid)
2816 or return $e->die_event;
2817 my $user = $e->retrieve_actor_user($note->usr)
2818 or return $e->die_event;
2819 return $e->die_event unless
2820 $e->allowed('UPDATE_USER', $user->home_ou);
2822 $e->delete_actor_usr_note($note) or return $e->die_event;
2828 __PACKAGE__->register_method(
2829 method => 'update_user_note',
2830 api_name => 'open-ils.actor.note.update',
2832 @param authtoken The login session key
2833 @param note The note
2837 sub update_user_note {
2838 my( $self, $conn, $auth, $note ) = @_;
2839 my $e = new_editor(authtoken=>$auth, xact=>1);
2840 return $e->die_event unless $e->checkauth;
2841 my $patron = $e->retrieve_actor_user($note->usr)
2842 or return $e->die_event;
2843 return $e->die_event unless
2844 $e->allowed('UPDATE_USER', $patron->home_ou);
2845 $e->update_actor_user_note($note)
2846 or return $e->die_event;
2851 __PACKAGE__->register_method(
2852 method => 'fetch_patron_messages',
2853 api_name => 'open-ils.actor.message.retrieve',
2856 Returns a list of notes for a given user, not
2857 including ones marked deleted
2858 @param authtoken The login session key
2859 @param patronid patron ID
2860 @param options hash containing optional limit and offset
2864 sub fetch_patron_messages {
2865 my( $self, $conn, $auth, $patronid, $options ) = @_;
2869 my $e = new_editor(authtoken => $auth);
2870 return $e->die_event unless $e->checkauth;
2872 if ($e->requestor->id ne $patronid) {
2873 return $e->die_event unless $e->allowed('VIEW_USER');
2876 my $select_clause = { usr => $patronid };
2877 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2878 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2879 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2881 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2886 __PACKAGE__->register_method(
2887 method => 'usrname_exists',
2888 api_name => 'open-ils.actor.username.exists',
2890 desc => 'Check if a username is already taken (by an undeleted patron)',
2892 {desc => 'Authentication token', type => 'string'},
2893 {desc => 'Username', type => 'string'}
2896 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2901 sub usrname_exists {
2902 my( $self, $conn, $auth, $usrname ) = @_;
2903 my $e = new_editor(authtoken=>$auth);
2904 return $e->event unless $e->checkauth;
2905 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2906 return $$a[0] if $a and @$a;
2910 __PACKAGE__->register_method(
2911 method => 'barcode_exists',
2912 api_name => 'open-ils.actor.barcode.exists',
2914 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2917 sub barcode_exists {
2918 my( $self, $conn, $auth, $barcode ) = @_;
2919 my $e = new_editor(authtoken=>$auth);
2920 return $e->event unless $e->checkauth;
2921 my $card = $e->search_actor_card({barcode => $barcode});
2927 #return undef unless @$card;
2928 #return $card->[0]->usr;
2932 __PACKAGE__->register_method(
2933 method => 'retrieve_net_levels',
2934 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2937 sub retrieve_net_levels {
2938 my( $self, $conn, $auth ) = @_;
2939 my $e = new_editor(authtoken=>$auth);
2940 return $e->event unless $e->checkauth;
2941 return $e->retrieve_all_config_net_access_level();
2944 # Retain the old typo API name just in case
2945 __PACKAGE__->register_method(
2946 method => 'fetch_org_by_shortname',
2947 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2949 __PACKAGE__->register_method(
2950 method => 'fetch_org_by_shortname',
2951 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2953 sub fetch_org_by_shortname {
2954 my( $self, $conn, $sname ) = @_;
2955 my $e = new_editor();
2956 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2957 return $e->event unless $org;
2962 __PACKAGE__->register_method(
2963 method => 'session_home_lib',
2964 api_name => 'open-ils.actor.session.home_lib',
2967 sub session_home_lib {
2968 my( $self, $conn, $auth ) = @_;
2969 my $e = new_editor(authtoken=>$auth);
2970 return undef unless $e->checkauth;
2971 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2972 return $org->shortname;
2975 __PACKAGE__->register_method(
2976 method => 'session_safe_token',
2977 api_name => 'open-ils.actor.session.safe_token',
2979 Returns a hashed session ID that is safe for export to the world.
2980 This safe token will expire after 1 hour of non-use.
2981 @param auth Active authentication token
2985 sub session_safe_token {
2986 my( $self, $conn, $auth ) = @_;
2987 my $e = new_editor(authtoken=>$auth);
2988 return undef unless $e->checkauth;
2990 my $safe_token = md5_hex($auth);
2992 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2994 # add more user fields as needed
2996 "safe-token-user-$safe_token", {
2997 id => $e->requestor->id,
2998 home_ou_shortname => $e->retrieve_actor_org_unit(
2999 $e->requestor->home_ou)->shortname,
3008 __PACKAGE__->register_method(
3009 method => 'safe_token_home_lib',
3010 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
3012 Returns the home library shortname from the session
3013 asscociated with a safe token from generated by
3014 open-ils.actor.session.safe_token.
3015 @param safe_token Active safe token
3016 @param who Optional user activity "ewho" value
3020 sub safe_token_home_lib {
3021 my( $self, $conn, $safe_token, $who ) = @_;
3022 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3024 my $blob = $cache->get_cache("safe-token-user-$safe_token");
3025 return unless $blob;
3027 $U->log_user_activity($blob->{id}, $who, 'verify');
3028 return $blob->{home_ou_shortname};
3032 __PACKAGE__->register_method(
3033 method => "update_penalties",
3034 api_name => "open-ils.actor.user.penalties.update"
3037 sub update_penalties {
3038 my($self, $conn, $auth, $user_id) = @_;
3039 my $e = new_editor(authtoken=>$auth, xact => 1);
3040 return $e->die_event unless $e->checkauth;
3041 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3042 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3043 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3044 return $evt if $evt;
3050 __PACKAGE__->register_method(
3051 method => "apply_penalty",
3052 api_name => "open-ils.actor.user.penalty.apply"
3056 my($self, $conn, $auth, $penalty) = @_;
3058 my $e = new_editor(authtoken=>$auth, xact => 1);
3059 return $e->die_event unless $e->checkauth;
3061 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3062 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3064 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3067 (defined $ptype->org_depth) ?
3068 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
3071 $penalty->org_unit($ctx_org);
3072 $penalty->staff($e->requestor->id);
3073 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3076 return $penalty->id;
3079 __PACKAGE__->register_method(
3080 method => "remove_penalty",
3081 api_name => "open-ils.actor.user.penalty.remove"
3084 sub remove_penalty {
3085 my($self, $conn, $auth, $penalty) = @_;
3086 my $e = new_editor(authtoken=>$auth, xact => 1);
3087 return $e->die_event unless $e->checkauth;
3088 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3089 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3091 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3096 __PACKAGE__->register_method(
3097 method => "update_penalty_note",
3098 api_name => "open-ils.actor.user.penalty.note.update"
3101 sub update_penalty_note {
3102 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3103 my $e = new_editor(authtoken=>$auth, xact => 1);
3104 return $e->die_event unless $e->checkauth;
3105 for my $penalty_id (@$penalty_ids) {
3106 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3107 if (! $penalty ) { return $e->die_event; }
3108 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3109 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3111 $penalty->note( $note ); $penalty->ischanged( 1 );
3113 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3119 __PACKAGE__->register_method(
3120 method => "ranged_penalty_thresholds",
3121 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3125 sub ranged_penalty_thresholds {
3126 my($self, $conn, $auth, $context_org) = @_;
3127 my $e = new_editor(authtoken=>$auth);
3128 return $e->event unless $e->checkauth;
3129 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3130 my $list = $e->search_permission_grp_penalty_threshold([
3131 {org_unit => $U->get_org_ancestors($context_org)},
3132 {order_by => {pgpt => 'id'}}
3134 $conn->respond($_) for @$list;
3140 __PACKAGE__->register_method(
3141 method => "user_retrieve_fleshed_by_id",
3143 api_name => "open-ils.actor.user.fleshed.retrieve",
3146 sub user_retrieve_fleshed_by_id {
3147 my( $self, $client, $auth, $user_id, $fields ) = @_;
3148 my $e = new_editor(authtoken => $auth);
3149 return $e->event unless $e->checkauth;
3151 if( $e->requestor->id != $user_id ) {
3152 return $e->event unless $e->allowed('VIEW_USER');
3159 "standing_penalties",
3167 return new_flesh_user($user_id, $fields, $e);
3171 sub new_flesh_user {
3174 my $fields = shift || [];
3177 my $fetch_penalties = 0;
3178 if(grep {$_ eq 'standing_penalties'} @$fields) {
3179 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3180 $fetch_penalties = 1;
3183 my $fetch_usr_act = 0;
3184 if(grep {$_ eq 'usr_activity'} @$fields) {
3185 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3189 my $user = $e->retrieve_actor_user(
3194 "flesh_fields" => { "au" => $fields }
3197 ) or return $e->die_event;
3200 if( grep { $_ eq 'addresses' } @$fields ) {
3202 $user->addresses([]) unless @{$user->addresses};
3203 # don't expose "replaced" addresses by default
3204 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3206 if( ref $user->billing_address ) {
3207 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3208 push( @{$user->addresses}, $user->billing_address );
3212 if( ref $user->mailing_address ) {
3213 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3214 push( @{$user->addresses}, $user->mailing_address );
3219 if($fetch_penalties) {
3220 # grab the user penalties ranged for this location
3221 $user->standing_penalties(
3222 $e->search_actor_user_standing_penalty([
3225 {stop_date => undef},
3226 {stop_date => {'>' => 'now'}}
3228 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3231 flesh_fields => {ausp => ['standing_penalty']}
3237 # retrieve the most recent usr_activity entry
3238 if ($fetch_usr_act) {
3240 # max number to return for simple patron fleshing
3241 my $limit = $U->ou_ancestor_setting_value(
3242 $e->requestor->ws_ou,
3243 'circ.patron.usr_activity_retrieve.max');
3247 flesh_fields => {auact => ['etype']},
3248 order_by => {auact => 'event_time DESC'},
3251 # 0 == none, <0 == return all
3252 $limit = 1 unless defined $limit;
3253 $opts->{limit} = $limit if $limit > 0;
3255 $user->usr_activity(
3257 [] : # skip the DB call
3258 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3263 $user->clear_passwd();
3270 __PACKAGE__->register_method(
3271 method => "user_retrieve_parts",
3272 api_name => "open-ils.actor.user.retrieve.parts",
3275 sub user_retrieve_parts {
3276 my( $self, $client, $auth, $user_id, $fields ) = @_;
3277 my $e = new_editor(authtoken => $auth);
3278 return $e->event unless $e->checkauth;
3279 $user_id ||= $e->requestor->id;
3280 if( $e->requestor->id != $user_id ) {
3281 return $e->event unless $e->allowed('VIEW_USER');
3284 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3285 push(@resp, $user->$_()) for(@$fields);
3291 __PACKAGE__->register_method(
3292 method => 'user_opt_in_enabled',
3293 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3294 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3297 sub user_opt_in_enabled {
3298 my($self, $conn) = @_;
3299 my $sc = OpenSRF::Utils::SettingsClient->new;
3300 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3305 __PACKAGE__->register_method(
3306 method => 'user_opt_in_at_org',
3307 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3309 @param $auth The auth token
3310 @param user_id The ID of the user to test
3311 @return 1 if the user has opted in at the specified org,
3312 2 if opt-in is disallowed for the user's home org,
3313 event on error, and 0 otherwise. /
3315 sub user_opt_in_at_org {
3316 my($self, $conn, $auth, $user_id) = @_;
3318 # see if we even need to enforce the opt-in value
3319 return 1 unless user_opt_in_enabled($self);
3321 my $e = new_editor(authtoken => $auth);
3322 return $e->event unless $e->checkauth;
3324 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3325 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3327 my $ws_org = $e->requestor->ws_ou;
3328 # user is automatically opted-in if they are from the local org
3329 return 1 if $user->home_ou eq $ws_org;
3331 # get the boundary setting
3332 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3334 # auto opt in if user falls within the opt boundary
3335 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3337 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3339 # check whether opt-in is restricted at the user's home library
3340 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3341 if ($opt_restrict_depth) {
3342 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3343 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3345 # opt-in is disallowed unless the workstation org is within the home
3346 # library's opt-in scope
3347 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3350 my $vals = $e->search_actor_usr_org_unit_opt_in(
3351 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3357 __PACKAGE__->register_method(
3358 method => 'create_user_opt_in_at_org',
3359 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3361 @param $auth The auth token
3362 @param user_id The ID of the user to test
3363 @return The ID of the newly created object, event on error./
3366 sub create_user_opt_in_at_org {
3367 my($self, $conn, $auth, $user_id, $org_id) = @_;
3369 my $e = new_editor(authtoken => $auth, xact=>1);
3370 return $e->die_event unless $e->checkauth;
3372 # if a specific org unit wasn't passed in, get one based on the defaults;
3374 my $wsou = $e->requestor->ws_ou;
3375 # get the default opt depth
3376 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3377 # get the org unit at that depth
3378 my $org = $e->json_query({
3379 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3380 $org_id = $org->{id};
3383 # fall back to the workstation OU, the pre-opt-in-boundary way
3384 $org_id = $e->requestor->ws_ou;
3387 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3388 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3390 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3392 $opt_in->org_unit($org_id);
3393 $opt_in->usr($user_id);
3394 $opt_in->staff($e->requestor->id);
3395 $opt_in->opt_in_ts('now');
3396 $opt_in->opt_in_ws($e->requestor->wsid);
3398 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3399 or return $e->die_event;
3407 __PACKAGE__->register_method (
3408 method => 'retrieve_org_hours',
3409 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3411 Returns the hours of operation for a specified org unit
3412 @param authtoken The login session key
3413 @param org_id The org_unit ID
3417 sub retrieve_org_hours {
3418 my($self, $conn, $auth, $org_id) = @_;
3419 my $e = new_editor(authtoken => $auth);
3420 return $e->die_event unless $e->checkauth;
3421 $org_id ||= $e->requestor->ws_ou;
3422 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3426 __PACKAGE__->register_method (
3427 method => 'verify_user_password',
3428 api_name => 'open-ils.actor.verify_user_password',
3430 Given a barcode or username and the MD5 encoded password,
3431 returns 1 if the password is correct. Returns 0 otherwise.
3435 sub verify_user_password {
3436 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3437 my $e = new_editor(authtoken => $auth);
3438 return $e->die_event unless $e->checkauth;
3440 my $user_by_barcode;
3441 my $user_by_username;
3443 my $card = $e->search_actor_card([
3444 {barcode => $barcode},
3445 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3446 $user_by_barcode = $card->usr;
3447 $user = $user_by_barcode;
3450 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3451 $user = $user_by_username;
3453 return 0 if (!$user || $U->is_true($user->deleted));
3454 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3455 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3456 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3459 __PACKAGE__->register_method (
3460 method => 'retrieve_usr_id_via_barcode_or_usrname',
3461 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3463 Given a barcode or username returns the id for the user or
3468 sub retrieve_usr_id_via_barcode_or_usrname {
3469 my($self, $conn, $auth, $barcode, $username) = @_;
3470 my $e = new_editor(authtoken => $auth);
3471 return $e->die_event unless $e->checkauth;
3472 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3474 my $user_by_barcode;
3475 my $user_by_username;
3476 $logger->info("$id_as_barcode is the ID as BARCODE");
3478 my $card = $e->search_actor_card([
3479 {barcode => $barcode},
3480 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3481 if ($id_as_barcode =~ /^t/i) {
3483 $user = $e->retrieve_actor_user($barcode);
3484 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3486 $user_by_barcode = $card->usr;
3487 $user = $user_by_barcode;
3490 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3491 $user_by_barcode = $card->usr;
3492 $user = $user_by_barcode;
3497 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3499 $user = $user_by_username;
3501 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3502 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3503 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3508 __PACKAGE__->register_method (
3509 method => 'merge_users',
3510 api_name => 'open-ils.actor.user.merge',
3513 Given a list of source users and destination user, transfer all data from the source
3514 to the dest user and delete the source user. All user related data is
3515 transferred, including circulations, holds, bookbags, etc.
3521 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3522 my $e = new_editor(xact => 1, authtoken => $auth);
3523 return $e->die_event unless $e->checkauth;
3525 # disallow the merge if any subordinate accounts are in collections
3526 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3527 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3529 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3530 if $master_id == $e->requestor->id;
3532 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3533 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3534 return $evt if $evt;
3536 my $del_addrs = ($U->ou_ancestor_setting_value(
3537 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3538 my $del_cards = ($U->ou_ancestor_setting_value(
3539 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3540 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3541 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3543 for my $src_id (@$user_ids) {
3545 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3546 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3547 return $evt if $evt;
3549 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3550 if $src_id == $e->requestor->id;
3552 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3553 if($src_user->home_ou ne $master_user->home_ou) {
3554 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3557 return $e->die_event unless
3558 $e->json_query({from => [
3573 __PACKAGE__->register_method (
3574 method => 'approve_user_address',
3575 api_name => 'open-ils.actor.user.pending_address.approve',
3582 sub approve_user_address {
3583 my($self, $conn, $auth, $addr) = @_;
3584 my $e = new_editor(xact => 1, authtoken => $auth);
3585 return $e->die_event unless $e->checkauth;
3587 # if the caller passes an address object, assume they want to
3588 # update it first before approving it
3589 $e->update_actor_user_address($addr) or return $e->die_event;
3591 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3593 my $user = $e->retrieve_actor_user($addr->usr);
3594 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3595 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3596 or return $e->die_event;
3598 return [values %$result]->[0];
3602 __PACKAGE__->register_method (
3603 method => 'retrieve_friends',
3604 api_name => 'open-ils.actor.friends.retrieve',
3607 returns { confirmed: [], pending_out: [], pending_in: []}
3608 pending_out are users I'm requesting friendship with
3609 pending_in are users requesting friendship with me
3614 sub retrieve_friends {
3615 my($self, $conn, $auth, $user_id, $options) = @_;
3616 my $e = new_editor(authtoken => $auth);
3617 return $e->event unless $e->checkauth;
3618 $user_id ||= $e->requestor->id;
3620 if($user_id != $e->requestor->id) {
3621 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3622 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3625 return OpenILS::Application::Actor::Friends->retrieve_friends(
3626 $e, $user_id, $options);
3631 __PACKAGE__->register_method (
3632 method => 'apply_friend_perms',
3633 api_name => 'open-ils.actor.friends.perms.apply',
3639 sub apply_friend_perms {
3640 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3641 my $e = new_editor(authtoken => $auth, xact => 1);
3642 return $e->die_event unless $e->checkauth;
3644 if($user_id != $e->requestor->id) {
3645 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3646 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3649 for my $perm (@perms) {
3651 OpenILS::Application::Actor::Friends->apply_friend_perm(
3652 $e, $user_id, $delegate_id, $perm);
3653 return $evt if $evt;
3661 __PACKAGE__->register_method (
3662 method => 'update_user_pending_address',
3663 api_name => 'open-ils.actor.user.address.pending.cud'
3666 sub update_user_pending_address {
3667 my($self, $conn, $auth, $addr) = @_;
3668 my $e = new_editor(authtoken => $auth, xact => 1);
3669 return $e->die_event unless $e->checkauth;
3671 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3672 if($addr->usr != $e->requestor->id) {
3673 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3677 $e->create_actor_user_address($addr) or return $e->die_event;
3678 } elsif($addr->isdeleted) {
3679 $e->delete_actor_user_address($addr) or return $e->die_event;
3681 $e->update_actor_user_address($addr) or return $e->die_event;
3685 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3691 __PACKAGE__->register_method (
3692 method => 'user_events',
3693 api_name => 'open-ils.actor.user.events.circ',
3696 __PACKAGE__->register_method (
3697 method => 'user_events',
3698 api_name => 'open-ils.actor.user.events.ahr',
3703 my($self, $conn, $auth, $user_id, $filters) = @_;
3704 my $e = new_editor(authtoken => $auth);
3705 return $e->event unless $e->checkauth;
3707 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3708 my $user_field = 'usr';
3711 $filters->{target} = {
3712 select => { $obj_type => ['id'] },
3714 where => {usr => $user_id}
3717 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3718 if($e->requestor->id != $user_id) {
3719 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3722 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3723 my $req = $ses->request('open-ils.trigger.events_by_target',
3724 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3726 while(my $resp = $req->recv) {
3727 my $val = $resp->content;
3728 my $tgt = $val->target;
3730 if($obj_type eq 'circ') {
3731 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3733 } elsif($obj_type eq 'ahr') {
3734 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3735 if $tgt->current_copy;
3738 $conn->respond($val) if $val;
3744 __PACKAGE__->register_method (
3745 method => 'copy_events',
3746 api_name => 'open-ils.actor.copy.events.circ',
3749 __PACKAGE__->register_method (
3750 method => 'copy_events',
3751 api_name => 'open-ils.actor.copy.events.ahr',
3756 my($self, $conn, $auth, $copy_id, $filters) = @_;
3757 my $e = new_editor(authtoken => $auth);
3758 return $e->event unless $e->checkauth;
3760 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3762 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3764 my $copy_field = 'target_copy';
3765 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3768 $filters->{target} = {
3769 select => { $obj_type => ['id'] },
3771 where => {$copy_field => $copy_id}
3775 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3776 my $req = $ses->request('open-ils.trigger.events_by_target',
3777 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3779 while(my $resp = $req->recv) {
3780 my $val = $resp->content;
3781 my $tgt = $val->target;
3783 my $user = $e->retrieve_actor_user($tgt->usr);
3784 if($e->requestor->id != $user->id) {
3785 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3788 $tgt->$copy_field($copy);
3791 $conn->respond($val) if $val;
3798 __PACKAGE__->register_method (
3799 method => 'get_itemsout_notices',
3800 api_name => 'open-ils.actor.user.itemsout.notices',
3804 desc => q/Summary counts of circulat notices/,
3806 {desc => 'authtoken', type => 'string'},
3807 {desc => 'circulation identifiers', type => 'array of numbers'}
3809 return => q/Stream of summary objects/
3813 sub get_itemsout_notices {
3814 my ($self, $client, $auth, $circ_ids) = @_;
3816 my $e = new_editor(authtoken => $auth);
3817 return $e->event unless $e->checkauth;
3819 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3821 for my $circ_id (@$circ_ids) {
3822 my $resp = get_itemsout_notices_impl($e, $circ_id);
3824 if ($U->is_event($resp)) {
3825 $client->respond($resp);
3829 $client->respond({circ_id => $circ_id, %$resp});
3837 sub get_itemsout_notices_impl {
3838 my ($e, $circId) = @_;
3840 my $requestorId = $e->requestor->id;
3842 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3844 my $patronId = $circ->usr;
3846 if( $patronId ne $requestorId ){
3847 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3848 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3851 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3852 #my $req = $ses->request('open-ils.trigger.events_by_target',
3853 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3854 # ^ Above removed in favor of faster json_query.
3857 # select complete_time
3858 # from action_trigger.event atev
3859 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3860 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3861 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3864 my $ctx_loc = $e->requestor->ws_ou;
3865 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3866 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3869 select => { atev => ["complete_time"] },
3872 atevdef => { field => "id",fkey => "event_def"}
3876 "+atevdef" => { active => 't', hook => 'checkout.due' },
3877 "+atev" => { target => $circId, state => 'complete' }
3881 if ($exclude_courtesy_notices){
3882 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3885 my %resblob = ( numNotices => 0, lastDt => undef );
3887 my $res = $e->json_query($query);
3888 for my $ndate (@$res) {
3889 $resblob{numNotices}++;
3890 if( !defined $resblob{lastDt}){
3891 $resblob{lastDt} = $$ndate{complete_time};
3894 if ($resblob{lastDt} lt $$ndate{complete_time}){
3895 $resblob{lastDt} = $$ndate{complete_time};
3902 __PACKAGE__->register_method (
3903 method => 'update_events',
3904 api_name => 'open-ils.actor.user.event.cancel.batch',
3907 __PACKAGE__->register_method (
3908 method => 'update_events',
3909 api_name => 'open-ils.actor.user.event.reset.batch',
3914 my($self, $conn, $auth, $event_ids) = @_;
3915 my $e = new_editor(xact => 1, authtoken => $auth);
3916 return $e->die_event unless $e->checkauth;
3919 for my $id (@$event_ids) {
3921 # do a little dance to determine what user we are ultimately affecting
3922 my $event = $e->retrieve_action_trigger_event([
3925 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3927 ]) or return $e->die_event;
3930 if($event->event_def->hook->core_type eq 'circ') {
3931 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3932 } elsif($event->event_def->hook->core_type eq 'ahr') {
3933 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3938 my $user = $e->retrieve_actor_user($user_id);
3939 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3941 if($self->api_name =~ /cancel/) {
3942 $event->state('invalid');
3943 } elsif($self->api_name =~ /reset/) {
3944 $event->clear_start_time;
3945 $event->clear_update_time;
3946 $event->state('pending');
3949 $e->update_action_trigger_event($event) or return $e->die_event;
3950 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3954 return {complete => 1};
3958 __PACKAGE__->register_method (
3959 method => 'really_delete_user',
3960 api_name => 'open-ils.actor.user.delete.override',
3961 signature => q/@see open-ils.actor.user.delete/
3964 __PACKAGE__->register_method (
3965 method => 'really_delete_user',
3966 api_name => 'open-ils.actor.user.delete',
3968 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3969 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3970 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3971 dest_usr_id is only required when deleting a user that performs staff functions.
3975 sub really_delete_user {
3976 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3977 my $e = new_editor(authtoken => $auth, xact => 1);
3978 return $e->die_event unless $e->checkauth;
3979 $oargs = { all => 1 } unless defined $oargs;
3981 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3982 my $open_bills = $e->json_query({
3983 select => { mbts => ['id'] },
3986 xact_finish => { '=' => undef },
3987 usr => { '=' => $user_id },
3989 }) or return $e->die_event;
3991 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3993 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3995 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3996 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3997 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3999 # No deleting yourself - UI is supposed to stop you first, though.
4000 return $e->die_event unless $e->requestor->id != $user->id;
4001 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4002 # Check if you are allowed to mess with this patron permission group at all
4003 my $evt = group_perm_failed($e, $e->requestor, $user);
4004 return $e->die_event($evt) if $evt;
4005 my $stat = $e->json_query(
4006 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4007 or return $e->die_event;
4013 __PACKAGE__->register_method (
4014 method => 'user_payments',
4015 api_name => 'open-ils.actor.user.payments.retrieve',
4018 Returns all payments for a given user. Default order is newest payments first.
4019 @param auth Authentication token
4020 @param user_id The user ID
4021 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4026 my($self, $conn, $auth, $user_id, $filters) = @_;
4029 my $e = new_editor(authtoken => $auth);
4030 return $e->die_event unless $e->checkauth;
4032 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4033 return $e->event unless
4034 $e->requestor->id == $user_id or
4035 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4037 # Find all payments for all transactions for user $user_id
4039 select => {mp => ['id']},
4044 select => {mbt => ['id']},
4046 where => {usr => $user_id}
4051 { # by default, order newest payments first
4053 field => 'payment_ts',
4056 # secondary sort in ID as a tie-breaker, since payments created
4057 # within the same transaction will have identical payment_ts's
4064 for (qw/order_by limit offset/) {
4065 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4068 if(defined $filters->{where}) {
4069 foreach (keys %{$filters->{where}}) {
4070 # don't allow the caller to expand the result set to other users
4071 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4075 my $payment_ids = $e->json_query($query);
4076 for my $pid (@$payment_ids) {
4077 my $pay = $e->retrieve_money_payment([
4082 mbt => ['summary', 'circulation', 'grocery'],
4083 circ => ['target_copy'],
4084 acp => ['call_number'],
4092 xact_type => $pay->xact->summary->xact_type,
4093 last_billing_type => $pay->xact->summary->last_billing_type,
4096 if($pay->xact->summary->xact_type eq 'circulation') {
4097 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4098 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4101 $pay->xact($pay->xact->id); # de-flesh
4102 $conn->respond($resp);
4110 __PACKAGE__->register_method (
4111 method => 'negative_balance_users',
4112 api_name => 'open-ils.actor.users.negative_balance',
4115 Returns all users that have an overall negative balance
4116 @param auth Authentication token
4117 @param org_id The context org unit as an ID or list of IDs. This will be the home
4118 library of the user. If no org_unit is specified, no org unit filter is applied
4122 sub negative_balance_users {
4123 my($self, $conn, $auth, $org_id) = @_;
4125 my $e = new_editor(authtoken => $auth);
4126 return $e->die_event unless $e->checkauth;
4127 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4131 mous => ['usr', 'balance_owed'],
4134 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4135 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4152 where => {'+mous' => {balance_owed => {'<' => 0}}}
4155 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4157 my $list = $e->json_query($query, {timeout => 600});
4159 for my $data (@$list) {
4161 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4162 balance_owed => $data->{balance_owed},
4163 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4170 __PACKAGE__->register_method(
4171 method => "request_password_reset",
4172 api_name => "open-ils.actor.patron.password_reset.request",
4174 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4175 "method for changing a user's password. The UUID token is distributed via A/T " .
4176 "templates (i.e. email to the user).",
4178 { desc => 'user_id_type', type => 'string' },
4179 { desc => 'user_id', type => 'string' },
4180 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4182 return => {desc => '1 on success, Event on error'}
4185 sub request_password_reset {
4186 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4188 # Check to see if password reset requests are already being throttled:
4189 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4191 my $e = new_editor(xact => 1);
4194 # Get the user, if any, depending on the input value
4195 if ($user_id_type eq 'username') {
4196 $user = $e->search_actor_user({usrname => $user_id})->[0];
4199 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4201 } elsif ($user_id_type eq 'barcode') {
4202 my $card = $e->search_actor_card([
4203 {barcode => $user_id},
4204 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4207 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4212 # If the user doesn't have an email address, we can't help them
4213 if (!$user->email) {
4215 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4218 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4219 if ($email_must_match) {
4220 if (lc($user->email) ne lc($email)) {
4221 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4225 _reset_password_request($conn, $e, $user);
4228 # Once we have the user, we can issue the password reset request
4229 # XXX Add a wrapper method that accepts barcode + email input
4230 sub _reset_password_request {
4231 my ($conn, $e, $user) = @_;
4233 # 1. Get throttle threshold and time-to-live from OU_settings
4234 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4235 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4237 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4239 # 2. Get time of last request and number of active requests (num_active)
4240 my $active_requests = $e->json_query({
4246 transform => 'COUNT'
4249 column => 'request_time',
4255 has_been_reset => { '=' => 'f' },
4256 request_time => { '>' => $threshold_time }
4260 # Guard against no active requests
4261 if ($active_requests->[0]->{'request_time'}) {
4262 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4263 my $now = DateTime::Format::ISO8601->new();
4265 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4266 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4267 ($last_request->add_duration('1 minute') > $now)) {
4268 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4270 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4274 # TODO Check to see if the user is in a password-reset-restricted group
4276 # Otherwise, go ahead and try to get the user.
4278 # Check the number of active requests for this user
4279 $active_requests = $e->json_query({
4285 transform => 'COUNT'
4290 usr => { '=' => $user->id },
4291 has_been_reset => { '=' => 'f' },
4292 request_time => { '>' => $threshold_time }
4296 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4298 # if less than or equal to per-user threshold, proceed; otherwise, return event
4299 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4300 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4302 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4305 # Create the aupr object and insert into the database
4306 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4307 my $uuid = create_uuid_as_string(UUID_V4);
4308 $reset_request->uuid($uuid);
4309 $reset_request->usr($user->id);
4311 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4314 # Create an event to notify user of the URL to reset their password
4316 # Can we stuff this in the user_data param for trigger autocreate?
4317 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4319 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4320 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4323 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4328 __PACKAGE__->register_method(
4329 method => "commit_password_reset",
4330 api_name => "open-ils.actor.patron.password_reset.commit",
4332 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4333 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4334 "with the supplied password.",
4336 { desc => 'uuid', type => 'string' },
4337 { desc => 'password', type => 'string' },
4339 return => {desc => '1 on success, Event on error'}
4342 sub commit_password_reset {
4343 my($self, $conn, $uuid, $password) = @_;
4345 # Check to see if password reset requests are already being throttled:
4346 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4347 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4348 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4350 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4353 my $e = new_editor(xact => 1);
4355 my $aupr = $e->search_actor_usr_password_reset({
4362 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4364 my $user_id = $aupr->[0]->usr;
4365 my $user = $e->retrieve_actor_user($user_id);
4367 # Ensure we're still within the TTL for the request
4368 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4369 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4370 if ($threshold < DateTime->now(time_zone => 'local')) {
4372 $logger->info("Password reset request needed to be submitted before $threshold");
4373 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4376 # Check complexity of password against OU-defined regex
4377 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4381 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4382 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4383 $is_strong = check_password_strength_custom($password, $pw_regex);
4385 $is_strong = check_password_strength_default($password);
4390 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4393 # All is well; update the password
4394 modify_migrated_user_password($e, $user->id, $password);
4396 # And flag that this password reset request has been honoured
4397 $aupr->[0]->has_been_reset('t');
4398 $e->update_actor_usr_password_reset($aupr->[0]);
4404 sub check_password_strength_default {
4405 my $password = shift;
4406 # Use the default set of checks
4407 if ( (length($password) < 7) or
4408 ($password !~ m/.*\d+.*/) or
4409 ($password !~ m/.*[A-Za-z]+.*/)
4416 sub check_password_strength_custom {
4417 my ($password, $pw_regex) = @_;
4419 $pw_regex = qr/$pw_regex/;
4420 if ($password !~ /$pw_regex/) {
4426 __PACKAGE__->register_method(
4427 method => "fire_test_notification",
4428 api_name => "open-ils.actor.event.test_notification"
4431 sub fire_test_notification {
4432 my($self, $conn, $auth, $args) = @_;
4433 my $e = new_editor(authtoken => $auth);
4434 return $e->event unless $e->checkauth;
4435 if ($e->requestor->id != $$args{target}) {
4436 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4437 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4440 my $event_hook = $$args{hook} or return $e->event;
4441 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4443 my $usr = $e->retrieve_actor_user($$args{target});
4444 return $e->event unless $usr;
4446 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4450 __PACKAGE__->register_method(
4451 method => "event_def_opt_in_settings",
4452 api_name => "open-ils.actor.event_def.opt_in.settings",
4455 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4457 { desc => 'Authentication token', type => 'string'},
4459 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4464 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4471 sub event_def_opt_in_settings {
4472 my($self, $conn, $auth, $org_id) = @_;
4473 my $e = new_editor(authtoken => $auth);
4474 return $e->event unless $e->checkauth;
4476 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4477 return $e->event unless
4478 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4480 $org_id = $e->requestor->home_ou;
4483 # find all config.user_setting_type's related to event_defs for the requested org unit
4484 my $types = $e->json_query({
4485 select => {cust => ['name']},
4486 from => {atevdef => 'cust'},
4489 owner => $U->get_org_ancestors($org_id), # context org plus parents
4496 $conn->respond($_) for
4497 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4504 __PACKAGE__->register_method(
4505 method => "user_circ_history",
4506 api_name => "open-ils.actor.history.circ",
4510 desc => 'Returns user circ history objects for the calling user',
4512 { desc => 'Authentication token', type => 'string'},
4513 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4516 desc => q/Stream of 'auch' circ history objects/,
4522 __PACKAGE__->register_method(
4523 method => "user_circ_history",
4524 api_name => "open-ils.actor.history.circ.clear",
4527 desc => 'Delete all user circ history entries for the calling user',
4529 { desc => 'Authentication token', type => 'string'},
4530 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4533 desc => q/1 on success, event on error/,
4539 __PACKAGE__->register_method(
4540 method => "user_circ_history",
4541 api_name => "open-ils.actor.history.circ.print",
4544 desc => q/Returns printable output for the caller's circ history objects/,
4546 { desc => 'Authentication token', type => 'string'},
4547 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4550 desc => q/An action_trigger.event object or error event./,
4556 __PACKAGE__->register_method(
4557 method => "user_circ_history",
4558 api_name => "open-ils.actor.history.circ.email",
4561 desc => q/Emails the caller's circ history/,
4563 { desc => 'Authentication token', type => 'string'},
4564 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4565 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4568 desc => q/undef, or event on error/
4573 sub user_circ_history {
4574 my ($self, $conn, $auth, $options) = @_;
4577 my $for_print = ($self->api_name =~ /print/);
4578 my $for_email = ($self->api_name =~ /email/);
4579 my $for_clear = ($self->api_name =~ /clear/);
4581 # No perm check is performed. Caller may only access his/her own
4582 # circ history entries.
4583 my $e = new_editor(authtoken => $auth);
4584 return $e->event unless $e->checkauth;
4587 if (!$for_clear) { # clear deletes all
4588 $limits{offset} = $options->{offset} if defined $options->{offset};
4589 $limits{limit} = $options->{limit} if defined $options->{limit};
4592 my %circ_id_filter = $options->{circ_ids} ?
4593 (id => $options->{circ_ids}) : ();
4595 my $circs = $e->search_action_user_circ_history([
4596 { usr => $e->requestor->id,
4599 { # order newest to oldest by default
4600 order_by => {auch => 'xact_start DESC'},
4603 {substream => 1} # could be a large list
4607 return $U->fire_object_event(undef,
4608 'circ.format.history.print', $circs, $e->requestor->home_ou);
4611 $e->xact_begin if $for_clear;
4612 $conn->respond_complete(1) if $for_email; # no sense in waiting
4614 for my $circ (@$circs) {
4617 # events will be fired from action_trigger_runner
4618 $U->create_events_for_hook('circ.format.history.email',
4619 $circ, $e->editor->home_ou, undef, undef, 1);
4621 } elsif ($for_clear) {
4623 $e->delete_action_user_circ_history($circ)
4624 or return $e->die_event;
4627 $conn->respond($circ);
4640 __PACKAGE__->register_method(
4641 method => "user_visible_holds",
4642 api_name => "open-ils.actor.history.hold.visible",
4645 desc => 'Returns the set of opt-in visible holds',
4647 { desc => 'Authentication token', type => 'string'},
4648 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4649 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4652 desc => q/An object with 1 field: "hold"/,
4658 __PACKAGE__->register_method(
4659 method => "user_visible_holds",
4660 api_name => "open-ils.actor.history.hold.visible.print",
4663 desc => 'Returns printable output for the set of opt-in visible holds',
4665 { desc => 'Authentication token', type => 'string'},
4666 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4667 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4670 desc => q/An action_trigger.event object or error event./,
4676 __PACKAGE__->register_method(
4677 method => "user_visible_holds",
4678 api_name => "open-ils.actor.history.hold.visible.email",
4681 desc => 'Emails the set of opt-in visible holds to the requestor',
4683 { desc => 'Authentication token', type => 'string'},
4684 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4685 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4688 desc => q/undef, or event on error/
4693 sub user_visible_holds {
4694 my($self, $conn, $auth, $user_id, $options) = @_;
4697 my $for_print = ($self->api_name =~ /print/);
4698 my $for_email = ($self->api_name =~ /email/);
4699 my $e = new_editor(authtoken => $auth);
4700 return $e->event unless $e->checkauth;
4702 $user_id ||= $e->requestor->id;
4704 $options->{limit} ||= 50;
4705 $options->{offset} ||= 0;
4707 if($user_id != $e->requestor->id) {
4708 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4709 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4710 return $e->event unless $e->allowed($perm, $user->home_ou);
4713 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4715 my $data = $e->json_query({
4716 from => [$db_func, $user_id],
4717 limit => $$options{limit},
4718 offset => $$options{offset}
4720 # TODO: I only want IDs. code below didn't get me there
4721 # {"select":{"au":[{"column":"id", "result_field":"id",
4722 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4727 return undef unless @$data;
4731 # collect the batch of objects
4735 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4736 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4740 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4741 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4744 } elsif ($for_email) {
4746 $conn->respond_complete(1) if $for_email; # no sense in waiting
4754 my $hold = $e->retrieve_action_hold_request($id);
4755 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4756 # events will be fired from action_trigger_runner
4760 my $circ = $e->retrieve_action_circulation($id);
4761 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4762 # events will be fired from action_trigger_runner
4766 } else { # just give me the data please
4774 my $hold = $e->retrieve_action_hold_request($id);
4775 $conn->respond({hold => $hold});
4779 my $circ = $e->retrieve_action_circulation($id);
4782 summary => $U->create_circ_chain_summary($e, $id)
4791 __PACKAGE__->register_method(
4792 method => "user_saved_search_cud",
4793 api_name => "open-ils.actor.user.saved_search.cud",
4796 desc => 'Create/Update/Delete Access to user saved searches',
4798 { desc => 'Authentication token', type => 'string' },
4799 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4802 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4808 __PACKAGE__->register_method(
4809 method => "user_saved_search_cud",
4810 api_name => "open-ils.actor.user.saved_search.retrieve",
4813 desc => 'Retrieve a saved search object',
4815 { desc => 'Authentication token', type => 'string' },
4816 { desc => 'Saved Search ID', type => 'number' }
4819 desc => q/The saved search object, Event on error/,
4825 sub user_saved_search_cud {
4826 my( $self, $client, $auth, $search ) = @_;
4827 my $e = new_editor( authtoken=>$auth );
4828 return $e->die_event unless $e->checkauth;
4830 my $o_search; # prior version of the object, if any
4831 my $res; # to be returned
4833 # branch on the operation type
4835 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4837 # Get the old version, to check ownership
4838 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4839 or return $e->die_event;
4841 # You can't read somebody else's search
4842 return OpenILS::Event->new('BAD_PARAMS')
4843 unless $o_search->owner == $e->requestor->id;
4849 $e->xact_begin; # start an editor transaction
4851 if( $search->isnew ) { # Create
4853 # You can't create a search for somebody else
4854 return OpenILS::Event->new('BAD_PARAMS')
4855 unless $search->owner == $e->requestor->id;
4857 $e->create_actor_usr_saved_search( $search )
4858 or return $e->die_event;
4862 } elsif( $search->ischanged ) { # Update
4864 # You can't change ownership of a search
4865 return OpenILS::Event->new('BAD_PARAMS')
4866 unless $search->owner == $e->requestor->id;
4868 # Get the old version, to check ownership
4869 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4870 or return $e->die_event;
4872 # You can't update somebody else's search
4873 return OpenILS::Event->new('BAD_PARAMS')
4874 unless $o_search->owner == $e->requestor->id;
4877 $e->update_actor_usr_saved_search( $search )
4878 or return $e->die_event;
4882 } elsif( $search->isdeleted ) { # Delete
4884 # Get the old version, to check ownership
4885 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4886 or return $e->die_event;
4888 # You can't delete somebody else's search
4889 return OpenILS::Event->new('BAD_PARAMS')
4890 unless $o_search->owner == $e->requestor->id;
4893 $e->delete_actor_usr_saved_search( $o_search )
4894 or return $e->die_event;
4905 __PACKAGE__->register_method(
4906 method => "get_barcodes",
4907 api_name => "open-ils.actor.get_barcodes"
4911 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4912 my $e = new_editor(authtoken => $auth);
4913 return $e->event unless $e->checkauth;
4914 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4916 my $db_result = $e->json_query(
4918 'evergreen.get_barcodes',
4919 $org_id, $context, $barcode,
4923 if($context =~ /actor/) {
4924 my $filter_result = ();
4926 foreach my $result (@$db_result) {
4927 if($result->{type} eq 'actor') {
4928 if($e->requestor->id != $result->{id}) {
4929 $patron = $e->retrieve_actor_user($result->{id});
4931 push(@$filter_result, $e->event);
4934 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4935 push(@$filter_result, $result);
4938 push(@$filter_result, $e->event);
4942 push(@$filter_result, $result);
4946 push(@$filter_result, $result);
4949 return $filter_result;
4955 __PACKAGE__->register_method(
4956 method => 'address_alert_test',
4957 api_name => 'open-ils.actor.address_alert.test',
4959 desc => "Tests a set of address fields to determine if they match with an address_alert",
4961 {desc => 'Authentication token', type => 'string'},
4962 {desc => 'Org Unit', type => 'number'},
4963 {desc => 'Fields', type => 'hash'},
4965 return => {desc => 'List of matching address_alerts'}
4969 sub address_alert_test {
4970 my ($self, $client, $auth, $org_unit, $fields) = @_;
4971 return [] unless $fields and grep {$_} values %$fields;
4973 my $e = new_editor(authtoken => $auth);
4974 return $e->event unless $e->checkauth;
4975 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4976 $org_unit ||= $e->requestor->ws_ou;
4978 my $alerts = $e->json_query({
4980 'actor.address_alert_matches',
4988 $$fields{post_code},
4989 $$fields{mailing_address},
4990 $$fields{billing_address}
4994 # map the json_query hashes to real objects
4996 map {$e->retrieve_actor_address_alert($_)}
4997 (map {$_->{id}} @$alerts)
5001 __PACKAGE__->register_method(
5002 method => "mark_users_contact_invalid",
5003 api_name => "open-ils.actor.invalidate.email",
5005 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",
5007 {desc => "Authentication token", type => "string"},
5008 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5009 {desc => "Additional note text (optional)", type => "string"},
5010 {desc => "penalty org unit ID (optional)", type => "number"},
5011 {desc => "Email address (optional)", type => "string"}
5013 return => {desc => "Event describing success or failure", type => "object"}
5017 __PACKAGE__->register_method(
5018 method => "mark_users_contact_invalid",
5019 api_name => "open-ils.actor.invalidate.day_phone",
5021 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",
5023 {desc => "Authentication token", type => "string"},
5024 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5025 {desc => "Additional note text (optional)", type => "string"},
5026 {desc => "penalty org unit ID (optional)", type => "number"},
5027 {desc => "Phone Number (optional)", type => "string"}
5029 return => {desc => "Event describing success or failure", type => "object"}
5033 __PACKAGE__->register_method(
5034 method => "mark_users_contact_invalid",
5035 api_name => "open-ils.actor.invalidate.evening_phone",
5037 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",
5039 {desc => "Authentication token", type => "string"},
5040 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5041 {desc => "Additional note text (optional)", type => "string"},
5042 {desc => "penalty org unit ID (optional)", type => "number"},
5043 {desc => "Phone Number (optional)", type => "string"}
5045 return => {desc => "Event describing success or failure", type => "object"}
5049 __PACKAGE__->register_method(
5050 method => "mark_users_contact_invalid",
5051 api_name => "open-ils.actor.invalidate.other_phone",
5053 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",
5055 {desc => "Authentication token", type => "string"},
5056 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5057 {desc => "Additional note text (optional)", type => "string"},
5058 {desc => "penalty org unit ID (optional, default to top of org tree)",
5060 {desc => "Phone Number (optional)", type => "string"}
5062 return => {desc => "Event describing success or failure", type => "object"}
5066 sub mark_users_contact_invalid {
5067 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5069 # This method invalidates an email address or a phone_number which
5070 # removes the bad email address or phone number, copying its contents
5071 # to a patron note, and institutes a standing penalty for "bad email"
5072 # or "bad phone number" which is cleared when the user is saved or
5073 # optionally only when the user is saved with an email address or
5074 # phone number (or staff manually delete the penalty).
5076 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5078 my $e = new_editor(authtoken => $auth, xact => 1);
5079 return $e->die_event unless $e->checkauth;
5082 if (defined $patron_id && $patron_id ne "") {
5083 $howfind = {usr => $patron_id};
5084 } elsif (defined $contact && $contact ne "") {
5085 $howfind = {$contact_type => $contact};
5087 # Error out if no patron id set or no contact is set.
5088 return OpenILS::Event->new('BAD_PARAMS');
5091 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5092 $e, $contact_type, $howfind,
5093 $addl_note, $penalty_ou, $e->requestor->id
5097 # Putting the following method in open-ils.actor is a bad fit, except in that
5098 # it serves an interface that lives under 'actor' in the templates directory,
5099 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5101 __PACKAGE__->register_method(
5102 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5103 method => "get_all_at_reactors_in_use",
5108 { name => 'authtoken', type => 'string' }
5111 desc => 'list of reactor names', type => 'array'
5116 sub get_all_at_reactors_in_use {
5117 my ($self, $conn, $auth) = @_;
5119 my $e = new_editor(authtoken => $auth);
5120 $e->checkauth or return $e->die_event;
5121 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5123 my $reactors = $e->json_query({
5125 atevdef => [{column => "reactor", transform => "distinct"}]
5127 from => {atevdef => {}}
5130 return $e->die_event unless ref $reactors eq "ARRAY";
5133 return [ map { $_->{reactor} } @$reactors ];
5136 __PACKAGE__->register_method(
5137 method => "filter_group_entry_crud",
5138 api_name => "open-ils.actor.filter_group_entry.crud",
5141 Provides CRUD access to filter group entry objects. These are not full accessible
5142 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5143 are not accessible via PCRUD (because they have no fields against which to link perms)
5146 {desc => "Authentication token", type => "string"},
5147 {desc => "Entry ID / Entry Object", type => "number"},
5148 {desc => "Additional note text (optional)", type => "string"},
5149 {desc => "penalty org unit ID (optional, default to top of org tree)",
5153 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5159 sub filter_group_entry_crud {
5160 my ($self, $conn, $auth, $arg) = @_;
5162 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5163 my $e = new_editor(authtoken => $auth, xact => 1);
5164 return $e->die_event unless $e->checkauth;
5170 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5171 or return $e->die_event;
5173 return $e->die_event unless $e->allowed(
5174 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5176 my $query = $arg->query;
5177 $query = $e->create_actor_search_query($query) or return $e->die_event;
5178 $arg->query($query->id);
5179 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5180 $entry->query($query);
5185 } elsif ($arg->ischanged) {
5187 my $entry = $e->retrieve_actor_search_filter_group_entry([
5190 flesh_fields => {asfge => ['grp']}
5192 ]) or return $e->die_event;
5194 return $e->die_event unless $e->allowed(
5195 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5197 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5198 $arg->query($arg->query->id);
5199 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5200 $arg->query($query);
5205 } elsif ($arg->isdeleted) {
5207 my $entry = $e->retrieve_actor_search_filter_group_entry([
5210 flesh_fields => {asfge => ['grp', 'query']}
5212 ]) or return $e->die_event;
5214 return $e->die_event unless $e->allowed(
5215 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5217 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5218 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5231 my $entry = $e->retrieve_actor_search_filter_group_entry([
5234 flesh_fields => {asfge => ['grp', 'query']}
5236 ]) or return $e->die_event;
5238 return $e->die_event unless $e->allowed(
5239 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5240 $entry->grp->owner);
5243 $entry->grp($entry->grp->id); # for consistency