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 => "get_my_org_ancestor_at_depth",
1512 api_name => "open-ils.actor.org_unit.ancestor_at_depth.retrieve"
1515 sub get_my_org_ancestor_at_depth {
1516 my( $self, $client, $auth, $org_id, $depth ) = @_;
1517 my $e = new_editor(authtoken=>$auth);
1518 return $e->event unless $e->checkauth;
1519 $org_id = $e->requestor->ws_ou unless defined $org_id;
1521 return $apputils->org_unit_ancestor_at_depth( $org_id, $depth );
1524 __PACKAGE__->register_method(
1525 method => "patron_adv_search",
1526 api_name => "open-ils.actor.patron.search.advanced"
1529 __PACKAGE__->register_method(
1530 method => "patron_adv_search",
1531 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1533 # Flush the response stream at most 5 patrons in for UI responsiveness.
1534 max_bundle_count => 5,
1536 desc => q/Returns a stream of fleshed user objects instead of
1537 a pile of identifiers/
1541 sub patron_adv_search {
1542 my( $self, $client, $auth, $search_hash, $search_limit,
1543 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1545 # API params sanity checks.
1546 # Exit early with empty result if no filter exists.
1547 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1548 my $fleshed = ($self->api_name =~ /fleshed/);
1549 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1551 for my $key (keys %$search_hash) {
1552 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1556 return ($fleshed ? undef : []) unless $search_ok;
1558 my $e = new_editor(authtoken=>$auth);
1559 return $e->event unless $e->checkauth;
1560 return $e->event unless $e->allowed('VIEW_USER');
1562 # depth boundary outside of which patrons must opt-in, default to 0
1563 my $opt_boundary = 0;
1564 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1566 if (not defined $search_ou) {
1567 my $depth = $U->ou_ancestor_setting_value(
1568 $e->requestor->ws_ou,
1569 'circ.patron_edit.duplicate_patron_check_depth'
1572 if (defined $depth) {
1573 $search_ou = $U->org_unit_ancestor_at_depth(
1574 $e->requestor->ws_ou, $depth
1579 my $ids = $U->storagereq(
1580 "open-ils.storage.actor.user.crazy_search", $search_hash,
1581 $search_limit, $search_sort, $include_inactive,
1582 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1584 return $ids unless $self->api_name =~ /fleshed/;
1586 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1592 # A migrated (main) password has the form:
1593 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1594 sub modify_migrated_user_password {
1595 my ($e, $user_id, $passwd) = @_;
1597 # new password gets a new salt
1598 my $new_salt = $e->json_query({
1599 from => ['actor.create_salt', 'main']})->[0];
1600 $new_salt = $new_salt->{'actor.create_salt'};
1607 md5_hex($new_salt . md5_hex($passwd)),
1615 __PACKAGE__->register_method(
1616 method => "update_passwd",
1617 api_name => "open-ils.actor.user.password.update",
1619 desc => "Update the operator's password",
1621 { desc => 'Authentication token', type => 'string' },
1622 { desc => 'New password', 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.username.update",
1633 desc => "Update the operator's username",
1635 { desc => 'Authentication token', type => 'string' },
1636 { desc => 'New username', type => 'string' },
1637 { desc => 'Current password', type => 'string' }
1639 return => {desc => '1 on success, Event on error or incorrect current password'}
1643 __PACKAGE__->register_method(
1644 method => "update_passwd",
1645 api_name => "open-ils.actor.user.email.update",
1647 desc => "Update the operator's email address",
1649 { desc => 'Authentication token', type => 'string' },
1650 { desc => 'New email address', type => 'string' },
1651 { desc => 'Current password', type => 'string' }
1653 return => {desc => '1 on success, Event on error or incorrect current password'}
1657 __PACKAGE__->register_method(
1658 method => "update_passwd",
1659 api_name => "open-ils.actor.user.locale.update",
1661 desc => "Update the operator's i18n locale",
1663 { desc => 'Authentication token', type => 'string' },
1664 { desc => 'New locale', type => 'string' },
1665 { desc => 'Current password', type => 'string' }
1667 return => {desc => '1 on success, Event on error or incorrect current password'}
1672 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1673 my $e = new_editor(xact=>1, authtoken=>$auth);
1674 return $e->die_event unless $e->checkauth;
1676 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1677 or return $e->die_event;
1678 my $api = $self->api_name;
1680 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1682 return new OpenILS::Event('INCORRECT_PASSWORD');
1686 if( $api =~ /password/o ) {
1687 # NOTE: with access to the plain text password we could crypt
1688 # the password without the extra MD5 pre-hashing. Other changes
1689 # would be required. Noting here for future reference.
1690 modify_migrated_user_password($e, $db_user->id, $new_val);
1691 $db_user->passwd('');
1695 # if we don't clear the password, the user will be updated with
1696 # a hashed version of the hashed version of their password
1697 $db_user->clear_passwd;
1699 if( $api =~ /username/o ) {
1701 # make sure no one else has this username
1702 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1705 return new OpenILS::Event('USERNAME_EXISTS');
1707 $db_user->usrname($new_val);
1710 } elsif( $api =~ /email/o ) {
1711 $db_user->email($new_val);
1714 } elsif( $api =~ /locale/o ) {
1715 $db_user->locale($new_val);
1720 $e->update_actor_user($db_user) or return $e->die_event;
1723 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1726 # update the cached user to pick up these changes
1727 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1733 __PACKAGE__->register_method(
1734 method => "check_user_perms",
1735 api_name => "open-ils.actor.user.perm.check",
1736 notes => <<" NOTES");
1737 Takes a login session, user id, an org id, and an array of perm type strings. For each
1738 perm type, if the user does *not* have the given permission it is added
1739 to a list which is returned from the method. If all permissions
1740 are allowed, an empty list is returned
1741 if the logged in user does not match 'user_id', then the logged in user must
1742 have VIEW_PERMISSION priveleges.
1745 sub check_user_perms {
1746 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1748 my( $staff, $evt ) = $apputils->checkses($login_session);
1749 return $evt if $evt;
1751 if($staff->id ne $user_id) {
1752 if( $evt = $apputils->check_perms(
1753 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1759 for my $perm (@$perm_types) {
1760 if($apputils->check_perms($user_id, $org_id, $perm)) {
1761 push @not_allowed, $perm;
1765 return \@not_allowed
1768 __PACKAGE__->register_method(
1769 method => "check_user_perms2",
1770 api_name => "open-ils.actor.user.perm.check.multi_org",
1772 Checks the permissions on a list of perms and orgs for a user
1773 @param authtoken The login session key
1774 @param user_id The id of the user to check
1775 @param orgs The array of org ids
1776 @param perms The array of permission names
1777 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1778 if the logged in user does not match 'user_id', then the logged in user must
1779 have VIEW_PERMISSION priveleges.
1782 sub check_user_perms2 {
1783 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1785 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1786 $authtoken, $user_id, 'VIEW_PERMISSION' );
1787 return $evt if $evt;
1790 for my $org (@$orgs) {
1791 for my $perm (@$perms) {
1792 if($apputils->check_perms($user_id, $org, $perm)) {
1793 push @not_allowed, [ $org, $perm ];
1798 return \@not_allowed
1802 __PACKAGE__->register_method(
1803 method => 'check_user_perms3',
1804 api_name => 'open-ils.actor.user.perm.highest_org',
1806 Returns the highest org unit id at which a user has a given permission
1807 If the requestor does not match the target user, the requestor must have
1808 'VIEW_PERMISSION' rights at the home org unit of the target user
1809 @param authtoken The login session key
1810 @param userid The id of the user in question
1811 @param perm The permission to check
1812 @return The org unit highest in the org tree within which the user has
1813 the requested permission
1816 sub check_user_perms3 {
1817 my($self, $client, $authtoken, $user_id, $perm) = @_;
1818 my $e = new_editor(authtoken=>$authtoken);
1819 return $e->event unless $e->checkauth;
1821 my $tree = $U->get_org_tree();
1823 unless($e->requestor->id == $user_id) {
1824 my $user = $e->retrieve_actor_user($user_id)
1825 or return $e->event;
1826 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1827 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1830 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1833 __PACKAGE__->register_method(
1834 method => 'user_has_work_perm_at',
1835 api_name => 'open-ils.actor.user.has_work_perm_at',
1839 Returns a set of org unit IDs which represent the highest orgs in
1840 the org tree where the user has the requested permission. The
1841 purpose of this method is to return the smallest set of org units
1842 which represent the full expanse of the user's ability to perform
1843 the requested action. The user whose perms this method should
1844 check is implied by the authtoken. /,
1846 {desc => 'authtoken', type => 'string'},
1847 {desc => 'permission name', type => 'string'},
1848 {desc => q/user id, optional. If present, check perms for
1849 this user instead of the logged in user/, type => 'number'},
1851 return => {desc => 'An array of org IDs'}
1855 sub user_has_work_perm_at {
1856 my($self, $conn, $auth, $perm, $user_id) = @_;
1857 my $e = new_editor(authtoken=>$auth);
1858 return $e->event unless $e->checkauth;
1859 if(defined $user_id) {
1860 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1861 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1863 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1866 __PACKAGE__->register_method(
1867 method => 'user_has_work_perm_at_batch',
1868 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1872 sub user_has_work_perm_at_batch {
1873 my($self, $conn, $auth, $perms, $user_id) = @_;
1874 my $e = new_editor(authtoken=>$auth);
1875 return $e->event unless $e->checkauth;
1876 if(defined $user_id) {
1877 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1878 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1881 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1887 __PACKAGE__->register_method(
1888 method => 'check_user_perms4',
1889 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1891 Returns the highest org unit id at which a user has a given permission
1892 If the requestor does not match the target user, the requestor must have
1893 'VIEW_PERMISSION' rights at the home org unit of the target user
1894 @param authtoken The login session key
1895 @param userid The id of the user in question
1896 @param perms An array of perm names to check
1897 @return An array of orgId's representing the org unit
1898 highest in the org tree within which the user has the requested permission
1899 The arrah of orgId's has matches the order of the perms array
1902 sub check_user_perms4 {
1903 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1905 my( $staff, $target, $org, $evt );
1907 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1908 $authtoken, $userid, 'VIEW_PERMISSION' );
1909 return $evt if $evt;
1912 return [] unless ref($perms);
1913 my $tree = $U->get_org_tree();
1915 for my $p (@$perms) {
1916 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1922 __PACKAGE__->register_method(
1923 method => "user_fines_summary",
1924 api_name => "open-ils.actor.user.fines.summary",
1927 desc => 'Returns a short summary of the users total open fines, ' .
1928 'excluding voided fines Params are login_session, user_id' ,
1930 {desc => 'Authentication token', type => 'string'},
1931 {desc => 'User ID', type => 'string'} # number?
1934 desc => "a 'mous' object, event on error",
1939 sub user_fines_summary {
1940 my( $self, $client, $auth, $user_id ) = @_;
1942 my $e = new_editor(authtoken=>$auth);
1943 return $e->event unless $e->checkauth;
1945 if( $user_id ne $e->requestor->id ) {
1946 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1947 return $e->event unless
1948 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1951 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1955 __PACKAGE__->register_method(
1956 method => "user_opac_vitals",
1957 api_name => "open-ils.actor.user.opac.vital_stats",
1961 desc => 'Returns a short summary of the users vital stats, including ' .
1962 'identification information, accumulated balance, number of holds, ' .
1963 'and current open circulation stats' ,
1965 {desc => 'Authentication token', type => 'string'},
1966 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1969 desc => "An object with four properties: user, fines, checkouts and holds."
1974 sub user_opac_vitals {
1975 my( $self, $client, $auth, $user_id ) = @_;
1977 my $e = new_editor(authtoken=>$auth);
1978 return $e->event unless $e->checkauth;
1980 $user_id ||= $e->requestor->id;
1982 my $user = $e->retrieve_actor_user( $user_id );
1985 ->method_lookup('open-ils.actor.user.fines.summary')
1986 ->run($auth => $user_id);
1987 return $fines if (defined($U->event_code($fines)));
1990 $fines = new Fieldmapper::money::open_user_summary ();
1991 $fines->balance_owed(0.00);
1992 $fines->total_owed(0.00);
1993 $fines->total_paid(0.00);
1994 $fines->usr($user_id);
1998 ->method_lookup('open-ils.actor.user.hold_requests.count')
1999 ->run($auth => $user_id);
2000 return $holds if (defined($U->event_code($holds)));
2003 ->method_lookup('open-ils.actor.user.checked_out.count')
2004 ->run($auth => $user_id);
2005 return $out if (defined($U->event_code($out)));
2007 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
2009 my $unread_msgs = $e->search_actor_usr_message([
2010 {usr => $user_id, read_date => undef, deleted => 'f',
2011 'pub' => 't', # this is for the unread message count in the opac
2012 #'-or' => [ # Hiding Archived messages are for staff UI, not this
2013 # {stop_date => undef},
2014 # {stop_date => {'>' => 'now'}}
2022 first_given_name => $user->first_given_name,
2023 second_given_name => $user->second_given_name,
2024 family_name => $user->family_name,
2025 alias => $user->alias,
2026 usrname => $user->usrname
2028 fines => $fines->to_bare_hash,
2031 messages => { unread => scalar(@$unread_msgs) }
2036 ##### a small consolidation of related method registrations
2037 my $common_params = [
2038 { desc => 'Authentication token', type => 'string' },
2039 { desc => 'User ID', type => 'string' },
2040 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
2041 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
2044 'open-ils.actor.user.transactions' => '',
2045 'open-ils.actor.user.transactions.fleshed' => '',
2046 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
2047 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
2048 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
2049 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
2052 foreach (keys %methods) {
2054 method => "user_transactions",
2057 desc => 'For a given user, retrieve a list of '
2058 . (/\.fleshed/ ? 'fleshed ' : '')
2059 . 'transactions' . $methods{$_}
2060 . ' optionally limited to transactions of a given type.',
2061 params => $common_params,
2063 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
2064 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
2068 $args{authoritative} = 1;
2069 __PACKAGE__->register_method(%args);
2072 # Now for the counts
2074 'open-ils.actor.user.transactions.count' => '',
2075 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
2076 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
2079 foreach (keys %methods) {
2081 method => "user_transactions",
2084 desc => 'For a given user, retrieve a count of open '
2085 . 'transactions' . $methods{$_}
2086 . ' optionally limited to transactions of a given type.',
2087 params => $common_params,
2088 return => { desc => "Integer count of transactions, or event on error" }
2091 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2092 __PACKAGE__->register_method(%args);
2095 __PACKAGE__->register_method(
2096 method => "user_transactions",
2097 api_name => "open-ils.actor.user.transactions.have_balance.total",
2100 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2101 . ' optionally limited to transactions of a given type.',
2102 params => $common_params,
2103 return => { desc => "Decimal balance value, or event on error" }
2108 sub user_transactions {
2109 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2112 my $e = new_editor(authtoken => $auth);
2113 return $e->event unless $e->checkauth;
2115 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2117 return $e->event unless
2118 $e->requestor->id == $user_id or
2119 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2121 my $api = $self->api_name();
2123 my $filter = ($api =~ /have_balance/o) ?
2124 { 'balance_owed' => { '<>' => 0 } }:
2125 { 'total_owed' => { '>' => 0 } };
2127 my $method = 'open-ils.actor.user.transactions.history.still_open';
2128 $method = "$method.authoritative" if $api =~ /authoritative/;
2129 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2131 if($api =~ /total/o) {
2133 $total += $_->balance_owed for @$trans;
2137 ($api =~ /count/o ) and return scalar @$trans;
2138 ($api !~ /fleshed/o) and return $trans;
2141 for my $t (@$trans) {
2143 if( $t->xact_type ne 'circulation' ) {
2144 push @resp, {transaction => $t};
2148 my $circ_data = flesh_circ($e, $t->id);
2149 push @resp, {transaction => $t, %$circ_data};
2156 __PACKAGE__->register_method(
2157 method => "user_transaction_retrieve",
2158 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2161 notes => "Returns a fleshed transaction record"
2164 __PACKAGE__->register_method(
2165 method => "user_transaction_retrieve",
2166 api_name => "open-ils.actor.user.transaction.retrieve",
2169 notes => "Returns a transaction record"
2172 sub user_transaction_retrieve {
2173 my($self, $client, $auth, $bill_id) = @_;
2175 my $e = new_editor(authtoken => $auth);
2176 return $e->event unless $e->checkauth;
2178 my $trans = $e->retrieve_money_billable_transaction_summary(
2179 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2181 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2183 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2185 return $trans unless $self->api_name =~ /flesh/;
2186 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2188 my $circ_data = flesh_circ($e, $trans->id, 1);
2190 return {transaction => $trans, %$circ_data};
2195 my $circ_id = shift;
2196 my $flesh_copy = shift;
2198 my $circ = $e->retrieve_action_circulation([
2202 circ => ['target_copy'],
2203 acp => ['call_number'],
2210 my $copy = $circ->target_copy;
2212 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2213 $mods = new Fieldmapper::metabib::virtual_record;
2214 $mods->doc_id(OILS_PRECAT_RECORD);
2215 $mods->title($copy->dummy_title);
2216 $mods->author($copy->dummy_author);
2219 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2223 $circ->target_copy($circ->target_copy->id);
2224 $copy->call_number($copy->call_number->id);
2226 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2230 __PACKAGE__->register_method(
2231 method => "hold_request_count",
2232 api_name => "open-ils.actor.user.hold_requests.count",
2236 Returns hold ready vs. total counts.
2237 If a context org unit is provided, a third value
2238 is returned with key 'behind_desk', which reports
2239 how many holds are ready at the pickup library
2240 with the behind_desk flag set to true.
2244 sub hold_request_count {
2245 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2246 my $e = new_editor(authtoken => $authtoken);
2247 return $e->event unless $e->checkauth;
2249 $user_id = $e->requestor->id unless defined $user_id;
2251 if($e->requestor->id ne $user_id) {
2252 my $user = $e->retrieve_actor_user($user_id);
2253 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2256 my $holds = $e->json_query({
2257 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2261 fulfillment_time => {"=" => undef },
2262 cancel_time => undef,
2267 $_->{current_shelf_lib} and # avoid undef warnings
2268 $_->{pickup_lib} eq $_->{current_shelf_lib}
2272 total => scalar(@$holds),
2273 ready => int(scalar(@ready))
2277 # count of holds ready at pickup lib with behind_desk true.
2278 $resp->{behind_desk} = int(scalar(
2280 $_->{pickup_lib} == $ctx_org and
2281 $U->is_true($_->{behind_desk})
2289 __PACKAGE__->register_method(
2290 method => "checked_out",
2291 api_name => "open-ils.actor.user.checked_out",
2295 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2296 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2297 . "(i.e., outstanding balance or some other pending action on the circ). "
2298 . "The .count method also includes a 'total' field which sums all open circs.",
2300 { desc => 'Authentication Token', type => 'string'},
2301 { desc => 'User ID', type => 'string'},
2304 desc => 'Returns event on error, or an object with ID lists, like: '
2305 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2310 __PACKAGE__->register_method(
2311 method => "checked_out",
2312 api_name => "open-ils.actor.user.checked_out.count",
2315 signature => q/@see open-ils.actor.user.checked_out/
2319 my( $self, $conn, $auth, $userid ) = @_;
2321 my $e = new_editor(authtoken=>$auth);
2322 return $e->event unless $e->checkauth;
2324 if( $userid ne $e->requestor->id ) {
2325 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2326 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2328 # see if there is a friend link allowing circ.view perms
2329 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2330 $e, $userid, $e->requestor->id, 'circ.view');
2331 return $e->event unless $allowed;
2335 my $count = $self->api_name =~ /count/;
2336 return _checked_out( $count, $e, $userid );
2340 my( $iscount, $e, $userid ) = @_;
2346 claims_returned => [],
2349 my $meth = 'retrieve_action_open_circ_';
2357 claims_returned => 0,
2364 my $data = $e->$meth($userid);
2368 $result{$_} += $data->$_() for (keys %result);
2369 $result{total} += $data->$_() for (keys %result);
2371 for my $k (keys %result) {
2372 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2382 __PACKAGE__->register_method(
2383 method => "checked_in_with_fines",
2384 api_name => "open-ils.actor.user.checked_in_with_fines",
2387 signature => q/@see open-ils.actor.user.checked_out/
2390 sub checked_in_with_fines {
2391 my( $self, $conn, $auth, $userid ) = @_;
2393 my $e = new_editor(authtoken=>$auth);
2394 return $e->event unless $e->checkauth;
2396 if( $userid ne $e->requestor->id ) {
2397 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2400 # money is owed on these items and they are checked in
2401 my $open = $e->search_action_circulation(
2404 xact_finish => undef,
2405 checkin_time => { "!=" => undef },
2410 my( @lost, @cr, @lo );
2411 for my $c (@$open) {
2412 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2413 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2414 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2419 claims_returned => \@cr,
2420 long_overdue => \@lo
2426 my ($api, $desc, $auth) = @_;
2427 $desc = $desc ? (" " . $desc) : '';
2428 my $ids = ($api =~ /ids$/) ? 1 : 0;
2431 method => "user_transaction_history",
2432 api_name => "open-ils.actor.user.transactions.$api",
2434 desc => "For a given User ID, returns a list of billable transaction" .
2435 ($ids ? " id" : '') .
2436 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2437 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2439 {desc => 'Authentication token', type => 'string'},
2440 {desc => 'User ID', type => 'number'},
2441 {desc => 'Transaction type (optional)', type => 'number'},
2442 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2445 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2449 $auth and push @sig, (authoritative => 1);
2453 my %auth_hist_methods = (
2455 'history.have_charge' => 'that have an initial charge',
2456 'history.still_open' => 'that are not finished',
2457 'history.have_balance' => 'that have a balance',
2458 'history.have_bill' => 'that have billings',
2459 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2460 'history.have_payment' => 'that have at least 1 payment',
2463 foreach (keys %auth_hist_methods) {
2464 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2465 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2466 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2469 sub user_transaction_history {
2470 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2474 my $e = new_editor(authtoken=>$auth);
2475 return $e->die_event unless $e->checkauth;
2477 if ($e->requestor->id ne $userid) {
2478 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2481 my $api = $self->api_name;
2482 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2484 if(defined($type)) {
2485 $filter->{'xact_type'} = $type;
2488 if($api =~ /have_bill_or_payment/o) {
2490 # transactions that have a non-zero sum across all billings or at least 1 payment
2491 $filter->{'-or'} = {
2492 'balance_owed' => { '<>' => 0 },
2493 'last_payment_ts' => { '<>' => undef }
2496 } elsif($api =~ /have_payment/) {
2498 $filter->{last_payment_ts} ||= {'<>' => undef};
2500 } elsif( $api =~ /have_balance/o) {
2502 # transactions that have a non-zero overall balance
2503 $filter->{'balance_owed'} = { '<>' => 0 };
2505 } elsif( $api =~ /have_charge/o) {
2507 # transactions that have at least 1 billing, regardless of whether it was voided
2508 $filter->{'last_billing_ts'} = { '<>' => undef };
2510 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2512 # transactions that have non-zero sum across all billings. This will exclude
2513 # xacts where all billings have been voided
2514 $filter->{'total_owed'} = { '<>' => 0 };
2517 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2518 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2519 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2521 my $mbts = $e->search_money_billable_transaction_summary(
2522 [ { usr => $userid, @xact_finish, %$filter },
2527 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2528 return $mbts unless $api =~ /fleshed/;
2531 for my $t (@$mbts) {
2533 if( $t->xact_type ne 'circulation' ) {
2534 push @resp, {transaction => $t};
2538 my $circ_data = flesh_circ($e, $t->id);
2539 push @resp, {transaction => $t, %$circ_data};
2547 __PACKAGE__->register_method(
2548 method => "user_perms",
2549 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2551 notes => "Returns a list of permissions"
2555 my( $self, $client, $authtoken, $user ) = @_;
2557 my( $staff, $evt ) = $apputils->checkses($authtoken);
2558 return $evt if $evt;
2560 $user ||= $staff->id;
2562 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2566 return $apputils->simple_scalar_request(
2568 "open-ils.storage.permission.user_perms.atomic",
2572 __PACKAGE__->register_method(
2573 method => "retrieve_perms",
2574 api_name => "open-ils.actor.permissions.retrieve",
2575 notes => "Returns a list of permissions"
2577 sub retrieve_perms {
2578 my( $self, $client ) = @_;
2579 return $apputils->simple_scalar_request(
2581 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2582 { id => { '!=' => undef } }
2586 __PACKAGE__->register_method(
2587 method => "retrieve_groups",
2588 api_name => "open-ils.actor.groups.retrieve",
2589 notes => "Returns a list of user groups"
2591 sub retrieve_groups {
2592 my( $self, $client ) = @_;
2593 return new_editor()->retrieve_all_permission_grp_tree();
2596 __PACKAGE__->register_method(
2597 method => "retrieve_org_address",
2598 api_name => "open-ils.actor.org_unit.address.retrieve",
2599 notes => <<' NOTES');
2600 Returns an org_unit address by ID
2601 @param An org_address ID
2603 sub retrieve_org_address {
2604 my( $self, $client, $id ) = @_;
2605 return $apputils->simple_scalar_request(
2607 "open-ils.cstore.direct.actor.org_address.retrieve",
2612 __PACKAGE__->register_method(
2613 method => "retrieve_groups_tree",
2614 api_name => "open-ils.actor.groups.tree.retrieve",
2615 notes => "Returns a list of user groups"
2618 sub retrieve_groups_tree {
2619 my( $self, $client ) = @_;
2620 return new_editor()->search_permission_grp_tree(
2625 flesh_fields => { pgt => ["children"] },
2626 order_by => { pgt => 'name'}
2633 __PACKAGE__->register_method(
2634 method => "add_user_to_groups",
2635 api_name => "open-ils.actor.user.set_groups",
2636 notes => "Adds a user to one or more permission groups"
2639 sub add_user_to_groups {
2640 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2642 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2643 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2644 return $evt if $evt;
2646 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2647 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2648 return $evt if $evt;
2650 $apputils->simplereq(
2652 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2654 for my $group (@$groups) {
2655 my $link = Fieldmapper::permission::usr_grp_map->new;
2657 $link->usr($userid);
2659 my $id = $apputils->simplereq(
2661 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2667 __PACKAGE__->register_method(
2668 method => "get_user_perm_groups",
2669 api_name => "open-ils.actor.user.get_groups",
2670 notes => "Retrieve a user's permission groups."
2674 sub get_user_perm_groups {
2675 my( $self, $client, $authtoken, $userid ) = @_;
2677 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2678 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2679 return $evt if $evt;
2681 return $apputils->simplereq(
2683 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2687 __PACKAGE__->register_method(
2688 method => "get_user_work_ous",
2689 api_name => "open-ils.actor.user.get_work_ous",
2690 notes => "Retrieve a user's work org units."
2693 __PACKAGE__->register_method(
2694 method => "get_user_work_ous",
2695 api_name => "open-ils.actor.user.get_work_ous.ids",
2696 notes => "Retrieve a user's work org units."
2699 sub get_user_work_ous {
2700 my( $self, $client, $auth, $userid ) = @_;
2701 my $e = new_editor(authtoken=>$auth);
2702 return $e->event unless $e->checkauth;
2703 $userid ||= $e->requestor->id;
2705 if($e->requestor->id != $userid) {
2706 my $user = $e->retrieve_actor_user($userid)
2707 or return $e->event;
2708 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2711 return $e->search_permission_usr_work_ou_map({usr => $userid})
2712 unless $self->api_name =~ /.ids$/;
2714 # client just wants a list of org IDs
2715 return $U->get_user_work_ou_ids($e, $userid);
2720 __PACKAGE__->register_method(
2721 method => 'register_workstation',
2722 api_name => 'open-ils.actor.workstation.register.override',
2723 signature => q/@see open-ils.actor.workstation.register/
2726 __PACKAGE__->register_method(
2727 method => 'register_workstation',
2728 api_name => 'open-ils.actor.workstation.register',
2730 Registers a new workstion in the system
2731 @param authtoken The login session key
2732 @param name The name of the workstation id
2733 @param owner The org unit that owns this workstation
2734 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2735 if the name is already in use.
2739 sub register_workstation {
2740 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2742 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2743 return $e->die_event unless $e->checkauth;
2744 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2745 my $existing = $e->search_actor_workstation({name => $name})->[0];
2746 $oargs = { all => 1 } unless defined $oargs;
2750 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2751 # workstation with the given name exists.
2753 if($owner ne $existing->owning_lib) {
2754 # if necessary, update the owning_lib of the workstation
2756 $logger->info("changing owning lib of workstation ".$existing->id.
2757 " from ".$existing->owning_lib." to $owner");
2758 return $e->die_event unless
2759 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2761 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2763 $existing->owning_lib($owner);
2764 return $e->die_event unless $e->update_actor_workstation($existing);
2770 "attempt to register an existing workstation. returning existing ID");
2773 return $existing->id;
2776 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2780 my $ws = Fieldmapper::actor::workstation->new;
2781 $ws->owning_lib($owner);
2783 $e->create_actor_workstation($ws) or return $e->die_event;
2785 return $ws->id; # note: editor sets the id on the new object for us
2788 __PACKAGE__->register_method(
2789 method => 'workstation_list',
2790 api_name => 'open-ils.actor.workstation.list',
2792 Returns a list of workstations registered at the given location
2793 @param authtoken The login session key
2794 @param ids A list of org_unit.id's for the workstation owners
2798 sub workstation_list {
2799 my( $self, $conn, $authtoken, @orgs ) = @_;
2801 my $e = new_editor(authtoken=>$authtoken);
2802 return $e->event unless $e->checkauth;
2807 unless $e->allowed('REGISTER_WORKSTATION', $o);
2808 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2813 __PACKAGE__->register_method(
2814 method => 'fetch_patron_messages',
2815 api_name => 'open-ils.actor.message.retrieve',
2818 Returns a list of notes for a given user, not
2819 including ones marked deleted
2820 @param authtoken The login session key
2821 @param patronid patron ID
2822 @param options hash containing optional limit and offset
2826 sub fetch_patron_messages {
2827 my( $self, $conn, $auth, $patronid, $options ) = @_;
2831 my $e = new_editor(authtoken => $auth);
2832 return $e->die_event unless $e->checkauth;
2834 if ($e->requestor->id ne $patronid) {
2835 return $e->die_event unless $e->allowed('VIEW_USER');
2838 my $select_clause = { usr => $patronid };
2839 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2840 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2841 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2843 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2848 __PACKAGE__->register_method(
2849 method => 'usrname_exists',
2850 api_name => 'open-ils.actor.username.exists',
2852 desc => 'Check if a username is already taken (by an undeleted patron)',
2854 {desc => 'Authentication token', type => 'string'},
2855 {desc => 'Username', type => 'string'}
2858 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2863 sub usrname_exists {
2864 my( $self, $conn, $auth, $usrname ) = @_;
2865 my $e = new_editor(authtoken=>$auth);
2866 return $e->event unless $e->checkauth;
2867 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2868 return $$a[0] if $a and @$a;
2872 __PACKAGE__->register_method(
2873 method => 'barcode_exists',
2874 api_name => 'open-ils.actor.barcode.exists',
2876 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2879 sub barcode_exists {
2880 my( $self, $conn, $auth, $barcode ) = @_;
2881 my $e = new_editor(authtoken=>$auth);
2882 return $e->event unless $e->checkauth;
2883 my $card = $e->search_actor_card({barcode => $barcode});
2889 #return undef unless @$card;
2890 #return $card->[0]->usr;
2894 __PACKAGE__->register_method(
2895 method => 'retrieve_net_levels',
2896 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2899 sub retrieve_net_levels {
2900 my( $self, $conn, $auth ) = @_;
2901 my $e = new_editor(authtoken=>$auth);
2902 return $e->event unless $e->checkauth;
2903 return $e->retrieve_all_config_net_access_level();
2906 # Retain the old typo API name just in case
2907 __PACKAGE__->register_method(
2908 method => 'fetch_org_by_shortname',
2909 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2911 __PACKAGE__->register_method(
2912 method => 'fetch_org_by_shortname',
2913 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2915 sub fetch_org_by_shortname {
2916 my( $self, $conn, $sname ) = @_;
2917 my $e = new_editor();
2918 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2919 return $e->event unless $org;
2924 __PACKAGE__->register_method(
2925 method => 'session_home_lib',
2926 api_name => 'open-ils.actor.session.home_lib',
2929 sub session_home_lib {
2930 my( $self, $conn, $auth ) = @_;
2931 my $e = new_editor(authtoken=>$auth);
2932 return undef unless $e->checkauth;
2933 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2934 return $org->shortname;
2937 __PACKAGE__->register_method(
2938 method => 'session_safe_token',
2939 api_name => 'open-ils.actor.session.safe_token',
2941 Returns a hashed session ID that is safe for export to the world.
2942 This safe token will expire after 1 hour of non-use.
2943 @param auth Active authentication token
2947 sub session_safe_token {
2948 my( $self, $conn, $auth ) = @_;
2949 my $e = new_editor(authtoken=>$auth);
2950 return undef unless $e->checkauth;
2952 my $safe_token = md5_hex($auth);
2954 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2956 # add more user fields as needed
2958 "safe-token-user-$safe_token", {
2959 id => $e->requestor->id,
2960 home_ou_shortname => $e->retrieve_actor_org_unit(
2961 $e->requestor->home_ou)->shortname,
2970 __PACKAGE__->register_method(
2971 method => 'safe_token_home_lib',
2972 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2974 Returns the home library shortname from the session
2975 asscociated with a safe token from generated by
2976 open-ils.actor.session.safe_token.
2977 @param safe_token Active safe token
2978 @param who Optional user activity "ewho" value
2982 sub safe_token_home_lib {
2983 my( $self, $conn, $safe_token, $who ) = @_;
2984 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2986 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2987 return unless $blob;
2989 $U->log_user_activity($blob->{id}, $who, 'verify');
2990 return $blob->{home_ou_shortname};
2994 __PACKAGE__->register_method(
2995 method => "update_penalties",
2996 api_name => "open-ils.actor.user.penalties.update"
2999 sub update_penalties {
3000 my($self, $conn, $auth, $user_id) = @_;
3001 my $e = new_editor(authtoken=>$auth, xact => 1);
3002 return $e->die_event unless $e->checkauth;
3003 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3004 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3005 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3006 return $evt if $evt;
3012 __PACKAGE__->register_method(
3013 method => "apply_penalty",
3014 api_name => "open-ils.actor.user.penalty.apply"
3018 my($self, $conn, $auth, $penalty, $msg) = @_;
3022 my $e = new_editor(authtoken=>$auth, xact => 1);
3023 return $e->die_event unless $e->checkauth;
3025 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3026 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3028 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3030 my $ctx_org = $penalty->org_unit; # csp org_depth is now considered in the UI for the org drop-down menu
3032 if (($msg->{title} || $msg->{message}) && ($msg->{title} ne '' || $msg->{message} ne '')) {
3033 my $aum = Fieldmapper::actor::usr_message->new;
3035 $aum->create_date('now');
3036 $aum->sending_lib($e->requestor->ws_ou);
3037 $aum->title($msg->{title});
3038 $aum->usr($penalty->usr);
3039 $aum->message($msg->{message});
3040 $aum->pub($msg->{pub});
3042 $aum = $e->create_actor_usr_message($aum)
3043 or return $e->die_event;
3045 $penalty->usr_message($aum->id);
3048 $penalty->org_unit($ctx_org);
3049 $penalty->staff($e->requestor->id);
3050 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3053 return $penalty->id;
3056 __PACKAGE__->register_method(
3057 method => "modify_penalty",
3058 api_name => "open-ils.actor.user.penalty.modify"
3061 sub modify_penalty {
3062 my($self, $conn, $auth, $penalty, $usr_msg) = @_;
3064 my $e = new_editor(authtoken=>$auth, xact => 1);
3065 return $e->die_event unless $e->checkauth;
3067 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3068 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3070 $usr_msg->editor($e->requestor->id);
3071 $usr_msg->edit_date('now');
3073 if ($usr_msg->isnew) {
3074 $usr_msg = $e->create_actor_usr_message($usr_msg)
3075 or return $e->die_event;
3076 $penalty->usr_message($usr_msg->id);
3078 $usr_msg = $e->update_actor_usr_message($usr_msg)
3079 or return $e->die_event;
3082 if ($penalty->isnew) {
3083 $penalty = $e->create_actor_user_standing_penalty($penalty)
3084 or return $e->die_event;
3086 $penalty = $e->update_actor_user_standing_penalty($penalty)
3087 or return $e->die_event;
3094 __PACKAGE__->register_method(
3095 method => "remove_penalty",
3096 api_name => "open-ils.actor.user.penalty.remove"
3099 sub remove_penalty {
3100 my($self, $conn, $auth, $penalty) = @_;
3101 my $e = new_editor(authtoken=>$auth, xact => 1);
3102 return $e->die_event unless $e->checkauth;
3103 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3104 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3106 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3111 __PACKAGE__->register_method(
3112 method => "update_penalty_note",
3113 api_name => "open-ils.actor.user.penalty.note.update"
3116 sub update_penalty_note {
3117 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3118 my $e = new_editor(authtoken=>$auth, xact => 1);
3119 return $e->die_event unless $e->checkauth;
3120 for my $penalty_id (@$penalty_ids) {
3121 my $penalty = $e->search_actor_user_standing_penalty([
3122 { id => $penalty_id },
3124 flesh_fields => {aum => ['usr_message']}
3127 if (! $penalty ) { return $e->die_event; }
3128 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3129 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3131 my $aum = $penalty->usr_message();
3133 $aum = Fieldmapper::actor::usr_message->new;
3135 $aum->create_date('now');
3136 $aum->sending_lib($e->requestor->ws_ou);
3138 $aum->usr($penalty->usr);
3139 $aum->message($note);
3143 $aum = $e->create_actor_usr_message($aum)
3144 or return $e->die_event;
3146 $penalty->usr_message($aum->id);
3147 $penalty->ischanged(1);
3148 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3150 $aum = $e->retrieve_actor_usr_message($aum) or return $e->die_event;
3151 $aum->message($note); $aum->ischanged(1);
3152 $e->update_actor_usr_message($aum) or return $e->die_event;
3159 __PACKAGE__->register_method(
3160 method => "ranged_penalty_thresholds",
3161 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3165 sub ranged_penalty_thresholds {
3166 my($self, $conn, $auth, $context_org) = @_;
3167 my $e = new_editor(authtoken=>$auth);
3168 return $e->event unless $e->checkauth;
3169 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3170 my $list = $e->search_permission_grp_penalty_threshold([
3171 {org_unit => $U->get_org_ancestors($context_org)},
3172 {order_by => {pgpt => 'id'}}
3174 $conn->respond($_) for @$list;
3180 __PACKAGE__->register_method(
3181 method => "user_retrieve_fleshed_by_id",
3183 api_name => "open-ils.actor.user.fleshed.retrieve",
3186 sub user_retrieve_fleshed_by_id {
3187 my( $self, $client, $auth, $user_id, $fields ) = @_;
3188 my $e = new_editor(authtoken => $auth);
3189 return $e->event unless $e->checkauth;
3191 if( $e->requestor->id != $user_id ) {
3192 return $e->event unless $e->allowed('VIEW_USER');
3199 "standing_penalties",
3207 return new_flesh_user($user_id, $fields, $e);
3211 sub new_flesh_user {
3214 my $fields = shift || [];
3217 my $fetch_penalties = 0;
3218 if(grep {$_ eq 'standing_penalties'} @$fields) {
3219 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3220 $fetch_penalties = 1;
3223 my $fetch_notes = 0;
3224 if(grep {$_ eq 'notes'} @$fields) {
3225 $fields = [grep {$_ ne 'notes'} @$fields];
3229 my $fetch_usr_act = 0;
3230 if(grep {$_ eq 'usr_activity'} @$fields) {
3231 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3235 my $user = $e->retrieve_actor_user(
3240 "flesh_fields" => { "au" => $fields }
3243 ) or return $e->die_event;
3246 if( grep { $_ eq 'addresses' } @$fields ) {
3248 $user->addresses([]) unless @{$user->addresses};
3249 # don't expose "replaced" addresses by default
3250 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3252 if( ref $user->billing_address ) {
3253 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3254 push( @{$user->addresses}, $user->billing_address );
3258 if( ref $user->mailing_address ) {
3259 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3260 push( @{$user->addresses}, $user->mailing_address );
3265 if($fetch_penalties) {
3266 # grab the user penalties ranged for this location
3267 $user->standing_penalties(
3268 $e->search_actor_user_standing_penalty([
3271 {stop_date => undef},
3272 {stop_date => {'>' => 'now'}}
3274 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3277 flesh_fields => {ausp => ['standing_penalty','usr_message']}
3284 # grab notes (now actor.usr_message_penalty) that have not hit their stop_date
3285 # NOTE: This is a view that already filters out deleted messages that are not
3286 # attached to a penalty
3288 @{ $e->search_actor_usr_message_penalty([
3291 {stop_date => undef},
3292 {stop_date => {'>' => 'now'}}
3299 # retrieve the most recent usr_activity entry
3300 if ($fetch_usr_act) {
3302 # max number to return for simple patron fleshing
3303 my $limit = $U->ou_ancestor_setting_value(
3304 $e->requestor->ws_ou,
3305 'circ.patron.usr_activity_retrieve.max');
3309 flesh_fields => {auact => ['etype']},
3310 order_by => {auact => 'event_time DESC'},
3313 # 0 == none, <0 == return all
3314 $limit = 1 unless defined $limit;
3315 $opts->{limit} = $limit if $limit > 0;
3317 $user->usr_activity(
3319 [] : # skip the DB call
3320 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3325 $user->clear_passwd();
3332 __PACKAGE__->register_method(
3333 method => "user_retrieve_parts",
3334 api_name => "open-ils.actor.user.retrieve.parts",
3337 sub user_retrieve_parts {
3338 my( $self, $client, $auth, $user_id, $fields ) = @_;
3339 my $e = new_editor(authtoken => $auth);
3340 return $e->event unless $e->checkauth;
3341 $user_id ||= $e->requestor->id;
3342 if( $e->requestor->id != $user_id ) {
3343 return $e->event unless $e->allowed('VIEW_USER');
3346 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3347 push(@resp, $user->$_()) for(@$fields);
3353 __PACKAGE__->register_method(
3354 method => 'user_opt_in_enabled',
3355 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3356 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3359 sub user_opt_in_enabled {
3360 my($self, $conn) = @_;
3361 my $sc = OpenSRF::Utils::SettingsClient->new;
3362 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3367 __PACKAGE__->register_method(
3368 method => 'user_opt_in_at_org',
3369 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3371 @param $auth The auth token
3372 @param user_id The ID of the user to test
3373 @return 1 if the user has opted in at the specified org,
3374 2 if opt-in is disallowed for the user's home org,
3375 event on error, and 0 otherwise. /
3377 sub user_opt_in_at_org {
3378 my($self, $conn, $auth, $user_id) = @_;
3380 # see if we even need to enforce the opt-in value
3381 return 1 unless user_opt_in_enabled($self);
3383 my $e = new_editor(authtoken => $auth);
3384 return $e->event unless $e->checkauth;
3386 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3387 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3389 my $ws_org = $e->requestor->ws_ou;
3390 # user is automatically opted-in if they are from the local org
3391 return 1 if $user->home_ou eq $ws_org;
3393 # get the boundary setting
3394 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3396 # auto opt in if user falls within the opt boundary
3397 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3399 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3401 # check whether opt-in is restricted at the user's home library
3402 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3403 if ($opt_restrict_depth) {
3404 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3405 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3407 # opt-in is disallowed unless the workstation org is within the home
3408 # library's opt-in scope
3409 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3412 my $vals = $e->search_actor_usr_org_unit_opt_in(
3413 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3419 __PACKAGE__->register_method(
3420 method => 'create_user_opt_in_at_org',
3421 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3423 @param $auth The auth token
3424 @param user_id The ID of the user to test
3425 @return The ID of the newly created object, event on error./
3428 sub create_user_opt_in_at_org {
3429 my($self, $conn, $auth, $user_id, $org_id) = @_;
3431 my $e = new_editor(authtoken => $auth, xact=>1);
3432 return $e->die_event unless $e->checkauth;
3434 # if a specific org unit wasn't passed in, get one based on the defaults;
3436 my $wsou = $e->requestor->ws_ou;
3437 # get the default opt depth
3438 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3439 # get the org unit at that depth
3440 my $org = $e->json_query({
3441 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3442 $org_id = $org->{id};
3445 # fall back to the workstation OU, the pre-opt-in-boundary way
3446 $org_id = $e->requestor->ws_ou;
3449 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3450 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3452 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3454 $opt_in->org_unit($org_id);
3455 $opt_in->usr($user_id);
3456 $opt_in->staff($e->requestor->id);
3457 $opt_in->opt_in_ts('now');
3458 $opt_in->opt_in_ws($e->requestor->wsid);
3460 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3461 or return $e->die_event;
3469 __PACKAGE__->register_method (
3470 method => 'retrieve_org_hours',
3471 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3473 Returns the hours of operation for a specified org unit
3474 @param authtoken The login session key
3475 @param org_id The org_unit ID
3479 sub retrieve_org_hours {
3480 my($self, $conn, $auth, $org_id) = @_;
3481 my $e = new_editor(authtoken => $auth);
3482 return $e->die_event unless $e->checkauth;
3483 $org_id ||= $e->requestor->ws_ou;
3484 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3488 __PACKAGE__->register_method (
3489 method => 'verify_user_password',
3490 api_name => 'open-ils.actor.verify_user_password',
3492 Given a barcode or username and the MD5 encoded password,
3493 returns 1 if the password is correct. Returns 0 otherwise.
3497 sub verify_user_password {
3498 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3499 my $e = new_editor(authtoken => $auth);
3500 return $e->die_event unless $e->checkauth;
3502 my $user_by_barcode;
3503 my $user_by_username;
3505 my $card = $e->search_actor_card([
3506 {barcode => $barcode},
3507 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3508 $user_by_barcode = $card->usr;
3509 $user = $user_by_barcode;
3512 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3513 $user = $user_by_username;
3515 return 0 if (!$user || $U->is_true($user->deleted));
3516 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3517 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3518 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3521 __PACKAGE__->register_method (
3522 method => 'retrieve_usr_id_via_barcode_or_usrname',
3523 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3525 Given a barcode or username returns the id for the user or
3530 sub retrieve_usr_id_via_barcode_or_usrname {
3531 my($self, $conn, $auth, $barcode, $username) = @_;
3532 my $e = new_editor(authtoken => $auth);
3533 return $e->die_event unless $e->checkauth;
3534 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3536 my $user_by_barcode;
3537 my $user_by_username;
3538 $logger->info("$id_as_barcode is the ID as BARCODE");
3540 my $card = $e->search_actor_card([
3541 {barcode => $barcode},
3542 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3543 if ($id_as_barcode =~ /^t/i) {
3545 $user = $e->retrieve_actor_user($barcode);
3546 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3548 $user_by_barcode = $card->usr;
3549 $user = $user_by_barcode;
3552 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3553 $user_by_barcode = $card->usr;
3554 $user = $user_by_barcode;
3559 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3561 $user = $user_by_username;
3563 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3564 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3565 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3570 __PACKAGE__->register_method (
3571 method => 'merge_users',
3572 api_name => 'open-ils.actor.user.merge',
3575 Given a list of source users and destination user, transfer all data from the source
3576 to the dest user and delete the source user. All user related data is
3577 transferred, including circulations, holds, bookbags, etc.
3583 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3584 my $e = new_editor(xact => 1, authtoken => $auth);
3585 return $e->die_event unless $e->checkauth;
3587 # disallow the merge if any subordinate accounts are in collections
3588 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3589 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3591 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3592 if $master_id == $e->requestor->id;
3594 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3595 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3596 return $evt if $evt;
3598 my $del_addrs = ($U->ou_ancestor_setting_value(
3599 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3600 my $del_cards = ($U->ou_ancestor_setting_value(
3601 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3602 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3603 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3605 for my $src_id (@$user_ids) {
3607 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3608 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3609 return $evt if $evt;
3611 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3612 if $src_id == $e->requestor->id;
3614 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3615 if($src_user->home_ou ne $master_user->home_ou) {
3616 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3619 return $e->die_event unless
3620 $e->json_query({from => [
3635 __PACKAGE__->register_method (
3636 method => 'approve_user_address',
3637 api_name => 'open-ils.actor.user.pending_address.approve',
3644 sub approve_user_address {
3645 my($self, $conn, $auth, $addr) = @_;
3646 my $e = new_editor(xact => 1, authtoken => $auth);
3647 return $e->die_event unless $e->checkauth;
3649 # if the caller passes an address object, assume they want to
3650 # update it first before approving it
3651 $e->update_actor_user_address($addr) or return $e->die_event;
3653 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3655 my $user = $e->retrieve_actor_user($addr->usr);
3656 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3657 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3658 or return $e->die_event;
3660 return [values %$result]->[0];
3664 __PACKAGE__->register_method (
3665 method => 'retrieve_friends',
3666 api_name => 'open-ils.actor.friends.retrieve',
3669 returns { confirmed: [], pending_out: [], pending_in: []}
3670 pending_out are users I'm requesting friendship with
3671 pending_in are users requesting friendship with me
3676 sub retrieve_friends {
3677 my($self, $conn, $auth, $user_id, $options) = @_;
3678 my $e = new_editor(authtoken => $auth);
3679 return $e->event unless $e->checkauth;
3680 $user_id ||= $e->requestor->id;
3682 if($user_id != $e->requestor->id) {
3683 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3684 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3687 return OpenILS::Application::Actor::Friends->retrieve_friends(
3688 $e, $user_id, $options);
3693 __PACKAGE__->register_method (
3694 method => 'apply_friend_perms',
3695 api_name => 'open-ils.actor.friends.perms.apply',
3701 sub apply_friend_perms {
3702 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3703 my $e = new_editor(authtoken => $auth, xact => 1);
3704 return $e->die_event unless $e->checkauth;
3706 if($user_id != $e->requestor->id) {
3707 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3708 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3711 for my $perm (@perms) {
3713 OpenILS::Application::Actor::Friends->apply_friend_perm(
3714 $e, $user_id, $delegate_id, $perm);
3715 return $evt if $evt;
3723 __PACKAGE__->register_method (
3724 method => 'update_user_pending_address',
3725 api_name => 'open-ils.actor.user.address.pending.cud'
3728 sub update_user_pending_address {
3729 my($self, $conn, $auth, $addr) = @_;
3730 my $e = new_editor(authtoken => $auth, xact => 1);
3731 return $e->die_event unless $e->checkauth;
3733 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3734 if($addr->usr != $e->requestor->id) {
3735 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3739 $e->create_actor_user_address($addr) or return $e->die_event;
3740 } elsif($addr->isdeleted) {
3741 $e->delete_actor_user_address($addr) or return $e->die_event;
3743 $e->update_actor_user_address($addr) or return $e->die_event;
3747 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3753 __PACKAGE__->register_method (
3754 method => 'user_events',
3755 api_name => 'open-ils.actor.user.events.circ',
3758 __PACKAGE__->register_method (
3759 method => 'user_events',
3760 api_name => 'open-ils.actor.user.events.ahr',
3765 my($self, $conn, $auth, $user_id, $filters) = @_;
3766 my $e = new_editor(authtoken => $auth);
3767 return $e->event unless $e->checkauth;
3769 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3770 my $user_field = 'usr';
3773 $filters->{target} = {
3774 select => { $obj_type => ['id'] },
3776 where => {usr => $user_id}
3779 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3780 if($e->requestor->id != $user_id) {
3781 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3784 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3785 my $req = $ses->request('open-ils.trigger.events_by_target',
3786 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3788 while(my $resp = $req->recv) {
3789 my $val = $resp->content;
3790 my $tgt = $val->target;
3792 if($obj_type eq 'circ') {
3793 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3795 } elsif($obj_type eq 'ahr') {
3796 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3797 if $tgt->current_copy;
3800 $conn->respond($val) if $val;
3806 __PACKAGE__->register_method (
3807 method => 'copy_events',
3808 api_name => 'open-ils.actor.copy.events.circ',
3811 __PACKAGE__->register_method (
3812 method => 'copy_events',
3813 api_name => 'open-ils.actor.copy.events.ahr',
3818 my($self, $conn, $auth, $copy_id, $filters) = @_;
3819 my $e = new_editor(authtoken => $auth);
3820 return $e->event unless $e->checkauth;
3822 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3824 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3826 my $copy_field = 'target_copy';
3827 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3830 $filters->{target} = {
3831 select => { $obj_type => ['id'] },
3833 where => {$copy_field => $copy_id}
3837 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3838 my $req = $ses->request('open-ils.trigger.events_by_target',
3839 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3841 while(my $resp = $req->recv) {
3842 my $val = $resp->content;
3843 my $tgt = $val->target;
3845 my $user = $e->retrieve_actor_user($tgt->usr);
3846 if($e->requestor->id != $user->id) {
3847 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3850 $tgt->$copy_field($copy);
3853 $conn->respond($val) if $val;
3860 __PACKAGE__->register_method (
3861 method => 'get_itemsout_notices',
3862 api_name => 'open-ils.actor.user.itemsout.notices',
3866 desc => q/Summary counts of circulat notices/,
3868 {desc => 'authtoken', type => 'string'},
3869 {desc => 'circulation identifiers', type => 'array of numbers'}
3871 return => q/Stream of summary objects/
3875 sub get_itemsout_notices {
3876 my ($self, $client, $auth, $circ_ids) = @_;
3878 my $e = new_editor(authtoken => $auth);
3879 return $e->event unless $e->checkauth;
3881 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3883 for my $circ_id (@$circ_ids) {
3884 my $resp = get_itemsout_notices_impl($e, $circ_id);
3886 if ($U->is_event($resp)) {
3887 $client->respond($resp);
3891 $client->respond({circ_id => $circ_id, %$resp});
3899 sub get_itemsout_notices_impl {
3900 my ($e, $circId) = @_;
3902 my $requestorId = $e->requestor->id;
3904 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3906 my $patronId = $circ->usr;
3908 if( $patronId ne $requestorId ){
3909 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3910 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3913 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3914 #my $req = $ses->request('open-ils.trigger.events_by_target',
3915 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3916 # ^ Above removed in favor of faster json_query.
3919 # select complete_time
3920 # from action_trigger.event atev
3921 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3922 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3923 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3926 my $ctx_loc = $e->requestor->ws_ou;
3927 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3928 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3931 select => { atev => ["complete_time"] },
3934 atevdef => { field => "id",fkey => "event_def"}
3938 "+atevdef" => { active => 't', hook => 'checkout.due' },
3939 "+atev" => { target => $circId, state => 'complete' }
3943 if ($exclude_courtesy_notices){
3944 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3947 my %resblob = ( numNotices => 0, lastDt => undef );
3949 my $res = $e->json_query($query);
3950 for my $ndate (@$res) {
3951 $resblob{numNotices}++;
3952 if( !defined $resblob{lastDt}){
3953 $resblob{lastDt} = $$ndate{complete_time};
3956 if ($resblob{lastDt} lt $$ndate{complete_time}){
3957 $resblob{lastDt} = $$ndate{complete_time};
3964 __PACKAGE__->register_method (
3965 method => 'update_events',
3966 api_name => 'open-ils.actor.user.event.cancel.batch',
3969 __PACKAGE__->register_method (
3970 method => 'update_events',
3971 api_name => 'open-ils.actor.user.event.reset.batch',
3976 my($self, $conn, $auth, $event_ids) = @_;
3977 my $e = new_editor(xact => 1, authtoken => $auth);
3978 return $e->die_event unless $e->checkauth;
3981 for my $id (@$event_ids) {
3983 # do a little dance to determine what user we are ultimately affecting
3984 my $event = $e->retrieve_action_trigger_event([
3987 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3989 ]) or return $e->die_event;
3992 if($event->event_def->hook->core_type eq 'circ') {
3993 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3994 } elsif($event->event_def->hook->core_type eq 'ahr') {
3995 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
4000 my $user = $e->retrieve_actor_user($user_id);
4001 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
4003 if($self->api_name =~ /cancel/) {
4004 $event->state('invalid');
4005 } elsif($self->api_name =~ /reset/) {
4006 $event->clear_start_time;
4007 $event->clear_update_time;
4008 $event->state('pending');
4011 $e->update_action_trigger_event($event) or return $e->die_event;
4012 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
4016 return {complete => 1};
4020 __PACKAGE__->register_method (
4021 method => 'really_delete_user',
4022 api_name => 'open-ils.actor.user.delete.override',
4023 signature => q/@see open-ils.actor.user.delete/
4026 __PACKAGE__->register_method (
4027 method => 'really_delete_user',
4028 api_name => 'open-ils.actor.user.delete',
4030 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
4031 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
4032 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
4033 dest_usr_id is only required when deleting a user that performs staff functions.
4037 sub really_delete_user {
4038 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
4039 my $e = new_editor(authtoken => $auth, xact => 1);
4040 return $e->die_event unless $e->checkauth;
4041 $oargs = { all => 1 } unless defined $oargs;
4043 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
4044 my $open_bills = $e->json_query({
4045 select => { mbts => ['id'] },
4048 xact_finish => { '=' => undef },
4049 usr => { '=' => $user_id },
4051 }) or return $e->die_event;
4053 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
4055 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
4057 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
4058 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
4059 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
4061 # No deleting yourself - UI is supposed to stop you first, though.
4062 return $e->die_event unless $e->requestor->id != $user->id;
4063 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4064 # Check if you are allowed to mess with this patron permission group at all
4065 my $evt = group_perm_failed($e, $e->requestor, $user);
4066 return $e->die_event($evt) if $evt;
4067 my $stat = $e->json_query(
4068 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4069 or return $e->die_event;
4075 __PACKAGE__->register_method (
4076 method => 'user_payments',
4077 api_name => 'open-ils.actor.user.payments.retrieve',
4080 Returns all payments for a given user. Default order is newest payments first.
4081 @param auth Authentication token
4082 @param user_id The user ID
4083 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4088 my($self, $conn, $auth, $user_id, $filters) = @_;
4091 my $e = new_editor(authtoken => $auth);
4092 return $e->die_event unless $e->checkauth;
4094 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4095 return $e->event unless
4096 $e->requestor->id == $user_id or
4097 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4099 # Find all payments for all transactions for user $user_id
4101 select => {mp => ['id']},
4106 select => {mbt => ['id']},
4108 where => {usr => $user_id}
4113 { # by default, order newest payments first
4115 field => 'payment_ts',
4118 # secondary sort in ID as a tie-breaker, since payments created
4119 # within the same transaction will have identical payment_ts's
4126 for (qw/order_by limit offset/) {
4127 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4130 if(defined $filters->{where}) {
4131 foreach (keys %{$filters->{where}}) {
4132 # don't allow the caller to expand the result set to other users
4133 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4137 my $payment_ids = $e->json_query($query);
4138 for my $pid (@$payment_ids) {
4139 my $pay = $e->retrieve_money_payment([
4144 mbt => ['summary', 'circulation', 'grocery'],
4145 circ => ['target_copy'],
4146 acp => ['call_number'],
4154 xact_type => $pay->xact->summary->xact_type,
4155 last_billing_type => $pay->xact->summary->last_billing_type,
4158 if($pay->xact->summary->xact_type eq 'circulation') {
4159 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4160 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4163 $pay->xact($pay->xact->id); # de-flesh
4164 $conn->respond($resp);
4172 __PACKAGE__->register_method (
4173 method => 'negative_balance_users',
4174 api_name => 'open-ils.actor.users.negative_balance',
4177 Returns all users that have an overall negative balance
4178 @param auth Authentication token
4179 @param org_id The context org unit as an ID or list of IDs. This will be the home
4180 library of the user. If no org_unit is specified, no org unit filter is applied
4184 sub negative_balance_users {
4185 my($self, $conn, $auth, $org_id) = @_;
4187 my $e = new_editor(authtoken => $auth);
4188 return $e->die_event unless $e->checkauth;
4189 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4193 mous => ['usr', 'balance_owed'],
4196 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4197 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4214 where => {'+mous' => {balance_owed => {'<' => 0}}}
4217 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4219 my $list = $e->json_query($query, {timeout => 600});
4221 for my $data (@$list) {
4223 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4224 balance_owed => $data->{balance_owed},
4225 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4232 __PACKAGE__->register_method(
4233 method => "request_password_reset",
4234 api_name => "open-ils.actor.patron.password_reset.request",
4236 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4237 "method for changing a user's password. The UUID token is distributed via A/T " .
4238 "templates (i.e. email to the user).",
4240 { desc => 'user_id_type', type => 'string' },
4241 { desc => 'user_id', type => 'string' },
4242 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4244 return => {desc => '1 on success, Event on error'}
4247 sub request_password_reset {
4248 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4250 # Check to see if password reset requests are already being throttled:
4251 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4253 my $e = new_editor(xact => 1);
4256 # Get the user, if any, depending on the input value
4257 if ($user_id_type eq 'username') {
4258 $user = $e->search_actor_user({usrname => $user_id})->[0];
4261 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4263 } elsif ($user_id_type eq 'barcode') {
4264 my $card = $e->search_actor_card([
4265 {barcode => $user_id},
4266 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4269 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4274 # If the user doesn't have an email address, we can't help them
4275 if (!$user->email) {
4277 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4280 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4281 if ($email_must_match) {
4282 if (lc($user->email) ne lc($email)) {
4283 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4287 _reset_password_request($conn, $e, $user);
4290 # Once we have the user, we can issue the password reset request
4291 # XXX Add a wrapper method that accepts barcode + email input
4292 sub _reset_password_request {
4293 my ($conn, $e, $user) = @_;
4295 # 1. Get throttle threshold and time-to-live from OU_settings
4296 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4297 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4299 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4301 # 2. Get time of last request and number of active requests (num_active)
4302 my $active_requests = $e->json_query({
4308 transform => 'COUNT'
4311 column => 'request_time',
4317 has_been_reset => { '=' => 'f' },
4318 request_time => { '>' => $threshold_time }
4322 # Guard against no active requests
4323 if ($active_requests->[0]->{'request_time'}) {
4324 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4325 my $now = DateTime::Format::ISO8601->new();
4327 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4328 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4329 ($last_request->add_duration('1 minute') > $now)) {
4330 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4332 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4336 # TODO Check to see if the user is in a password-reset-restricted group
4338 # Otherwise, go ahead and try to get the user.
4340 # Check the number of active requests for this user
4341 $active_requests = $e->json_query({
4347 transform => 'COUNT'
4352 usr => { '=' => $user->id },
4353 has_been_reset => { '=' => 'f' },
4354 request_time => { '>' => $threshold_time }
4358 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4360 # if less than or equal to per-user threshold, proceed; otherwise, return event
4361 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4362 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4364 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4367 # Create the aupr object and insert into the database
4368 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4369 my $uuid = create_uuid_as_string(UUID_V4);
4370 $reset_request->uuid($uuid);
4371 $reset_request->usr($user->id);
4373 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4376 # Create an event to notify user of the URL to reset their password
4378 # Can we stuff this in the user_data param for trigger autocreate?
4379 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4381 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4382 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4385 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4390 __PACKAGE__->register_method(
4391 method => "commit_password_reset",
4392 api_name => "open-ils.actor.patron.password_reset.commit",
4394 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4395 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4396 "with the supplied password.",
4398 { desc => 'uuid', type => 'string' },
4399 { desc => 'password', type => 'string' },
4401 return => {desc => '1 on success, Event on error'}
4404 sub commit_password_reset {
4405 my($self, $conn, $uuid, $password) = @_;
4407 # Check to see if password reset requests are already being throttled:
4408 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4409 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4410 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4412 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4415 my $e = new_editor(xact => 1);
4417 my $aupr = $e->search_actor_usr_password_reset({
4424 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4426 my $user_id = $aupr->[0]->usr;
4427 my $user = $e->retrieve_actor_user($user_id);
4429 # Ensure we're still within the TTL for the request
4430 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4431 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4432 if ($threshold < DateTime->now(time_zone => 'local')) {
4434 $logger->info("Password reset request needed to be submitted before $threshold");
4435 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4438 # Check complexity of password against OU-defined regex
4439 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4443 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4444 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4445 $is_strong = check_password_strength_custom($password, $pw_regex);
4447 $is_strong = check_password_strength_default($password);
4452 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4455 # All is well; update the password
4456 modify_migrated_user_password($e, $user->id, $password);
4458 # And flag that this password reset request has been honoured
4459 $aupr->[0]->has_been_reset('t');
4460 $e->update_actor_usr_password_reset($aupr->[0]);
4466 sub check_password_strength_default {
4467 my $password = shift;
4468 # Use the default set of checks
4469 if ( (length($password) < 7) or
4470 ($password !~ m/.*\d+.*/) or
4471 ($password !~ m/.*[A-Za-z]+.*/)
4478 sub check_password_strength_custom {
4479 my ($password, $pw_regex) = @_;
4481 $pw_regex = qr/$pw_regex/;
4482 if ($password !~ /$pw_regex/) {
4488 __PACKAGE__->register_method(
4489 method => "fire_test_notification",
4490 api_name => "open-ils.actor.event.test_notification"
4493 sub fire_test_notification {
4494 my($self, $conn, $auth, $args) = @_;
4495 my $e = new_editor(authtoken => $auth);
4496 return $e->event unless $e->checkauth;
4497 if ($e->requestor->id != $$args{target}) {
4498 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4499 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4502 my $event_hook = $$args{hook} or return $e->event;
4503 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4505 my $usr = $e->retrieve_actor_user($$args{target});
4506 return $e->event unless $usr;
4508 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4512 __PACKAGE__->register_method(
4513 method => "event_def_opt_in_settings",
4514 api_name => "open-ils.actor.event_def.opt_in.settings",
4517 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4519 { desc => 'Authentication token', type => 'string'},
4521 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4526 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4533 sub event_def_opt_in_settings {
4534 my($self, $conn, $auth, $org_id) = @_;
4535 my $e = new_editor(authtoken => $auth);
4536 return $e->event unless $e->checkauth;
4538 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4539 return $e->event unless
4540 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4542 $org_id = $e->requestor->home_ou;
4545 # find all config.user_setting_type's related to event_defs for the requested org unit
4546 my $types = $e->json_query({
4547 select => {cust => ['name']},
4548 from => {atevdef => 'cust'},
4551 owner => $U->get_org_ancestors($org_id), # context org plus parents
4558 $conn->respond($_) for
4559 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4566 __PACKAGE__->register_method(
4567 method => "user_circ_history",
4568 api_name => "open-ils.actor.history.circ",
4572 desc => 'Returns user circ history objects for the calling user',
4574 { desc => 'Authentication token', type => 'string'},
4575 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4578 desc => q/Stream of 'auch' circ history objects/,
4584 __PACKAGE__->register_method(
4585 method => "user_circ_history",
4586 api_name => "open-ils.actor.history.circ.clear",
4589 desc => 'Delete all user circ history entries for the calling user',
4591 { desc => 'Authentication token', type => 'string'},
4592 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4595 desc => q/1 on success, event on error/,
4601 __PACKAGE__->register_method(
4602 method => "user_circ_history",
4603 api_name => "open-ils.actor.history.circ.print",
4606 desc => q/Returns printable output for the caller's circ history objects/,
4608 { desc => 'Authentication token', type => 'string'},
4609 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4612 desc => q/An action_trigger.event object or error event./,
4618 __PACKAGE__->register_method(
4619 method => "user_circ_history",
4620 api_name => "open-ils.actor.history.circ.email",
4623 desc => q/Emails the caller's circ history/,
4625 { desc => 'Authentication token', type => 'string'},
4626 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4627 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4630 desc => q/undef, or event on error/
4635 sub user_circ_history {
4636 my ($self, $conn, $auth, $options) = @_;
4639 my $for_print = ($self->api_name =~ /print/);
4640 my $for_email = ($self->api_name =~ /email/);
4641 my $for_clear = ($self->api_name =~ /clear/);
4643 # No perm check is performed. Caller may only access his/her own
4644 # circ history entries.
4645 my $e = new_editor(authtoken => $auth);
4646 return $e->event unless $e->checkauth;
4649 if (!$for_clear) { # clear deletes all
4650 $limits{offset} = $options->{offset} if defined $options->{offset};
4651 $limits{limit} = $options->{limit} if defined $options->{limit};
4654 my %circ_id_filter = $options->{circ_ids} ?
4655 (id => $options->{circ_ids}) : ();
4657 my $circs = $e->search_action_user_circ_history([
4658 { usr => $e->requestor->id,
4661 { # order newest to oldest by default
4662 order_by => {auch => 'xact_start DESC'},
4665 {substream => 1} # could be a large list
4669 return $U->fire_object_event(undef,
4670 'circ.format.history.print', $circs, $e->requestor->home_ou);
4673 $e->xact_begin if $for_clear;
4674 $conn->respond_complete(1) if $for_email; # no sense in waiting
4676 for my $circ (@$circs) {
4679 # events will be fired from action_trigger_runner
4680 $U->create_events_for_hook('circ.format.history.email',
4681 $circ, $e->editor->home_ou, undef, undef, 1);
4683 } elsif ($for_clear) {
4685 $e->delete_action_user_circ_history($circ)
4686 or return $e->die_event;
4689 $conn->respond($circ);
4702 __PACKAGE__->register_method(
4703 method => "user_visible_holds",
4704 api_name => "open-ils.actor.history.hold.visible",
4707 desc => 'Returns 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 object with 1 field: "hold"/,
4720 __PACKAGE__->register_method(
4721 method => "user_visible_holds",
4722 api_name => "open-ils.actor.history.hold.visible.print",
4725 desc => 'Returns printable output for the set of opt-in visible holds',
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/An action_trigger.event object or error event./,
4738 __PACKAGE__->register_method(
4739 method => "user_visible_holds",
4740 api_name => "open-ils.actor.history.hold.visible.email",
4743 desc => 'Emails the set of opt-in visible holds to the requestor',
4745 { desc => 'Authentication token', type => 'string'},
4746 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4747 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4750 desc => q/undef, or event on error/
4755 sub user_visible_holds {
4756 my($self, $conn, $auth, $user_id, $options) = @_;
4759 my $for_print = ($self->api_name =~ /print/);
4760 my $for_email = ($self->api_name =~ /email/);
4761 my $e = new_editor(authtoken => $auth);
4762 return $e->event unless $e->checkauth;
4764 $user_id ||= $e->requestor->id;
4766 $options->{limit} ||= 50;
4767 $options->{offset} ||= 0;
4769 if($user_id != $e->requestor->id) {
4770 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4771 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4772 return $e->event unless $e->allowed($perm, $user->home_ou);
4775 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4777 my $data = $e->json_query({
4778 from => [$db_func, $user_id],
4779 limit => $$options{limit},
4780 offset => $$options{offset}
4782 # TODO: I only want IDs. code below didn't get me there
4783 # {"select":{"au":[{"column":"id", "result_field":"id",
4784 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4789 return undef unless @$data;
4793 # collect the batch of objects
4797 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4798 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4802 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4803 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4806 } elsif ($for_email) {
4808 $conn->respond_complete(1) if $for_email; # no sense in waiting
4816 my $hold = $e->retrieve_action_hold_request($id);
4817 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4818 # events will be fired from action_trigger_runner
4822 my $circ = $e->retrieve_action_circulation($id);
4823 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4824 # events will be fired from action_trigger_runner
4828 } else { # just give me the data please
4836 my $hold = $e->retrieve_action_hold_request($id);
4837 $conn->respond({hold => $hold});
4841 my $circ = $e->retrieve_action_circulation($id);
4844 summary => $U->create_circ_chain_summary($e, $id)
4853 __PACKAGE__->register_method(
4854 method => "user_saved_search_cud",
4855 api_name => "open-ils.actor.user.saved_search.cud",
4858 desc => 'Create/Update/Delete Access to user saved searches',
4860 { desc => 'Authentication token', type => 'string' },
4861 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4864 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4870 __PACKAGE__->register_method(
4871 method => "user_saved_search_cud",
4872 api_name => "open-ils.actor.user.saved_search.retrieve",
4875 desc => 'Retrieve a saved search object',
4877 { desc => 'Authentication token', type => 'string' },
4878 { desc => 'Saved Search ID', type => 'number' }
4881 desc => q/The saved search object, Event on error/,
4887 sub user_saved_search_cud {
4888 my( $self, $client, $auth, $search ) = @_;
4889 my $e = new_editor( authtoken=>$auth );
4890 return $e->die_event unless $e->checkauth;
4892 my $o_search; # prior version of the object, if any
4893 my $res; # to be returned
4895 # branch on the operation type
4897 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4899 # Get the old version, to check ownership
4900 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4901 or return $e->die_event;
4903 # You can't read somebody else's search
4904 return OpenILS::Event->new('BAD_PARAMS')
4905 unless $o_search->owner == $e->requestor->id;
4911 $e->xact_begin; # start an editor transaction
4913 if( $search->isnew ) { # Create
4915 # You can't create a search for somebody else
4916 return OpenILS::Event->new('BAD_PARAMS')
4917 unless $search->owner == $e->requestor->id;
4919 $e->create_actor_usr_saved_search( $search )
4920 or return $e->die_event;
4924 } elsif( $search->ischanged ) { # Update
4926 # You can't change ownership of a search
4927 return OpenILS::Event->new('BAD_PARAMS')
4928 unless $search->owner == $e->requestor->id;
4930 # Get the old version, to check ownership
4931 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4932 or return $e->die_event;
4934 # You can't update somebody else's search
4935 return OpenILS::Event->new('BAD_PARAMS')
4936 unless $o_search->owner == $e->requestor->id;
4939 $e->update_actor_usr_saved_search( $search )
4940 or return $e->die_event;
4944 } elsif( $search->isdeleted ) { # Delete
4946 # Get the old version, to check ownership
4947 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4948 or return $e->die_event;
4950 # You can't delete somebody else's search
4951 return OpenILS::Event->new('BAD_PARAMS')
4952 unless $o_search->owner == $e->requestor->id;
4955 $e->delete_actor_usr_saved_search( $o_search )
4956 or return $e->die_event;
4967 __PACKAGE__->register_method(
4968 method => "get_barcodes",
4969 api_name => "open-ils.actor.get_barcodes"
4973 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4974 my $e = new_editor(authtoken => $auth);
4975 return $e->event unless $e->checkauth;
4976 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4978 my $db_result = $e->json_query(
4980 'evergreen.get_barcodes',
4981 $org_id, $context, $barcode,
4985 if($context =~ /actor/) {
4986 my $filter_result = ();
4988 foreach my $result (@$db_result) {
4989 if($result->{type} eq 'actor') {
4990 if($e->requestor->id != $result->{id}) {
4991 $patron = $e->retrieve_actor_user($result->{id});
4993 push(@$filter_result, $e->event);
4996 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4997 push(@$filter_result, $result);
5000 push(@$filter_result, $e->event);
5004 push(@$filter_result, $result);
5008 push(@$filter_result, $result);
5011 return $filter_result;
5017 __PACKAGE__->register_method(
5018 method => 'address_alert_test',
5019 api_name => 'open-ils.actor.address_alert.test',
5021 desc => "Tests a set of address fields to determine if they match with an address_alert",
5023 {desc => 'Authentication token', type => 'string'},
5024 {desc => 'Org Unit', type => 'number'},
5025 {desc => 'Fields', type => 'hash'},
5027 return => {desc => 'List of matching address_alerts'}
5031 sub address_alert_test {
5032 my ($self, $client, $auth, $org_unit, $fields) = @_;
5033 return [] unless $fields and grep {$_} values %$fields;
5035 my $e = new_editor(authtoken => $auth);
5036 return $e->event unless $e->checkauth;
5037 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
5038 $org_unit ||= $e->requestor->ws_ou;
5040 my $alerts = $e->json_query({
5042 'actor.address_alert_matches',
5050 $$fields{post_code},
5051 $$fields{mailing_address},
5052 $$fields{billing_address}
5056 # map the json_query hashes to real objects
5058 map {$e->retrieve_actor_address_alert($_)}
5059 (map {$_->{id}} @$alerts)
5063 __PACKAGE__->register_method(
5064 method => "mark_users_contact_invalid",
5065 api_name => "open-ils.actor.invalidate.email",
5067 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",
5069 {desc => "Authentication token", type => "string"},
5070 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5071 {desc => "Additional note text (optional)", type => "string"},
5072 {desc => "penalty org unit ID (optional)", type => "number"},
5073 {desc => "Email address (optional)", type => "string"}
5075 return => {desc => "Event describing success or failure", type => "object"}
5079 __PACKAGE__->register_method(
5080 method => "mark_users_contact_invalid",
5081 api_name => "open-ils.actor.invalidate.day_phone",
5083 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",
5085 {desc => "Authentication token", type => "string"},
5086 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5087 {desc => "Additional note text (optional)", type => "string"},
5088 {desc => "penalty org unit ID (optional)", type => "number"},
5089 {desc => "Phone Number (optional)", type => "string"}
5091 return => {desc => "Event describing success or failure", type => "object"}
5095 __PACKAGE__->register_method(
5096 method => "mark_users_contact_invalid",
5097 api_name => "open-ils.actor.invalidate.evening_phone",
5099 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",
5101 {desc => "Authentication token", type => "string"},
5102 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5103 {desc => "Additional note text (optional)", type => "string"},
5104 {desc => "penalty org unit ID (optional)", type => "number"},
5105 {desc => "Phone Number (optional)", type => "string"}
5107 return => {desc => "Event describing success or failure", type => "object"}
5111 __PACKAGE__->register_method(
5112 method => "mark_users_contact_invalid",
5113 api_name => "open-ils.actor.invalidate.other_phone",
5115 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",
5117 {desc => "Authentication token", type => "string"},
5118 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5119 {desc => "Additional note text (optional)", type => "string"},
5120 {desc => "penalty org unit ID (optional, default to top of org tree)",
5122 {desc => "Phone Number (optional)", type => "string"}
5124 return => {desc => "Event describing success or failure", type => "object"}
5128 sub mark_users_contact_invalid {
5129 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5131 # This method invalidates an email address or a phone_number which
5132 # removes the bad email address or phone number, copying its contents
5133 # to a patron note, and institutes a standing penalty for "bad email"
5134 # or "bad phone number" which is cleared when the user is saved or
5135 # optionally only when the user is saved with an email address or
5136 # phone number (or staff manually delete the penalty).
5138 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5140 my $e = new_editor(authtoken => $auth, xact => 1);
5141 return $e->die_event unless $e->checkauth;
5144 if (defined $patron_id && $patron_id ne "") {
5145 $howfind = {usr => $patron_id};
5146 } elsif (defined $contact && $contact ne "") {
5147 $howfind = {$contact_type => $contact};
5149 # Error out if no patron id set or no contact is set.
5150 return OpenILS::Event->new('BAD_PARAMS');
5153 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5154 $e, $contact_type, $howfind,
5155 $addl_note, $penalty_ou, $e->requestor->id
5159 # Putting the following method in open-ils.actor is a bad fit, except in that
5160 # it serves an interface that lives under 'actor' in the templates directory,
5161 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5163 __PACKAGE__->register_method(
5164 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5165 method => "get_all_at_reactors_in_use",
5170 { name => 'authtoken', type => 'string' }
5173 desc => 'list of reactor names', type => 'array'
5178 sub get_all_at_reactors_in_use {
5179 my ($self, $conn, $auth) = @_;
5181 my $e = new_editor(authtoken => $auth);
5182 $e->checkauth or return $e->die_event;
5183 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5185 my $reactors = $e->json_query({
5187 atevdef => [{column => "reactor", transform => "distinct"}]
5189 from => {atevdef => {}}
5192 return $e->die_event unless ref $reactors eq "ARRAY";
5195 return [ map { $_->{reactor} } @$reactors ];
5198 __PACKAGE__->register_method(
5199 method => "filter_group_entry_crud",
5200 api_name => "open-ils.actor.filter_group_entry.crud",
5203 Provides CRUD access to filter group entry objects. These are not full accessible
5204 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5205 are not accessible via PCRUD (because they have no fields against which to link perms)
5208 {desc => "Authentication token", type => "string"},
5209 {desc => "Entry ID / Entry Object", type => "number"},
5210 {desc => "Additional note text (optional)", type => "string"},
5211 {desc => "penalty org unit ID (optional, default to top of org tree)",
5215 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5221 sub filter_group_entry_crud {
5222 my ($self, $conn, $auth, $arg) = @_;
5224 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5225 my $e = new_editor(authtoken => $auth, xact => 1);
5226 return $e->die_event unless $e->checkauth;
5232 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5233 or return $e->die_event;
5235 return $e->die_event unless $e->allowed(
5236 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5238 my $query = $arg->query;
5239 $query = $e->create_actor_search_query($query) or return $e->die_event;
5240 $arg->query($query->id);
5241 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5242 $entry->query($query);
5247 } elsif ($arg->ischanged) {
5249 my $entry = $e->retrieve_actor_search_filter_group_entry([
5252 flesh_fields => {asfge => ['grp']}
5254 ]) or return $e->die_event;
5256 return $e->die_event unless $e->allowed(
5257 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5259 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5260 $arg->query($arg->query->id);
5261 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5262 $arg->query($query);
5267 } elsif ($arg->isdeleted) {
5269 my $entry = $e->retrieve_actor_search_filter_group_entry([
5272 flesh_fields => {asfge => ['grp', 'query']}
5274 ]) or return $e->die_event;
5276 return $e->die_event unless $e->allowed(
5277 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5279 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5280 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5293 my $entry = $e->retrieve_actor_search_filter_group_entry([
5296 flesh_fields => {asfge => ['grp', 'query']}
5298 ]) or return $e->die_event;
5300 return $e->die_event unless $e->allowed(
5301 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5302 $entry->grp->owner);
5305 $entry->grp($entry->grp->id); # for consistency