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;
189 __PACKAGE__->register_method(
190 method => "get_ou_setting_history",
191 api_name => "open-ils.actor.org_unit.settings.history.retrieve",
193 desc => "Retrieves the history of an Org Unit Setting. The permission to retrieve " .
194 "an org unit setting's history is dependant on a specific permission specified " .
195 "in the view_perm column of the config.org_unit_setting_type " .
196 "table's row corresponding to the setting being changed." ,
198 {desc => 'Authentication token', type => 'string'},
199 {desc => 'Org Unit ID', type => 'number'},
200 {desc => 'Setting Type Name', type => 'string'}
202 return => {desc => 'History IDL Object'}
206 sub get_ou_setting_history {
207 my( $self, $client, $auth, $setting, $orgid ) = @_;
208 my $e = new_editor(authtoken => $auth, xact => 1);
209 return $e->die_event unless $e->checkauth;
211 return $U->ou_ancestor_setting_log(
212 $orgid, $setting, $e, $auth
217 __PACKAGE__->register_method(
218 method => "set_ou_settings",
219 api_name => "open-ils.actor.org_unit.settings.update",
221 desc => "Updates the value for a given org unit setting. The permission to update " .
222 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
223 "permission specified in the update_perm column of the config.org_unit_setting_type " .
224 "table's row corresponding to the setting being changed." ,
226 {desc => 'Authentication token', type => 'string'},
227 {desc => 'Org unit ID', type => 'number'},
228 {desc => 'Hash of setting name-value pairs', type => 'object'}
230 return => {desc => '1 on success, Event on error'}
234 sub set_ou_settings {
235 my( $self, $client, $auth, $org_id, $settings ) = @_;
237 my $e = new_editor(authtoken => $auth, xact => 1);
238 return $e->die_event unless $e->checkauth;
240 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
242 for my $name (keys %$settings) {
243 my $val = $$settings{$name};
245 my $type = $e->retrieve_config_org_unit_setting_type([
247 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
248 ]) or return $e->die_event;
249 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
251 # If there is no relevant permission, the default assumption will
252 # be, "no, the caller cannot change that value."
253 return $e->die_event unless ($all_allowed ||
254 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
257 $val = OpenSRF::Utils::JSON->perl2JSON($val);
260 $e->update_actor_org_unit_setting($set) or return $e->die_event;
262 $set = Fieldmapper::actor::org_unit_setting->new;
263 $set->org_unit($org_id);
266 $e->create_actor_org_unit_setting($set) or return $e->die_event;
269 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
277 __PACKAGE__->register_method(
278 method => "fetch_visible_ou_settings_log",
279 api_name => "open-ils.actor.org_unit.settings.history.visible.retrieve",
281 desc => "Retrieves the log entries for the specified OU setting. " .
282 "If the setting has a view permission, the results are limited " .
283 "to entries at the OUs that the user has the view permission. ",
285 {desc => 'Authentication token', type => 'string'},
286 {desc => 'Setting name', type => 'string'}
288 return => {desc => 'List of fieldmapper objects of the log entries, Event on error'}
292 sub fetch_visible_ou_settings_log {
293 my( $self, $client, $auth, $setting ) = @_;
295 my $e = new_editor(authtoken => $auth);
296 return $e->event unless $e->checkauth;
297 return $e->die_event unless $e->allowed("STAFF_LOGIN");
298 return OpenILS::Event->new('BAD_PARAMS') unless defined($setting);
300 my $type = $e->retrieve_config_org_unit_setting_type([
302 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
304 return OpenILS::Event->new('BAD_PARAMS', note => 'setting type not found')
307 my $query = { field_name => $setting };
308 if ($type->view_perm) {
309 $query->{org} = $U->user_has_work_perm_at($e, $type->view_perm->code, {descendants => 1});
310 if (scalar @{ $query->{org} } == 0) {
311 # user doesn't have the view permission anywhere, so return nothing
316 my $results = $e->search_config_org_unit_setting_type_log([$query, {'order_by' => 'date_applied ASC'}])
317 or return $e->die_event;
321 __PACKAGE__->register_method(
322 method => "user_settings",
324 api_name => "open-ils.actor.patron.settings.retrieve",
327 my( $self, $client, $auth, $user_id, $setting ) = @_;
329 my $e = new_editor(authtoken => $auth);
330 return $e->event unless $e->checkauth;
331 $user_id = $e->requestor->id unless defined $user_id;
333 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
334 if($e->requestor->id != $user_id) {
335 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
339 my($e, $user_id, $setting) = @_;
340 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
341 return undef unless $val; # XXX this should really return undef, but needs testing
342 return OpenSRF::Utils::JSON->JSON2perl($val->value);
346 if(ref $setting eq 'ARRAY') {
348 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
351 return get_setting($e, $user_id, $setting);
354 my $s = $e->search_actor_user_setting({usr => $user_id});
355 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
360 __PACKAGE__->register_method(
361 method => "ranged_ou_settings",
362 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
364 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
365 "is implied for retrieving OU settings by the authenticated users' permissions.",
367 {desc => 'Authentication token', type => 'string'},
368 {desc => 'Org unit ID', type => 'number'},
370 return => {desc => 'A hashref of "ranged" settings, event on error'}
373 sub ranged_ou_settings {
374 my( $self, $client, $auth, $org_id ) = @_;
376 my $e = new_editor(authtoken => $auth);
377 return $e->event unless $e->checkauth;
380 my $org_list = $U->get_org_ancestors($org_id);
381 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
382 $org_list = [ reverse @$org_list ];
384 # start at the context org and capture the setting value
385 # without clobbering settings we've already captured
386 for my $this_org_id (@$org_list) {
388 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
390 for my $set (@sets) {
391 my $type = $e->retrieve_config_org_unit_setting_type([
393 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
396 # If there is no relevant permission, the default assumption will
397 # be, "yes, the caller can have that value."
398 if ($type && $type->view_perm) {
399 next if not $e->allowed($type->view_perm->code, $org_id);
402 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
403 unless defined $ranged_settings{$set->name};
407 return \%ranged_settings;
412 __PACKAGE__->register_method(
413 api_name => 'open-ils.actor.ou_setting.ancestor_default',
414 method => 'ou_ancestor_setting',
416 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
417 'This method will make sure that the given user has permission to view that setting, if there is a ' .
418 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
419 'the user lacks the permisssion, undef will be returned.' ,
421 { desc => 'Org unit ID', type => 'number' },
422 { desc => 'setting name', type => 'string' },
423 { desc => 'authtoken (optional)', type => 'string' }
425 return => {desc => 'A value for the org unit setting, or undef'}
429 # ------------------------------------------------------------------
430 # Attempts to find the org setting value for a given org. if not
431 # found at the requested org, searches up the org tree until it
432 # finds a parent that has the requested setting.
433 # when found, returns { org => $id, value => $value }
434 # otherwise, returns NULL
435 # ------------------------------------------------------------------
436 sub ou_ancestor_setting {
437 my( $self, $client, $orgid, $name, $auth ) = @_;
438 # Make sure $auth is set to something if not given.
440 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
443 __PACKAGE__->register_method(
444 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
445 method => 'ou_ancestor_setting_batch',
447 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
448 'This method will make sure that the given user has permission to view that setting, if there is a ' .
449 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
450 'the user lacks the permisssion, undef will be returned.' ,
452 { desc => 'Org unit ID', type => 'number' },
453 { desc => 'setting name list', type => 'array' },
454 { desc => 'authtoken (optional)', type => 'string' }
456 return => {desc => 'A hash with name => value pairs for the org unit settings'}
459 sub ou_ancestor_setting_batch {
460 my( $self, $client, $orgid, $name_list, $auth ) = @_;
462 # splitting the list of settings to fetch values
463 # so that ones that *don't* require view_perm checks
464 # can be fetched in one fell swoop, which is
465 # significantly faster in cases where a large
466 # number of settings need to be fetched.
467 my %perm_check_required = ();
468 my @perm_check_not_required = ();
470 # Note that ->ou_ancestor_setting also can check
471 # to see if the setting has a view_perm, but testing
472 # suggests that the redundant checks do not significantly
473 # increase the time it takes to fetch the values of
474 # permission-controlled settings.
475 my $e = new_editor();
476 my $res = $e->search_config_org_unit_setting_type({
478 view_perm => { "!=" => undef },
480 %perm_check_required = map { $_->name() => 1 } @$res;
481 foreach my $setting (@$name_list) {
482 push @perm_check_not_required, $setting
483 unless exists($perm_check_required{$setting});
487 if (@perm_check_not_required) {
488 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
490 $values{$_} = $U->ou_ancestor_setting(
493 ) for keys(%perm_check_required);
499 __PACKAGE__->register_method(
500 method => "update_patron",
501 api_name => "open-ils.actor.patron.update",
504 Update an existing user, or create a new one. Related objects,
505 like cards, addresses, survey responses, and stat cats,
506 can be updated by attaching them to the user object in their
507 respective fields. For examples, the billing address object
508 may be inserted into the 'billing_address' field, etc. For each
509 attached object, indicate if the object should be created,
510 updated, or deleted using the built-in 'isnew', 'ischanged',
511 and 'isdeleted' fields on the object.
512 This method intentionally does not handle updates to patron
513 notes, user activity, and standing penalties; if any values
514 are supplied for those fields in the patron data object,
515 they will be ignored. Please refer to bug 1976126 before
519 { desc => 'Authentication token', type => 'string' },
520 { desc => 'Patron data object', type => 'object' }
522 return => {desc => 'A fleshed user object, event on error'}
527 my( $self, $client, $auth, $patron ) = @_;
529 my $e = new_editor(xact => 1, authtoken => $auth);
530 return $e->event unless $e->checkauth;
532 $logger->info($patron->isnew ? "Creating new patron..." :
533 "Updating Patron: " . $patron->id);
535 my $evt = check_group_perm($e, $e->requestor, $patron);
538 # $new_patron is the patron in progress. $patron is the original patron
539 # passed in with the method. new_patron will change as the components
540 # of patron are added/updated.
544 # unflesh the real items on the patron
545 $patron->card( $patron->card->id ) if(ref($patron->card));
546 $patron->billing_address( $patron->billing_address->id )
547 if(ref($patron->billing_address));
548 $patron->mailing_address( $patron->mailing_address->id )
549 if(ref($patron->mailing_address));
551 # create/update the patron first so we can use his id
553 # $patron is the obj from the client (new data) and $new_patron is the
554 # patron object properly built for db insertion, so we need a third variable
555 # if we want to represent the old patron.
558 my $barred_hook = '';
561 if($patron->isnew()) {
562 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
564 if($U->is_true($patron->barred)) {
565 return $e->die_event unless
566 $e->allowed('BAR_PATRON', $patron->home_ou);
568 if(($patron->photo_url)) {
569 return $e->die_event unless
570 $e->allowed('UPDATE_USER_PHOTO_URL', $patron->home_ou);
573 $new_patron = $patron;
575 # Did auth checking above already.
576 $old_patron = $e->retrieve_actor_user($patron->id) or
577 return $e->die_event;
579 $renew_hook = 'au.renewed' if ($old_patron->expire_date ne $new_patron->expire_date);
581 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
582 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
583 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
585 $barred_hook = $U->is_true($new_patron->barred) ?
586 'au.barred' : 'au.unbarred';
589 if($old_patron->photo_url ne $new_patron->photo_url) {
590 my $perm = 'UPDATE_USER_PHOTO_URL';
591 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
594 # update the password by itself to avoid the password protection magic
595 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
596 modify_migrated_user_password($e, $patron->id, $patron->passwd);
597 $new_patron->passwd(''); # subsequent update will set
598 # actor.usr.passwd to MD5('')
602 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
605 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
608 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
611 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
614 # re-update the patron if anything has happened to him during this process
615 if($new_patron->ischanged()) {
616 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
620 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
623 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
626 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
629 $evt = apply_invalid_addr_penalty($e, $patron);
634 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
636 $tses->request('open-ils.trigger.event.autocreate',
637 'au.created', $new_patron, $new_patron->home_ou);
639 $tses->request('open-ils.trigger.event.autocreate',
640 'au.updated', $new_patron, $new_patron->home_ou);
642 $tses->request('open-ils.trigger.event.autocreate', $renew_hook,
643 $new_patron, $new_patron->home_ou) if $renew_hook;
645 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
646 $new_patron, $new_patron->home_ou) if $barred_hook;
649 $e->xact_begin; # $e->rollback is called in new_flesh_user
650 return flesh_user($new_patron->id(), $e);
653 sub apply_invalid_addr_penalty {
657 # grab the invalid address penalty if set
658 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
660 my ($addr_penalty) = grep
661 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
663 # do we enforce invalid address penalty
664 my $enforce = $U->ou_ancestor_setting_value(
665 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
667 my $addrs = $e->search_actor_user_address(
668 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
669 my $addr_count = scalar(@$addrs);
671 if($addr_count == 0 and $addr_penalty) {
673 # regardless of any settings, remove the penalty when the user has no invalid addresses
674 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
677 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
679 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
680 my $depth = $ptype->org_depth;
681 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
682 $ctx_org = $patron->home_ou unless defined $ctx_org;
684 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
685 $penalty->usr($patron->id);
686 $penalty->org_unit($ctx_org);
687 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
689 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
704 "standing_penalties",
714 push @$fields, "home_ou" if $home_ou;
715 return new_flesh_user($id, $fields, $e );
723 # clone and clear stuff that would break the database
727 my $new_patron = $patron->clone;
729 $new_patron->clear_billing_address();
730 $new_patron->clear_mailing_address();
731 $new_patron->clear_addresses();
732 $new_patron->clear_card();
733 $new_patron->clear_cards();
734 $new_patron->clear_id();
735 $new_patron->clear_isnew();
736 $new_patron->clear_ischanged();
737 $new_patron->clear_isdeleted();
738 $new_patron->clear_stat_cat_entries();
739 $new_patron->clear_waiver_entries();
740 $new_patron->clear_permissions();
741 $new_patron->clear_standing_penalties();
752 return (undef, $e->die_event) unless
753 $e->allowed('CREATE_USER', $patron->home_ou);
755 my $ex = $e->search_actor_user(
756 {usrname => $patron->usrname}, {idlist => 1});
757 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
759 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
761 # do a dance to get the password hashed securely
762 my $saved_password = $patron->passwd;
764 $e->create_actor_user($patron) or return (undef, $e->die_event);
765 modify_migrated_user_password($e, $patron->id, $saved_password);
767 my $id = $patron->id; # added by CStoreEditor
769 $logger->info("Successfully created new user [$id] in DB");
770 return ($e->retrieve_actor_user($id), undef);
774 sub check_group_perm {
775 my( $e, $requestor, $patron ) = @_;
778 # first let's see if the requestor has
779 # priveleges to update this user in any way
780 if( ! $patron->isnew ) {
781 my $p = $e->retrieve_actor_user($patron->id);
783 # If we are the requestor (trying to update our own account)
784 # and we are not trying to change our profile, we're good
785 if( $p->id == $requestor->id and
786 $p->profile == $patron->profile ) {
791 $evt = group_perm_failed($e, $requestor, $p);
795 # They are allowed to edit this patron.. can they put the
796 # patron into the group requested?
797 $evt = group_perm_failed($e, $requestor, $patron);
803 sub group_perm_failed {
804 my( $e, $requestor, $patron ) = @_;
808 my $grpid = $patron->profile;
812 $logger->debug("user update looking for group perm for group $grpid");
813 $grp = $e->retrieve_permission_grp_tree($grpid);
815 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
817 $logger->info("user update checking perm $perm on user ".
818 $requestor->id." for update/create on user username=".$patron->usrname);
820 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
826 my( $e, $patron, $noperm) = @_;
828 $logger->info("Updating patron ".$patron->id." in DB");
833 return (undef, $e->die_event)
834 unless $e->allowed('UPDATE_USER', $patron->home_ou);
837 if(!$patron->ident_type) {
838 $patron->clear_ident_type;
839 $patron->clear_ident_value;
842 $evt = verify_last_xact($e, $patron);
843 return (undef, $evt) if $evt;
845 $e->update_actor_user($patron) or return (undef, $e->die_event);
847 # re-fetch the user to pick up the latest last_xact_id value
848 # to avoid collisions.
849 $patron = $e->retrieve_actor_user($patron->id);
854 sub verify_last_xact {
855 my( $e, $patron ) = @_;
856 return undef unless $patron->id and $patron->id > 0;
857 my $p = $e->retrieve_actor_user($patron->id);
858 my $xact = $p->last_xact_id;
859 return undef unless $xact;
860 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
861 return OpenILS::Event->new('XACT_COLLISION')
862 if $xact ne $patron->last_xact_id;
867 sub _check_dup_ident {
868 my( $session, $patron ) = @_;
870 return undef unless $patron->ident_value;
873 ident_type => $patron->ident_type,
874 ident_value => $patron->ident_value,
877 $logger->debug("patron update searching for dup ident values: " .
878 $patron->ident_type . ':' . $patron->ident_value);
880 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
882 my $dups = $session->request(
883 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
886 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
893 sub _add_update_addresses {
897 my $new_patron = shift;
901 my $current_id; # id of the address before creation
903 my $addresses = $patron->addresses();
905 for my $address (@$addresses) {
907 next unless ref $address;
908 $current_id = $address->id();
910 if( $patron->billing_address() and
911 $patron->billing_address() == $current_id ) {
912 $logger->info("setting billing addr to $current_id");
913 $new_patron->billing_address($address->id());
914 $new_patron->ischanged(1);
917 if( $patron->mailing_address() and
918 $patron->mailing_address() == $current_id ) {
919 $new_patron->mailing_address($address->id());
920 $logger->info("setting mailing addr to $current_id");
921 $new_patron->ischanged(1);
925 if($address->isnew()) {
927 $address->usr($new_patron->id());
929 ($address, $evt) = _add_address($e,$address);
930 return (undef, $evt) if $evt;
932 # we need to get the new id
933 if( $patron->billing_address() and
934 $patron->billing_address() == $current_id ) {
935 $new_patron->billing_address($address->id());
936 $logger->info("setting billing addr to $current_id");
937 $new_patron->ischanged(1);
940 if( $patron->mailing_address() and
941 $patron->mailing_address() == $current_id ) {
942 $new_patron->mailing_address($address->id());
943 $logger->info("setting mailing addr to $current_id");
944 $new_patron->ischanged(1);
947 } elsif($address->ischanged() ) {
949 ($address, $evt) = _update_address($e, $address);
950 return (undef, $evt) if $evt;
952 } elsif($address->isdeleted() ) {
954 if( $address->id() == $new_patron->mailing_address() ) {
955 $new_patron->clear_mailing_address();
956 ($new_patron, $evt) = _update_patron($e, $new_patron);
957 return (undef, $evt) if $evt;
960 if( $address->id() == $new_patron->billing_address() ) {
961 $new_patron->clear_billing_address();
962 ($new_patron, $evt) = _update_patron($e, $new_patron);
963 return (undef, $evt) if $evt;
966 $evt = _delete_address($e, $address);
967 return (undef, $evt) if $evt;
971 return ( $new_patron, undef );
975 # adds an address to the db and returns the address with new id
977 my($e, $address) = @_;
978 $address->clear_id();
980 $logger->info("Creating new address at street ".$address->street1);
982 # put the address into the database
983 $e->create_actor_user_address($address) or return (undef, $e->die_event);
984 return ($address, undef);
988 sub _update_address {
989 my( $e, $address ) = @_;
991 $logger->info("Updating address ".$address->id." in the DB");
993 $e->update_actor_user_address($address) or return (undef, $e->die_event);
995 return ($address, undef);
1000 sub _add_update_cards {
1004 my $new_patron = shift;
1008 my $virtual_id; #id of the card before creation
1010 my $card_changed = 0;
1011 my $cards = $patron->cards();
1012 for my $card (@$cards) {
1014 $card->usr($new_patron->id());
1016 if(ref($card) and $card->isnew()) {
1018 $virtual_id = $card->id();
1019 ( $card, $evt ) = _add_card($e, $card);
1020 return (undef, $evt) if $evt;
1022 #if(ref($patron->card)) { $patron->card($patron->card->id); }
1023 if($patron->card() == $virtual_id) {
1024 $new_patron->card($card->id());
1025 $new_patron->ischanged(1);
1029 } elsif( ref($card) and $card->ischanged() ) {
1030 $evt = _update_card($e, $card);
1031 return (undef, $evt) if $evt;
1036 $U->create_events_for_hook('au.barcode_changed', $new_patron, $e->requestor->ws_ou)
1039 return ( $new_patron, undef );
1043 # adds an card to the db and returns the card with new id
1045 my( $e, $card ) = @_;
1048 $logger->info("Adding new patron card ".$card->barcode);
1050 $e->create_actor_card($card) or return (undef, $e->die_event);
1052 return ( $card, undef );
1056 # returns event on error. returns undef otherwise
1058 my( $e, $card ) = @_;
1059 $logger->info("Updating patron card ".$card->id);
1061 $e->update_actor_card($card) or return $e->die_event;
1066 sub _add_update_waiver_entries {
1069 my $new_patron = shift;
1072 my $waiver_entries = $patron->waiver_entries();
1073 for my $waiver (@$waiver_entries) {
1074 next unless ref $waiver;
1075 $waiver->usr($new_patron->id());
1076 if ($waiver->isnew()) {
1077 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
1078 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
1079 $logger->info("Adding new patron waiver entry");
1080 $waiver->clear_id();
1081 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1082 } elsif ($waiver->ischanged()) {
1083 $logger->info("Updating patron waiver entry " . $waiver->id);
1084 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1085 } elsif ($waiver->isdeleted()) {
1086 $logger->info("Deleting patron waiver entry " . $waiver->id);
1087 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1090 return ($new_patron, undef);
1094 # returns event on error. returns undef otherwise
1095 sub _delete_address {
1096 my( $e, $address ) = @_;
1098 $logger->info("Deleting address ".$address->id." from DB");
1100 $e->delete_actor_user_address($address) or return $e->die_event;
1106 sub _add_survey_responses {
1107 my ($e, $patron, $new_patron) = @_;
1109 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1111 my $responses = $patron->survey_responses;
1115 $_->usr($new_patron->id) for (@$responses);
1117 my $evt = $U->simplereq( "open-ils.circ",
1118 "open-ils.circ.survey.submit.user_id", $responses );
1120 return (undef, $evt) if defined($U->event_code($evt));
1124 return ( $new_patron, undef );
1127 sub _clear_badcontact_penalties {
1128 my ($e, $old_patron, $new_patron) = @_;
1130 return ($new_patron, undef) unless $old_patron;
1132 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1134 # This ignores whether the caller of update_patron has any permission
1135 # to remove penalties, but these penalties no longer make sense
1136 # if an email address field (for example) is changed (and the caller must
1137 # have perms to do *that*) so there's no reason not to clear the penalties.
1139 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1141 "+csp" => {"name" => [values(%$PNM)]},
1142 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1144 "join" => {"csp" => {}},
1146 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1148 ]) or return (undef, $e->die_event);
1150 return ($new_patron, undef) unless @$bad_contact_penalties;
1152 my @penalties_to_clear;
1153 my ($field, $penalty_name);
1155 # For each field that might have an associated bad contact penalty,
1156 # check for such penalties and add them to the to-clear list if that
1157 # field has changed.
1158 while (($field, $penalty_name) = each(%$PNM)) {
1159 if ($old_patron->$field ne $new_patron->$field) {
1160 push @penalties_to_clear, grep {
1161 $_->standing_penalty->name eq $penalty_name
1162 } @$bad_contact_penalties;
1166 foreach (@penalties_to_clear) {
1167 # Note that this "archives" penalties, in the terminology of the staff
1168 # client, instead of just deleting them. This may assist reporting,
1169 # or preserving old contact information when it is still potentially
1171 $_->standing_penalty($_->standing_penalty->id); # deflesh
1172 $_->stop_date('now');
1173 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1176 return ($new_patron, undef);
1180 sub _create_stat_maps {
1182 my($e, $patron, $new_patron) = @_;
1184 my $maps = $patron->stat_cat_entries();
1186 for my $map (@$maps) {
1188 my $method = "update_actor_stat_cat_entry_user_map";
1190 if ($map->isdeleted()) {
1191 $method = "delete_actor_stat_cat_entry_user_map";
1193 } elsif ($map->isnew()) {
1194 $method = "create_actor_stat_cat_entry_user_map";
1199 $map->target_usr($new_patron->id);
1201 $logger->info("Updating stat entry with method $method and map $map");
1203 $e->$method($map) or return (undef, $e->die_event);
1206 return ($new_patron, undef);
1209 sub _create_perm_maps {
1211 my($e, $patron, $new_patron) = @_;
1213 my $maps = $patron->permissions;
1215 for my $map (@$maps) {
1217 my $method = "update_permission_usr_perm_map";
1218 if ($map->isdeleted()) {
1219 $method = "delete_permission_usr_perm_map";
1220 } elsif ($map->isnew()) {
1221 $method = "create_permission_usr_perm_map";
1225 $map->usr($new_patron->id);
1227 $logger->info( "Updating permissions with method $method and map $map" );
1229 $e->$method($map) or return (undef, $e->die_event);
1232 return ($new_patron, undef);
1236 __PACKAGE__->register_method(
1237 method => "set_user_work_ous",
1238 api_name => "open-ils.actor.user.work_ous.update",
1241 sub set_user_work_ous {
1247 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1248 return $evt if $evt;
1250 my $session = $apputils->start_db_session();
1251 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1253 for my $map (@$maps) {
1255 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1256 if ($map->isdeleted()) {
1257 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1258 } elsif ($map->isnew()) {
1259 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1263 #warn( "Updating permissions with method $method and session $ses and map $map" );
1264 $logger->info( "Updating work_ou map with method $method and map $map" );
1266 my $stat = $session->request($method, $map)->gather(1);
1267 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1271 $apputils->commit_db_session($session);
1273 return scalar(@$maps);
1277 __PACKAGE__->register_method(
1278 method => "set_user_perms",
1279 api_name => "open-ils.actor.user.permissions.update",
1282 sub set_user_perms {
1288 my $session = $apputils->start_db_session();
1290 my( $user_obj, $evt ) = $U->checkses($ses);
1291 return $evt if $evt;
1292 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1294 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1297 $all = 1 if ($U->is_true($user_obj->super_user()));
1298 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1300 for my $map (@$maps) {
1302 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1303 if ($map->isdeleted()) {
1304 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1305 } elsif ($map->isnew()) {
1306 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1310 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1311 #warn( "Updating permissions with method $method and session $ses and map $map" );
1312 $logger->info( "Updating permissions with method $method and map $map" );
1314 my $stat = $session->request($method, $map)->gather(1);
1315 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1319 $apputils->commit_db_session($session);
1321 return scalar(@$maps);
1325 __PACKAGE__->register_method(
1326 method => "user_retrieve_by_barcode",
1328 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1330 sub user_retrieve_by_barcode {
1331 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1333 my $e = new_editor(authtoken => $auth);
1334 return $e->event unless $e->checkauth;
1336 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1337 or return $e->event;
1339 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1340 return $e->event unless $e->allowed(
1341 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1348 __PACKAGE__->register_method(
1349 method => "get_user_by_id",
1351 api_name => "open-ils.actor.user.retrieve",
1354 sub get_user_by_id {
1355 my ($self, $client, $auth, $id) = @_;
1356 my $e = new_editor(authtoken=>$auth);
1357 return $e->event unless $e->checkauth;
1358 my $user = $e->retrieve_actor_user($id) or return $e->event;
1359 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1364 __PACKAGE__->register_method(
1365 method => "get_org_types",
1366 api_name => "open-ils.actor.org_types.retrieve",
1369 return $U->get_org_types();
1373 __PACKAGE__->register_method(
1374 method => "get_user_ident_types",
1375 api_name => "open-ils.actor.user.ident_types.retrieve",
1378 sub get_user_ident_types {
1379 return $ident_types if $ident_types;
1380 return $ident_types =
1381 new_editor()->retrieve_all_config_identification_type();
1385 __PACKAGE__->register_method(
1386 method => "get_org_unit",
1387 api_name => "open-ils.actor.org_unit.retrieve",
1391 my( $self, $client, $user_session, $org_id ) = @_;
1392 my $e = new_editor(authtoken => $user_session);
1394 return $e->event unless $e->checkauth;
1395 $org_id = $e->requestor->ws_ou;
1397 my $o = $e->retrieve_actor_org_unit($org_id)
1398 or return $e->event;
1402 __PACKAGE__->register_method(
1403 method => "search_org_unit",
1404 api_name => "open-ils.actor.org_unit_list.search",
1407 sub search_org_unit {
1409 my( $self, $client, $field, $value ) = @_;
1411 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1413 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1414 { $field => $value } );
1420 # build the org tree
1422 __PACKAGE__->register_method(
1423 method => "get_org_tree",
1424 api_name => "open-ils.actor.org_tree.retrieve",
1426 note => "Returns the entire org tree structure",
1432 return $U->get_org_tree($client->session->session_locale);
1436 __PACKAGE__->register_method(
1437 method => "get_org_descendants",
1438 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1441 # depth is optional. org_unit is the id
1442 sub get_org_descendants {
1443 my( $self, $client, $org_unit, $depth ) = @_;
1445 if(ref $org_unit eq 'ARRAY') {
1448 for my $i (0..scalar(@$org_unit)-1) {
1449 my $list = $U->simple_scalar_request(
1451 "open-ils.storage.actor.org_unit.descendants.atomic",
1452 $org_unit->[$i], $depth->[$i] );
1453 push(@trees, $U->build_org_tree($list));
1458 my $orglist = $apputils->simple_scalar_request(
1460 "open-ils.storage.actor.org_unit.descendants.atomic",
1461 $org_unit, $depth );
1462 return $U->build_org_tree($orglist);
1467 __PACKAGE__->register_method(
1468 method => "get_org_ancestors",
1469 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1472 # depth is optional. org_unit is the id
1473 sub get_org_ancestors {
1474 my( $self, $client, $org_unit, $depth ) = @_;
1475 my $orglist = $apputils->simple_scalar_request(
1477 "open-ils.storage.actor.org_unit.ancestors.atomic",
1478 $org_unit, $depth );
1479 return $U->build_org_tree($orglist);
1483 __PACKAGE__->register_method(
1484 method => "get_standings",
1485 api_name => "open-ils.actor.standings.retrieve"
1490 return $user_standings if $user_standings;
1491 return $user_standings =
1492 $apputils->simple_scalar_request(
1494 "open-ils.cstore.direct.config.standing.search.atomic",
1495 { id => { "!=" => undef } }
1500 __PACKAGE__->register_method(
1501 method => "get_my_org_path",
1502 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1505 sub get_my_org_path {
1506 my( $self, $client, $auth, $org_id ) = @_;
1507 my $e = new_editor(authtoken=>$auth);
1508 return $e->event unless $e->checkauth;
1509 $org_id = $e->requestor->ws_ou unless defined $org_id;
1511 return $apputils->simple_scalar_request(
1513 "open-ils.storage.actor.org_unit.full_path.atomic",
1517 __PACKAGE__->register_method(
1518 method => "retrieve_coordinates",
1519 api_name => "open-ils.actor.geo.retrieve_coordinates",
1522 {desc => 'Authentication token', type => 'string' },
1523 {type => 'number', desc => 'Context Organizational Unit'},
1524 {type => 'string', desc => 'Address to look-up as a text string'}
1526 return => { desc => 'Hash/object containing latitude and longitude for the provided address.'}
1530 sub retrieve_coordinates {
1531 my( $self, $client, $auth, $org_id, $addr_string ) = @_;
1532 my $e = new_editor(authtoken=>$auth);
1533 return $e->event unless $e->checkauth;
1534 $org_id = $e->requestor->ws_ou unless defined $org_id;
1536 return $apputils->simple_scalar_request(
1538 "open-ils.geo.retrieve_coordinates",
1539 $org_id, $addr_string );
1542 __PACKAGE__->register_method(
1543 method => "get_my_org_ancestor_at_depth",
1544 api_name => "open-ils.actor.org_unit.ancestor_at_depth.retrieve"
1547 sub get_my_org_ancestor_at_depth {
1548 my( $self, $client, $auth, $org_id, $depth ) = @_;
1549 my $e = new_editor(authtoken=>$auth);
1550 return $e->event unless $e->checkauth;
1551 $org_id = $e->requestor->ws_ou unless defined $org_id;
1553 return $apputils->org_unit_ancestor_at_depth( $org_id, $depth );
1556 __PACKAGE__->register_method(
1557 method => "patron_adv_search",
1558 api_name => "open-ils.actor.patron.search.advanced"
1561 __PACKAGE__->register_method(
1562 method => "patron_adv_search",
1563 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1565 # Flush the response stream at most 5 patrons in for UI responsiveness.
1566 max_bundle_count => 5,
1568 desc => q/Returns a stream of fleshed user objects instead of
1569 a pile of identifiers/
1573 sub patron_adv_search {
1574 my( $self, $client, $auth, $search_hash, $search_limit,
1575 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1577 # API params sanity checks.
1578 # Exit early with empty result if no filter exists.
1579 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1580 my $fleshed = ($self->api_name =~ /fleshed/);
1581 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1583 for my $key (keys %$search_hash) {
1584 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1588 return ($fleshed ? undef : []) unless $search_ok;
1590 my $e = new_editor(authtoken=>$auth);
1591 return $e->event unless $e->checkauth;
1592 return $e->event unless $e->allowed('VIEW_USER');
1594 # depth boundary outside of which patrons must opt-in, default to 0
1595 my $opt_boundary = 0;
1596 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1598 if (not defined $search_ou) {
1599 my $depth = $U->ou_ancestor_setting_value(
1600 $e->requestor->ws_ou,
1601 'circ.patron_edit.duplicate_patron_check_depth'
1604 if (defined $depth) {
1605 $search_ou = $U->org_unit_ancestor_at_depth(
1606 $e->requestor->ws_ou, $depth
1611 my $ids = $U->storagereq(
1612 "open-ils.storage.actor.user.crazy_search", $search_hash,
1613 $search_limit, $search_sort, $include_inactive,
1614 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1616 return $ids unless $self->api_name =~ /fleshed/;
1618 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1624 # A migrated (main) password has the form:
1625 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1626 sub modify_migrated_user_password {
1627 my ($e, $user_id, $passwd) = @_;
1629 # new password gets a new salt
1630 my $new_salt = $e->json_query({
1631 from => ['actor.create_salt', 'main']})->[0];
1632 $new_salt = $new_salt->{'actor.create_salt'};
1639 md5_hex($new_salt . md5_hex($passwd)),
1647 __PACKAGE__->register_method(
1648 method => "update_passwd",
1649 api_name => "open-ils.actor.user.password.update",
1651 desc => "Update the operator's password",
1653 { desc => 'Authentication token', type => 'string' },
1654 { desc => 'New password', type => 'string' },
1655 { desc => 'Current password', type => 'string' }
1657 return => {desc => '1 on success, Event on error or incorrect current password'}
1661 __PACKAGE__->register_method(
1662 method => "update_passwd",
1663 api_name => "open-ils.actor.user.username.update",
1665 desc => "Update the operator's username",
1667 { desc => 'Authentication token', type => 'string' },
1668 { desc => 'New username', type => 'string' },
1669 { desc => 'Current password', type => 'string' }
1671 return => {desc => '1 on success, Event on error or incorrect current password'}
1675 __PACKAGE__->register_method(
1676 method => "update_passwd",
1677 api_name => "open-ils.actor.user.email.update",
1679 desc => "Update the operator's email address",
1681 { desc => 'Authentication token', type => 'string' },
1682 { desc => 'New email address', type => 'string' },
1683 { desc => 'Current password', type => 'string' }
1685 return => {desc => '1 on success, Event on error or incorrect current password'}
1689 __PACKAGE__->register_method(
1690 method => "update_passwd",
1691 api_name => "open-ils.actor.user.locale.update",
1693 desc => "Update the operator's i18n locale",
1695 { desc => 'Authentication token', type => 'string' },
1696 { desc => 'New locale', type => 'string' },
1697 { desc => 'Current password', type => 'string' }
1699 return => {desc => '1 on success, Event on error or incorrect current password'}
1704 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1705 my $e = new_editor(xact=>1, authtoken=>$auth);
1706 return $e->die_event unless $e->checkauth;
1708 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1709 or return $e->die_event;
1710 my $api = $self->api_name;
1712 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1714 return new OpenILS::Event('INCORRECT_PASSWORD');
1718 if( $api =~ /password/o ) {
1719 # NOTE: with access to the plain text password we could crypt
1720 # the password without the extra MD5 pre-hashing. Other changes
1721 # would be required. Noting here for future reference.
1722 modify_migrated_user_password($e, $db_user->id, $new_val);
1723 $db_user->passwd('');
1727 # if we don't clear the password, the user will be updated with
1728 # a hashed version of the hashed version of their password
1729 $db_user->clear_passwd;
1731 if( $api =~ /username/o ) {
1733 # make sure no one else has this username
1734 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1737 return new OpenILS::Event('USERNAME_EXISTS');
1739 $db_user->usrname($new_val);
1742 } elsif( $api =~ /email/o ) {
1743 $db_user->email($new_val);
1746 } elsif( $api =~ /locale/o ) {
1747 $db_user->locale($new_val);
1752 $e->update_actor_user($db_user) or return $e->die_event;
1755 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1758 # update the cached user to pick up these changes
1759 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1765 __PACKAGE__->register_method(
1766 method => "check_user_perms",
1767 api_name => "open-ils.actor.user.perm.check",
1768 notes => <<" NOTES");
1769 Takes a login session, user id, an org id, and an array of perm type strings. For each
1770 perm type, if the user does *not* have the given permission it is added
1771 to a list which is returned from the method. If all permissions
1772 are allowed, an empty list is returned
1773 if the logged in user does not match 'user_id', then the logged in user must
1774 have VIEW_PERMISSION priveleges.
1777 sub check_user_perms {
1778 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1780 my( $staff, $evt ) = $apputils->checkses($login_session);
1781 return $evt if $evt;
1783 if($staff->id ne $user_id) {
1784 if( $evt = $apputils->check_perms(
1785 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1791 for my $perm (@$perm_types) {
1792 if($apputils->check_perms($user_id, $org_id, $perm)) {
1793 push @not_allowed, $perm;
1797 return \@not_allowed
1800 __PACKAGE__->register_method(
1801 method => "check_user_perms2",
1802 api_name => "open-ils.actor.user.perm.check.multi_org",
1804 Checks the permissions on a list of perms and orgs for a user
1805 @param authtoken The login session key
1806 @param user_id The id of the user to check
1807 @param orgs The array of org ids
1808 @param perms The array of permission names
1809 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1810 if the logged in user does not match 'user_id', then the logged in user must
1811 have VIEW_PERMISSION priveleges.
1814 sub check_user_perms2 {
1815 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1817 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1818 $authtoken, $user_id, 'VIEW_PERMISSION' );
1819 return $evt if $evt;
1822 for my $org (@$orgs) {
1823 for my $perm (@$perms) {
1824 if($apputils->check_perms($user_id, $org, $perm)) {
1825 push @not_allowed, [ $org, $perm ];
1830 return \@not_allowed
1834 __PACKAGE__->register_method(
1835 method => 'check_user_perms3',
1836 api_name => 'open-ils.actor.user.perm.highest_org',
1838 Returns the highest org unit id at which a user has a given permission
1839 If the requestor does not match the target user, the requestor must have
1840 'VIEW_PERMISSION' rights at the home org unit of the target user
1841 @param authtoken The login session key
1842 @param userid The id of the user in question
1843 @param perm The permission to check
1844 @return The org unit highest in the org tree within which the user has
1845 the requested permission
1848 sub check_user_perms3 {
1849 my($self, $client, $authtoken, $user_id, $perm) = @_;
1850 my $e = new_editor(authtoken=>$authtoken);
1851 return $e->event unless $e->checkauth;
1853 my $tree = $U->get_org_tree();
1855 unless($e->requestor->id == $user_id) {
1856 my $user = $e->retrieve_actor_user($user_id)
1857 or return $e->event;
1858 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1859 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1862 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1865 __PACKAGE__->register_method(
1866 method => 'user_has_work_perm_at',
1867 api_name => 'open-ils.actor.user.has_work_perm_at',
1871 Returns a set of org unit IDs which represent the highest orgs in
1872 the org tree where the user has the requested permission. The
1873 purpose of this method is to return the smallest set of org units
1874 which represent the full expanse of the user's ability to perform
1875 the requested action. The user whose perms this method should
1876 check is implied by the authtoken. /,
1878 {desc => 'authtoken', type => 'string'},
1879 {desc => 'permission name', type => 'string'},
1880 {desc => q/user id, optional. If present, check perms for
1881 this user instead of the logged in user/, type => 'number'},
1883 return => {desc => 'An array of org IDs'}
1887 sub user_has_work_perm_at {
1888 my($self, $conn, $auth, $perm, $user_id) = @_;
1889 my $e = new_editor(authtoken=>$auth);
1890 return $e->event unless $e->checkauth;
1891 if(defined $user_id) {
1892 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1893 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1895 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1898 __PACKAGE__->register_method(
1899 method => 'user_has_work_perm_at_batch',
1900 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1904 sub user_has_work_perm_at_batch {
1905 my($self, $conn, $auth, $perms, $user_id) = @_;
1906 my $e = new_editor(authtoken=>$auth);
1907 return $e->event unless $e->checkauth;
1908 if(defined $user_id) {
1909 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1910 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1913 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1919 __PACKAGE__->register_method(
1920 method => 'check_user_perms4',
1921 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1923 Returns the highest org unit id at which a user has a given permission
1924 If the requestor does not match the target user, the requestor must have
1925 'VIEW_PERMISSION' rights at the home org unit of the target user
1926 @param authtoken The login session key
1927 @param userid The id of the user in question
1928 @param perms An array of perm names to check
1929 @return An array of orgId's representing the org unit
1930 highest in the org tree within which the user has the requested permission
1931 The arrah of orgId's has matches the order of the perms array
1934 sub check_user_perms4 {
1935 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1937 my( $staff, $target, $org, $evt );
1939 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1940 $authtoken, $userid, 'VIEW_PERMISSION' );
1941 return $evt if $evt;
1944 return [] unless ref($perms);
1945 my $tree = $U->get_org_tree();
1947 for my $p (@$perms) {
1948 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1954 __PACKAGE__->register_method(
1955 method => "user_fines_summary",
1956 api_name => "open-ils.actor.user.fines.summary",
1959 desc => 'Returns a short summary of the users total open fines, ' .
1960 'excluding voided fines Params are login_session, user_id' ,
1962 {desc => 'Authentication token', type => 'string'},
1963 {desc => 'User ID', type => 'string'} # number?
1966 desc => "a 'mous' object, event on error",
1971 sub user_fines_summary {
1972 my( $self, $client, $auth, $user_id ) = @_;
1974 my $e = new_editor(authtoken=>$auth);
1975 return $e->event unless $e->checkauth;
1977 if( $user_id ne $e->requestor->id ) {
1978 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1979 return $e->event unless
1980 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1983 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1987 __PACKAGE__->register_method(
1988 method => "user_opac_vitals",
1989 api_name => "open-ils.actor.user.opac.vital_stats",
1993 desc => 'Returns a short summary of the users vital stats, including ' .
1994 'identification information, accumulated balance, number of holds, ' .
1995 'and current open circulation stats' ,
1997 {desc => 'Authentication token', type => 'string'},
1998 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
2001 desc => "An object with four properties: user, fines, checkouts and holds."
2006 sub user_opac_vitals {
2007 my( $self, $client, $auth, $user_id ) = @_;
2009 my $e = new_editor(authtoken=>$auth);
2010 return $e->event unless $e->checkauth;
2012 $user_id ||= $e->requestor->id;
2014 my $user = $e->retrieve_actor_user( $user_id );
2017 ->method_lookup('open-ils.actor.user.fines.summary')
2018 ->run($auth => $user_id);
2019 return $fines if (defined($U->event_code($fines)));
2022 $fines = new Fieldmapper::money::open_user_summary ();
2023 $fines->balance_owed(0.00);
2024 $fines->total_owed(0.00);
2025 $fines->total_paid(0.00);
2026 $fines->usr($user_id);
2030 ->method_lookup('open-ils.actor.user.hold_requests.count')
2031 ->run($auth => $user_id);
2032 return $holds if (defined($U->event_code($holds)));
2035 ->method_lookup('open-ils.actor.user.checked_out.count')
2036 ->run($auth => $user_id);
2037 return $out if (defined($U->event_code($out)));
2039 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
2041 my $unread_msgs = $e->search_actor_usr_message([
2042 {usr => $user_id, read_date => undef, deleted => 'f',
2043 'pub' => 't', # this is for the unread message count in the opac
2044 #'-or' => [ # Hiding Archived messages are for staff UI, not this
2045 # {stop_date => undef},
2046 # {stop_date => {'>' => 'now'}}
2054 first_given_name => $user->first_given_name,
2055 second_given_name => $user->second_given_name,
2056 family_name => $user->family_name,
2057 alias => $user->alias,
2058 usrname => $user->usrname
2060 fines => $fines->to_bare_hash,
2063 messages => { unread => scalar(@$unread_msgs) }
2068 ##### a small consolidation of related method registrations
2069 my $common_params = [
2070 { desc => 'Authentication token', type => 'string' },
2071 { desc => 'User ID', type => 'string' },
2072 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
2073 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
2076 'open-ils.actor.user.transactions' => '',
2077 'open-ils.actor.user.transactions.fleshed' => '',
2078 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
2079 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
2080 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
2081 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
2084 foreach (keys %methods) {
2086 method => "user_transactions",
2089 desc => 'For a given user, retrieve a list of '
2090 . (/\.fleshed/ ? 'fleshed ' : '')
2091 . 'transactions' . $methods{$_}
2092 . ' optionally limited to transactions of a given type.',
2093 params => $common_params,
2095 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
2096 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
2100 $args{authoritative} = 1;
2101 __PACKAGE__->register_method(%args);
2104 # Now for the counts
2106 'open-ils.actor.user.transactions.count' => '',
2107 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
2108 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
2111 foreach (keys %methods) {
2113 method => "user_transactions",
2116 desc => 'For a given user, retrieve a count of open '
2117 . 'transactions' . $methods{$_}
2118 . ' optionally limited to transactions of a given type.',
2119 params => $common_params,
2120 return => { desc => "Integer count of transactions, or event on error" }
2123 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2124 __PACKAGE__->register_method(%args);
2127 __PACKAGE__->register_method(
2128 method => "user_transactions",
2129 api_name => "open-ils.actor.user.transactions.have_balance.total",
2132 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2133 . ' optionally limited to transactions of a given type.',
2134 params => $common_params,
2135 return => { desc => "Decimal balance value, or event on error" }
2140 sub user_transactions {
2141 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2144 my $e = new_editor(authtoken => $auth);
2145 return $e->event unless $e->checkauth;
2147 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2149 return $e->event unless
2150 $e->requestor->id == $user_id or
2151 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2153 my $api = $self->api_name();
2155 my $filter = ($api =~ /have_balance/o) ?
2156 { 'balance_owed' => { '<>' => 0 } }:
2157 { 'total_owed' => { '>' => 0 } };
2159 my $method = 'open-ils.actor.user.transactions.history.still_open';
2160 $method = "$method.authoritative" if $api =~ /authoritative/;
2161 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2163 if($api =~ /total/o) {
2165 $total += $_->balance_owed for @$trans;
2169 ($api =~ /count/o ) and return scalar @$trans;
2170 ($api !~ /fleshed/o) and return $trans;
2173 for my $t (@$trans) {
2175 if( $t->xact_type ne 'circulation' ) {
2176 push @resp, {transaction => $t};
2180 my $circ_data = flesh_circ($e, $t->id);
2181 push @resp, {transaction => $t, %$circ_data};
2188 __PACKAGE__->register_method(
2189 method => "user_transaction_retrieve",
2190 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2193 notes => "Returns a fleshed transaction record"
2196 __PACKAGE__->register_method(
2197 method => "user_transaction_retrieve",
2198 api_name => "open-ils.actor.user.transaction.retrieve",
2201 notes => "Returns a transaction record"
2204 sub user_transaction_retrieve {
2205 my($self, $client, $auth, $bill_id) = @_;
2207 my $e = new_editor(authtoken => $auth);
2208 return $e->event unless $e->checkauth;
2210 my $trans = $e->retrieve_money_billable_transaction_summary(
2211 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2213 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2215 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2217 return $trans unless $self->api_name =~ /flesh/;
2218 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2220 my $circ_data = flesh_circ($e, $trans->id, 1);
2222 return {transaction => $trans, %$circ_data};
2227 my $circ_id = shift;
2228 my $flesh_copy = shift;
2230 my $circ = $e->retrieve_action_circulation([
2234 circ => ['target_copy'],
2235 acp => ['call_number'],
2242 my $copy = $circ->target_copy;
2244 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2245 $mods = new Fieldmapper::metabib::virtual_record;
2246 $mods->doc_id(OILS_PRECAT_RECORD);
2247 $mods->title($copy->dummy_title);
2248 $mods->author($copy->dummy_author);
2251 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2255 $circ->target_copy($circ->target_copy->id);
2256 $copy->call_number($copy->call_number->id);
2258 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2262 __PACKAGE__->register_method(
2263 method => "hold_request_count",
2264 api_name => "open-ils.actor.user.hold_requests.count",
2268 Returns hold ready vs. total counts.
2269 If a context org unit is provided, a third value
2270 is returned with key 'behind_desk', which reports
2271 how many holds are ready at the pickup library
2272 with the behind_desk flag set to true.
2276 sub hold_request_count {
2277 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2278 my $e = new_editor(authtoken => $authtoken);
2279 return $e->event unless $e->checkauth;
2281 $user_id = $e->requestor->id unless defined $user_id;
2283 if($e->requestor->id ne $user_id) {
2284 my $user = $e->retrieve_actor_user($user_id);
2285 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2288 my $holds = $e->json_query({
2289 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2293 fulfillment_time => {"=" => undef },
2294 cancel_time => undef,
2299 $_->{current_shelf_lib} and # avoid undef warnings
2300 $_->{pickup_lib} eq $_->{current_shelf_lib}
2304 total => scalar(@$holds),
2305 ready => int(scalar(@ready))
2309 # count of holds ready at pickup lib with behind_desk true.
2310 $resp->{behind_desk} = int(scalar(
2312 $_->{pickup_lib} == $ctx_org and
2313 $U->is_true($_->{behind_desk})
2321 __PACKAGE__->register_method(
2322 method => "checked_out",
2323 api_name => "open-ils.actor.user.checked_out",
2327 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2328 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2329 . "(i.e., outstanding balance or some other pending action on the circ). "
2330 . "The .count method also includes a 'total' field which sums all open circs.",
2332 { desc => 'Authentication Token', type => 'string'},
2333 { desc => 'User ID', type => 'string'},
2336 desc => 'Returns event on error, or an object with ID lists, like: '
2337 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2342 __PACKAGE__->register_method(
2343 method => "checked_out",
2344 api_name => "open-ils.actor.user.checked_out.count",
2347 signature => q/@see open-ils.actor.user.checked_out/
2351 my( $self, $conn, $auth, $userid ) = @_;
2353 my $e = new_editor(authtoken=>$auth);
2354 return $e->event unless $e->checkauth;
2356 if( $userid ne $e->requestor->id ) {
2357 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2358 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2360 # see if there is a friend link allowing circ.view perms
2361 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2362 $e, $userid, $e->requestor->id, 'circ.view');
2363 return $e->event unless $allowed;
2367 my $count = $self->api_name =~ /count/;
2368 return _checked_out( $count, $e, $userid );
2372 my( $iscount, $e, $userid ) = @_;
2378 claims_returned => [],
2381 my $meth = 'retrieve_action_open_circ_';
2389 claims_returned => 0,
2396 my $data = $e->$meth($userid);
2400 $result{$_} += $data->$_() for (keys %result);
2401 $result{total} += $data->$_() for (keys %result);
2403 for my $k (keys %result) {
2404 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2414 __PACKAGE__->register_method(
2415 method => "checked_in_with_fines",
2416 api_name => "open-ils.actor.user.checked_in_with_fines",
2419 signature => q/@see open-ils.actor.user.checked_out/
2422 sub checked_in_with_fines {
2423 my( $self, $conn, $auth, $userid ) = @_;
2425 my $e = new_editor(authtoken=>$auth);
2426 return $e->event unless $e->checkauth;
2428 if( $userid ne $e->requestor->id ) {
2429 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2432 # money is owed on these items and they are checked in
2433 my $open = $e->search_action_circulation(
2436 xact_finish => undef,
2437 checkin_time => { "!=" => undef },
2442 my( @lost, @cr, @lo );
2443 for my $c (@$open) {
2444 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2445 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2446 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2451 claims_returned => \@cr,
2452 long_overdue => \@lo
2458 my ($api, $desc, $auth) = @_;
2459 $desc = $desc ? (" " . $desc) : '';
2460 my $ids = ($api =~ /ids$/) ? 1 : 0;
2463 method => "user_transaction_history",
2464 api_name => "open-ils.actor.user.transactions.$api",
2466 desc => "For a given User ID, returns a list of billable transaction" .
2467 ($ids ? " id" : '') .
2468 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2469 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2471 {desc => 'Authentication token', type => 'string'},
2472 {desc => 'User ID', type => 'number'},
2473 {desc => 'Transaction type (optional)', type => 'number'},
2474 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2477 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2481 $auth and push @sig, (authoritative => 1);
2485 my %auth_hist_methods = (
2487 'history.have_charge' => 'that have an initial charge',
2488 'history.still_open' => 'that are not finished',
2489 'history.have_balance' => 'that have a balance',
2490 'history.have_bill' => 'that have billings',
2491 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2492 'history.have_payment' => 'that have at least 1 payment',
2495 foreach (keys %auth_hist_methods) {
2496 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2497 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2498 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2501 sub user_transaction_history {
2502 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2506 my $e = new_editor(authtoken=>$auth);
2507 return $e->die_event unless $e->checkauth;
2509 if ($e->requestor->id ne $userid) {
2510 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2513 my $api = $self->api_name;
2514 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2516 if(defined($type)) {
2517 $filter->{'xact_type'} = $type;
2520 if($api =~ /have_bill_or_payment/o) {
2522 # transactions that have a non-zero sum across all billings or at least 1 payment
2523 $filter->{'-or'} = {
2524 'balance_owed' => { '<>' => 0 },
2525 'last_payment_ts' => { '<>' => undef }
2528 } elsif($api =~ /have_payment/) {
2530 $filter->{last_payment_ts} ||= {'<>' => undef};
2532 } elsif( $api =~ /have_balance/o) {
2534 # transactions that have a non-zero overall balance
2535 $filter->{'balance_owed'} = { '<>' => 0 };
2537 } elsif( $api =~ /have_charge/o) {
2539 # transactions that have at least 1 billing, regardless of whether it was voided
2540 $filter->{'last_billing_ts'} = { '<>' => undef };
2542 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2544 # transactions that have non-zero sum across all billings. This will exclude
2545 # xacts where all billings have been voided
2546 $filter->{'total_owed'} = { '<>' => 0 };
2549 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2550 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2551 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2553 my $mbts = $e->search_money_billable_transaction_summary(
2554 [ { usr => $userid, @xact_finish, %$filter },
2559 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2560 return $mbts unless $api =~ /fleshed/;
2563 for my $t (@$mbts) {
2565 if( $t->xact_type ne 'circulation' ) {
2566 push @resp, {transaction => $t};
2570 my $circ_data = flesh_circ($e, $t->id);
2571 push @resp, {transaction => $t, %$circ_data};
2579 __PACKAGE__->register_method(
2580 method => "user_perms",
2581 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2583 notes => "Returns a list of permissions"
2587 my( $self, $client, $authtoken, $user ) = @_;
2589 my( $staff, $evt ) = $apputils->checkses($authtoken);
2590 return $evt if $evt;
2592 $user ||= $staff->id;
2594 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2598 return $apputils->simple_scalar_request(
2600 "open-ils.storage.permission.user_perms.atomic",
2604 __PACKAGE__->register_method(
2605 method => "retrieve_perms",
2606 api_name => "open-ils.actor.permissions.retrieve",
2607 notes => "Returns a list of permissions"
2609 sub retrieve_perms {
2610 my( $self, $client ) = @_;
2611 return $apputils->simple_scalar_request(
2613 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2614 { id => { '!=' => undef } }
2618 __PACKAGE__->register_method(
2619 method => "retrieve_groups",
2620 api_name => "open-ils.actor.groups.retrieve",
2621 notes => "Returns a list of user groups"
2623 sub retrieve_groups {
2624 my( $self, $client ) = @_;
2625 return new_editor()->retrieve_all_permission_grp_tree();
2628 __PACKAGE__->register_method(
2629 method => "retrieve_org_address",
2630 api_name => "open-ils.actor.org_unit.address.retrieve",
2631 notes => <<' NOTES');
2632 Returns an org_unit address by ID
2633 @param An org_address ID
2635 sub retrieve_org_address {
2636 my( $self, $client, $id ) = @_;
2637 return $apputils->simple_scalar_request(
2639 "open-ils.cstore.direct.actor.org_address.retrieve",
2644 __PACKAGE__->register_method(
2645 method => "retrieve_groups_tree",
2646 api_name => "open-ils.actor.groups.tree.retrieve",
2647 notes => "Returns a list of user groups"
2650 sub retrieve_groups_tree {
2651 my( $self, $client ) = @_;
2652 return new_editor()->search_permission_grp_tree(
2657 flesh_fields => { pgt => ["children"] },
2658 order_by => { pgt => 'name'}
2665 __PACKAGE__->register_method(
2666 method => "add_user_to_groups",
2667 api_name => "open-ils.actor.user.set_groups",
2668 notes => "Adds a user to one or more permission groups"
2671 sub add_user_to_groups {
2672 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2674 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2675 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2676 return $evt if $evt;
2678 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2679 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2680 return $evt if $evt;
2682 $apputils->simplereq(
2684 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2686 for my $group (@$groups) {
2687 my $link = Fieldmapper::permission::usr_grp_map->new;
2689 $link->usr($userid);
2691 my $id = $apputils->simplereq(
2693 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2699 __PACKAGE__->register_method(
2700 method => "get_user_perm_groups",
2701 api_name => "open-ils.actor.user.get_groups",
2702 notes => "Retrieve a user's permission groups."
2706 sub get_user_perm_groups {
2707 my( $self, $client, $authtoken, $userid ) = @_;
2709 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2710 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2711 return $evt if $evt;
2713 return $apputils->simplereq(
2715 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2719 __PACKAGE__->register_method(
2720 method => "get_user_work_ous",
2721 api_name => "open-ils.actor.user.get_work_ous",
2722 notes => "Retrieve a user's work org units."
2725 __PACKAGE__->register_method(
2726 method => "get_user_work_ous",
2727 api_name => "open-ils.actor.user.get_work_ous.ids",
2728 notes => "Retrieve a user's work org units."
2731 sub get_user_work_ous {
2732 my( $self, $client, $auth, $userid ) = @_;
2733 my $e = new_editor(authtoken=>$auth);
2734 return $e->event unless $e->checkauth;
2735 $userid ||= $e->requestor->id;
2737 if($e->requestor->id != $userid) {
2738 my $user = $e->retrieve_actor_user($userid)
2739 or return $e->event;
2740 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2743 return $e->search_permission_usr_work_ou_map({usr => $userid})
2744 unless $self->api_name =~ /.ids$/;
2746 # client just wants a list of org IDs
2747 return $U->get_user_work_ou_ids($e, $userid);
2752 __PACKAGE__->register_method(
2753 method => 'register_workstation',
2754 api_name => 'open-ils.actor.workstation.register.override',
2755 signature => q/@see open-ils.actor.workstation.register/
2758 __PACKAGE__->register_method(
2759 method => 'register_workstation',
2760 api_name => 'open-ils.actor.workstation.register',
2762 Registers a new workstion in the system
2763 @param authtoken The login session key
2764 @param name The name of the workstation id
2765 @param owner The org unit that owns this workstation
2766 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2767 if the name is already in use.
2771 sub register_workstation {
2772 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2774 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2775 return $e->die_event unless $e->checkauth;
2776 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2777 my $existing = $e->search_actor_workstation({name => $name})->[0];
2778 $oargs = { all => 1 } unless defined $oargs;
2782 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2783 # workstation with the given name exists.
2785 if($owner ne $existing->owning_lib) {
2786 # if necessary, update the owning_lib of the workstation
2788 $logger->info("changing owning lib of workstation ".$existing->id.
2789 " from ".$existing->owning_lib." to $owner");
2790 return $e->die_event unless
2791 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2793 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2795 $existing->owning_lib($owner);
2796 return $e->die_event unless $e->update_actor_workstation($existing);
2802 "attempt to register an existing workstation. returning existing ID");
2805 return $existing->id;
2808 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2812 my $ws = Fieldmapper::actor::workstation->new;
2813 $ws->owning_lib($owner);
2815 $e->create_actor_workstation($ws) or return $e->die_event;
2817 return $ws->id; # note: editor sets the id on the new object for us
2820 __PACKAGE__->register_method(
2821 method => 'workstation_list',
2822 api_name => 'open-ils.actor.workstation.list',
2824 Returns a list of workstations registered at the given location
2825 @param authtoken The login session key
2826 @param ids A list of org_unit.id's for the workstation owners
2830 sub workstation_list {
2831 my( $self, $conn, $authtoken, @orgs ) = @_;
2833 my $e = new_editor(authtoken=>$authtoken);
2834 return $e->event unless $e->checkauth;
2839 unless $e->allowed('REGISTER_WORKSTATION', $o);
2840 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2845 __PACKAGE__->register_method(
2846 method => 'fetch_patron_messages',
2847 api_name => 'open-ils.actor.message.retrieve',
2850 Returns a list of notes for a given user, not
2851 including ones marked deleted
2852 @param authtoken The login session key
2853 @param patronid patron ID
2854 @param options hash containing optional limit and offset
2858 sub fetch_patron_messages {
2859 my( $self, $conn, $auth, $patronid, $options ) = @_;
2863 my $e = new_editor(authtoken => $auth);
2864 return $e->die_event unless $e->checkauth;
2866 if ($e->requestor->id ne $patronid) {
2867 return $e->die_event unless $e->allowed('VIEW_USER');
2870 my $select_clause = { usr => $patronid };
2871 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2872 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2873 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2875 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2880 __PACKAGE__->register_method(
2881 method => 'usrname_exists',
2882 api_name => 'open-ils.actor.username.exists',
2884 desc => 'Check if a username is already taken (by an undeleted patron)',
2886 {desc => 'Authentication token', type => 'string'},
2887 {desc => 'Username', type => 'string'}
2890 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2895 sub usrname_exists {
2896 my( $self, $conn, $auth, $usrname ) = @_;
2897 my $e = new_editor(authtoken=>$auth);
2898 return $e->event unless $e->checkauth;
2899 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2900 return $$a[0] if $a and @$a;
2904 __PACKAGE__->register_method(
2905 method => 'barcode_exists',
2906 api_name => 'open-ils.actor.barcode.exists',
2908 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2911 sub barcode_exists {
2912 my( $self, $conn, $auth, $barcode ) = @_;
2913 my $e = new_editor(authtoken=>$auth);
2914 return $e->event unless $e->checkauth;
2915 my $card = $e->search_actor_card({barcode => $barcode});
2921 #return undef unless @$card;
2922 #return $card->[0]->usr;
2926 __PACKAGE__->register_method(
2927 method => 'retrieve_net_levels',
2928 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2931 sub retrieve_net_levels {
2932 my( $self, $conn, $auth ) = @_;
2933 my $e = new_editor(authtoken=>$auth);
2934 return $e->event unless $e->checkauth;
2935 return $e->retrieve_all_config_net_access_level();
2938 # Retain the old typo API name just in case
2939 __PACKAGE__->register_method(
2940 method => 'fetch_org_by_shortname',
2941 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2943 __PACKAGE__->register_method(
2944 method => 'fetch_org_by_shortname',
2945 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2947 sub fetch_org_by_shortname {
2948 my( $self, $conn, $sname ) = @_;
2949 my $e = new_editor();
2950 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2951 return $e->event unless $org;
2956 __PACKAGE__->register_method(
2957 method => 'session_home_lib',
2958 api_name => 'open-ils.actor.session.home_lib',
2961 sub session_home_lib {
2962 my( $self, $conn, $auth ) = @_;
2963 my $e = new_editor(authtoken=>$auth);
2964 return undef unless $e->checkauth;
2965 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2966 return $org->shortname;
2969 __PACKAGE__->register_method(
2970 method => 'session_safe_token',
2971 api_name => 'open-ils.actor.session.safe_token',
2973 Returns a hashed session ID that is safe for export to the world.
2974 This safe token will expire after 1 hour of non-use.
2975 @param auth Active authentication token
2979 sub session_safe_token {
2980 my( $self, $conn, $auth ) = @_;
2981 my $e = new_editor(authtoken=>$auth);
2982 return undef unless $e->checkauth;
2984 my $safe_token = md5_hex($auth);
2986 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2988 # add more user fields as needed
2990 "safe-token-user-$safe_token", {
2991 id => $e->requestor->id,
2992 home_ou_shortname => $e->retrieve_actor_org_unit(
2993 $e->requestor->home_ou)->shortname,
3002 __PACKAGE__->register_method(
3003 method => 'safe_token_home_lib',
3004 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
3006 Returns the home library shortname from the session
3007 asscociated with a safe token from generated by
3008 open-ils.actor.session.safe_token.
3009 @param safe_token Active safe token
3010 @param who Optional user activity "ewho" value
3014 sub safe_token_home_lib {
3015 my( $self, $conn, $safe_token, $who ) = @_;
3016 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3018 my $blob = $cache->get_cache("safe-token-user-$safe_token");
3019 return unless $blob;
3021 $U->log_user_activity($blob->{id}, $who, 'verify');
3022 return $blob->{home_ou_shortname};
3026 __PACKAGE__->register_method(
3027 method => "update_penalties",
3028 api_name => "open-ils.actor.user.penalties.update"
3031 __PACKAGE__->register_method(
3032 method => "update_penalties",
3033 api_name => "open-ils.actor.user.penalties.update_at_home"
3036 sub update_penalties {
3037 my($self, $conn, $auth, $user_id, @penalties) = @_;
3038 my $e = new_editor(authtoken=>$auth, xact => 1);
3039 return $e->die_event unless $e->checkauth;
3040 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3041 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3042 my $context_org = ($self->api_name =~ /_at_home$/) ? $user->home_ou : $e->requestor->ws_ou;
3043 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $context_org, @penalties);
3044 return $evt if $evt;
3050 __PACKAGE__->register_method(
3051 method => "apply_penalty",
3052 api_name => "open-ils.actor.user.penalty.apply"
3056 my($self, $conn, $auth, $penalty, $msg) = @_;
3060 my $e = new_editor(authtoken=>$auth, xact => 1);
3061 return $e->die_event unless $e->checkauth;
3063 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3064 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3066 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3068 my $ctx_org = $penalty->org_unit; # csp org_depth is now considered in the UI for the org drop-down menu
3070 if (($msg->{title} || $msg->{message}) && ($msg->{title} ne '' || $msg->{message} ne '')) {
3071 my $aum = Fieldmapper::actor::usr_message->new;
3073 $aum->create_date('now');
3074 $aum->sending_lib($e->requestor->ws_ou);
3075 $aum->title($msg->{title});
3076 $aum->usr($penalty->usr);
3077 $aum->message($msg->{message});
3078 $aum->pub($msg->{pub});
3080 $aum = $e->create_actor_usr_message($aum)
3081 or return $e->die_event;
3083 $penalty->usr_message($aum->id);
3086 $penalty->org_unit($ctx_org);
3087 $penalty->staff($e->requestor->id);
3088 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3091 return $penalty->id;
3094 __PACKAGE__->register_method(
3095 method => "modify_penalty",
3096 api_name => "open-ils.actor.user.penalty.modify"
3099 sub modify_penalty {
3100 my($self, $conn, $auth, $penalty, $usr_msg) = @_;
3102 my $e = new_editor(authtoken=>$auth, xact => 1);
3103 return $e->die_event unless $e->checkauth;
3105 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3106 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3108 $usr_msg->editor($e->requestor->id);
3109 $usr_msg->edit_date('now');
3111 if ($usr_msg->isnew) {
3112 $usr_msg = $e->create_actor_usr_message($usr_msg)
3113 or return $e->die_event;
3114 $penalty->usr_message($usr_msg->id);
3116 $usr_msg = $e->update_actor_usr_message($usr_msg)
3117 or return $e->die_event;
3120 if ($penalty->isnew) {
3121 $penalty = $e->create_actor_user_standing_penalty($penalty)
3122 or return $e->die_event;
3124 $penalty = $e->update_actor_user_standing_penalty($penalty)
3125 or return $e->die_event;
3132 __PACKAGE__->register_method(
3133 method => "remove_penalty",
3134 api_name => "open-ils.actor.user.penalty.remove"
3137 sub remove_penalty {
3138 my($self, $conn, $auth, $penalty) = @_;
3139 my $e = new_editor(authtoken=>$auth, xact => 1);
3140 return $e->die_event unless $e->checkauth;
3141 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3142 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3144 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3149 __PACKAGE__->register_method(
3150 method => "update_penalty_note",
3151 api_name => "open-ils.actor.user.penalty.note.update"
3154 sub update_penalty_note {
3155 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3156 my $e = new_editor(authtoken=>$auth, xact => 1);
3157 return $e->die_event unless $e->checkauth;
3158 for my $penalty_id (@$penalty_ids) {
3159 my $penalty = $e->search_actor_user_standing_penalty([
3160 { id => $penalty_id },
3162 flesh_fields => {aum => ['usr_message']}
3165 if (! $penalty ) { return $e->die_event; }
3166 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3167 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3169 my $aum = $penalty->usr_message();
3171 $aum = Fieldmapper::actor::usr_message->new;
3173 $aum->create_date('now');
3174 $aum->sending_lib($e->requestor->ws_ou);
3176 $aum->usr($penalty->usr);
3177 $aum->message($note);
3181 $aum = $e->create_actor_usr_message($aum)
3182 or return $e->die_event;
3184 $penalty->usr_message($aum->id);
3185 $penalty->ischanged(1);
3186 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3188 $aum = $e->retrieve_actor_usr_message($aum) or return $e->die_event;
3189 $aum->message($note); $aum->ischanged(1);
3190 $e->update_actor_usr_message($aum) or return $e->die_event;
3197 __PACKAGE__->register_method(
3198 method => "ranged_penalty_thresholds",
3199 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3203 sub ranged_penalty_thresholds {
3204 my($self, $conn, $auth, $context_org) = @_;
3205 my $e = new_editor(authtoken=>$auth);
3206 return $e->event unless $e->checkauth;
3207 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3208 my $list = $e->search_permission_grp_penalty_threshold([
3209 {org_unit => $U->get_org_ancestors($context_org)},
3210 {order_by => {pgpt => 'id'}}
3212 $conn->respond($_) for @$list;
3218 __PACKAGE__->register_method(
3219 method => "user_retrieve_fleshed_by_id",
3221 api_name => "open-ils.actor.user.fleshed.retrieve",
3224 sub user_retrieve_fleshed_by_id {
3225 my( $self, $client, $auth, $user_id, $fields ) = @_;
3226 my $e = new_editor(authtoken => $auth);
3227 return $e->event unless $e->checkauth;
3229 if( $e->requestor->id != $user_id ) {
3230 return $e->event unless $e->allowed('VIEW_USER');
3237 "standing_penalties",
3245 return new_flesh_user($user_id, $fields, $e);
3249 sub new_flesh_user {
3252 my $fields = shift || [];
3255 my $fetch_penalties = 0;
3256 if(grep {$_ eq 'standing_penalties'} @$fields) {
3257 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3258 $fetch_penalties = 1;
3261 my $fetch_notes = 0;
3262 if(grep {$_ eq 'notes'} @$fields) {
3263 $fields = [grep {$_ ne 'notes'} @$fields];
3267 my $fetch_usr_act = 0;
3268 if(grep {$_ eq 'usr_activity'} @$fields) {
3269 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3273 my $user = $e->retrieve_actor_user(
3278 "flesh_fields" => { "au" => $fields }
3281 ) or return $e->die_event;
3284 if( grep { $_ eq 'addresses' } @$fields ) {
3286 $user->addresses([]) unless @{$user->addresses};
3287 # don't expose "replaced" addresses by default
3288 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3290 if( ref $user->billing_address ) {
3291 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3292 push( @{$user->addresses}, $user->billing_address );
3296 if( ref $user->mailing_address ) {
3297 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3298 push( @{$user->addresses}, $user->mailing_address );
3303 if($fetch_penalties) {
3304 # grab the user penalties ranged for this location
3305 $user->standing_penalties(
3306 $e->search_actor_user_standing_penalty([
3309 {stop_date => undef},
3310 {stop_date => {'>' => 'now'}}
3312 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3315 flesh_fields => {ausp => ['standing_penalty','usr_message']}
3322 # grab notes (now actor.usr_message_penalty) that have not hit their stop_date
3323 # NOTE: This is a view that already filters out deleted messages that are not
3324 # attached to a penalty
3326 @{ $e->search_actor_usr_message_penalty([
3329 {stop_date => undef},
3330 {stop_date => {'>' => 'now'}}
3337 # retrieve the most recent usr_activity entry
3338 if ($fetch_usr_act) {
3340 # max number to return for simple patron fleshing
3341 my $limit = $U->ou_ancestor_setting_value(
3342 $e->requestor->ws_ou,
3343 'circ.patron.usr_activity_retrieve.max');
3347 flesh_fields => {auact => ['etype']},
3348 order_by => {auact => 'event_time DESC'},
3351 # 0 == none, <0 == return all
3352 $limit = 1 unless defined $limit;
3353 $opts->{limit} = $limit if $limit > 0;
3355 $user->usr_activity(
3357 [] : # skip the DB call
3358 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3363 $user->clear_passwd();
3370 __PACKAGE__->register_method(
3371 method => "user_retrieve_parts",
3372 api_name => "open-ils.actor.user.retrieve.parts",
3375 sub user_retrieve_parts {
3376 my( $self, $client, $auth, $user_id, $fields ) = @_;
3377 my $e = new_editor(authtoken => $auth);
3378 return $e->event unless $e->checkauth;
3379 $user_id ||= $e->requestor->id;
3380 if( $e->requestor->id != $user_id ) {
3381 return $e->event unless $e->allowed('VIEW_USER');
3384 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3385 push(@resp, $user->$_()) for(@$fields);
3391 __PACKAGE__->register_method(
3392 method => 'user_opt_in_enabled',
3393 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3394 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3397 sub user_opt_in_enabled {
3398 my($self, $conn) = @_;
3399 my $sc = OpenSRF::Utils::SettingsClient->new;
3400 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3405 __PACKAGE__->register_method(
3406 method => 'user_opt_in_at_org',
3407 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3409 @param $auth The auth token
3410 @param user_id The ID of the user to test
3411 @return 1 if the user has opted in at the specified org,
3412 2 if opt-in is disallowed for the user's home org,
3413 event on error, and 0 otherwise. /
3415 sub user_opt_in_at_org {
3416 my($self, $conn, $auth, $user_id) = @_;
3418 # see if we even need to enforce the opt-in value
3419 return 1 unless user_opt_in_enabled($self);
3421 my $e = new_editor(authtoken => $auth);
3422 return $e->event unless $e->checkauth;
3424 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3425 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3427 my $ws_org = $e->requestor->ws_ou;
3428 # user is automatically opted-in if they are from the local org
3429 return 1 if $user->home_ou eq $ws_org;
3431 # get the boundary setting
3432 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3434 # auto opt in if user falls within the opt boundary
3435 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3437 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3439 # check whether opt-in is restricted at the user's home library
3440 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3441 if ($opt_restrict_depth) {
3442 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3443 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3445 # opt-in is disallowed unless the workstation org is within the home
3446 # library's opt-in scope
3447 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3450 my $vals = $e->search_actor_usr_org_unit_opt_in(
3451 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3457 __PACKAGE__->register_method(
3458 method => 'create_user_opt_in_at_org',
3459 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3461 @param $auth The auth token
3462 @param user_id The ID of the user to test
3463 @return The ID of the newly created object, event on error./
3466 sub create_user_opt_in_at_org {
3467 my($self, $conn, $auth, $user_id, $org_id) = @_;
3469 my $e = new_editor(authtoken => $auth, xact=>1);
3470 return $e->die_event unless $e->checkauth;
3472 # if a specific org unit wasn't passed in, get one based on the defaults;
3474 my $wsou = $e->requestor->ws_ou;
3475 # get the default opt depth
3476 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3477 # get the org unit at that depth
3478 my $org = $e->json_query({
3479 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3480 $org_id = $org->{id};
3483 # fall back to the workstation OU, the pre-opt-in-boundary way
3484 $org_id = $e->requestor->ws_ou;
3487 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3488 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3490 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3492 $opt_in->org_unit($org_id);
3493 $opt_in->usr($user_id);
3494 $opt_in->staff($e->requestor->id);
3495 $opt_in->opt_in_ts('now');
3496 $opt_in->opt_in_ws($e->requestor->wsid);
3498 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3499 or return $e->die_event;
3507 __PACKAGE__->register_method (
3508 method => 'retrieve_org_hours',
3509 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3511 Returns the hours of operation for a specified org unit
3512 @param authtoken The login session key
3513 @param org_id The org_unit ID
3517 sub retrieve_org_hours {
3518 my($self, $conn, $auth, $org_id) = @_;
3519 my $e = new_editor(authtoken => $auth);
3520 return $e->die_event unless $e->checkauth;
3521 $org_id ||= $e->requestor->ws_ou;
3522 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3526 __PACKAGE__->register_method (
3527 method => 'verify_user_password',
3528 api_name => 'open-ils.actor.verify_user_password',
3530 Given a barcode or username and the MD5 encoded password,
3531 The password can also be passed without the MD5 hashing.
3532 returns 1 if the password is correct. Returns 0 otherwise.
3536 sub verify_user_password {
3537 my($self, $conn, $auth, $barcode, $username, $password, $pass_nohash) = @_;
3538 my $e = new_editor(authtoken => $auth);
3539 return $e->die_event unless $e->checkauth;
3541 my $user_by_barcode;
3542 my $user_by_username;
3544 my $card = $e->search_actor_card([
3545 {barcode => $barcode},
3546 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3547 $user_by_barcode = $card->usr;
3548 $user = $user_by_barcode;
3551 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3552 $user = $user_by_username;
3554 return 0 if (!$user || $U->is_true($user->deleted));
3555 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3556 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3559 return $U->verify_migrated_user_password($e, $user->id, $pass_nohash);
3561 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3565 __PACKAGE__->register_method (
3566 method => 'retrieve_usr_id_via_barcode_or_usrname',
3567 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3569 Given a barcode or username returns the id for the user or
3574 sub retrieve_usr_id_via_barcode_or_usrname {
3575 my($self, $conn, $auth, $barcode, $username) = @_;
3576 my $e = new_editor(authtoken => $auth);
3577 return $e->die_event unless $e->checkauth;
3578 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3580 my $user_by_barcode;
3581 my $user_by_username;
3582 $logger->info("$id_as_barcode is the ID as BARCODE");
3584 my $card = $e->search_actor_card([
3585 {barcode => $barcode},
3586 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3587 if ($id_as_barcode =~ /^t/i) {
3589 $user = $e->retrieve_actor_user($barcode);
3590 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3592 $user_by_barcode = $card->usr;
3593 $user = $user_by_barcode;
3596 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3597 $user_by_barcode = $card->usr;
3598 $user = $user_by_barcode;
3603 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3605 $user = $user_by_username;
3607 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3608 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3609 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3614 __PACKAGE__->register_method (
3615 method => 'merge_users',
3616 api_name => 'open-ils.actor.user.merge',
3619 Given a list of source users and destination user, transfer all data from the source
3620 to the dest user and delete the source user. All user related data is
3621 transferred, including circulations, holds, bookbags, etc.
3627 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3628 my $e = new_editor(xact => 1, authtoken => $auth);
3629 return $e->die_event unless $e->checkauth;
3631 # disallow the merge if any subordinate accounts are in collections
3632 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3633 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3635 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3636 if $master_id == $e->requestor->id;
3638 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3639 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3640 return $evt if $evt;
3642 my $del_addrs = ($U->ou_ancestor_setting_value(
3643 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3644 my $del_cards = ($U->ou_ancestor_setting_value(
3645 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3646 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3647 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3649 for my $src_id (@$user_ids) {
3651 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3652 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3653 return $evt if $evt;
3655 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3656 if $src_id == $e->requestor->id;
3658 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3659 if($src_user->home_ou ne $master_user->home_ou) {
3660 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3663 return $e->die_event unless
3664 $e->json_query({from => [
3679 __PACKAGE__->register_method (
3680 method => 'approve_user_address',
3681 api_name => 'open-ils.actor.user.pending_address.approve',
3688 sub approve_user_address {
3689 my($self, $conn, $auth, $addr) = @_;
3690 my $e = new_editor(xact => 1, authtoken => $auth);
3691 return $e->die_event unless $e->checkauth;
3693 # if the caller passes an address object, assume they want to
3694 # update it first before approving it
3695 $e->update_actor_user_address($addr) or return $e->die_event;
3697 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3699 my $user = $e->retrieve_actor_user($addr->usr);
3700 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3701 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3702 or return $e->die_event;
3704 return [values %$result]->[0];
3708 __PACKAGE__->register_method (
3709 method => 'retrieve_friends',
3710 api_name => 'open-ils.actor.friends.retrieve',
3713 returns { confirmed: [], pending_out: [], pending_in: []}
3714 pending_out are users I'm requesting friendship with
3715 pending_in are users requesting friendship with me
3720 sub retrieve_friends {
3721 my($self, $conn, $auth, $user_id, $options) = @_;
3722 my $e = new_editor(authtoken => $auth);
3723 return $e->event unless $e->checkauth;
3724 $user_id ||= $e->requestor->id;
3726 if($user_id != $e->requestor->id) {
3727 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3728 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3731 return OpenILS::Application::Actor::Friends->retrieve_friends(
3732 $e, $user_id, $options);
3737 __PACKAGE__->register_method (
3738 method => 'apply_friend_perms',
3739 api_name => 'open-ils.actor.friends.perms.apply',
3745 sub apply_friend_perms {
3746 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3747 my $e = new_editor(authtoken => $auth, xact => 1);
3748 return $e->die_event unless $e->checkauth;
3750 if($user_id != $e->requestor->id) {
3751 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3752 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3755 for my $perm (@perms) {
3757 OpenILS::Application::Actor::Friends->apply_friend_perm(
3758 $e, $user_id, $delegate_id, $perm);
3759 return $evt if $evt;
3767 __PACKAGE__->register_method (
3768 method => 'update_user_pending_address',
3769 api_name => 'open-ils.actor.user.address.pending.cud'
3772 sub update_user_pending_address {
3773 my($self, $conn, $auth, $addr) = @_;
3774 my $e = new_editor(authtoken => $auth, xact => 1);
3775 return $e->die_event unless $e->checkauth;
3777 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3778 if($addr->usr != $e->requestor->id) {
3779 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3783 $e->create_actor_user_address($addr) or return $e->die_event;
3784 } elsif($addr->isdeleted) {
3785 $e->delete_actor_user_address($addr) or return $e->die_event;
3787 $e->update_actor_user_address($addr) or return $e->die_event;
3791 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3797 __PACKAGE__->register_method (
3798 method => 'user_events',
3799 api_name => 'open-ils.actor.user.events.circ',
3802 __PACKAGE__->register_method (
3803 method => 'user_events',
3804 api_name => 'open-ils.actor.user.events.ahr',
3809 my($self, $conn, $auth, $user_id, $filters) = @_;
3810 my $e = new_editor(authtoken => $auth);
3811 return $e->event unless $e->checkauth;
3813 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3814 my $user_field = 'usr';
3817 $filters->{target} = {
3818 select => { $obj_type => ['id'] },
3820 where => {usr => $user_id}
3823 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3824 if($e->requestor->id != $user_id) {
3825 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3828 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3829 my $req = $ses->request('open-ils.trigger.events_by_target',
3830 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3832 while(my $resp = $req->recv) {
3833 my $val = $resp->content;
3834 my $tgt = $val->target;
3836 if($obj_type eq 'circ') {
3837 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3839 } elsif($obj_type eq 'ahr') {
3840 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3841 if $tgt->current_copy;
3844 $conn->respond($val) if $val;
3850 __PACKAGE__->register_method (
3851 method => 'copy_events',
3852 api_name => 'open-ils.actor.copy.events.circ',
3855 __PACKAGE__->register_method (
3856 method => 'copy_events',
3857 api_name => 'open-ils.actor.copy.events.ahr',
3862 my($self, $conn, $auth, $copy_id, $filters) = @_;
3863 my $e = new_editor(authtoken => $auth);
3864 return $e->event unless $e->checkauth;
3866 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3868 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3870 my $copy_field = 'target_copy';
3871 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3874 $filters->{target} = {
3875 select => { $obj_type => ['id'] },
3877 where => {$copy_field => $copy_id}
3881 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3882 my $req = $ses->request('open-ils.trigger.events_by_target',
3883 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3885 while(my $resp = $req->recv) {
3886 my $val = $resp->content;
3887 my $tgt = $val->target;
3889 my $user = $e->retrieve_actor_user($tgt->usr);
3890 if($e->requestor->id != $user->id) {
3891 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3894 $tgt->$copy_field($copy);
3897 $conn->respond($val) if $val;
3904 __PACKAGE__->register_method (
3905 method => 'get_itemsout_notices',
3906 api_name => 'open-ils.actor.user.itemsout.notices',
3910 desc => q/Summary counts of circulat notices/,
3912 {desc => 'authtoken', type => 'string'},
3913 {desc => 'circulation identifiers', type => 'array of numbers'}
3915 return => q/Stream of summary objects/
3919 sub get_itemsout_notices {
3920 my ($self, $client, $auth, $circ_ids) = @_;
3922 my $e = new_editor(authtoken => $auth);
3923 return $e->event unless $e->checkauth;
3925 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3927 for my $circ_id (@$circ_ids) {
3928 my $resp = get_itemsout_notices_impl($e, $circ_id);
3930 if ($U->is_event($resp)) {
3931 $client->respond($resp);
3935 $client->respond({circ_id => $circ_id, %$resp});
3943 sub get_itemsout_notices_impl {
3944 my ($e, $circId) = @_;
3946 my $requestorId = $e->requestor->id;
3948 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3950 my $patronId = $circ->usr;
3952 if( $patronId ne $requestorId ){
3953 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3954 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3957 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3958 #my $req = $ses->request('open-ils.trigger.events_by_target',
3959 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3960 # ^ Above removed in favor of faster json_query.
3963 # select complete_time
3964 # from action_trigger.event atev
3965 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3966 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3967 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3970 my $ctx_loc = $e->requestor->ws_ou;
3971 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3972 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3975 select => { atev => ["complete_time"] },
3978 atevdef => { field => "id",fkey => "event_def"}
3982 "+atevdef" => { active => 't', hook => 'checkout.due' },
3983 "+atev" => { target => $circId, state => 'complete' }
3987 if ($exclude_courtesy_notices){
3988 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3991 my %resblob = ( numNotices => 0, lastDt => undef );
3993 my $res = $e->json_query($query);
3994 for my $ndate (@$res) {
3995 $resblob{numNotices}++;
3996 if( !defined $resblob{lastDt}){
3997 $resblob{lastDt} = $$ndate{complete_time};
4000 if ($resblob{lastDt} lt $$ndate{complete_time}){
4001 $resblob{lastDt} = $$ndate{complete_time};
4008 __PACKAGE__->register_method (
4009 method => 'update_events',
4010 api_name => 'open-ils.actor.user.event.cancel.batch',
4013 __PACKAGE__->register_method (
4014 method => 'update_events',
4015 api_name => 'open-ils.actor.user.event.reset.batch',
4020 my($self, $conn, $auth, $event_ids) = @_;
4021 my $e = new_editor(xact => 1, authtoken => $auth);
4022 return $e->die_event unless $e->checkauth;
4025 for my $id (@$event_ids) {
4027 # do a little dance to determine what user we are ultimately affecting
4028 my $event = $e->retrieve_action_trigger_event([
4031 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
4033 ]) or return $e->die_event;
4036 if($event->event_def->hook->core_type eq 'circ') {
4037 $user_id = $e->retrieve_action_circulation($event->target)->usr;
4038 } elsif($event->event_def->hook->core_type eq 'ahr') {
4039 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
4044 my $user = $e->retrieve_actor_user($user_id);
4045 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
4047 if($self->api_name =~ /cancel/) {
4048 $event->state('invalid');
4049 } elsif($self->api_name =~ /reset/) {
4050 $event->clear_start_time;
4051 $event->clear_update_time;
4052 $event->state('pending');
4055 $e->update_action_trigger_event($event) or return $e->die_event;
4056 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
4060 return {complete => 1};
4064 __PACKAGE__->register_method (
4065 method => 'really_delete_user',
4066 api_name => 'open-ils.actor.user.delete.override',
4067 signature => q/@see open-ils.actor.user.delete/
4070 __PACKAGE__->register_method (
4071 method => 'really_delete_user',
4072 api_name => 'open-ils.actor.user.delete',
4074 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
4075 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
4076 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
4077 dest_usr_id is only required when deleting a user that performs staff functions.
4081 sub really_delete_user {
4082 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
4083 my $e = new_editor(authtoken => $auth, xact => 1);
4084 return $e->die_event unless $e->checkauth;
4085 $oargs = { all => 1 } unless defined $oargs;
4087 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
4088 my $open_bills = $e->json_query({
4089 select => { mbts => ['id'] },
4092 xact_finish => { '=' => undef },
4093 usr => { '=' => $user_id },
4095 }) or return $e->die_event;
4097 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
4099 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
4101 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
4102 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
4103 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
4105 # No deleting yourself - UI is supposed to stop you first, though.
4106 return $e->die_event unless $e->requestor->id != $user->id;
4107 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4108 # Check if you are allowed to mess with this patron permission group at all
4109 my $evt = group_perm_failed($e, $e->requestor, $user);
4110 return $e->die_event($evt) if $evt;
4111 my $stat = $e->json_query(
4112 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4113 or return $e->die_event;
4119 __PACKAGE__->register_method (
4120 method => 'user_payments',
4121 api_name => 'open-ils.actor.user.payments.retrieve',
4124 Returns all payments for a given user. Default order is newest payments first.
4125 @param auth Authentication token
4126 @param user_id The user ID
4127 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4132 my($self, $conn, $auth, $user_id, $filters) = @_;
4135 my $e = new_editor(authtoken => $auth);
4136 return $e->die_event unless $e->checkauth;
4138 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4139 return $e->event unless
4140 $e->requestor->id == $user_id or
4141 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4143 # Find all payments for all transactions for user $user_id
4145 select => {mp => ['id']},
4150 select => {mbt => ['id']},
4152 where => {usr => $user_id}
4157 { # by default, order newest payments first
4159 field => 'payment_ts',
4162 # secondary sort in ID as a tie-breaker, since payments created
4163 # within the same transaction will have identical payment_ts's
4170 for (qw/order_by limit offset/) {
4171 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4174 if(defined $filters->{where}) {
4175 foreach (keys %{$filters->{where}}) {
4176 # don't allow the caller to expand the result set to other users
4177 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4181 my $payment_ids = $e->json_query($query);
4182 for my $pid (@$payment_ids) {
4183 my $pay = $e->retrieve_money_payment([
4188 mbt => ['summary', 'circulation', 'grocery'],
4189 circ => ['target_copy'],
4190 acp => ['call_number'],
4198 xact_type => $pay->xact->summary->xact_type,
4199 last_billing_type => $pay->xact->summary->last_billing_type,
4202 if($pay->xact->summary->xact_type eq 'circulation') {
4203 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4204 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4207 $pay->xact($pay->xact->id); # de-flesh
4208 $conn->respond($resp);
4216 __PACKAGE__->register_method (
4217 method => 'negative_balance_users',
4218 api_name => 'open-ils.actor.users.negative_balance',
4221 Returns all users that have an overall negative balance
4222 @param auth Authentication token
4223 @param org_id The context org unit as an ID or list of IDs. This will be the home
4224 library of the user. If no org_unit is specified, no org unit filter is applied
4228 sub negative_balance_users {
4229 my($self, $conn, $auth, $org_id, $options) = @_;
4232 $options->{limit} = 1000 unless $options->{limit};
4233 $options->{offset} = 0 unless $options->{offset};
4235 my $e = new_editor(authtoken => $auth);
4236 return $e->die_event unless $e->checkauth;
4237 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4241 mous => ['usr', 'balance_owed'],
4244 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4245 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4262 where => {'+mous' => {balance_owed => {'<' => 0}}},
4263 offset => $options->{offset},
4264 limit => $options->{limit},
4265 order_by => [{class => 'mous', field => 'usr'}]
4268 $org_id = $U->get_org_descendants($org_id) if $options->{org_descendants};
4270 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4272 my $list = $e->json_query($query, {timeout => 600});
4274 for my $data (@$list) {
4276 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4277 balance_owed => $data->{balance_owed},
4278 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4285 __PACKAGE__->register_method(
4286 method => "request_password_reset",
4287 api_name => "open-ils.actor.patron.password_reset.request",
4289 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4290 "method for changing a user's password. The UUID token is distributed via A/T " .
4291 "templates (i.e. email to the user).",
4293 { desc => 'user_id_type', type => 'string' },
4294 { desc => 'user_id', type => 'string' },
4295 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4297 return => {desc => '1 on success, Event on error'}
4300 sub request_password_reset {
4301 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4303 # Check to see if password reset requests are already being throttled:
4304 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4306 my $e = new_editor(xact => 1);
4309 # Get the user, if any, depending on the input value
4310 if ($user_id_type eq 'username') {
4311 $user = $e->search_actor_user({usrname => $user_id})->[0];
4314 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4316 } elsif ($user_id_type eq 'barcode') {
4317 my $card = $e->search_actor_card([
4318 {barcode => $user_id},
4319 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4322 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4327 # If the user doesn't have an email address, we can't help them
4328 if (!$user->email) {
4330 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4333 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4334 if ($email_must_match) {
4335 if (lc($user->email) ne lc($email)) {
4336 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4340 _reset_password_request($conn, $e, $user);
4343 # Once we have the user, we can issue the password reset request
4344 # XXX Add a wrapper method that accepts barcode + email input
4345 sub _reset_password_request {
4346 my ($conn, $e, $user) = @_;
4348 # 1. Get throttle threshold and time-to-live from OU_settings
4349 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4350 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4352 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4354 # 2. Get time of last request and number of active requests (num_active)
4355 my $active_requests = $e->json_query({
4361 transform => 'COUNT'
4364 column => 'request_time',
4370 has_been_reset => { '=' => 'f' },
4371 request_time => { '>' => $threshold_time }
4375 # Guard against no active requests
4376 if ($active_requests->[0]->{'request_time'}) {
4377 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4378 my $now = DateTime::Format::ISO8601->new();
4380 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4381 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4382 ($last_request->add_duration('1 minute') > $now)) {
4383 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4385 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4389 # TODO Check to see if the user is in a password-reset-restricted group
4391 # Otherwise, go ahead and try to get the user.
4393 # Check the number of active requests for this user
4394 $active_requests = $e->json_query({
4400 transform => 'COUNT'
4405 usr => { '=' => $user->id },
4406 has_been_reset => { '=' => 'f' },
4407 request_time => { '>' => $threshold_time }
4411 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4413 # if less than or equal to per-user threshold, proceed; otherwise, return event
4414 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4415 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4417 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4420 # Create the aupr object and insert into the database
4421 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4422 my $uuid = create_uuid_as_string(UUID_V4);
4423 $reset_request->uuid($uuid);
4424 $reset_request->usr($user->id);
4426 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4429 # Create an event to notify user of the URL to reset their password
4431 # Can we stuff this in the user_data param for trigger autocreate?
4432 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4434 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4435 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4438 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4443 __PACKAGE__->register_method(
4444 method => "commit_password_reset",
4445 api_name => "open-ils.actor.patron.password_reset.commit",
4447 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4448 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4449 "with the supplied password.",
4451 { desc => 'uuid', type => 'string' },
4452 { desc => 'password', type => 'string' },
4454 return => {desc => '1 on success, Event on error'}
4457 sub commit_password_reset {
4458 my($self, $conn, $uuid, $password) = @_;
4460 # Check to see if password reset requests are already being throttled:
4461 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4462 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4463 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4465 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4468 my $e = new_editor(xact => 1);
4470 my $aupr = $e->search_actor_usr_password_reset({
4477 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4479 my $user_id = $aupr->[0]->usr;
4480 my $user = $e->retrieve_actor_user($user_id);
4482 # Ensure we're still within the TTL for the request
4483 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4484 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4485 if ($threshold < DateTime->now(time_zone => 'local')) {
4487 $logger->info("Password reset request needed to be submitted before $threshold");
4488 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4491 # Check complexity of password against OU-defined regex
4492 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4496 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4497 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4498 $is_strong = check_password_strength_custom($password, $pw_regex);
4500 $is_strong = check_password_strength_default($password);
4505 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4508 # All is well; update the password
4509 modify_migrated_user_password($e, $user->id, $password);
4511 # And flag that this password reset request has been honoured
4512 $aupr->[0]->has_been_reset('t');
4513 $e->update_actor_usr_password_reset($aupr->[0]);
4519 sub check_password_strength_default {
4520 my $password = shift;
4521 # Use the default set of checks
4522 if ( (length($password) < 7) or
4523 ($password !~ m/.*\d+.*/) or
4524 ($password !~ m/.*[A-Za-z]+.*/)
4531 sub check_password_strength_custom {
4532 my ($password, $pw_regex) = @_;
4534 $pw_regex = qr/$pw_regex/;
4535 if ($password !~ /$pw_regex/) {
4541 __PACKAGE__->register_method(
4542 method => "fire_test_notification",
4543 api_name => "open-ils.actor.event.test_notification"
4546 sub fire_test_notification {
4547 my($self, $conn, $auth, $args) = @_;
4548 my $e = new_editor(authtoken => $auth);
4549 return $e->event unless $e->checkauth;
4550 if ($e->requestor->id != $$args{target}) {
4551 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4552 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4555 my $event_hook = $$args{hook} or return $e->event;
4556 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4558 my $usr = $e->retrieve_actor_user($$args{target});
4559 return $e->event unless $usr;
4561 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4565 __PACKAGE__->register_method(
4566 method => "event_def_opt_in_settings",
4567 api_name => "open-ils.actor.event_def.opt_in.settings",
4570 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4572 { desc => 'Authentication token', type => 'string'},
4574 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4579 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4586 sub event_def_opt_in_settings {
4587 my($self, $conn, $auth, $org_id) = @_;
4588 my $e = new_editor(authtoken => $auth);
4589 return $e->event unless $e->checkauth;
4591 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4592 return $e->event unless
4593 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4595 $org_id = $e->requestor->home_ou;
4598 # find all config.user_setting_type's related to event_defs for the requested org unit
4599 my $types = $e->json_query({
4600 select => {cust => ['name']},
4601 from => {atevdef => 'cust'},
4604 owner => $U->get_org_ancestors($org_id), # context org plus parents
4611 $conn->respond($_) for
4612 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4619 __PACKAGE__->register_method(
4620 method => "user_circ_history",
4621 api_name => "open-ils.actor.history.circ",
4625 desc => 'Returns user circ history objects for the calling user',
4627 { desc => 'Authentication token', type => 'string'},
4628 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4631 desc => q/Stream of 'auch' circ history objects/,
4637 __PACKAGE__->register_method(
4638 method => "user_circ_history",
4639 api_name => "open-ils.actor.history.circ.clear",
4642 desc => 'Delete all user circ history entries for the calling user',
4644 { desc => 'Authentication token', type => 'string'},
4645 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4648 desc => q/1 on success, event on error/,
4654 __PACKAGE__->register_method(
4655 method => "user_circ_history",
4656 api_name => "open-ils.actor.history.circ.print",
4659 desc => q/Returns printable output for the caller's circ history objects/,
4661 { desc => 'Authentication token', type => 'string'},
4662 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4665 desc => q/An action_trigger.event object or error event./,
4671 __PACKAGE__->register_method(
4672 method => "user_circ_history",
4673 api_name => "open-ils.actor.history.circ.email",
4676 desc => q/Emails the caller's circ history/,
4678 { desc => 'Authentication token', type => 'string'},
4679 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4680 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4683 desc => q/undef, or event on error/
4688 sub user_circ_history {
4689 my ($self, $conn, $auth, $options) = @_;
4692 my $for_print = ($self->api_name =~ /print/);
4693 my $for_email = ($self->api_name =~ /email/);
4694 my $for_clear = ($self->api_name =~ /clear/);
4696 # No perm check is performed. Caller may only access his/her own
4697 # circ history entries.
4698 my $e = new_editor(authtoken => $auth);
4699 return $e->event unless $e->checkauth;
4702 if (!$for_clear) { # clear deletes all
4703 $limits{offset} = $options->{offset} if defined $options->{offset};
4704 $limits{limit} = $options->{limit} if defined $options->{limit};
4707 my %circ_id_filter = $options->{circ_ids} ?
4708 (id => $options->{circ_ids}) : ();
4710 my $circs = $e->search_action_user_circ_history([
4711 { usr => $e->requestor->id,
4714 { # order newest to oldest by default
4715 order_by => {auch => 'xact_start DESC'},
4718 {substream => 1} # could be a large list
4722 return $U->fire_object_event(undef,
4723 'circ.format.history.print', $circs, $e->requestor->home_ou);
4726 $e->xact_begin if $for_clear;
4727 $conn->respond_complete(1) if $for_email; # no sense in waiting
4729 for my $circ (@$circs) {
4732 # events will be fired from action_trigger_runner
4733 $U->create_events_for_hook('circ.format.history.email',
4734 $circ, $e->editor->home_ou, undef, undef, 1);
4736 } elsif ($for_clear) {
4738 $e->delete_action_user_circ_history($circ)
4739 or return $e->die_event;
4742 $conn->respond($circ);
4755 __PACKAGE__->register_method(
4756 method => "user_visible_holds",
4757 api_name => "open-ils.actor.history.hold.visible",
4760 desc => 'Returns the set of opt-in visible holds',
4762 { desc => 'Authentication token', type => 'string'},
4763 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4764 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4767 desc => q/An object with 1 field: "hold"/,
4773 __PACKAGE__->register_method(
4774 method => "user_visible_holds",
4775 api_name => "open-ils.actor.history.hold.visible.print",
4778 desc => 'Returns printable output for the set of opt-in visible holds',
4780 { desc => 'Authentication token', type => 'string'},
4781 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4782 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4785 desc => q/An action_trigger.event object or error event./,
4791 __PACKAGE__->register_method(
4792 method => "user_visible_holds",
4793 api_name => "open-ils.actor.history.hold.visible.email",
4796 desc => 'Emails the set of opt-in visible holds to the requestor',
4798 { desc => 'Authentication token', type => 'string'},
4799 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4800 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4803 desc => q/undef, or event on error/
4808 sub user_visible_holds {
4809 my($self, $conn, $auth, $user_id, $options) = @_;
4812 my $for_print = ($self->api_name =~ /print/);
4813 my $for_email = ($self->api_name =~ /email/);
4814 my $e = new_editor(authtoken => $auth);
4815 return $e->event unless $e->checkauth;
4817 $user_id ||= $e->requestor->id;
4819 $options->{limit} ||= 50;
4820 $options->{offset} ||= 0;
4822 if($user_id != $e->requestor->id) {
4823 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4824 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4825 return $e->event unless $e->allowed($perm, $user->home_ou);
4828 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4830 my $data = $e->json_query({
4831 from => [$db_func, $user_id],
4832 limit => $$options{limit},
4833 offset => $$options{offset}
4835 # TODO: I only want IDs. code below didn't get me there
4836 # {"select":{"au":[{"column":"id", "result_field":"id",
4837 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4842 return undef unless @$data;
4846 # collect the batch of objects
4850 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4851 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4855 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4856 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4859 } elsif ($for_email) {
4861 $conn->respond_complete(1) if $for_email; # no sense in waiting
4869 my $hold = $e->retrieve_action_hold_request($id);
4870 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4871 # events will be fired from action_trigger_runner
4875 my $circ = $e->retrieve_action_circulation($id);
4876 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4877 # events will be fired from action_trigger_runner
4881 } else { # just give me the data please
4889 my $hold = $e->retrieve_action_hold_request($id);
4890 $conn->respond({hold => $hold});
4894 my $circ = $e->retrieve_action_circulation($id);
4897 summary => $U->create_circ_chain_summary($e, $id)
4906 __PACKAGE__->register_method(
4907 method => "user_saved_search_cud",
4908 api_name => "open-ils.actor.user.saved_search.cud",
4911 desc => 'Create/Update/Delete Access to user saved searches',
4913 { desc => 'Authentication token', type => 'string' },
4914 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4917 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4923 __PACKAGE__->register_method(
4924 method => "user_saved_search_cud",
4925 api_name => "open-ils.actor.user.saved_search.retrieve",
4928 desc => 'Retrieve a saved search object',
4930 { desc => 'Authentication token', type => 'string' },
4931 { desc => 'Saved Search ID', type => 'number' }
4934 desc => q/The saved search object, Event on error/,
4940 sub user_saved_search_cud {
4941 my( $self, $client, $auth, $search ) = @_;
4942 my $e = new_editor( authtoken=>$auth );
4943 return $e->die_event unless $e->checkauth;
4945 my $o_search; # prior version of the object, if any
4946 my $res; # to be returned
4948 # branch on the operation type
4950 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4952 # Get the old version, to check ownership
4953 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4954 or return $e->die_event;
4956 # You can't read somebody else's search
4957 return OpenILS::Event->new('BAD_PARAMS')
4958 unless $o_search->owner == $e->requestor->id;
4964 $e->xact_begin; # start an editor transaction
4966 if( $search->isnew ) { # Create
4968 # You can't create a search for somebody else
4969 return OpenILS::Event->new('BAD_PARAMS')
4970 unless $search->owner == $e->requestor->id;
4972 $e->create_actor_usr_saved_search( $search )
4973 or return $e->die_event;
4977 } elsif( $search->ischanged ) { # Update
4979 # You can't change ownership of a search
4980 return OpenILS::Event->new('BAD_PARAMS')
4981 unless $search->owner == $e->requestor->id;
4983 # Get the old version, to check ownership
4984 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4985 or return $e->die_event;
4987 # You can't update somebody else's search
4988 return OpenILS::Event->new('BAD_PARAMS')
4989 unless $o_search->owner == $e->requestor->id;
4992 $e->update_actor_usr_saved_search( $search )
4993 or return $e->die_event;
4997 } elsif( $search->isdeleted ) { # Delete
4999 # Get the old version, to check ownership
5000 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
5001 or return $e->die_event;
5003 # You can't delete somebody else's search
5004 return OpenILS::Event->new('BAD_PARAMS')
5005 unless $o_search->owner == $e->requestor->id;
5008 $e->delete_actor_usr_saved_search( $o_search )
5009 or return $e->die_event;
5020 __PACKAGE__->register_method(
5021 method => "get_barcodes",
5022 api_name => "open-ils.actor.get_barcodes"
5026 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
5027 my $e = new_editor(authtoken => $auth);
5028 return $e->event unless $e->checkauth;
5029 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
5031 my $db_result = $e->json_query(
5033 'evergreen.get_barcodes',
5034 $org_id, $context, $barcode,
5038 if($context =~ /actor/) {
5039 my $filter_result = ();
5041 foreach my $result (@$db_result) {
5042 if($result->{type} eq 'actor') {
5043 if($e->requestor->id != $result->{id}) {
5044 $patron = $e->retrieve_actor_user($result->{id});
5046 push(@$filter_result, $e->event);
5049 if($e->allowed('VIEW_USER', $patron->home_ou)) {
5050 push(@$filter_result, $result);
5053 push(@$filter_result, $e->event);
5057 push(@$filter_result, $result);
5061 push(@$filter_result, $result);
5064 return $filter_result;
5070 __PACKAGE__->register_method(
5071 method => 'address_alert_test',
5072 api_name => 'open-ils.actor.address_alert.test',
5074 desc => "Tests a set of address fields to determine if they match with an address_alert",
5076 {desc => 'Authentication token', type => 'string'},
5077 {desc => 'Org Unit', type => 'number'},
5078 {desc => 'Fields', type => 'hash'},
5080 return => {desc => 'List of matching address_alerts'}
5084 sub address_alert_test {
5085 my ($self, $client, $auth, $org_unit, $fields) = @_;
5086 return [] unless $fields and grep {$_} values %$fields;
5088 my $e = new_editor(authtoken => $auth);
5089 return $e->event unless $e->checkauth;
5090 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
5091 $org_unit ||= $e->requestor->ws_ou;
5093 my $alerts = $e->json_query({
5095 'actor.address_alert_matches',
5103 $$fields{post_code},
5104 $$fields{mailing_address},
5105 $$fields{billing_address}
5109 # map the json_query hashes to real objects
5111 map {$e->retrieve_actor_address_alert($_)}
5112 (map {$_->{id}} @$alerts)
5116 __PACKAGE__->register_method(
5117 method => "mark_users_contact_invalid",
5118 api_name => "open-ils.actor.invalidate.email",
5120 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",
5122 {desc => "Authentication token", type => "string"},
5123 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5124 {desc => "Additional note text (optional)", type => "string"},
5125 {desc => "penalty org unit ID (optional)", type => "number"},
5126 {desc => "Email address (optional)", type => "string"}
5128 return => {desc => "Event describing success or failure", type => "object"}
5132 __PACKAGE__->register_method(
5133 method => "mark_users_contact_invalid",
5134 api_name => "open-ils.actor.invalidate.day_phone",
5136 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",
5138 {desc => "Authentication token", type => "string"},
5139 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5140 {desc => "Additional note text (optional)", type => "string"},
5141 {desc => "penalty org unit ID (optional)", type => "number"},
5142 {desc => "Phone Number (optional)", type => "string"}
5144 return => {desc => "Event describing success or failure", type => "object"}
5148 __PACKAGE__->register_method(
5149 method => "mark_users_contact_invalid",
5150 api_name => "open-ils.actor.invalidate.evening_phone",
5152 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",
5154 {desc => "Authentication token", type => "string"},
5155 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5156 {desc => "Additional note text (optional)", type => "string"},
5157 {desc => "penalty org unit ID (optional)", type => "number"},
5158 {desc => "Phone Number (optional)", type => "string"}
5160 return => {desc => "Event describing success or failure", type => "object"}
5164 __PACKAGE__->register_method(
5165 method => "mark_users_contact_invalid",
5166 api_name => "open-ils.actor.invalidate.other_phone",
5168 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",
5170 {desc => "Authentication token", type => "string"},
5171 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5172 {desc => "Additional note text (optional)", type => "string"},
5173 {desc => "penalty org unit ID (optional, default to top of org tree)",
5175 {desc => "Phone Number (optional)", type => "string"}
5177 return => {desc => "Event describing success or failure", type => "object"}
5181 sub mark_users_contact_invalid {
5182 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5184 # This method invalidates an email address or a phone_number which
5185 # removes the bad email address or phone number, copying its contents
5186 # to a patron note, and institutes a standing penalty for "bad email"
5187 # or "bad phone number" which is cleared when the user is saved or
5188 # optionally only when the user is saved with an email address or
5189 # phone number (or staff manually delete the penalty).
5191 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5193 my $e = new_editor(authtoken => $auth, xact => 1);
5194 return $e->die_event unless $e->checkauth;
5197 if (defined $patron_id && $patron_id ne "") {
5198 $howfind = {usr => $patron_id};
5199 } elsif (defined $contact && $contact ne "") {
5200 $howfind = {$contact_type => $contact};
5202 # Error out if no patron id set or no contact is set.
5203 return OpenILS::Event->new('BAD_PARAMS');
5206 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5207 $e, $contact_type, $howfind,
5208 $addl_note, $penalty_ou, $e->requestor->id
5212 # Putting the following method in open-ils.actor is a bad fit, except in that
5213 # it serves an interface that lives under 'actor' in the templates directory,
5214 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5216 __PACKAGE__->register_method(
5217 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5218 method => "get_all_at_reactors_in_use",
5223 { name => 'authtoken', type => 'string' }
5226 desc => 'list of reactor names', type => 'array'
5231 sub get_all_at_reactors_in_use {
5232 my ($self, $conn, $auth) = @_;
5234 my $e = new_editor(authtoken => $auth);
5235 $e->checkauth or return $e->die_event;
5236 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5238 my $reactors = $e->json_query({
5240 atevdef => [{column => "reactor", transform => "distinct"}]
5242 from => {atevdef => {}}
5245 return $e->die_event unless ref $reactors eq "ARRAY";
5248 return [ map { $_->{reactor} } @$reactors ];
5251 __PACKAGE__->register_method(
5252 method => "filter_group_entry_crud",
5253 api_name => "open-ils.actor.filter_group_entry.crud",
5256 Provides CRUD access to filter group entry objects. These are not full accessible
5257 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5258 are not accessible via PCRUD (because they have no fields against which to link perms)
5261 {desc => "Authentication token", type => "string"},
5262 {desc => "Entry ID / Entry Object", type => "number"},
5263 {desc => "Additional note text (optional)", type => "string"},
5264 {desc => "penalty org unit ID (optional, default to top of org tree)",
5268 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5274 sub filter_group_entry_crud {
5275 my ($self, $conn, $auth, $arg) = @_;
5277 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5278 my $e = new_editor(authtoken => $auth, xact => 1);
5279 return $e->die_event unless $e->checkauth;
5285 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5286 or return $e->die_event;
5288 return $e->die_event unless $e->allowed(
5289 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5291 my $query = $arg->query;
5292 $query = $e->create_actor_search_query($query) or return $e->die_event;
5293 $arg->query($query->id);
5294 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5295 $entry->query($query);
5300 } elsif ($arg->ischanged) {
5302 my $entry = $e->retrieve_actor_search_filter_group_entry([
5305 flesh_fields => {asfge => ['grp']}
5307 ]) or return $e->die_event;
5309 return $e->die_event unless $e->allowed(
5310 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5312 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5313 $arg->query($arg->query->id);
5314 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5315 $arg->query($query);
5320 } elsif ($arg->isdeleted) {
5322 my $entry = $e->retrieve_actor_search_filter_group_entry([
5325 flesh_fields => {asfge => ['grp', 'query']}
5327 ]) or return $e->die_event;
5329 return $e->die_event unless $e->allowed(
5330 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5332 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5333 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5346 my $entry = $e->retrieve_actor_search_filter_group_entry([
5349 flesh_fields => {asfge => ['grp', 'query']}
5351 ]) or return $e->die_event;
5353 return $e->die_event unless $e->allowed(
5354 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5355 $entry->grp->owner);
5358 $entry->grp($entry->grp->id); # for consistency