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 => "fetch_visible_ou_settings_log",
252 api_name => "open-ils.actor.org_unit.settings.history.visible.retrieve",
254 desc => "Retrieves the log entries for the specified OU setting. " .
255 "If the setting has a view permission, the results are limited " .
256 "to entries at the OUs that the user has the view permission. ",
258 {desc => 'Authentication token', type => 'string'},
259 {desc => 'Setting name', type => 'string'}
261 return => {desc => 'List of fieldmapper objects of the log entries, Event on error'}
265 sub fetch_visible_ou_settings_log {
266 my( $self, $client, $auth, $setting ) = @_;
268 my $e = new_editor(authtoken => $auth);
269 return $e->event unless $e->checkauth;
270 return $e->die_event unless $e->allowed("STAFF_LOGIN");
271 return OpenILS::Event->new('BAD_PARAMS') unless defined($setting);
273 my $type = $e->retrieve_config_org_unit_setting_type([
275 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
277 return OpenILS::Event->new('BAD_PARAMS', note => 'setting type not found')
280 my $query = { field_name => $setting };
281 if ($type->view_perm) {
282 $query->{org} = $U->user_has_work_perm_at($e, $type->view_perm->code, {descendants => 1});
283 if (scalar @{ $query->{org} } == 0) {
284 # user doesn't have the view permission anywhere, so return nothing
289 my $results = $e->search_config_org_unit_setting_type_log([$query, {'order_by' => 'date_applied ASC'}])
290 or return $e->die_event;
294 __PACKAGE__->register_method(
295 method => "user_settings",
297 api_name => "open-ils.actor.patron.settings.retrieve",
300 my( $self, $client, $auth, $user_id, $setting ) = @_;
302 my $e = new_editor(authtoken => $auth);
303 return $e->event unless $e->checkauth;
304 $user_id = $e->requestor->id unless defined $user_id;
306 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
307 if($e->requestor->id != $user_id) {
308 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
312 my($e, $user_id, $setting) = @_;
313 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
314 return undef unless $val; # XXX this should really return undef, but needs testing
315 return OpenSRF::Utils::JSON->JSON2perl($val->value);
319 if(ref $setting eq 'ARRAY') {
321 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
324 return get_setting($e, $user_id, $setting);
327 my $s = $e->search_actor_user_setting({usr => $user_id});
328 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
333 __PACKAGE__->register_method(
334 method => "ranged_ou_settings",
335 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
337 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
338 "is implied for retrieving OU settings by the authenticated users' permissions.",
340 {desc => 'Authentication token', type => 'string'},
341 {desc => 'Org unit ID', type => 'number'},
343 return => {desc => 'A hashref of "ranged" settings, event on error'}
346 sub ranged_ou_settings {
347 my( $self, $client, $auth, $org_id ) = @_;
349 my $e = new_editor(authtoken => $auth);
350 return $e->event unless $e->checkauth;
353 my $org_list = $U->get_org_ancestors($org_id);
354 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
355 $org_list = [ reverse @$org_list ];
357 # start at the context org and capture the setting value
358 # without clobbering settings we've already captured
359 for my $this_org_id (@$org_list) {
361 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
363 for my $set (@sets) {
364 my $type = $e->retrieve_config_org_unit_setting_type([
366 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
369 # If there is no relevant permission, the default assumption will
370 # be, "yes, the caller can have that value."
371 if ($type && $type->view_perm) {
372 next if not $e->allowed($type->view_perm->code, $org_id);
375 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
376 unless defined $ranged_settings{$set->name};
380 return \%ranged_settings;
385 __PACKAGE__->register_method(
386 api_name => 'open-ils.actor.ou_setting.ancestor_default',
387 method => 'ou_ancestor_setting',
389 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
390 'This method will make sure that the given user has permission to view that setting, if there is a ' .
391 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
392 'the user lacks the permisssion, undef will be returned.' ,
394 { desc => 'Org unit ID', type => 'number' },
395 { desc => 'setting name', type => 'string' },
396 { desc => 'authtoken (optional)', type => 'string' }
398 return => {desc => 'A value for the org unit setting, or undef'}
402 # ------------------------------------------------------------------
403 # Attempts to find the org setting value for a given org. if not
404 # found at the requested org, searches up the org tree until it
405 # finds a parent that has the requested setting.
406 # when found, returns { org => $id, value => $value }
407 # otherwise, returns NULL
408 # ------------------------------------------------------------------
409 sub ou_ancestor_setting {
410 my( $self, $client, $orgid, $name, $auth ) = @_;
411 # Make sure $auth is set to something if not given.
413 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
416 __PACKAGE__->register_method(
417 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
418 method => 'ou_ancestor_setting_batch',
420 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
421 'This method will make sure that the given user has permission to view that setting, if there is a ' .
422 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
423 'the user lacks the permisssion, undef will be returned.' ,
425 { desc => 'Org unit ID', type => 'number' },
426 { desc => 'setting name list', type => 'array' },
427 { desc => 'authtoken (optional)', type => 'string' }
429 return => {desc => 'A hash with name => value pairs for the org unit settings'}
432 sub ou_ancestor_setting_batch {
433 my( $self, $client, $orgid, $name_list, $auth ) = @_;
435 # splitting the list of settings to fetch values
436 # so that ones that *don't* require view_perm checks
437 # can be fetched in one fell swoop, which is
438 # significantly faster in cases where a large
439 # number of settings need to be fetched.
440 my %perm_check_required = ();
441 my @perm_check_not_required = ();
443 # Note that ->ou_ancestor_setting also can check
444 # to see if the setting has a view_perm, but testing
445 # suggests that the redundant checks do not significantly
446 # increase the time it takes to fetch the values of
447 # permission-controlled settings.
448 my $e = new_editor();
449 my $res = $e->search_config_org_unit_setting_type({
451 view_perm => { "!=" => undef },
453 %perm_check_required = map { $_->name() => 1 } @$res;
454 foreach my $setting (@$name_list) {
455 push @perm_check_not_required, $setting
456 unless exists($perm_check_required{$setting});
460 if (@perm_check_not_required) {
461 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
463 $values{$_} = $U->ou_ancestor_setting(
466 ) for keys(%perm_check_required);
472 __PACKAGE__->register_method(
473 method => "update_patron",
474 api_name => "open-ils.actor.patron.update",
477 Update an existing user, or create a new one. Related objects,
478 like cards, addresses, survey responses, and stat cats,
479 can be updated by attaching them to the user object in their
480 respective fields. For examples, the billing address object
481 may be inserted into the 'billing_address' field, etc. For each
482 attached object, indicate if the object should be created,
483 updated, or deleted using the built-in 'isnew', 'ischanged',
484 and 'isdeleted' fields on the object.
487 { desc => 'Authentication token', type => 'string' },
488 { desc => 'Patron data object', type => 'object' }
490 return => {desc => 'A fleshed user object, event on error'}
495 my( $self, $client, $auth, $patron ) = @_;
497 my $e = new_editor(xact => 1, authtoken => $auth);
498 return $e->event unless $e->checkauth;
500 $logger->info($patron->isnew ? "Creating new patron..." :
501 "Updating Patron: " . $patron->id);
503 my $evt = check_group_perm($e, $e->requestor, $patron);
506 # $new_patron is the patron in progress. $patron is the original patron
507 # passed in with the method. new_patron will change as the components
508 # of patron are added/updated.
512 # unflesh the real items on the patron
513 $patron->card( $patron->card->id ) if(ref($patron->card));
514 $patron->billing_address( $patron->billing_address->id )
515 if(ref($patron->billing_address));
516 $patron->mailing_address( $patron->mailing_address->id )
517 if(ref($patron->mailing_address));
519 # create/update the patron first so we can use his id
521 # $patron is the obj from the client (new data) and $new_patron is the
522 # patron object properly built for db insertion, so we need a third variable
523 # if we want to represent the old patron.
526 my $barred_hook = '';
529 if($patron->isnew()) {
530 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
532 if($U->is_true($patron->barred)) {
533 return $e->die_event unless
534 $e->allowed('BAR_PATRON', $patron->home_ou);
536 if(($patron->photo_url)) {
537 return $e->die_event unless
538 $e->allowed('UPDATE_USER_PHOTO_URL', $patron->home_ou);
541 $new_patron = $patron;
543 # Did auth checking above already.
544 $old_patron = $e->retrieve_actor_user($patron->id) or
545 return $e->die_event;
547 $renew_hook = 'au.renewed' if ($old_patron->expire_date ne $new_patron->expire_date);
549 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
550 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
551 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
553 $barred_hook = $U->is_true($new_patron->barred) ?
554 'au.barred' : 'au.unbarred';
557 if($old_patron->photo_url ne $new_patron->photo_url) {
558 my $perm = 'UPDATE_USER_PHOTO_URL';
559 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
562 # update the password by itself to avoid the password protection magic
563 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
564 modify_migrated_user_password($e, $patron->id, $patron->passwd);
565 $new_patron->passwd(''); # subsequent update will set
566 # actor.usr.passwd to MD5('')
570 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
573 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
576 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
579 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
582 # re-update the patron if anything has happened to him during this process
583 if($new_patron->ischanged()) {
584 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
588 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
591 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
594 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
597 $evt = apply_invalid_addr_penalty($e, $patron);
602 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
604 $tses->request('open-ils.trigger.event.autocreate',
605 'au.created', $new_patron, $new_patron->home_ou);
607 $tses->request('open-ils.trigger.event.autocreate',
608 'au.updated', $new_patron, $new_patron->home_ou);
610 $tses->request('open-ils.trigger.event.autocreate', $renew_hook,
611 $new_patron, $new_patron->home_ou) if $renew_hook;
613 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
614 $new_patron, $new_patron->home_ou) if $barred_hook;
617 $e->xact_begin; # $e->rollback is called in new_flesh_user
618 return flesh_user($new_patron->id(), $e);
621 sub apply_invalid_addr_penalty {
625 # grab the invalid address penalty if set
626 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
628 my ($addr_penalty) = grep
629 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
631 # do we enforce invalid address penalty
632 my $enforce = $U->ou_ancestor_setting_value(
633 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
635 my $addrs = $e->search_actor_user_address(
636 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
637 my $addr_count = scalar(@$addrs);
639 if($addr_count == 0 and $addr_penalty) {
641 # regardless of any settings, remove the penalty when the user has no invalid addresses
642 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
645 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
647 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
648 my $depth = $ptype->org_depth;
649 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
650 $ctx_org = $patron->home_ou unless defined $ctx_org;
652 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
653 $penalty->usr($patron->id);
654 $penalty->org_unit($ctx_org);
655 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
657 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
672 "standing_penalties",
682 push @$fields, "home_ou" if $home_ou;
683 return new_flesh_user($id, $fields, $e );
691 # clone and clear stuff that would break the database
695 my $new_patron = $patron->clone;
697 $new_patron->clear_billing_address();
698 $new_patron->clear_mailing_address();
699 $new_patron->clear_addresses();
700 $new_patron->clear_card();
701 $new_patron->clear_cards();
702 $new_patron->clear_id();
703 $new_patron->clear_isnew();
704 $new_patron->clear_ischanged();
705 $new_patron->clear_isdeleted();
706 $new_patron->clear_stat_cat_entries();
707 $new_patron->clear_waiver_entries();
708 $new_patron->clear_permissions();
709 $new_patron->clear_standing_penalties();
720 return (undef, $e->die_event) unless
721 $e->allowed('CREATE_USER', $patron->home_ou);
723 my $ex = $e->search_actor_user(
724 {usrname => $patron->usrname}, {idlist => 1});
725 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
727 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
729 # do a dance to get the password hashed securely
730 my $saved_password = $patron->passwd;
732 $e->create_actor_user($patron) or return (undef, $e->die_event);
733 modify_migrated_user_password($e, $patron->id, $saved_password);
735 my $id = $patron->id; # added by CStoreEditor
737 $logger->info("Successfully created new user [$id] in DB");
738 return ($e->retrieve_actor_user($id), undef);
742 sub check_group_perm {
743 my( $e, $requestor, $patron ) = @_;
746 # first let's see if the requestor has
747 # priveleges to update this user in any way
748 if( ! $patron->isnew ) {
749 my $p = $e->retrieve_actor_user($patron->id);
751 # If we are the requestor (trying to update our own account)
752 # and we are not trying to change our profile, we're good
753 if( $p->id == $requestor->id and
754 $p->profile == $patron->profile ) {
759 $evt = group_perm_failed($e, $requestor, $p);
763 # They are allowed to edit this patron.. can they put the
764 # patron into the group requested?
765 $evt = group_perm_failed($e, $requestor, $patron);
771 sub group_perm_failed {
772 my( $e, $requestor, $patron ) = @_;
776 my $grpid = $patron->profile;
780 $logger->debug("user update looking for group perm for group $grpid");
781 $grp = $e->retrieve_permission_grp_tree($grpid);
783 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
785 $logger->info("user update checking perm $perm on user ".
786 $requestor->id." for update/create on user username=".$patron->usrname);
788 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
794 my( $e, $patron, $noperm) = @_;
796 $logger->info("Updating patron ".$patron->id." in DB");
801 return (undef, $e->die_event)
802 unless $e->allowed('UPDATE_USER', $patron->home_ou);
805 if(!$patron->ident_type) {
806 $patron->clear_ident_type;
807 $patron->clear_ident_value;
810 $evt = verify_last_xact($e, $patron);
811 return (undef, $evt) if $evt;
813 $e->update_actor_user($patron) or return (undef, $e->die_event);
815 # re-fetch the user to pick up the latest last_xact_id value
816 # to avoid collisions.
817 $patron = $e->retrieve_actor_user($patron->id);
822 sub verify_last_xact {
823 my( $e, $patron ) = @_;
824 return undef unless $patron->id and $patron->id > 0;
825 my $p = $e->retrieve_actor_user($patron->id);
826 my $xact = $p->last_xact_id;
827 return undef unless $xact;
828 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
829 return OpenILS::Event->new('XACT_COLLISION')
830 if $xact ne $patron->last_xact_id;
835 sub _check_dup_ident {
836 my( $session, $patron ) = @_;
838 return undef unless $patron->ident_value;
841 ident_type => $patron->ident_type,
842 ident_value => $patron->ident_value,
845 $logger->debug("patron update searching for dup ident values: " .
846 $patron->ident_type . ':' . $patron->ident_value);
848 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
850 my $dups = $session->request(
851 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
854 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
861 sub _add_update_addresses {
865 my $new_patron = shift;
869 my $current_id; # id of the address before creation
871 my $addresses = $patron->addresses();
873 for my $address (@$addresses) {
875 next unless ref $address;
876 $current_id = $address->id();
878 if( $patron->billing_address() and
879 $patron->billing_address() == $current_id ) {
880 $logger->info("setting billing addr to $current_id");
881 $new_patron->billing_address($address->id());
882 $new_patron->ischanged(1);
885 if( $patron->mailing_address() and
886 $patron->mailing_address() == $current_id ) {
887 $new_patron->mailing_address($address->id());
888 $logger->info("setting mailing addr to $current_id");
889 $new_patron->ischanged(1);
893 if($address->isnew()) {
895 $address->usr($new_patron->id());
897 ($address, $evt) = _add_address($e,$address);
898 return (undef, $evt) if $evt;
900 # we need to get the new id
901 if( $patron->billing_address() and
902 $patron->billing_address() == $current_id ) {
903 $new_patron->billing_address($address->id());
904 $logger->info("setting billing addr to $current_id");
905 $new_patron->ischanged(1);
908 if( $patron->mailing_address() and
909 $patron->mailing_address() == $current_id ) {
910 $new_patron->mailing_address($address->id());
911 $logger->info("setting mailing addr to $current_id");
912 $new_patron->ischanged(1);
915 } elsif($address->ischanged() ) {
917 ($address, $evt) = _update_address($e, $address);
918 return (undef, $evt) if $evt;
920 } elsif($address->isdeleted() ) {
922 if( $address->id() == $new_patron->mailing_address() ) {
923 $new_patron->clear_mailing_address();
924 ($new_patron, $evt) = _update_patron($e, $new_patron);
925 return (undef, $evt) if $evt;
928 if( $address->id() == $new_patron->billing_address() ) {
929 $new_patron->clear_billing_address();
930 ($new_patron, $evt) = _update_patron($e, $new_patron);
931 return (undef, $evt) if $evt;
934 $evt = _delete_address($e, $address);
935 return (undef, $evt) if $evt;
939 return ( $new_patron, undef );
943 # adds an address to the db and returns the address with new id
945 my($e, $address) = @_;
946 $address->clear_id();
948 $logger->info("Creating new address at street ".$address->street1);
950 # put the address into the database
951 $e->create_actor_user_address($address) or return (undef, $e->die_event);
952 return ($address, undef);
956 sub _update_address {
957 my( $e, $address ) = @_;
959 $logger->info("Updating address ".$address->id." in the DB");
961 $e->update_actor_user_address($address) or return (undef, $e->die_event);
963 return ($address, undef);
968 sub _add_update_cards {
972 my $new_patron = shift;
976 my $virtual_id; #id of the card before creation
978 my $card_changed = 0;
979 my $cards = $patron->cards();
980 for my $card (@$cards) {
982 $card->usr($new_patron->id());
984 if(ref($card) and $card->isnew()) {
986 $virtual_id = $card->id();
987 ( $card, $evt ) = _add_card($e, $card);
988 return (undef, $evt) if $evt;
990 #if(ref($patron->card)) { $patron->card($patron->card->id); }
991 if($patron->card() == $virtual_id) {
992 $new_patron->card($card->id());
993 $new_patron->ischanged(1);
997 } elsif( ref($card) and $card->ischanged() ) {
998 $evt = _update_card($e, $card);
999 return (undef, $evt) if $evt;
1004 $U->create_events_for_hook('au.barcode_changed', $new_patron, $e->requestor->ws_ou)
1007 return ( $new_patron, undef );
1011 # adds an card to the db and returns the card with new id
1013 my( $e, $card ) = @_;
1016 $logger->info("Adding new patron card ".$card->barcode);
1018 $e->create_actor_card($card) or return (undef, $e->die_event);
1020 return ( $card, undef );
1024 # returns event on error. returns undef otherwise
1026 my( $e, $card ) = @_;
1027 $logger->info("Updating patron card ".$card->id);
1029 $e->update_actor_card($card) or return $e->die_event;
1034 sub _add_update_waiver_entries {
1037 my $new_patron = shift;
1040 my $waiver_entries = $patron->waiver_entries();
1041 for my $waiver (@$waiver_entries) {
1042 next unless ref $waiver;
1043 $waiver->usr($new_patron->id());
1044 if ($waiver->isnew()) {
1045 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
1046 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
1047 $logger->info("Adding new patron waiver entry");
1048 $waiver->clear_id();
1049 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1050 } elsif ($waiver->ischanged()) {
1051 $logger->info("Updating patron waiver entry " . $waiver->id);
1052 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1053 } elsif ($waiver->isdeleted()) {
1054 $logger->info("Deleting patron waiver entry " . $waiver->id);
1055 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1058 return ($new_patron, undef);
1062 # returns event on error. returns undef otherwise
1063 sub _delete_address {
1064 my( $e, $address ) = @_;
1066 $logger->info("Deleting address ".$address->id." from DB");
1068 $e->delete_actor_user_address($address) or return $e->die_event;
1074 sub _add_survey_responses {
1075 my ($e, $patron, $new_patron) = @_;
1077 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1079 my $responses = $patron->survey_responses;
1083 $_->usr($new_patron->id) for (@$responses);
1085 my $evt = $U->simplereq( "open-ils.circ",
1086 "open-ils.circ.survey.submit.user_id", $responses );
1088 return (undef, $evt) if defined($U->event_code($evt));
1092 return ( $new_patron, undef );
1095 sub _clear_badcontact_penalties {
1096 my ($e, $old_patron, $new_patron) = @_;
1098 return ($new_patron, undef) unless $old_patron;
1100 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1102 # This ignores whether the caller of update_patron has any permission
1103 # to remove penalties, but these penalties no longer make sense
1104 # if an email address field (for example) is changed (and the caller must
1105 # have perms to do *that*) so there's no reason not to clear the penalties.
1107 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1109 "+csp" => {"name" => [values(%$PNM)]},
1110 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1112 "join" => {"csp" => {}},
1114 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1116 ]) or return (undef, $e->die_event);
1118 return ($new_patron, undef) unless @$bad_contact_penalties;
1120 my @penalties_to_clear;
1121 my ($field, $penalty_name);
1123 # For each field that might have an associated bad contact penalty,
1124 # check for such penalties and add them to the to-clear list if that
1125 # field has changed.
1126 while (($field, $penalty_name) = each(%$PNM)) {
1127 if ($old_patron->$field ne $new_patron->$field) {
1128 push @penalties_to_clear, grep {
1129 $_->standing_penalty->name eq $penalty_name
1130 } @$bad_contact_penalties;
1134 foreach (@penalties_to_clear) {
1135 # Note that this "archives" penalties, in the terminology of the staff
1136 # client, instead of just deleting them. This may assist reporting,
1137 # or preserving old contact information when it is still potentially
1139 $_->standing_penalty($_->standing_penalty->id); # deflesh
1140 $_->stop_date('now');
1141 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1144 return ($new_patron, undef);
1148 sub _create_stat_maps {
1150 my($e, $patron, $new_patron) = @_;
1152 my $maps = $patron->stat_cat_entries();
1154 for my $map (@$maps) {
1156 my $method = "update_actor_stat_cat_entry_user_map";
1158 if ($map->isdeleted()) {
1159 $method = "delete_actor_stat_cat_entry_user_map";
1161 } elsif ($map->isnew()) {
1162 $method = "create_actor_stat_cat_entry_user_map";
1167 $map->target_usr($new_patron->id);
1169 $logger->info("Updating stat entry with method $method and map $map");
1171 $e->$method($map) or return (undef, $e->die_event);
1174 return ($new_patron, undef);
1177 sub _create_perm_maps {
1179 my($e, $patron, $new_patron) = @_;
1181 my $maps = $patron->permissions;
1183 for my $map (@$maps) {
1185 my $method = "update_permission_usr_perm_map";
1186 if ($map->isdeleted()) {
1187 $method = "delete_permission_usr_perm_map";
1188 } elsif ($map->isnew()) {
1189 $method = "create_permission_usr_perm_map";
1193 $map->usr($new_patron->id);
1195 $logger->info( "Updating permissions with method $method and map $map" );
1197 $e->$method($map) or return (undef, $e->die_event);
1200 return ($new_patron, undef);
1204 __PACKAGE__->register_method(
1205 method => "set_user_work_ous",
1206 api_name => "open-ils.actor.user.work_ous.update",
1209 sub set_user_work_ous {
1215 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1216 return $evt if $evt;
1218 my $session = $apputils->start_db_session();
1219 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1221 for my $map (@$maps) {
1223 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1224 if ($map->isdeleted()) {
1225 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1226 } elsif ($map->isnew()) {
1227 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1231 #warn( "Updating permissions with method $method and session $ses and map $map" );
1232 $logger->info( "Updating work_ou map with method $method and map $map" );
1234 my $stat = $session->request($method, $map)->gather(1);
1235 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1239 $apputils->commit_db_session($session);
1241 return scalar(@$maps);
1245 __PACKAGE__->register_method(
1246 method => "set_user_perms",
1247 api_name => "open-ils.actor.user.permissions.update",
1250 sub set_user_perms {
1256 my $session = $apputils->start_db_session();
1258 my( $user_obj, $evt ) = $U->checkses($ses);
1259 return $evt if $evt;
1260 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1262 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1265 $all = 1 if ($U->is_true($user_obj->super_user()));
1266 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1268 for my $map (@$maps) {
1270 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1271 if ($map->isdeleted()) {
1272 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1273 } elsif ($map->isnew()) {
1274 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1278 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1279 #warn( "Updating permissions with method $method and session $ses and map $map" );
1280 $logger->info( "Updating permissions with method $method and map $map" );
1282 my $stat = $session->request($method, $map)->gather(1);
1283 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1287 $apputils->commit_db_session($session);
1289 return scalar(@$maps);
1293 __PACKAGE__->register_method(
1294 method => "user_retrieve_by_barcode",
1296 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1298 sub user_retrieve_by_barcode {
1299 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1301 my $e = new_editor(authtoken => $auth);
1302 return $e->event unless $e->checkauth;
1304 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1305 or return $e->event;
1307 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1308 return $e->event unless $e->allowed(
1309 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1316 __PACKAGE__->register_method(
1317 method => "get_user_by_id",
1319 api_name => "open-ils.actor.user.retrieve",
1322 sub get_user_by_id {
1323 my ($self, $client, $auth, $id) = @_;
1324 my $e = new_editor(authtoken=>$auth);
1325 return $e->event unless $e->checkauth;
1326 my $user = $e->retrieve_actor_user($id) or return $e->event;
1327 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1332 __PACKAGE__->register_method(
1333 method => "get_org_types",
1334 api_name => "open-ils.actor.org_types.retrieve",
1337 return $U->get_org_types();
1341 __PACKAGE__->register_method(
1342 method => "get_user_ident_types",
1343 api_name => "open-ils.actor.user.ident_types.retrieve",
1346 sub get_user_ident_types {
1347 return $ident_types if $ident_types;
1348 return $ident_types =
1349 new_editor()->retrieve_all_config_identification_type();
1353 __PACKAGE__->register_method(
1354 method => "get_org_unit",
1355 api_name => "open-ils.actor.org_unit.retrieve",
1359 my( $self, $client, $user_session, $org_id ) = @_;
1360 my $e = new_editor(authtoken => $user_session);
1362 return $e->event unless $e->checkauth;
1363 $org_id = $e->requestor->ws_ou;
1365 my $o = $e->retrieve_actor_org_unit($org_id)
1366 or return $e->event;
1370 __PACKAGE__->register_method(
1371 method => "search_org_unit",
1372 api_name => "open-ils.actor.org_unit_list.search",
1375 sub search_org_unit {
1377 my( $self, $client, $field, $value ) = @_;
1379 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1381 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1382 { $field => $value } );
1388 # build the org tree
1390 __PACKAGE__->register_method(
1391 method => "get_org_tree",
1392 api_name => "open-ils.actor.org_tree.retrieve",
1394 note => "Returns the entire org tree structure",
1400 return $U->get_org_tree($client->session->session_locale);
1404 __PACKAGE__->register_method(
1405 method => "get_org_descendants",
1406 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1409 # depth is optional. org_unit is the id
1410 sub get_org_descendants {
1411 my( $self, $client, $org_unit, $depth ) = @_;
1413 if(ref $org_unit eq 'ARRAY') {
1416 for my $i (0..scalar(@$org_unit)-1) {
1417 my $list = $U->simple_scalar_request(
1419 "open-ils.storage.actor.org_unit.descendants.atomic",
1420 $org_unit->[$i], $depth->[$i] );
1421 push(@trees, $U->build_org_tree($list));
1426 my $orglist = $apputils->simple_scalar_request(
1428 "open-ils.storage.actor.org_unit.descendants.atomic",
1429 $org_unit, $depth );
1430 return $U->build_org_tree($orglist);
1435 __PACKAGE__->register_method(
1436 method => "get_org_ancestors",
1437 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1440 # depth is optional. org_unit is the id
1441 sub get_org_ancestors {
1442 my( $self, $client, $org_unit, $depth ) = @_;
1443 my $orglist = $apputils->simple_scalar_request(
1445 "open-ils.storage.actor.org_unit.ancestors.atomic",
1446 $org_unit, $depth );
1447 return $U->build_org_tree($orglist);
1451 __PACKAGE__->register_method(
1452 method => "get_standings",
1453 api_name => "open-ils.actor.standings.retrieve"
1458 return $user_standings if $user_standings;
1459 return $user_standings =
1460 $apputils->simple_scalar_request(
1462 "open-ils.cstore.direct.config.standing.search.atomic",
1463 { id => { "!=" => undef } }
1468 __PACKAGE__->register_method(
1469 method => "get_my_org_path",
1470 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1473 sub get_my_org_path {
1474 my( $self, $client, $auth, $org_id ) = @_;
1475 my $e = new_editor(authtoken=>$auth);
1476 return $e->event unless $e->checkauth;
1477 $org_id = $e->requestor->ws_ou unless defined $org_id;
1479 return $apputils->simple_scalar_request(
1481 "open-ils.storage.actor.org_unit.full_path.atomic",
1485 __PACKAGE__->register_method(
1486 method => "retrieve_coordinates",
1487 api_name => "open-ils.actor.geo.retrieve_coordinates",
1490 {desc => 'Authentication token', type => 'string' },
1491 {type => 'number', desc => 'Context Organizational Unit'},
1492 {type => 'string', desc => 'Address to look-up as a text string'}
1494 return => { desc => 'Hash/object containing latitude and longitude for the provided address.'}
1498 sub retrieve_coordinates {
1499 my( $self, $client, $auth, $org_id, $addr_string ) = @_;
1500 my $e = new_editor(authtoken=>$auth);
1501 return $e->event unless $e->checkauth;
1502 $org_id = $e->requestor->ws_ou unless defined $org_id;
1504 return $apputils->simple_scalar_request(
1506 "open-ils.geo.retrieve_coordinates",
1507 $org_id, $addr_string );
1510 __PACKAGE__->register_method(
1511 method => "patron_adv_search",
1512 api_name => "open-ils.actor.patron.search.advanced"
1515 __PACKAGE__->register_method(
1516 method => "patron_adv_search",
1517 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1519 # Flush the response stream at most 5 patrons in for UI responsiveness.
1520 max_bundle_count => 5,
1522 desc => q/Returns a stream of fleshed user objects instead of
1523 a pile of identifiers/
1527 sub patron_adv_search {
1528 my( $self, $client, $auth, $search_hash, $search_limit,
1529 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1531 # API params sanity checks.
1532 # Exit early with empty result if no filter exists.
1533 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1534 my $fleshed = ($self->api_name =~ /fleshed/);
1535 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1537 for my $key (keys %$search_hash) {
1538 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1542 return ($fleshed ? undef : []) unless $search_ok;
1544 my $e = new_editor(authtoken=>$auth);
1545 return $e->event unless $e->checkauth;
1546 return $e->event unless $e->allowed('VIEW_USER');
1548 # depth boundary outside of which patrons must opt-in, default to 0
1549 my $opt_boundary = 0;
1550 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1552 if (not defined $search_ou) {
1553 my $depth = $U->ou_ancestor_setting_value(
1554 $e->requestor->ws_ou,
1555 'circ.patron_edit.duplicate_patron_check_depth'
1558 if (defined $depth) {
1559 $search_ou = $U->org_unit_ancestor_at_depth(
1560 $e->requestor->ws_ou, $depth
1565 my $ids = $U->storagereq(
1566 "open-ils.storage.actor.user.crazy_search", $search_hash,
1567 $search_limit, $search_sort, $include_inactive,
1568 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1570 return $ids unless $self->api_name =~ /fleshed/;
1572 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1578 # A migrated (main) password has the form:
1579 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1580 sub modify_migrated_user_password {
1581 my ($e, $user_id, $passwd) = @_;
1583 # new password gets a new salt
1584 my $new_salt = $e->json_query({
1585 from => ['actor.create_salt', 'main']})->[0];
1586 $new_salt = $new_salt->{'actor.create_salt'};
1593 md5_hex($new_salt . md5_hex($passwd)),
1601 __PACKAGE__->register_method(
1602 method => "update_passwd",
1603 api_name => "open-ils.actor.user.password.update",
1605 desc => "Update the operator's password",
1607 { desc => 'Authentication token', type => 'string' },
1608 { desc => 'New password', type => 'string' },
1609 { desc => 'Current password', type => 'string' }
1611 return => {desc => '1 on success, Event on error or incorrect current password'}
1615 __PACKAGE__->register_method(
1616 method => "update_passwd",
1617 api_name => "open-ils.actor.user.username.update",
1619 desc => "Update the operator's username",
1621 { desc => 'Authentication token', type => 'string' },
1622 { desc => 'New username', type => 'string' },
1623 { desc => 'Current password', type => 'string' }
1625 return => {desc => '1 on success, Event on error or incorrect current password'}
1629 __PACKAGE__->register_method(
1630 method => "update_passwd",
1631 api_name => "open-ils.actor.user.email.update",
1633 desc => "Update the operator's email address",
1635 { desc => 'Authentication token', type => 'string' },
1636 { desc => 'New email address', type => 'string' },
1637 { desc => 'Current password', type => 'string' }
1639 return => {desc => '1 on success, Event on error or incorrect current password'}
1644 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1645 my $e = new_editor(xact=>1, authtoken=>$auth);
1646 return $e->die_event unless $e->checkauth;
1648 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1649 or return $e->die_event;
1650 my $api = $self->api_name;
1652 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1654 return new OpenILS::Event('INCORRECT_PASSWORD');
1658 if( $api =~ /password/o ) {
1659 # NOTE: with access to the plain text password we could crypt
1660 # the password without the extra MD5 pre-hashing. Other changes
1661 # would be required. Noting here for future reference.
1662 modify_migrated_user_password($e, $db_user->id, $new_val);
1663 $db_user->passwd('');
1667 # if we don't clear the password, the user will be updated with
1668 # a hashed version of the hashed version of their password
1669 $db_user->clear_passwd;
1671 if( $api =~ /username/o ) {
1673 # make sure no one else has this username
1674 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1677 return new OpenILS::Event('USERNAME_EXISTS');
1679 $db_user->usrname($new_val);
1682 } elsif( $api =~ /email/o ) {
1683 $db_user->email($new_val);
1688 $e->update_actor_user($db_user) or return $e->die_event;
1691 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1694 # update the cached user to pick up these changes
1695 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1701 __PACKAGE__->register_method(
1702 method => "check_user_perms",
1703 api_name => "open-ils.actor.user.perm.check",
1704 notes => <<" NOTES");
1705 Takes a login session, user id, an org id, and an array of perm type strings. For each
1706 perm type, if the user does *not* have the given permission it is added
1707 to a list which is returned from the method. If all permissions
1708 are allowed, an empty list is returned
1709 if the logged in user does not match 'user_id', then the logged in user must
1710 have VIEW_PERMISSION priveleges.
1713 sub check_user_perms {
1714 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1716 my( $staff, $evt ) = $apputils->checkses($login_session);
1717 return $evt if $evt;
1719 if($staff->id ne $user_id) {
1720 if( $evt = $apputils->check_perms(
1721 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1727 for my $perm (@$perm_types) {
1728 if($apputils->check_perms($user_id, $org_id, $perm)) {
1729 push @not_allowed, $perm;
1733 return \@not_allowed
1736 __PACKAGE__->register_method(
1737 method => "check_user_perms2",
1738 api_name => "open-ils.actor.user.perm.check.multi_org",
1740 Checks the permissions on a list of perms and orgs for a user
1741 @param authtoken The login session key
1742 @param user_id The id of the user to check
1743 @param orgs The array of org ids
1744 @param perms The array of permission names
1745 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1746 if the logged in user does not match 'user_id', then the logged in user must
1747 have VIEW_PERMISSION priveleges.
1750 sub check_user_perms2 {
1751 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1753 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1754 $authtoken, $user_id, 'VIEW_PERMISSION' );
1755 return $evt if $evt;
1758 for my $org (@$orgs) {
1759 for my $perm (@$perms) {
1760 if($apputils->check_perms($user_id, $org, $perm)) {
1761 push @not_allowed, [ $org, $perm ];
1766 return \@not_allowed
1770 __PACKAGE__->register_method(
1771 method => 'check_user_perms3',
1772 api_name => 'open-ils.actor.user.perm.highest_org',
1774 Returns the highest org unit id at which a user has a given permission
1775 If the requestor does not match the target user, the requestor must have
1776 'VIEW_PERMISSION' rights at the home org unit of the target user
1777 @param authtoken The login session key
1778 @param userid The id of the user in question
1779 @param perm The permission to check
1780 @return The org unit highest in the org tree within which the user has
1781 the requested permission
1784 sub check_user_perms3 {
1785 my($self, $client, $authtoken, $user_id, $perm) = @_;
1786 my $e = new_editor(authtoken=>$authtoken);
1787 return $e->event unless $e->checkauth;
1789 my $tree = $U->get_org_tree();
1791 unless($e->requestor->id == $user_id) {
1792 my $user = $e->retrieve_actor_user($user_id)
1793 or return $e->event;
1794 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1795 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1798 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1801 __PACKAGE__->register_method(
1802 method => 'user_has_work_perm_at',
1803 api_name => 'open-ils.actor.user.has_work_perm_at',
1807 Returns a set of org unit IDs which represent the highest orgs in
1808 the org tree where the user has the requested permission. The
1809 purpose of this method is to return the smallest set of org units
1810 which represent the full expanse of the user's ability to perform
1811 the requested action. The user whose perms this method should
1812 check is implied by the authtoken. /,
1814 {desc => 'authtoken', type => 'string'},
1815 {desc => 'permission name', type => 'string'},
1816 {desc => q/user id, optional. If present, check perms for
1817 this user instead of the logged in user/, type => 'number'},
1819 return => {desc => 'An array of org IDs'}
1823 sub user_has_work_perm_at {
1824 my($self, $conn, $auth, $perm, $user_id) = @_;
1825 my $e = new_editor(authtoken=>$auth);
1826 return $e->event unless $e->checkauth;
1827 if(defined $user_id) {
1828 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1829 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1831 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1834 __PACKAGE__->register_method(
1835 method => 'user_has_work_perm_at_batch',
1836 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1840 sub user_has_work_perm_at_batch {
1841 my($self, $conn, $auth, $perms, $user_id) = @_;
1842 my $e = new_editor(authtoken=>$auth);
1843 return $e->event unless $e->checkauth;
1844 if(defined $user_id) {
1845 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1846 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1849 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1855 __PACKAGE__->register_method(
1856 method => 'check_user_perms4',
1857 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1859 Returns the highest org unit id at which a user has a given permission
1860 If the requestor does not match the target user, the requestor must have
1861 'VIEW_PERMISSION' rights at the home org unit of the target user
1862 @param authtoken The login session key
1863 @param userid The id of the user in question
1864 @param perms An array of perm names to check
1865 @return An array of orgId's representing the org unit
1866 highest in the org tree within which the user has the requested permission
1867 The arrah of orgId's has matches the order of the perms array
1870 sub check_user_perms4 {
1871 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1873 my( $staff, $target, $org, $evt );
1875 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1876 $authtoken, $userid, 'VIEW_PERMISSION' );
1877 return $evt if $evt;
1880 return [] unless ref($perms);
1881 my $tree = $U->get_org_tree();
1883 for my $p (@$perms) {
1884 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1890 __PACKAGE__->register_method(
1891 method => "user_fines_summary",
1892 api_name => "open-ils.actor.user.fines.summary",
1895 desc => 'Returns a short summary of the users total open fines, ' .
1896 'excluding voided fines Params are login_session, user_id' ,
1898 {desc => 'Authentication token', type => 'string'},
1899 {desc => 'User ID', type => 'string'} # number?
1902 desc => "a 'mous' object, event on error",
1907 sub user_fines_summary {
1908 my( $self, $client, $auth, $user_id ) = @_;
1910 my $e = new_editor(authtoken=>$auth);
1911 return $e->event unless $e->checkauth;
1913 if( $user_id ne $e->requestor->id ) {
1914 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1915 return $e->event unless
1916 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1919 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1923 __PACKAGE__->register_method(
1924 method => "user_opac_vitals",
1925 api_name => "open-ils.actor.user.opac.vital_stats",
1929 desc => 'Returns a short summary of the users vital stats, including ' .
1930 'identification information, accumulated balance, number of holds, ' .
1931 'and current open circulation stats' ,
1933 {desc => 'Authentication token', type => 'string'},
1934 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1937 desc => "An object with four properties: user, fines, checkouts and holds."
1942 sub user_opac_vitals {
1943 my( $self, $client, $auth, $user_id ) = @_;
1945 my $e = new_editor(authtoken=>$auth);
1946 return $e->event unless $e->checkauth;
1948 $user_id ||= $e->requestor->id;
1950 my $user = $e->retrieve_actor_user( $user_id );
1953 ->method_lookup('open-ils.actor.user.fines.summary')
1954 ->run($auth => $user_id);
1955 return $fines if (defined($U->event_code($fines)));
1958 $fines = new Fieldmapper::money::open_user_summary ();
1959 $fines->balance_owed(0.00);
1960 $fines->total_owed(0.00);
1961 $fines->total_paid(0.00);
1962 $fines->usr($user_id);
1966 ->method_lookup('open-ils.actor.user.hold_requests.count')
1967 ->run($auth => $user_id);
1968 return $holds if (defined($U->event_code($holds)));
1971 ->method_lookup('open-ils.actor.user.checked_out.count')
1972 ->run($auth => $user_id);
1973 return $out if (defined($U->event_code($out)));
1975 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1977 my $unread_msgs = $e->search_actor_usr_message([
1978 {usr => $user_id, read_date => undef, deleted => 'f'},
1984 first_given_name => $user->first_given_name,
1985 second_given_name => $user->second_given_name,
1986 family_name => $user->family_name,
1987 alias => $user->alias,
1988 usrname => $user->usrname
1990 fines => $fines->to_bare_hash,
1993 messages => { unread => scalar(@$unread_msgs) }
1998 ##### a small consolidation of related method registrations
1999 my $common_params = [
2000 { desc => 'Authentication token', type => 'string' },
2001 { desc => 'User ID', type => 'string' },
2002 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
2003 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
2006 'open-ils.actor.user.transactions' => '',
2007 'open-ils.actor.user.transactions.fleshed' => '',
2008 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
2009 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
2010 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
2011 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
2014 foreach (keys %methods) {
2016 method => "user_transactions",
2019 desc => 'For a given user, retrieve a list of '
2020 . (/\.fleshed/ ? 'fleshed ' : '')
2021 . 'transactions' . $methods{$_}
2022 . ' optionally limited to transactions of a given type.',
2023 params => $common_params,
2025 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
2026 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
2030 $args{authoritative} = 1;
2031 __PACKAGE__->register_method(%args);
2034 # Now for the counts
2036 'open-ils.actor.user.transactions.count' => '',
2037 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
2038 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
2041 foreach (keys %methods) {
2043 method => "user_transactions",
2046 desc => 'For a given user, retrieve a count of open '
2047 . 'transactions' . $methods{$_}
2048 . ' optionally limited to transactions of a given type.',
2049 params => $common_params,
2050 return => { desc => "Integer count of transactions, or event on error" }
2053 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2054 __PACKAGE__->register_method(%args);
2057 __PACKAGE__->register_method(
2058 method => "user_transactions",
2059 api_name => "open-ils.actor.user.transactions.have_balance.total",
2062 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2063 . ' optionally limited to transactions of a given type.',
2064 params => $common_params,
2065 return => { desc => "Decimal balance value, or event on error" }
2070 sub user_transactions {
2071 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2074 my $e = new_editor(authtoken => $auth);
2075 return $e->event unless $e->checkauth;
2077 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2079 return $e->event unless
2080 $e->requestor->id == $user_id or
2081 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2083 my $api = $self->api_name();
2085 my $filter = ($api =~ /have_balance/o) ?
2086 { 'balance_owed' => { '<>' => 0 } }:
2087 { 'total_owed' => { '>' => 0 } };
2089 my $method = 'open-ils.actor.user.transactions.history.still_open';
2090 $method = "$method.authoritative" if $api =~ /authoritative/;
2091 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2093 if($api =~ /total/o) {
2095 $total += $_->balance_owed for @$trans;
2099 ($api =~ /count/o ) and return scalar @$trans;
2100 ($api !~ /fleshed/o) and return $trans;
2103 for my $t (@$trans) {
2105 if( $t->xact_type ne 'circulation' ) {
2106 push @resp, {transaction => $t};
2110 my $circ_data = flesh_circ($e, $t->id);
2111 push @resp, {transaction => $t, %$circ_data};
2118 __PACKAGE__->register_method(
2119 method => "user_transaction_retrieve",
2120 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2123 notes => "Returns a fleshed transaction record"
2126 __PACKAGE__->register_method(
2127 method => "user_transaction_retrieve",
2128 api_name => "open-ils.actor.user.transaction.retrieve",
2131 notes => "Returns a transaction record"
2134 sub user_transaction_retrieve {
2135 my($self, $client, $auth, $bill_id) = @_;
2137 my $e = new_editor(authtoken => $auth);
2138 return $e->event unless $e->checkauth;
2140 my $trans = $e->retrieve_money_billable_transaction_summary(
2141 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2143 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2145 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2147 return $trans unless $self->api_name =~ /flesh/;
2148 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2150 my $circ_data = flesh_circ($e, $trans->id, 1);
2152 return {transaction => $trans, %$circ_data};
2157 my $circ_id = shift;
2158 my $flesh_copy = shift;
2160 my $circ = $e->retrieve_action_circulation([
2164 circ => ['target_copy'],
2165 acp => ['call_number'],
2172 my $copy = $circ->target_copy;
2174 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2175 $mods = new Fieldmapper::metabib::virtual_record;
2176 $mods->doc_id(OILS_PRECAT_RECORD);
2177 $mods->title($copy->dummy_title);
2178 $mods->author($copy->dummy_author);
2181 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2185 $circ->target_copy($circ->target_copy->id);
2186 $copy->call_number($copy->call_number->id);
2188 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2192 __PACKAGE__->register_method(
2193 method => "hold_request_count",
2194 api_name => "open-ils.actor.user.hold_requests.count",
2198 Returns hold ready vs. total counts.
2199 If a context org unit is provided, a third value
2200 is returned with key 'behind_desk', which reports
2201 how many holds are ready at the pickup library
2202 with the behind_desk flag set to true.
2206 sub hold_request_count {
2207 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2208 my $e = new_editor(authtoken => $authtoken);
2209 return $e->event unless $e->checkauth;
2211 $user_id = $e->requestor->id unless defined $user_id;
2213 if($e->requestor->id ne $user_id) {
2214 my $user = $e->retrieve_actor_user($user_id);
2215 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2218 my $holds = $e->json_query({
2219 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2223 fulfillment_time => {"=" => undef },
2224 cancel_time => undef,
2229 $_->{current_shelf_lib} and # avoid undef warnings
2230 $_->{pickup_lib} eq $_->{current_shelf_lib}
2234 total => scalar(@$holds),
2235 ready => scalar(@ready)
2239 # count of holds ready at pickup lib with behind_desk true.
2240 $resp->{behind_desk} = scalar(
2242 $_->{pickup_lib} == $ctx_org and
2243 $U->is_true($_->{behind_desk})
2251 __PACKAGE__->register_method(
2252 method => "checked_out",
2253 api_name => "open-ils.actor.user.checked_out",
2257 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2258 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2259 . "(i.e., outstanding balance or some other pending action on the circ). "
2260 . "The .count method also includes a 'total' field which sums all open circs.",
2262 { desc => 'Authentication Token', type => 'string'},
2263 { desc => 'User ID', type => 'string'},
2266 desc => 'Returns event on error, or an object with ID lists, like: '
2267 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2272 __PACKAGE__->register_method(
2273 method => "checked_out",
2274 api_name => "open-ils.actor.user.checked_out.count",
2277 signature => q/@see open-ils.actor.user.checked_out/
2281 my( $self, $conn, $auth, $userid ) = @_;
2283 my $e = new_editor(authtoken=>$auth);
2284 return $e->event unless $e->checkauth;
2286 if( $userid ne $e->requestor->id ) {
2287 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2288 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2290 # see if there is a friend link allowing circ.view perms
2291 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2292 $e, $userid, $e->requestor->id, 'circ.view');
2293 return $e->event unless $allowed;
2297 my $count = $self->api_name =~ /count/;
2298 return _checked_out( $count, $e, $userid );
2302 my( $iscount, $e, $userid ) = @_;
2308 claims_returned => [],
2311 my $meth = 'retrieve_action_open_circ_';
2319 claims_returned => 0,
2326 my $data = $e->$meth($userid);
2330 $result{$_} += $data->$_() for (keys %result);
2331 $result{total} += $data->$_() for (keys %result);
2333 for my $k (keys %result) {
2334 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2344 __PACKAGE__->register_method(
2345 method => "checked_in_with_fines",
2346 api_name => "open-ils.actor.user.checked_in_with_fines",
2349 signature => q/@see open-ils.actor.user.checked_out/
2352 sub checked_in_with_fines {
2353 my( $self, $conn, $auth, $userid ) = @_;
2355 my $e = new_editor(authtoken=>$auth);
2356 return $e->event unless $e->checkauth;
2358 if( $userid ne $e->requestor->id ) {
2359 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2362 # money is owed on these items and they are checked in
2363 my $open = $e->search_action_circulation(
2366 xact_finish => undef,
2367 checkin_time => { "!=" => undef },
2372 my( @lost, @cr, @lo );
2373 for my $c (@$open) {
2374 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2375 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2376 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2381 claims_returned => \@cr,
2382 long_overdue => \@lo
2388 my ($api, $desc, $auth) = @_;
2389 $desc = $desc ? (" " . $desc) : '';
2390 my $ids = ($api =~ /ids$/) ? 1 : 0;
2393 method => "user_transaction_history",
2394 api_name => "open-ils.actor.user.transactions.$api",
2396 desc => "For a given User ID, returns a list of billable transaction" .
2397 ($ids ? " id" : '') .
2398 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2399 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2401 {desc => 'Authentication token', type => 'string'},
2402 {desc => 'User ID', type => 'number'},
2403 {desc => 'Transaction type (optional)', type => 'number'},
2404 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2407 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2411 $auth and push @sig, (authoritative => 1);
2415 my %auth_hist_methods = (
2417 'history.have_charge' => 'that have an initial charge',
2418 'history.still_open' => 'that are not finished',
2419 'history.have_balance' => 'that have a balance',
2420 'history.have_bill' => 'that have billings',
2421 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2422 'history.have_payment' => 'that have at least 1 payment',
2425 foreach (keys %auth_hist_methods) {
2426 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2427 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2428 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2431 sub user_transaction_history {
2432 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2436 my $e = new_editor(authtoken=>$auth);
2437 return $e->die_event unless $e->checkauth;
2439 if ($e->requestor->id ne $userid) {
2440 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2443 my $api = $self->api_name;
2444 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2446 if(defined($type)) {
2447 $filter->{'xact_type'} = $type;
2450 if($api =~ /have_bill_or_payment/o) {
2452 # transactions that have a non-zero sum across all billings or at least 1 payment
2453 $filter->{'-or'} = {
2454 'balance_owed' => { '<>' => 0 },
2455 'last_payment_ts' => { '<>' => undef }
2458 } elsif($api =~ /have_payment/) {
2460 $filter->{last_payment_ts} ||= {'<>' => undef};
2462 } elsif( $api =~ /have_balance/o) {
2464 # transactions that have a non-zero overall balance
2465 $filter->{'balance_owed'} = { '<>' => 0 };
2467 } elsif( $api =~ /have_charge/o) {
2469 # transactions that have at least 1 billing, regardless of whether it was voided
2470 $filter->{'last_billing_ts'} = { '<>' => undef };
2472 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2474 # transactions that have non-zero sum across all billings. This will exclude
2475 # xacts where all billings have been voided
2476 $filter->{'total_owed'} = { '<>' => 0 };
2479 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2480 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2481 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2483 my $mbts = $e->search_money_billable_transaction_summary(
2484 [ { usr => $userid, @xact_finish, %$filter },
2489 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2490 return $mbts unless $api =~ /fleshed/;
2493 for my $t (@$mbts) {
2495 if( $t->xact_type ne 'circulation' ) {
2496 push @resp, {transaction => $t};
2500 my $circ_data = flesh_circ($e, $t->id);
2501 push @resp, {transaction => $t, %$circ_data};
2509 __PACKAGE__->register_method(
2510 method => "user_perms",
2511 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2513 notes => "Returns a list of permissions"
2517 my( $self, $client, $authtoken, $user ) = @_;
2519 my( $staff, $evt ) = $apputils->checkses($authtoken);
2520 return $evt if $evt;
2522 $user ||= $staff->id;
2524 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2528 return $apputils->simple_scalar_request(
2530 "open-ils.storage.permission.user_perms.atomic",
2534 __PACKAGE__->register_method(
2535 method => "retrieve_perms",
2536 api_name => "open-ils.actor.permissions.retrieve",
2537 notes => "Returns a list of permissions"
2539 sub retrieve_perms {
2540 my( $self, $client ) = @_;
2541 return $apputils->simple_scalar_request(
2543 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2544 { id => { '!=' => undef } }
2548 __PACKAGE__->register_method(
2549 method => "retrieve_groups",
2550 api_name => "open-ils.actor.groups.retrieve",
2551 notes => "Returns a list of user groups"
2553 sub retrieve_groups {
2554 my( $self, $client ) = @_;
2555 return new_editor()->retrieve_all_permission_grp_tree();
2558 __PACKAGE__->register_method(
2559 method => "retrieve_org_address",
2560 api_name => "open-ils.actor.org_unit.address.retrieve",
2561 notes => <<' NOTES');
2562 Returns an org_unit address by ID
2563 @param An org_address ID
2565 sub retrieve_org_address {
2566 my( $self, $client, $id ) = @_;
2567 return $apputils->simple_scalar_request(
2569 "open-ils.cstore.direct.actor.org_address.retrieve",
2574 __PACKAGE__->register_method(
2575 method => "retrieve_groups_tree",
2576 api_name => "open-ils.actor.groups.tree.retrieve",
2577 notes => "Returns a list of user groups"
2580 sub retrieve_groups_tree {
2581 my( $self, $client ) = @_;
2582 return new_editor()->search_permission_grp_tree(
2587 flesh_fields => { pgt => ["children"] },
2588 order_by => { pgt => 'name'}
2595 __PACKAGE__->register_method(
2596 method => "add_user_to_groups",
2597 api_name => "open-ils.actor.user.set_groups",
2598 notes => "Adds a user to one or more permission groups"
2601 sub add_user_to_groups {
2602 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2604 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2605 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2606 return $evt if $evt;
2608 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2609 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2610 return $evt if $evt;
2612 $apputils->simplereq(
2614 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2616 for my $group (@$groups) {
2617 my $link = Fieldmapper::permission::usr_grp_map->new;
2619 $link->usr($userid);
2621 my $id = $apputils->simplereq(
2623 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2629 __PACKAGE__->register_method(
2630 method => "get_user_perm_groups",
2631 api_name => "open-ils.actor.user.get_groups",
2632 notes => "Retrieve a user's permission groups."
2636 sub get_user_perm_groups {
2637 my( $self, $client, $authtoken, $userid ) = @_;
2639 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2640 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2641 return $evt if $evt;
2643 return $apputils->simplereq(
2645 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2649 __PACKAGE__->register_method(
2650 method => "get_user_work_ous",
2651 api_name => "open-ils.actor.user.get_work_ous",
2652 notes => "Retrieve a user's work org units."
2655 __PACKAGE__->register_method(
2656 method => "get_user_work_ous",
2657 api_name => "open-ils.actor.user.get_work_ous.ids",
2658 notes => "Retrieve a user's work org units."
2661 sub get_user_work_ous {
2662 my( $self, $client, $auth, $userid ) = @_;
2663 my $e = new_editor(authtoken=>$auth);
2664 return $e->event unless $e->checkauth;
2665 $userid ||= $e->requestor->id;
2667 if($e->requestor->id != $userid) {
2668 my $user = $e->retrieve_actor_user($userid)
2669 or return $e->event;
2670 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2673 return $e->search_permission_usr_work_ou_map({usr => $userid})
2674 unless $self->api_name =~ /.ids$/;
2676 # client just wants a list of org IDs
2677 return $U->get_user_work_ou_ids($e, $userid);
2682 __PACKAGE__->register_method(
2683 method => 'register_workstation',
2684 api_name => 'open-ils.actor.workstation.register.override',
2685 signature => q/@see open-ils.actor.workstation.register/
2688 __PACKAGE__->register_method(
2689 method => 'register_workstation',
2690 api_name => 'open-ils.actor.workstation.register',
2692 Registers a new workstion in the system
2693 @param authtoken The login session key
2694 @param name The name of the workstation id
2695 @param owner The org unit that owns this workstation
2696 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2697 if the name is already in use.
2701 sub register_workstation {
2702 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2704 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2705 return $e->die_event unless $e->checkauth;
2706 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2707 my $existing = $e->search_actor_workstation({name => $name})->[0];
2708 $oargs = { all => 1 } unless defined $oargs;
2712 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2713 # workstation with the given name exists.
2715 if($owner ne $existing->owning_lib) {
2716 # if necessary, update the owning_lib of the workstation
2718 $logger->info("changing owning lib of workstation ".$existing->id.
2719 " from ".$existing->owning_lib." to $owner");
2720 return $e->die_event unless
2721 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2723 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2725 $existing->owning_lib($owner);
2726 return $e->die_event unless $e->update_actor_workstation($existing);
2732 "attempt to register an existing workstation. returning existing ID");
2735 return $existing->id;
2738 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2742 my $ws = Fieldmapper::actor::workstation->new;
2743 $ws->owning_lib($owner);
2745 $e->create_actor_workstation($ws) or return $e->die_event;
2747 return $ws->id; # note: editor sets the id on the new object for us
2750 __PACKAGE__->register_method(
2751 method => 'workstation_list',
2752 api_name => 'open-ils.actor.workstation.list',
2754 Returns a list of workstations registered at the given location
2755 @param authtoken The login session key
2756 @param ids A list of org_unit.id's for the workstation owners
2760 sub workstation_list {
2761 my( $self, $conn, $authtoken, @orgs ) = @_;
2763 my $e = new_editor(authtoken=>$authtoken);
2764 return $e->event unless $e->checkauth;
2769 unless $e->allowed('REGISTER_WORKSTATION', $o);
2770 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2776 __PACKAGE__->register_method(
2777 method => 'fetch_patron_note',
2778 api_name => 'open-ils.actor.note.retrieve.all',
2781 Returns a list of notes for a given user
2782 Requestor must have VIEW_USER permission if pub==false and
2783 @param authtoken The login session key
2784 @param args Hash of params including
2785 patronid : the patron's id
2786 pub : true if retrieving only public notes
2790 sub fetch_patron_note {
2791 my( $self, $conn, $authtoken, $args ) = @_;
2792 my $patronid = $$args{patronid};
2794 my($reqr, $evt) = $U->checkses($authtoken);
2795 return $evt if $evt;
2798 ($patron, $evt) = $U->fetch_user($patronid);
2799 return $evt if $evt;
2802 if( $patronid ne $reqr->id ) {
2803 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2804 return $evt if $evt;
2806 return $U->cstorereq(
2807 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2808 { usr => $patronid, pub => 't' } );
2811 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2812 return $evt if $evt;
2814 return $U->cstorereq(
2815 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2818 __PACKAGE__->register_method(
2819 method => 'create_user_note',
2820 api_name => 'open-ils.actor.note.create',
2822 Creates a new note for the given user
2823 @param authtoken The login session key
2824 @param note The note object
2827 sub create_user_note {
2828 my( $self, $conn, $authtoken, $note ) = @_;
2829 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2830 return $e->die_event unless $e->checkauth;
2832 my $user = $e->retrieve_actor_user($note->usr)
2833 or return $e->die_event;
2835 return $e->die_event unless
2836 $e->allowed('UPDATE_USER',$user->home_ou);
2838 $note->creator($e->requestor->id);
2839 $e->create_actor_usr_note($note) or return $e->die_event;
2845 __PACKAGE__->register_method(
2846 method => 'delete_user_note',
2847 api_name => 'open-ils.actor.note.delete',
2849 Deletes a note for the given user
2850 @param authtoken The login session key
2851 @param noteid The note id
2854 sub delete_user_note {
2855 my( $self, $conn, $authtoken, $noteid ) = @_;
2857 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2858 return $e->die_event unless $e->checkauth;
2859 my $note = $e->retrieve_actor_usr_note($noteid)
2860 or return $e->die_event;
2861 my $user = $e->retrieve_actor_user($note->usr)
2862 or return $e->die_event;
2863 return $e->die_event unless
2864 $e->allowed('UPDATE_USER', $user->home_ou);
2866 $e->delete_actor_usr_note($note) or return $e->die_event;
2872 __PACKAGE__->register_method(
2873 method => 'update_user_note',
2874 api_name => 'open-ils.actor.note.update',
2876 @param authtoken The login session key
2877 @param note The note
2881 sub update_user_note {
2882 my( $self, $conn, $auth, $note ) = @_;
2883 my $e = new_editor(authtoken=>$auth, xact=>1);
2884 return $e->die_event unless $e->checkauth;
2885 my $patron = $e->retrieve_actor_user($note->usr)
2886 or return $e->die_event;
2887 return $e->die_event unless
2888 $e->allowed('UPDATE_USER', $patron->home_ou);
2889 $e->update_actor_user_note($note)
2890 or return $e->die_event;
2895 __PACKAGE__->register_method(
2896 method => 'fetch_patron_messages',
2897 api_name => 'open-ils.actor.message.retrieve',
2900 Returns a list of notes for a given user, not
2901 including ones marked deleted
2902 @param authtoken The login session key
2903 @param patronid patron ID
2904 @param options hash containing optional limit and offset
2908 sub fetch_patron_messages {
2909 my( $self, $conn, $auth, $patronid, $options ) = @_;
2913 my $e = new_editor(authtoken => $auth);
2914 return $e->die_event unless $e->checkauth;
2916 if ($e->requestor->id ne $patronid) {
2917 return $e->die_event unless $e->allowed('VIEW_USER');
2920 my $select_clause = { usr => $patronid };
2921 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2922 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2923 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2925 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2930 __PACKAGE__->register_method(
2931 method => 'usrname_exists',
2932 api_name => 'open-ils.actor.username.exists',
2934 desc => 'Check if a username is already taken (by an undeleted patron)',
2936 {desc => 'Authentication token', type => 'string'},
2937 {desc => 'Username', type => 'string'}
2940 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2945 sub usrname_exists {
2946 my( $self, $conn, $auth, $usrname ) = @_;
2947 my $e = new_editor(authtoken=>$auth);
2948 return $e->event unless $e->checkauth;
2949 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2950 return $$a[0] if $a and @$a;
2954 __PACKAGE__->register_method(
2955 method => 'barcode_exists',
2956 api_name => 'open-ils.actor.barcode.exists',
2958 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2961 sub barcode_exists {
2962 my( $self, $conn, $auth, $barcode ) = @_;
2963 my $e = new_editor(authtoken=>$auth);
2964 return $e->event unless $e->checkauth;
2965 my $card = $e->search_actor_card({barcode => $barcode});
2971 #return undef unless @$card;
2972 #return $card->[0]->usr;
2976 __PACKAGE__->register_method(
2977 method => 'retrieve_net_levels',
2978 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2981 sub retrieve_net_levels {
2982 my( $self, $conn, $auth ) = @_;
2983 my $e = new_editor(authtoken=>$auth);
2984 return $e->event unless $e->checkauth;
2985 return $e->retrieve_all_config_net_access_level();
2988 # Retain the old typo API name just in case
2989 __PACKAGE__->register_method(
2990 method => 'fetch_org_by_shortname',
2991 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2993 __PACKAGE__->register_method(
2994 method => 'fetch_org_by_shortname',
2995 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2997 sub fetch_org_by_shortname {
2998 my( $self, $conn, $sname ) = @_;
2999 my $e = new_editor();
3000 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
3001 return $e->event unless $org;
3006 __PACKAGE__->register_method(
3007 method => 'session_home_lib',
3008 api_name => 'open-ils.actor.session.home_lib',
3011 sub session_home_lib {
3012 my( $self, $conn, $auth ) = @_;
3013 my $e = new_editor(authtoken=>$auth);
3014 return undef unless $e->checkauth;
3015 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
3016 return $org->shortname;
3019 __PACKAGE__->register_method(
3020 method => 'session_safe_token',
3021 api_name => 'open-ils.actor.session.safe_token',
3023 Returns a hashed session ID that is safe for export to the world.
3024 This safe token will expire after 1 hour of non-use.
3025 @param auth Active authentication token
3029 sub session_safe_token {
3030 my( $self, $conn, $auth ) = @_;
3031 my $e = new_editor(authtoken=>$auth);
3032 return undef unless $e->checkauth;
3034 my $safe_token = md5_hex($auth);
3036 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3038 # add more user fields as needed
3040 "safe-token-user-$safe_token", {
3041 id => $e->requestor->id,
3042 home_ou_shortname => $e->retrieve_actor_org_unit(
3043 $e->requestor->home_ou)->shortname,
3052 __PACKAGE__->register_method(
3053 method => 'safe_token_home_lib',
3054 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
3056 Returns the home library shortname from the session
3057 asscociated with a safe token from generated by
3058 open-ils.actor.session.safe_token.
3059 @param safe_token Active safe token
3060 @param who Optional user activity "ewho" value
3064 sub safe_token_home_lib {
3065 my( $self, $conn, $safe_token, $who ) = @_;
3066 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3068 my $blob = $cache->get_cache("safe-token-user-$safe_token");
3069 return unless $blob;
3071 $U->log_user_activity($blob->{id}, $who, 'verify');
3072 return $blob->{home_ou_shortname};
3076 __PACKAGE__->register_method(
3077 method => "update_penalties",
3078 api_name => "open-ils.actor.user.penalties.update"
3081 sub update_penalties {
3082 my($self, $conn, $auth, $user_id) = @_;
3083 my $e = new_editor(authtoken=>$auth, xact => 1);
3084 return $e->die_event unless $e->checkauth;
3085 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3086 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3087 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3088 return $evt if $evt;
3094 __PACKAGE__->register_method(
3095 method => "apply_penalty",
3096 api_name => "open-ils.actor.user.penalty.apply"
3100 my($self, $conn, $auth, $penalty) = @_;
3102 my $e = new_editor(authtoken=>$auth, xact => 1);
3103 return $e->die_event unless $e->checkauth;
3105 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3106 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3108 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3111 (defined $ptype->org_depth) ?
3112 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
3115 $penalty->org_unit($ctx_org);
3116 $penalty->staff($e->requestor->id);
3117 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3120 return $penalty->id;
3123 __PACKAGE__->register_method(
3124 method => "remove_penalty",
3125 api_name => "open-ils.actor.user.penalty.remove"
3128 sub remove_penalty {
3129 my($self, $conn, $auth, $penalty) = @_;
3130 my $e = new_editor(authtoken=>$auth, xact => 1);
3131 return $e->die_event unless $e->checkauth;
3132 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3133 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3135 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3140 __PACKAGE__->register_method(
3141 method => "update_penalty_note",
3142 api_name => "open-ils.actor.user.penalty.note.update"
3145 sub update_penalty_note {
3146 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3147 my $e = new_editor(authtoken=>$auth, xact => 1);
3148 return $e->die_event unless $e->checkauth;
3149 for my $penalty_id (@$penalty_ids) {
3150 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3151 if (! $penalty ) { return $e->die_event; }
3152 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3153 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3155 $penalty->note( $note ); $penalty->ischanged( 1 );
3157 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3163 __PACKAGE__->register_method(
3164 method => "ranged_penalty_thresholds",
3165 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3169 sub ranged_penalty_thresholds {
3170 my($self, $conn, $auth, $context_org) = @_;
3171 my $e = new_editor(authtoken=>$auth);
3172 return $e->event unless $e->checkauth;
3173 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3174 my $list = $e->search_permission_grp_penalty_threshold([
3175 {org_unit => $U->get_org_ancestors($context_org)},
3176 {order_by => {pgpt => 'id'}}
3178 $conn->respond($_) for @$list;
3184 __PACKAGE__->register_method(
3185 method => "user_retrieve_fleshed_by_id",
3187 api_name => "open-ils.actor.user.fleshed.retrieve",
3190 sub user_retrieve_fleshed_by_id {
3191 my( $self, $client, $auth, $user_id, $fields ) = @_;
3192 my $e = new_editor(authtoken => $auth);
3193 return $e->event unless $e->checkauth;
3195 if( $e->requestor->id != $user_id ) {
3196 return $e->event unless $e->allowed('VIEW_USER');
3203 "standing_penalties",
3211 return new_flesh_user($user_id, $fields, $e);
3215 sub new_flesh_user {
3218 my $fields = shift || [];
3221 my $fetch_penalties = 0;
3222 if(grep {$_ eq 'standing_penalties'} @$fields) {
3223 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3224 $fetch_penalties = 1;
3227 my $fetch_usr_act = 0;
3228 if(grep {$_ eq 'usr_activity'} @$fields) {
3229 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3233 my $user = $e->retrieve_actor_user(
3238 "flesh_fields" => { "au" => $fields }
3241 ) or return $e->die_event;
3244 if( grep { $_ eq 'addresses' } @$fields ) {
3246 $user->addresses([]) unless @{$user->addresses};
3247 # don't expose "replaced" addresses by default
3248 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3250 if( ref $user->billing_address ) {
3251 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3252 push( @{$user->addresses}, $user->billing_address );
3256 if( ref $user->mailing_address ) {
3257 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3258 push( @{$user->addresses}, $user->mailing_address );
3263 if($fetch_penalties) {
3264 # grab the user penalties ranged for this location
3265 $user->standing_penalties(
3266 $e->search_actor_user_standing_penalty([
3269 {stop_date => undef},
3270 {stop_date => {'>' => 'now'}}
3272 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3275 flesh_fields => {ausp => ['standing_penalty']}
3281 # retrieve the most recent usr_activity entry
3282 if ($fetch_usr_act) {
3284 # max number to return for simple patron fleshing
3285 my $limit = $U->ou_ancestor_setting_value(
3286 $e->requestor->ws_ou,
3287 'circ.patron.usr_activity_retrieve.max');
3291 flesh_fields => {auact => ['etype']},
3292 order_by => {auact => 'event_time DESC'},
3295 # 0 == none, <0 == return all
3296 $limit = 1 unless defined $limit;
3297 $opts->{limit} = $limit if $limit > 0;
3299 $user->usr_activity(
3301 [] : # skip the DB call
3302 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3307 $user->clear_passwd();
3314 __PACKAGE__->register_method(
3315 method => "user_retrieve_parts",
3316 api_name => "open-ils.actor.user.retrieve.parts",
3319 sub user_retrieve_parts {
3320 my( $self, $client, $auth, $user_id, $fields ) = @_;
3321 my $e = new_editor(authtoken => $auth);
3322 return $e->event unless $e->checkauth;
3323 $user_id ||= $e->requestor->id;
3324 if( $e->requestor->id != $user_id ) {
3325 return $e->event unless $e->allowed('VIEW_USER');
3328 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3329 push(@resp, $user->$_()) for(@$fields);
3335 __PACKAGE__->register_method(
3336 method => 'user_opt_in_enabled',
3337 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3338 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3341 sub user_opt_in_enabled {
3342 my($self, $conn) = @_;
3343 my $sc = OpenSRF::Utils::SettingsClient->new;
3344 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3349 __PACKAGE__->register_method(
3350 method => 'user_opt_in_at_org',
3351 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3353 @param $auth The auth token
3354 @param user_id The ID of the user to test
3355 @return 1 if the user has opted in at the specified org,
3356 2 if opt-in is disallowed for the user's home org,
3357 event on error, and 0 otherwise. /
3359 sub user_opt_in_at_org {
3360 my($self, $conn, $auth, $user_id) = @_;
3362 # see if we even need to enforce the opt-in value
3363 return 1 unless user_opt_in_enabled($self);
3365 my $e = new_editor(authtoken => $auth);
3366 return $e->event unless $e->checkauth;
3368 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3369 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3371 my $ws_org = $e->requestor->ws_ou;
3372 # user is automatically opted-in if they are from the local org
3373 return 1 if $user->home_ou eq $ws_org;
3375 # get the boundary setting
3376 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3378 # auto opt in if user falls within the opt boundary
3379 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3381 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3383 # check whether opt-in is restricted at the user's home library
3384 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3385 if ($opt_restrict_depth) {
3386 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3387 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3389 # opt-in is disallowed unless the workstation org is within the home
3390 # library's opt-in scope
3391 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3394 my $vals = $e->search_actor_usr_org_unit_opt_in(
3395 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3401 __PACKAGE__->register_method(
3402 method => 'create_user_opt_in_at_org',
3403 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3405 @param $auth The auth token
3406 @param user_id The ID of the user to test
3407 @return The ID of the newly created object, event on error./
3410 sub create_user_opt_in_at_org {
3411 my($self, $conn, $auth, $user_id, $org_id) = @_;
3413 my $e = new_editor(authtoken => $auth, xact=>1);
3414 return $e->die_event unless $e->checkauth;
3416 # if a specific org unit wasn't passed in, get one based on the defaults;
3418 my $wsou = $e->requestor->ws_ou;
3419 # get the default opt depth
3420 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3421 # get the org unit at that depth
3422 my $org = $e->json_query({
3423 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3424 $org_id = $org->{id};
3427 # fall back to the workstation OU, the pre-opt-in-boundary way
3428 $org_id = $e->requestor->ws_ou;
3431 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3432 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3434 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3436 $opt_in->org_unit($org_id);
3437 $opt_in->usr($user_id);
3438 $opt_in->staff($e->requestor->id);
3439 $opt_in->opt_in_ts('now');
3440 $opt_in->opt_in_ws($e->requestor->wsid);
3442 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3443 or return $e->die_event;
3451 __PACKAGE__->register_method (
3452 method => 'retrieve_org_hours',
3453 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3455 Returns the hours of operation for a specified org unit
3456 @param authtoken The login session key
3457 @param org_id The org_unit ID
3461 sub retrieve_org_hours {
3462 my($self, $conn, $auth, $org_id) = @_;
3463 my $e = new_editor(authtoken => $auth);
3464 return $e->die_event unless $e->checkauth;
3465 $org_id ||= $e->requestor->ws_ou;
3466 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3470 __PACKAGE__->register_method (
3471 method => 'verify_user_password',
3472 api_name => 'open-ils.actor.verify_user_password',
3474 Given a barcode or username and the MD5 encoded password,
3475 returns 1 if the password is correct. Returns 0 otherwise.
3479 sub verify_user_password {
3480 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3481 my $e = new_editor(authtoken => $auth);
3482 return $e->die_event unless $e->checkauth;
3484 my $user_by_barcode;
3485 my $user_by_username;
3487 my $card = $e->search_actor_card([
3488 {barcode => $barcode},
3489 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3490 $user_by_barcode = $card->usr;
3491 $user = $user_by_barcode;
3494 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3495 $user = $user_by_username;
3497 return 0 if (!$user || $U->is_true($user->deleted));
3498 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3499 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3500 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3503 __PACKAGE__->register_method (
3504 method => 'retrieve_usr_id_via_barcode_or_usrname',
3505 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3507 Given a barcode or username returns the id for the user or
3512 sub retrieve_usr_id_via_barcode_or_usrname {
3513 my($self, $conn, $auth, $barcode, $username) = @_;
3514 my $e = new_editor(authtoken => $auth);
3515 return $e->die_event unless $e->checkauth;
3516 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3518 my $user_by_barcode;
3519 my $user_by_username;
3520 $logger->info("$id_as_barcode is the ID as BARCODE");
3522 my $card = $e->search_actor_card([
3523 {barcode => $barcode},
3524 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3525 if ($id_as_barcode =~ /^t/i) {
3527 $user = $e->retrieve_actor_user($barcode);
3528 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3530 $user_by_barcode = $card->usr;
3531 $user = $user_by_barcode;
3534 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3535 $user_by_barcode = $card->usr;
3536 $user = $user_by_barcode;
3541 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3543 $user = $user_by_username;
3545 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3546 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3547 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3552 __PACKAGE__->register_method (
3553 method => 'merge_users',
3554 api_name => 'open-ils.actor.user.merge',
3557 Given a list of source users and destination user, transfer all data from the source
3558 to the dest user and delete the source user. All user related data is
3559 transferred, including circulations, holds, bookbags, etc.
3565 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3566 my $e = new_editor(xact => 1, authtoken => $auth);
3567 return $e->die_event unless $e->checkauth;
3569 # disallow the merge if any subordinate accounts are in collections
3570 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3571 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3573 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3574 if $master_id == $e->requestor->id;
3576 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3577 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3578 return $evt if $evt;
3580 my $del_addrs = ($U->ou_ancestor_setting_value(
3581 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3582 my $del_cards = ($U->ou_ancestor_setting_value(
3583 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3584 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3585 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3587 for my $src_id (@$user_ids) {
3589 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3590 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3591 return $evt if $evt;
3593 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3594 if $src_id == $e->requestor->id;
3596 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3597 if($src_user->home_ou ne $master_user->home_ou) {
3598 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3601 return $e->die_event unless
3602 $e->json_query({from => [
3617 __PACKAGE__->register_method (
3618 method => 'approve_user_address',
3619 api_name => 'open-ils.actor.user.pending_address.approve',
3626 sub approve_user_address {
3627 my($self, $conn, $auth, $addr) = @_;
3628 my $e = new_editor(xact => 1, authtoken => $auth);
3629 return $e->die_event unless $e->checkauth;
3631 # if the caller passes an address object, assume they want to
3632 # update it first before approving it
3633 $e->update_actor_user_address($addr) or return $e->die_event;
3635 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3637 my $user = $e->retrieve_actor_user($addr->usr);
3638 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3639 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3640 or return $e->die_event;
3642 return [values %$result]->[0];
3646 __PACKAGE__->register_method (
3647 method => 'retrieve_friends',
3648 api_name => 'open-ils.actor.friends.retrieve',
3651 returns { confirmed: [], pending_out: [], pending_in: []}
3652 pending_out are users I'm requesting friendship with
3653 pending_in are users requesting friendship with me
3658 sub retrieve_friends {
3659 my($self, $conn, $auth, $user_id, $options) = @_;
3660 my $e = new_editor(authtoken => $auth);
3661 return $e->event unless $e->checkauth;
3662 $user_id ||= $e->requestor->id;
3664 if($user_id != $e->requestor->id) {
3665 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3666 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3669 return OpenILS::Application::Actor::Friends->retrieve_friends(
3670 $e, $user_id, $options);
3675 __PACKAGE__->register_method (
3676 method => 'apply_friend_perms',
3677 api_name => 'open-ils.actor.friends.perms.apply',
3683 sub apply_friend_perms {
3684 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3685 my $e = new_editor(authtoken => $auth, xact => 1);
3686 return $e->die_event unless $e->checkauth;
3688 if($user_id != $e->requestor->id) {
3689 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3690 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3693 for my $perm (@perms) {
3695 OpenILS::Application::Actor::Friends->apply_friend_perm(
3696 $e, $user_id, $delegate_id, $perm);
3697 return $evt if $evt;
3705 __PACKAGE__->register_method (
3706 method => 'update_user_pending_address',
3707 api_name => 'open-ils.actor.user.address.pending.cud'
3710 sub update_user_pending_address {
3711 my($self, $conn, $auth, $addr) = @_;
3712 my $e = new_editor(authtoken => $auth, xact => 1);
3713 return $e->die_event unless $e->checkauth;
3715 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3716 if($addr->usr != $e->requestor->id) {
3717 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3721 $e->create_actor_user_address($addr) or return $e->die_event;
3722 } elsif($addr->isdeleted) {
3723 $e->delete_actor_user_address($addr) or return $e->die_event;
3725 $e->update_actor_user_address($addr) or return $e->die_event;
3729 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3735 __PACKAGE__->register_method (
3736 method => 'user_events',
3737 api_name => 'open-ils.actor.user.events.circ',
3740 __PACKAGE__->register_method (
3741 method => 'user_events',
3742 api_name => 'open-ils.actor.user.events.ahr',
3747 my($self, $conn, $auth, $user_id, $filters) = @_;
3748 my $e = new_editor(authtoken => $auth);
3749 return $e->event unless $e->checkauth;
3751 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3752 my $user_field = 'usr';
3755 $filters->{target} = {
3756 select => { $obj_type => ['id'] },
3758 where => {usr => $user_id}
3761 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3762 if($e->requestor->id != $user_id) {
3763 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3766 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3767 my $req = $ses->request('open-ils.trigger.events_by_target',
3768 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3770 while(my $resp = $req->recv) {
3771 my $val = $resp->content;
3772 my $tgt = $val->target;
3774 if($obj_type eq 'circ') {
3775 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3777 } elsif($obj_type eq 'ahr') {
3778 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3779 if $tgt->current_copy;
3782 $conn->respond($val) if $val;
3788 __PACKAGE__->register_method (
3789 method => 'copy_events',
3790 api_name => 'open-ils.actor.copy.events.circ',
3793 __PACKAGE__->register_method (
3794 method => 'copy_events',
3795 api_name => 'open-ils.actor.copy.events.ahr',
3800 my($self, $conn, $auth, $copy_id, $filters) = @_;
3801 my $e = new_editor(authtoken => $auth);
3802 return $e->event unless $e->checkauth;
3804 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3806 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3808 my $copy_field = 'target_copy';
3809 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3812 $filters->{target} = {
3813 select => { $obj_type => ['id'] },
3815 where => {$copy_field => $copy_id}
3819 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3820 my $req = $ses->request('open-ils.trigger.events_by_target',
3821 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3823 while(my $resp = $req->recv) {
3824 my $val = $resp->content;
3825 my $tgt = $val->target;
3827 my $user = $e->retrieve_actor_user($tgt->usr);
3828 if($e->requestor->id != $user->id) {
3829 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3832 $tgt->$copy_field($copy);
3835 $conn->respond($val) if $val;
3842 __PACKAGE__->register_method (
3843 method => 'get_itemsout_notices',
3844 api_name => 'open-ils.actor.user.itemsout.notices',
3848 desc => q/Summary counts of circulat notices/,
3850 {desc => 'authtoken', type => 'string'},
3851 {desc => 'circulation identifiers', type => 'array of numbers'}
3853 return => q/Stream of summary objects/
3857 sub get_itemsout_notices {
3858 my ($self, $client, $auth, $circ_ids) = @_;
3860 my $e = new_editor(authtoken => $auth);
3861 return $e->event unless $e->checkauth;
3863 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3865 for my $circ_id (@$circ_ids) {
3866 my $resp = get_itemsout_notices_impl($e, $circ_id);
3868 if ($U->is_event($resp)) {
3869 $client->respond($resp);
3873 $client->respond({circ_id => $circ_id, %$resp});
3881 sub get_itemsout_notices_impl {
3882 my ($e, $circId) = @_;
3884 my $requestorId = $e->requestor->id;
3886 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3888 my $patronId = $circ->usr;
3890 if( $patronId ne $requestorId ){
3891 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3892 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3895 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3896 #my $req = $ses->request('open-ils.trigger.events_by_target',
3897 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3898 # ^ Above removed in favor of faster json_query.
3901 # select complete_time
3902 # from action_trigger.event atev
3903 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3904 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3905 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3908 my $ctx_loc = $e->requestor->ws_ou;
3909 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3910 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3913 select => { atev => ["complete_time"] },
3916 atevdef => { field => "id",fkey => "event_def"}
3920 "+atevdef" => { active => 't', hook => 'checkout.due' },
3921 "+atev" => { target => $circId, state => 'complete' }
3925 if ($exclude_courtesy_notices){
3926 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3929 my %resblob = ( numNotices => 0, lastDt => undef );
3931 my $res = $e->json_query($query);
3932 for my $ndate (@$res) {
3933 $resblob{numNotices}++;
3934 if( !defined $resblob{lastDt}){
3935 $resblob{lastDt} = $$ndate{complete_time};
3938 if ($resblob{lastDt} lt $$ndate{complete_time}){
3939 $resblob{lastDt} = $$ndate{complete_time};
3946 __PACKAGE__->register_method (
3947 method => 'update_events',
3948 api_name => 'open-ils.actor.user.event.cancel.batch',
3951 __PACKAGE__->register_method (
3952 method => 'update_events',
3953 api_name => 'open-ils.actor.user.event.reset.batch',
3958 my($self, $conn, $auth, $event_ids) = @_;
3959 my $e = new_editor(xact => 1, authtoken => $auth);
3960 return $e->die_event unless $e->checkauth;
3963 for my $id (@$event_ids) {
3965 # do a little dance to determine what user we are ultimately affecting
3966 my $event = $e->retrieve_action_trigger_event([
3969 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3971 ]) or return $e->die_event;
3974 if($event->event_def->hook->core_type eq 'circ') {
3975 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3976 } elsif($event->event_def->hook->core_type eq 'ahr') {
3977 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3982 my $user = $e->retrieve_actor_user($user_id);
3983 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3985 if($self->api_name =~ /cancel/) {
3986 $event->state('invalid');
3987 } elsif($self->api_name =~ /reset/) {
3988 $event->clear_start_time;
3989 $event->clear_update_time;
3990 $event->state('pending');
3993 $e->update_action_trigger_event($event) or return $e->die_event;
3994 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3998 return {complete => 1};
4002 __PACKAGE__->register_method (
4003 method => 'really_delete_user',
4004 api_name => 'open-ils.actor.user.delete.override',
4005 signature => q/@see open-ils.actor.user.delete/
4008 __PACKAGE__->register_method (
4009 method => 'really_delete_user',
4010 api_name => 'open-ils.actor.user.delete',
4012 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
4013 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
4014 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
4015 dest_usr_id is only required when deleting a user that performs staff functions.
4019 sub really_delete_user {
4020 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
4021 my $e = new_editor(authtoken => $auth, xact => 1);
4022 return $e->die_event unless $e->checkauth;
4023 $oargs = { all => 1 } unless defined $oargs;
4025 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
4026 my $open_bills = $e->json_query({
4027 select => { mbts => ['id'] },
4030 xact_finish => { '=' => undef },
4031 usr => { '=' => $user_id },
4033 }) or return $e->die_event;
4035 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
4037 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
4039 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
4040 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
4041 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
4043 # No deleting yourself - UI is supposed to stop you first, though.
4044 return $e->die_event unless $e->requestor->id != $user->id;
4045 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4046 # Check if you are allowed to mess with this patron permission group at all
4047 my $evt = group_perm_failed($e, $e->requestor, $user);
4048 return $e->die_event($evt) if $evt;
4049 my $stat = $e->json_query(
4050 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4051 or return $e->die_event;
4057 __PACKAGE__->register_method (
4058 method => 'user_payments',
4059 api_name => 'open-ils.actor.user.payments.retrieve',
4062 Returns all payments for a given user. Default order is newest payments first.
4063 @param auth Authentication token
4064 @param user_id The user ID
4065 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4070 my($self, $conn, $auth, $user_id, $filters) = @_;
4073 my $e = new_editor(authtoken => $auth);
4074 return $e->die_event unless $e->checkauth;
4076 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4077 return $e->event unless
4078 $e->requestor->id == $user_id or
4079 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4081 # Find all payments for all transactions for user $user_id
4083 select => {mp => ['id']},
4088 select => {mbt => ['id']},
4090 where => {usr => $user_id}
4095 { # by default, order newest payments first
4097 field => 'payment_ts',
4100 # secondary sort in ID as a tie-breaker, since payments created
4101 # within the same transaction will have identical payment_ts's
4108 for (qw/order_by limit offset/) {
4109 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4112 if(defined $filters->{where}) {
4113 foreach (keys %{$filters->{where}}) {
4114 # don't allow the caller to expand the result set to other users
4115 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4119 my $payment_ids = $e->json_query($query);
4120 for my $pid (@$payment_ids) {
4121 my $pay = $e->retrieve_money_payment([
4126 mbt => ['summary', 'circulation', 'grocery'],
4127 circ => ['target_copy'],
4128 acp => ['call_number'],
4136 xact_type => $pay->xact->summary->xact_type,
4137 last_billing_type => $pay->xact->summary->last_billing_type,
4140 if($pay->xact->summary->xact_type eq 'circulation') {
4141 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4142 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4145 $pay->xact($pay->xact->id); # de-flesh
4146 $conn->respond($resp);
4154 __PACKAGE__->register_method (
4155 method => 'negative_balance_users',
4156 api_name => 'open-ils.actor.users.negative_balance',
4159 Returns all users that have an overall negative balance
4160 @param auth Authentication token
4161 @param org_id The context org unit as an ID or list of IDs. This will be the home
4162 library of the user. If no org_unit is specified, no org unit filter is applied
4166 sub negative_balance_users {
4167 my($self, $conn, $auth, $org_id) = @_;
4169 my $e = new_editor(authtoken => $auth);
4170 return $e->die_event unless $e->checkauth;
4171 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4175 mous => ['usr', 'balance_owed'],
4178 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4179 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4196 where => {'+mous' => {balance_owed => {'<' => 0}}}
4199 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4201 my $list = $e->json_query($query, {timeout => 600});
4203 for my $data (@$list) {
4205 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4206 balance_owed => $data->{balance_owed},
4207 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4214 __PACKAGE__->register_method(
4215 method => "request_password_reset",
4216 api_name => "open-ils.actor.patron.password_reset.request",
4218 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4219 "method for changing a user's password. The UUID token is distributed via A/T " .
4220 "templates (i.e. email to the user).",
4222 { desc => 'user_id_type', type => 'string' },
4223 { desc => 'user_id', type => 'string' },
4224 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4226 return => {desc => '1 on success, Event on error'}
4229 sub request_password_reset {
4230 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4232 # Check to see if password reset requests are already being throttled:
4233 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4235 my $e = new_editor(xact => 1);
4238 # Get the user, if any, depending on the input value
4239 if ($user_id_type eq 'username') {
4240 $user = $e->search_actor_user({usrname => $user_id})->[0];
4243 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4245 } elsif ($user_id_type eq 'barcode') {
4246 my $card = $e->search_actor_card([
4247 {barcode => $user_id},
4248 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4251 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4256 # If the user doesn't have an email address, we can't help them
4257 if (!$user->email) {
4259 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4262 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4263 if ($email_must_match) {
4264 if (lc($user->email) ne lc($email)) {
4265 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4269 _reset_password_request($conn, $e, $user);
4272 # Once we have the user, we can issue the password reset request
4273 # XXX Add a wrapper method that accepts barcode + email input
4274 sub _reset_password_request {
4275 my ($conn, $e, $user) = @_;
4277 # 1. Get throttle threshold and time-to-live from OU_settings
4278 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4279 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4281 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4283 # 2. Get time of last request and number of active requests (num_active)
4284 my $active_requests = $e->json_query({
4290 transform => 'COUNT'
4293 column => 'request_time',
4299 has_been_reset => { '=' => 'f' },
4300 request_time => { '>' => $threshold_time }
4304 # Guard against no active requests
4305 if ($active_requests->[0]->{'request_time'}) {
4306 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4307 my $now = DateTime::Format::ISO8601->new();
4309 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4310 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4311 ($last_request->add_duration('1 minute') > $now)) {
4312 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4314 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4318 # TODO Check to see if the user is in a password-reset-restricted group
4320 # Otherwise, go ahead and try to get the user.
4322 # Check the number of active requests for this user
4323 $active_requests = $e->json_query({
4329 transform => 'COUNT'
4334 usr => { '=' => $user->id },
4335 has_been_reset => { '=' => 'f' },
4336 request_time => { '>' => $threshold_time }
4340 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4342 # if less than or equal to per-user threshold, proceed; otherwise, return event
4343 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4344 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4346 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4349 # Create the aupr object and insert into the database
4350 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4351 my $uuid = create_uuid_as_string(UUID_V4);
4352 $reset_request->uuid($uuid);
4353 $reset_request->usr($user->id);
4355 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4358 # Create an event to notify user of the URL to reset their password
4360 # Can we stuff this in the user_data param for trigger autocreate?
4361 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4363 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4364 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4367 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4372 __PACKAGE__->register_method(
4373 method => "commit_password_reset",
4374 api_name => "open-ils.actor.patron.password_reset.commit",
4376 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4377 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4378 "with the supplied password.",
4380 { desc => 'uuid', type => 'string' },
4381 { desc => 'password', type => 'string' },
4383 return => {desc => '1 on success, Event on error'}
4386 sub commit_password_reset {
4387 my($self, $conn, $uuid, $password) = @_;
4389 # Check to see if password reset requests are already being throttled:
4390 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4391 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4392 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4394 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4397 my $e = new_editor(xact => 1);
4399 my $aupr = $e->search_actor_usr_password_reset({
4406 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4408 my $user_id = $aupr->[0]->usr;
4409 my $user = $e->retrieve_actor_user($user_id);
4411 # Ensure we're still within the TTL for the request
4412 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4413 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4414 if ($threshold < DateTime->now(time_zone => 'local')) {
4416 $logger->info("Password reset request needed to be submitted before $threshold");
4417 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4420 # Check complexity of password against OU-defined regex
4421 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4425 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4426 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4427 $is_strong = check_password_strength_custom($password, $pw_regex);
4429 $is_strong = check_password_strength_default($password);
4434 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4437 # All is well; update the password
4438 modify_migrated_user_password($e, $user->id, $password);
4440 # And flag that this password reset request has been honoured
4441 $aupr->[0]->has_been_reset('t');
4442 $e->update_actor_usr_password_reset($aupr->[0]);
4448 sub check_password_strength_default {
4449 my $password = shift;
4450 # Use the default set of checks
4451 if ( (length($password) < 7) or
4452 ($password !~ m/.*\d+.*/) or
4453 ($password !~ m/.*[A-Za-z]+.*/)
4460 sub check_password_strength_custom {
4461 my ($password, $pw_regex) = @_;
4463 $pw_regex = qr/$pw_regex/;
4464 if ($password !~ /$pw_regex/) {
4470 __PACKAGE__->register_method(
4471 method => "fire_test_notification",
4472 api_name => "open-ils.actor.event.test_notification"
4475 sub fire_test_notification {
4476 my($self, $conn, $auth, $args) = @_;
4477 my $e = new_editor(authtoken => $auth);
4478 return $e->event unless $e->checkauth;
4479 if ($e->requestor->id != $$args{target}) {
4480 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4481 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4484 my $event_hook = $$args{hook} or return $e->event;
4485 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4487 my $usr = $e->retrieve_actor_user($$args{target});
4488 return $e->event unless $usr;
4490 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4494 __PACKAGE__->register_method(
4495 method => "event_def_opt_in_settings",
4496 api_name => "open-ils.actor.event_def.opt_in.settings",
4499 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4501 { desc => 'Authentication token', type => 'string'},
4503 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4508 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4515 sub event_def_opt_in_settings {
4516 my($self, $conn, $auth, $org_id) = @_;
4517 my $e = new_editor(authtoken => $auth);
4518 return $e->event unless $e->checkauth;
4520 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4521 return $e->event unless
4522 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4524 $org_id = $e->requestor->home_ou;
4527 # find all config.user_setting_type's related to event_defs for the requested org unit
4528 my $types = $e->json_query({
4529 select => {cust => ['name']},
4530 from => {atevdef => 'cust'},
4533 owner => $U->get_org_ancestors($org_id), # context org plus parents
4540 $conn->respond($_) for
4541 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4548 __PACKAGE__->register_method(
4549 method => "user_circ_history",
4550 api_name => "open-ils.actor.history.circ",
4554 desc => 'Returns user circ history objects for the calling user',
4556 { desc => 'Authentication token', type => 'string'},
4557 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4560 desc => q/Stream of 'auch' circ history objects/,
4566 __PACKAGE__->register_method(
4567 method => "user_circ_history",
4568 api_name => "open-ils.actor.history.circ.clear",
4571 desc => 'Delete all user circ history entries for the calling user',
4573 { desc => 'Authentication token', type => 'string'},
4574 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4577 desc => q/1 on success, event on error/,
4583 __PACKAGE__->register_method(
4584 method => "user_circ_history",
4585 api_name => "open-ils.actor.history.circ.print",
4588 desc => q/Returns printable output for the caller's circ history objects/,
4590 { desc => 'Authentication token', type => 'string'},
4591 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4594 desc => q/An action_trigger.event object or error event./,
4600 __PACKAGE__->register_method(
4601 method => "user_circ_history",
4602 api_name => "open-ils.actor.history.circ.email",
4605 desc => q/Emails the caller's circ history/,
4607 { desc => 'Authentication token', type => 'string'},
4608 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4609 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4612 desc => q/undef, or event on error/
4617 sub user_circ_history {
4618 my ($self, $conn, $auth, $options) = @_;
4621 my $for_print = ($self->api_name =~ /print/);
4622 my $for_email = ($self->api_name =~ /email/);
4623 my $for_clear = ($self->api_name =~ /clear/);
4625 # No perm check is performed. Caller may only access his/her own
4626 # circ history entries.
4627 my $e = new_editor(authtoken => $auth);
4628 return $e->event unless $e->checkauth;
4631 if (!$for_clear) { # clear deletes all
4632 $limits{offset} = $options->{offset} if defined $options->{offset};
4633 $limits{limit} = $options->{limit} if defined $options->{limit};
4636 my %circ_id_filter = $options->{circ_ids} ?
4637 (id => $options->{circ_ids}) : ();
4639 my $circs = $e->search_action_user_circ_history([
4640 { usr => $e->requestor->id,
4643 { # order newest to oldest by default
4644 order_by => {auch => 'xact_start DESC'},
4647 {substream => 1} # could be a large list
4651 return $U->fire_object_event(undef,
4652 'circ.format.history.print', $circs, $e->requestor->home_ou);
4655 $e->xact_begin if $for_clear;
4656 $conn->respond_complete(1) if $for_email; # no sense in waiting
4658 for my $circ (@$circs) {
4661 # events will be fired from action_trigger_runner
4662 $U->create_events_for_hook('circ.format.history.email',
4663 $circ, $e->editor->home_ou, undef, undef, 1);
4665 } elsif ($for_clear) {
4667 $e->delete_action_user_circ_history($circ)
4668 or return $e->die_event;
4671 $conn->respond($circ);
4684 __PACKAGE__->register_method(
4685 method => "user_visible_holds",
4686 api_name => "open-ils.actor.history.hold.visible",
4689 desc => 'Returns the set of opt-in visible holds',
4691 { desc => 'Authentication token', type => 'string'},
4692 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4693 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4696 desc => q/An object with 1 field: "hold"/,
4702 __PACKAGE__->register_method(
4703 method => "user_visible_holds",
4704 api_name => "open-ils.actor.history.hold.visible.print",
4707 desc => 'Returns printable output for the set of opt-in visible holds',
4709 { desc => 'Authentication token', type => 'string'},
4710 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4711 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4714 desc => q/An action_trigger.event object or error event./,
4720 __PACKAGE__->register_method(
4721 method => "user_visible_holds",
4722 api_name => "open-ils.actor.history.hold.visible.email",
4725 desc => 'Emails the set of opt-in visible holds to the requestor',
4727 { desc => 'Authentication token', type => 'string'},
4728 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4729 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4732 desc => q/undef, or event on error/
4737 sub user_visible_holds {
4738 my($self, $conn, $auth, $user_id, $options) = @_;
4741 my $for_print = ($self->api_name =~ /print/);
4742 my $for_email = ($self->api_name =~ /email/);
4743 my $e = new_editor(authtoken => $auth);
4744 return $e->event unless $e->checkauth;
4746 $user_id ||= $e->requestor->id;
4748 $options->{limit} ||= 50;
4749 $options->{offset} ||= 0;
4751 if($user_id != $e->requestor->id) {
4752 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4753 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4754 return $e->event unless $e->allowed($perm, $user->home_ou);
4757 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4759 my $data = $e->json_query({
4760 from => [$db_func, $user_id],
4761 limit => $$options{limit},
4762 offset => $$options{offset}
4764 # TODO: I only want IDs. code below didn't get me there
4765 # {"select":{"au":[{"column":"id", "result_field":"id",
4766 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4771 return undef unless @$data;
4775 # collect the batch of objects
4779 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4780 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4784 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4785 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4788 } elsif ($for_email) {
4790 $conn->respond_complete(1) if $for_email; # no sense in waiting
4798 my $hold = $e->retrieve_action_hold_request($id);
4799 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4800 # events will be fired from action_trigger_runner
4804 my $circ = $e->retrieve_action_circulation($id);
4805 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4806 # events will be fired from action_trigger_runner
4810 } else { # just give me the data please
4818 my $hold = $e->retrieve_action_hold_request($id);
4819 $conn->respond({hold => $hold});
4823 my $circ = $e->retrieve_action_circulation($id);
4826 summary => $U->create_circ_chain_summary($e, $id)
4835 __PACKAGE__->register_method(
4836 method => "user_saved_search_cud",
4837 api_name => "open-ils.actor.user.saved_search.cud",
4840 desc => 'Create/Update/Delete Access to user saved searches',
4842 { desc => 'Authentication token', type => 'string' },
4843 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4846 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4852 __PACKAGE__->register_method(
4853 method => "user_saved_search_cud",
4854 api_name => "open-ils.actor.user.saved_search.retrieve",
4857 desc => 'Retrieve a saved search object',
4859 { desc => 'Authentication token', type => 'string' },
4860 { desc => 'Saved Search ID', type => 'number' }
4863 desc => q/The saved search object, Event on error/,
4869 sub user_saved_search_cud {
4870 my( $self, $client, $auth, $search ) = @_;
4871 my $e = new_editor( authtoken=>$auth );
4872 return $e->die_event unless $e->checkauth;
4874 my $o_search; # prior version of the object, if any
4875 my $res; # to be returned
4877 # branch on the operation type
4879 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4881 # Get the old version, to check ownership
4882 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4883 or return $e->die_event;
4885 # You can't read somebody else's search
4886 return OpenILS::Event->new('BAD_PARAMS')
4887 unless $o_search->owner == $e->requestor->id;
4893 $e->xact_begin; # start an editor transaction
4895 if( $search->isnew ) { # Create
4897 # You can't create a search for somebody else
4898 return OpenILS::Event->new('BAD_PARAMS')
4899 unless $search->owner == $e->requestor->id;
4901 $e->create_actor_usr_saved_search( $search )
4902 or return $e->die_event;
4906 } elsif( $search->ischanged ) { # Update
4908 # You can't change ownership of a search
4909 return OpenILS::Event->new('BAD_PARAMS')
4910 unless $search->owner == $e->requestor->id;
4912 # Get the old version, to check ownership
4913 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4914 or return $e->die_event;
4916 # You can't update somebody else's search
4917 return OpenILS::Event->new('BAD_PARAMS')
4918 unless $o_search->owner == $e->requestor->id;
4921 $e->update_actor_usr_saved_search( $search )
4922 or return $e->die_event;
4926 } elsif( $search->isdeleted ) { # Delete
4928 # Get the old version, to check ownership
4929 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4930 or return $e->die_event;
4932 # You can't delete somebody else's search
4933 return OpenILS::Event->new('BAD_PARAMS')
4934 unless $o_search->owner == $e->requestor->id;
4937 $e->delete_actor_usr_saved_search( $o_search )
4938 or return $e->die_event;
4949 __PACKAGE__->register_method(
4950 method => "get_barcodes",
4951 api_name => "open-ils.actor.get_barcodes"
4955 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4956 my $e = new_editor(authtoken => $auth);
4957 return $e->event unless $e->checkauth;
4958 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4960 my $db_result = $e->json_query(
4962 'evergreen.get_barcodes',
4963 $org_id, $context, $barcode,
4967 if($context =~ /actor/) {
4968 my $filter_result = ();
4970 foreach my $result (@$db_result) {
4971 if($result->{type} eq 'actor') {
4972 if($e->requestor->id != $result->{id}) {
4973 $patron = $e->retrieve_actor_user($result->{id});
4975 push(@$filter_result, $e->event);
4978 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4979 push(@$filter_result, $result);
4982 push(@$filter_result, $e->event);
4986 push(@$filter_result, $result);
4990 push(@$filter_result, $result);
4993 return $filter_result;
4999 __PACKAGE__->register_method(
5000 method => 'address_alert_test',
5001 api_name => 'open-ils.actor.address_alert.test',
5003 desc => "Tests a set of address fields to determine if they match with an address_alert",
5005 {desc => 'Authentication token', type => 'string'},
5006 {desc => 'Org Unit', type => 'number'},
5007 {desc => 'Fields', type => 'hash'},
5009 return => {desc => 'List of matching address_alerts'}
5013 sub address_alert_test {
5014 my ($self, $client, $auth, $org_unit, $fields) = @_;
5015 return [] unless $fields and grep {$_} values %$fields;
5017 my $e = new_editor(authtoken => $auth);
5018 return $e->event unless $e->checkauth;
5019 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
5020 $org_unit ||= $e->requestor->ws_ou;
5022 my $alerts = $e->json_query({
5024 'actor.address_alert_matches',
5032 $$fields{post_code},
5033 $$fields{mailing_address},
5034 $$fields{billing_address}
5038 # map the json_query hashes to real objects
5040 map {$e->retrieve_actor_address_alert($_)}
5041 (map {$_->{id}} @$alerts)
5045 __PACKAGE__->register_method(
5046 method => "mark_users_contact_invalid",
5047 api_name => "open-ils.actor.invalidate.email",
5049 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",
5051 {desc => "Authentication token", type => "string"},
5052 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5053 {desc => "Additional note text (optional)", type => "string"},
5054 {desc => "penalty org unit ID (optional)", type => "number"},
5055 {desc => "Email address (optional)", type => "string"}
5057 return => {desc => "Event describing success or failure", type => "object"}
5061 __PACKAGE__->register_method(
5062 method => "mark_users_contact_invalid",
5063 api_name => "open-ils.actor.invalidate.day_phone",
5065 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",
5067 {desc => "Authentication token", type => "string"},
5068 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5069 {desc => "Additional note text (optional)", type => "string"},
5070 {desc => "penalty org unit ID (optional)", type => "number"},
5071 {desc => "Phone Number (optional)", type => "string"}
5073 return => {desc => "Event describing success or failure", type => "object"}
5077 __PACKAGE__->register_method(
5078 method => "mark_users_contact_invalid",
5079 api_name => "open-ils.actor.invalidate.evening_phone",
5081 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",
5083 {desc => "Authentication token", type => "string"},
5084 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5085 {desc => "Additional note text (optional)", type => "string"},
5086 {desc => "penalty org unit ID (optional)", type => "number"},
5087 {desc => "Phone Number (optional)", type => "string"}
5089 return => {desc => "Event describing success or failure", type => "object"}
5093 __PACKAGE__->register_method(
5094 method => "mark_users_contact_invalid",
5095 api_name => "open-ils.actor.invalidate.other_phone",
5097 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",
5099 {desc => "Authentication token", type => "string"},
5100 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5101 {desc => "Additional note text (optional)", type => "string"},
5102 {desc => "penalty org unit ID (optional, default to top of org tree)",
5104 {desc => "Phone Number (optional)", type => "string"}
5106 return => {desc => "Event describing success or failure", type => "object"}
5110 sub mark_users_contact_invalid {
5111 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5113 # This method invalidates an email address or a phone_number which
5114 # removes the bad email address or phone number, copying its contents
5115 # to a patron note, and institutes a standing penalty for "bad email"
5116 # or "bad phone number" which is cleared when the user is saved or
5117 # optionally only when the user is saved with an email address or
5118 # phone number (or staff manually delete the penalty).
5120 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5122 my $e = new_editor(authtoken => $auth, xact => 1);
5123 return $e->die_event unless $e->checkauth;
5126 if (defined $patron_id && $patron_id ne "") {
5127 $howfind = {usr => $patron_id};
5128 } elsif (defined $contact && $contact ne "") {
5129 $howfind = {$contact_type => $contact};
5131 # Error out if no patron id set or no contact is set.
5132 return OpenILS::Event->new('BAD_PARAMS');
5135 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5136 $e, $contact_type, $howfind,
5137 $addl_note, $penalty_ou, $e->requestor->id
5141 # Putting the following method in open-ils.actor is a bad fit, except in that
5142 # it serves an interface that lives under 'actor' in the templates directory,
5143 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5145 __PACKAGE__->register_method(
5146 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5147 method => "get_all_at_reactors_in_use",
5152 { name => 'authtoken', type => 'string' }
5155 desc => 'list of reactor names', type => 'array'
5160 sub get_all_at_reactors_in_use {
5161 my ($self, $conn, $auth) = @_;
5163 my $e = new_editor(authtoken => $auth);
5164 $e->checkauth or return $e->die_event;
5165 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5167 my $reactors = $e->json_query({
5169 atevdef => [{column => "reactor", transform => "distinct"}]
5171 from => {atevdef => {}}
5174 return $e->die_event unless ref $reactors eq "ARRAY";
5177 return [ map { $_->{reactor} } @$reactors ];
5180 __PACKAGE__->register_method(
5181 method => "filter_group_entry_crud",
5182 api_name => "open-ils.actor.filter_group_entry.crud",
5185 Provides CRUD access to filter group entry objects. These are not full accessible
5186 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5187 are not accessible via PCRUD (because they have no fields against which to link perms)
5190 {desc => "Authentication token", type => "string"},
5191 {desc => "Entry ID / Entry Object", type => "number"},
5192 {desc => "Additional note text (optional)", type => "string"},
5193 {desc => "penalty org unit ID (optional, default to top of org tree)",
5197 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5203 sub filter_group_entry_crud {
5204 my ($self, $conn, $auth, $arg) = @_;
5206 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5207 my $e = new_editor(authtoken => $auth, xact => 1);
5208 return $e->die_event unless $e->checkauth;
5214 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5215 or return $e->die_event;
5217 return $e->die_event unless $e->allowed(
5218 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5220 my $query = $arg->query;
5221 $query = $e->create_actor_search_query($query) or return $e->die_event;
5222 $arg->query($query->id);
5223 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5224 $entry->query($query);
5229 } elsif ($arg->ischanged) {
5231 my $entry = $e->retrieve_actor_search_filter_group_entry([
5234 flesh_fields => {asfge => ['grp']}
5236 ]) or return $e->die_event;
5238 return $e->die_event unless $e->allowed(
5239 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5241 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5242 $arg->query($arg->query->id);
5243 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5244 $arg->query($query);
5249 } elsif ($arg->isdeleted) {
5251 my $entry = $e->retrieve_actor_search_filter_group_entry([
5254 flesh_fields => {asfge => ['grp', 'query']}
5256 ]) or return $e->die_event;
5258 return $e->die_event unless $e->allowed(
5259 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5261 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5262 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5275 my $entry = $e->retrieve_actor_search_filter_group_entry([
5278 flesh_fields => {asfge => ['grp', 'query']}
5280 ]) or return $e->die_event;
5282 return $e->die_event unless $e->allowed(
5283 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5284 $entry->grp->owner);
5287 $entry->grp($entry->grp->id); # for consistency