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 sub update_penalties {
3032 my($self, $conn, $auth, $user_id) = @_;
3033 my $e = new_editor(authtoken=>$auth, xact => 1);
3034 return $e->die_event unless $e->checkauth;
3035 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3036 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3037 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3038 return $evt if $evt;
3044 __PACKAGE__->register_method(
3045 method => "apply_penalty",
3046 api_name => "open-ils.actor.user.penalty.apply"
3050 my($self, $conn, $auth, $penalty, $msg) = @_;
3054 my $e = new_editor(authtoken=>$auth, xact => 1);
3055 return $e->die_event unless $e->checkauth;
3057 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3058 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3060 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3062 my $ctx_org = $penalty->org_unit; # csp org_depth is now considered in the UI for the org drop-down menu
3064 if (($msg->{title} || $msg->{message}) && ($msg->{title} ne '' || $msg->{message} ne '')) {
3065 my $aum = Fieldmapper::actor::usr_message->new;
3067 $aum->create_date('now');
3068 $aum->sending_lib($e->requestor->ws_ou);
3069 $aum->title($msg->{title});
3070 $aum->usr($penalty->usr);
3071 $aum->message($msg->{message});
3072 $aum->pub($msg->{pub});
3074 $aum = $e->create_actor_usr_message($aum)
3075 or return $e->die_event;
3077 $penalty->usr_message($aum->id);
3080 $penalty->org_unit($ctx_org);
3081 $penalty->staff($e->requestor->id);
3082 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3085 return $penalty->id;
3088 __PACKAGE__->register_method(
3089 method => "modify_penalty",
3090 api_name => "open-ils.actor.user.penalty.modify"
3093 sub modify_penalty {
3094 my($self, $conn, $auth, $penalty, $usr_msg) = @_;
3096 my $e = new_editor(authtoken=>$auth, xact => 1);
3097 return $e->die_event unless $e->checkauth;
3099 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3100 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3102 $usr_msg->editor($e->requestor->id);
3103 $usr_msg->edit_date('now');
3105 if ($usr_msg->isnew) {
3106 $usr_msg = $e->create_actor_usr_message($usr_msg)
3107 or return $e->die_event;
3108 $penalty->usr_message($usr_msg->id);
3110 $usr_msg = $e->update_actor_usr_message($usr_msg)
3111 or return $e->die_event;
3114 if ($penalty->isnew) {
3115 $penalty = $e->create_actor_user_standing_penalty($penalty)
3116 or return $e->die_event;
3118 $penalty = $e->update_actor_user_standing_penalty($penalty)
3119 or return $e->die_event;
3126 __PACKAGE__->register_method(
3127 method => "remove_penalty",
3128 api_name => "open-ils.actor.user.penalty.remove"
3131 sub remove_penalty {
3132 my($self, $conn, $auth, $penalty) = @_;
3133 my $e = new_editor(authtoken=>$auth, xact => 1);
3134 return $e->die_event unless $e->checkauth;
3135 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3136 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3138 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3143 __PACKAGE__->register_method(
3144 method => "update_penalty_note",
3145 api_name => "open-ils.actor.user.penalty.note.update"
3148 sub update_penalty_note {
3149 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3150 my $e = new_editor(authtoken=>$auth, xact => 1);
3151 return $e->die_event unless $e->checkauth;
3152 for my $penalty_id (@$penalty_ids) {
3153 my $penalty = $e->search_actor_user_standing_penalty([
3154 { id => $penalty_id },
3156 flesh_fields => {aum => ['usr_message']}
3159 if (! $penalty ) { return $e->die_event; }
3160 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3161 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3163 my $aum = $penalty->usr_message();
3165 $aum = Fieldmapper::actor::usr_message->new;
3167 $aum->create_date('now');
3168 $aum->sending_lib($e->requestor->ws_ou);
3170 $aum->usr($penalty->usr);
3171 $aum->message($note);
3175 $aum = $e->create_actor_usr_message($aum)
3176 or return $e->die_event;
3178 $penalty->usr_message($aum->id);
3179 $penalty->ischanged(1);
3180 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3182 $aum = $e->retrieve_actor_usr_message($aum) or return $e->die_event;
3183 $aum->message($note); $aum->ischanged(1);
3184 $e->update_actor_usr_message($aum) or return $e->die_event;
3191 __PACKAGE__->register_method(
3192 method => "ranged_penalty_thresholds",
3193 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3197 sub ranged_penalty_thresholds {
3198 my($self, $conn, $auth, $context_org) = @_;
3199 my $e = new_editor(authtoken=>$auth);
3200 return $e->event unless $e->checkauth;
3201 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3202 my $list = $e->search_permission_grp_penalty_threshold([
3203 {org_unit => $U->get_org_ancestors($context_org)},
3204 {order_by => {pgpt => 'id'}}
3206 $conn->respond($_) for @$list;
3212 __PACKAGE__->register_method(
3213 method => "user_retrieve_fleshed_by_id",
3215 api_name => "open-ils.actor.user.fleshed.retrieve",
3218 sub user_retrieve_fleshed_by_id {
3219 my( $self, $client, $auth, $user_id, $fields ) = @_;
3220 my $e = new_editor(authtoken => $auth);
3221 return $e->event unless $e->checkauth;
3223 if( $e->requestor->id != $user_id ) {
3224 return $e->event unless $e->allowed('VIEW_USER');
3231 "standing_penalties",
3239 return new_flesh_user($user_id, $fields, $e);
3243 sub new_flesh_user {
3246 my $fields = shift || [];
3249 my $fetch_penalties = 0;
3250 if(grep {$_ eq 'standing_penalties'} @$fields) {
3251 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3252 $fetch_penalties = 1;
3255 my $fetch_notes = 0;
3256 if(grep {$_ eq 'notes'} @$fields) {
3257 $fields = [grep {$_ ne 'notes'} @$fields];
3261 my $fetch_usr_act = 0;
3262 if(grep {$_ eq 'usr_activity'} @$fields) {
3263 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3267 my $user = $e->retrieve_actor_user(
3272 "flesh_fields" => { "au" => $fields }
3275 ) or return $e->die_event;
3278 if( grep { $_ eq 'addresses' } @$fields ) {
3280 $user->addresses([]) unless @{$user->addresses};
3281 # don't expose "replaced" addresses by default
3282 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3284 if( ref $user->billing_address ) {
3285 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3286 push( @{$user->addresses}, $user->billing_address );
3290 if( ref $user->mailing_address ) {
3291 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3292 push( @{$user->addresses}, $user->mailing_address );
3297 if($fetch_penalties) {
3298 # grab the user penalties ranged for this location
3299 $user->standing_penalties(
3300 $e->search_actor_user_standing_penalty([
3303 {stop_date => undef},
3304 {stop_date => {'>' => 'now'}}
3306 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3309 flesh_fields => {ausp => ['standing_penalty','usr_message']}
3316 # grab notes (now actor.usr_message_penalty) that have not hit their stop_date
3317 # NOTE: This is a view that already filters out deleted messages that are not
3318 # attached to a penalty
3320 @{ $e->search_actor_usr_message_penalty([
3323 {stop_date => undef},
3324 {stop_date => {'>' => 'now'}}
3331 # retrieve the most recent usr_activity entry
3332 if ($fetch_usr_act) {
3334 # max number to return for simple patron fleshing
3335 my $limit = $U->ou_ancestor_setting_value(
3336 $e->requestor->ws_ou,
3337 'circ.patron.usr_activity_retrieve.max');
3341 flesh_fields => {auact => ['etype']},
3342 order_by => {auact => 'event_time DESC'},
3345 # 0 == none, <0 == return all
3346 $limit = 1 unless defined $limit;
3347 $opts->{limit} = $limit if $limit > 0;
3349 $user->usr_activity(
3351 [] : # skip the DB call
3352 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3357 $user->clear_passwd();
3364 __PACKAGE__->register_method(
3365 method => "user_retrieve_parts",
3366 api_name => "open-ils.actor.user.retrieve.parts",
3369 sub user_retrieve_parts {
3370 my( $self, $client, $auth, $user_id, $fields ) = @_;
3371 my $e = new_editor(authtoken => $auth);
3372 return $e->event unless $e->checkauth;
3373 $user_id ||= $e->requestor->id;
3374 if( $e->requestor->id != $user_id ) {
3375 return $e->event unless $e->allowed('VIEW_USER');
3378 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3379 push(@resp, $user->$_()) for(@$fields);
3385 __PACKAGE__->register_method(
3386 method => 'user_opt_in_enabled',
3387 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3388 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3391 sub user_opt_in_enabled {
3392 my($self, $conn) = @_;
3393 my $sc = OpenSRF::Utils::SettingsClient->new;
3394 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3399 __PACKAGE__->register_method(
3400 method => 'user_opt_in_at_org',
3401 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3403 @param $auth The auth token
3404 @param user_id The ID of the user to test
3405 @return 1 if the user has opted in at the specified org,
3406 2 if opt-in is disallowed for the user's home org,
3407 event on error, and 0 otherwise. /
3409 sub user_opt_in_at_org {
3410 my($self, $conn, $auth, $user_id) = @_;
3412 # see if we even need to enforce the opt-in value
3413 return 1 unless user_opt_in_enabled($self);
3415 my $e = new_editor(authtoken => $auth);
3416 return $e->event unless $e->checkauth;
3418 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3419 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3421 my $ws_org = $e->requestor->ws_ou;
3422 # user is automatically opted-in if they are from the local org
3423 return 1 if $user->home_ou eq $ws_org;
3425 # get the boundary setting
3426 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3428 # auto opt in if user falls within the opt boundary
3429 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3431 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3433 # check whether opt-in is restricted at the user's home library
3434 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3435 if ($opt_restrict_depth) {
3436 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3437 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3439 # opt-in is disallowed unless the workstation org is within the home
3440 # library's opt-in scope
3441 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3444 my $vals = $e->search_actor_usr_org_unit_opt_in(
3445 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3451 __PACKAGE__->register_method(
3452 method => 'create_user_opt_in_at_org',
3453 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3455 @param $auth The auth token
3456 @param user_id The ID of the user to test
3457 @return The ID of the newly created object, event on error./
3460 sub create_user_opt_in_at_org {
3461 my($self, $conn, $auth, $user_id, $org_id) = @_;
3463 my $e = new_editor(authtoken => $auth, xact=>1);
3464 return $e->die_event unless $e->checkauth;
3466 # if a specific org unit wasn't passed in, get one based on the defaults;
3468 my $wsou = $e->requestor->ws_ou;
3469 # get the default opt depth
3470 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3471 # get the org unit at that depth
3472 my $org = $e->json_query({
3473 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3474 $org_id = $org->{id};
3477 # fall back to the workstation OU, the pre-opt-in-boundary way
3478 $org_id = $e->requestor->ws_ou;
3481 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3482 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3484 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3486 $opt_in->org_unit($org_id);
3487 $opt_in->usr($user_id);
3488 $opt_in->staff($e->requestor->id);
3489 $opt_in->opt_in_ts('now');
3490 $opt_in->opt_in_ws($e->requestor->wsid);
3492 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3493 or return $e->die_event;
3501 __PACKAGE__->register_method (
3502 method => 'retrieve_org_hours',
3503 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3505 Returns the hours of operation for a specified org unit
3506 @param authtoken The login session key
3507 @param org_id The org_unit ID
3511 sub retrieve_org_hours {
3512 my($self, $conn, $auth, $org_id) = @_;
3513 my $e = new_editor(authtoken => $auth);
3514 return $e->die_event unless $e->checkauth;
3515 $org_id ||= $e->requestor->ws_ou;
3516 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3520 __PACKAGE__->register_method (
3521 method => 'verify_user_password',
3522 api_name => 'open-ils.actor.verify_user_password',
3524 Given a barcode or username and the MD5 encoded password,
3525 The password can also be passed without the MD5 hashing.
3526 returns 1 if the password is correct. Returns 0 otherwise.
3530 sub verify_user_password {
3531 my($self, $conn, $auth, $barcode, $username, $password, $pass_nohash) = @_;
3532 my $e = new_editor(authtoken => $auth);
3533 return $e->die_event unless $e->checkauth;
3535 my $user_by_barcode;
3536 my $user_by_username;
3538 my $card = $e->search_actor_card([
3539 {barcode => $barcode},
3540 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3541 $user_by_barcode = $card->usr;
3542 $user = $user_by_barcode;
3545 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3546 $user = $user_by_username;
3548 return 0 if (!$user || $U->is_true($user->deleted));
3549 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3550 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3553 return $U->verify_migrated_user_password($e, $user->id, $pass_nohash);
3555 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3559 __PACKAGE__->register_method (
3560 method => 'retrieve_usr_id_via_barcode_or_usrname',
3561 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3563 Given a barcode or username returns the id for the user or
3568 sub retrieve_usr_id_via_barcode_or_usrname {
3569 my($self, $conn, $auth, $barcode, $username) = @_;
3570 my $e = new_editor(authtoken => $auth);
3571 return $e->die_event unless $e->checkauth;
3572 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3574 my $user_by_barcode;
3575 my $user_by_username;
3576 $logger->info("$id_as_barcode is the ID as BARCODE");
3578 my $card = $e->search_actor_card([
3579 {barcode => $barcode},
3580 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3581 if ($id_as_barcode =~ /^t/i) {
3583 $user = $e->retrieve_actor_user($barcode);
3584 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3586 $user_by_barcode = $card->usr;
3587 $user = $user_by_barcode;
3590 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3591 $user_by_barcode = $card->usr;
3592 $user = $user_by_barcode;
3597 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3599 $user = $user_by_username;
3601 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3602 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3603 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3608 __PACKAGE__->register_method (
3609 method => 'merge_users',
3610 api_name => 'open-ils.actor.user.merge',
3613 Given a list of source users and destination user, transfer all data from the source
3614 to the dest user and delete the source user. All user related data is
3615 transferred, including circulations, holds, bookbags, etc.
3621 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3622 my $e = new_editor(xact => 1, authtoken => $auth);
3623 return $e->die_event unless $e->checkauth;
3625 # disallow the merge if any subordinate accounts are in collections
3626 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3627 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3629 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3630 if $master_id == $e->requestor->id;
3632 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3633 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3634 return $evt if $evt;
3636 my $del_addrs = ($U->ou_ancestor_setting_value(
3637 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3638 my $del_cards = ($U->ou_ancestor_setting_value(
3639 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3640 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3641 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3643 for my $src_id (@$user_ids) {
3645 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3646 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3647 return $evt if $evt;
3649 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3650 if $src_id == $e->requestor->id;
3652 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3653 if($src_user->home_ou ne $master_user->home_ou) {
3654 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3657 return $e->die_event unless
3658 $e->json_query({from => [
3673 __PACKAGE__->register_method (
3674 method => 'approve_user_address',
3675 api_name => 'open-ils.actor.user.pending_address.approve',
3682 sub approve_user_address {
3683 my($self, $conn, $auth, $addr) = @_;
3684 my $e = new_editor(xact => 1, authtoken => $auth);
3685 return $e->die_event unless $e->checkauth;
3687 # if the caller passes an address object, assume they want to
3688 # update it first before approving it
3689 $e->update_actor_user_address($addr) or return $e->die_event;
3691 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3693 my $user = $e->retrieve_actor_user($addr->usr);
3694 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3695 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3696 or return $e->die_event;
3698 return [values %$result]->[0];
3702 __PACKAGE__->register_method (
3703 method => 'retrieve_friends',
3704 api_name => 'open-ils.actor.friends.retrieve',
3707 returns { confirmed: [], pending_out: [], pending_in: []}
3708 pending_out are users I'm requesting friendship with
3709 pending_in are users requesting friendship with me
3714 sub retrieve_friends {
3715 my($self, $conn, $auth, $user_id, $options) = @_;
3716 my $e = new_editor(authtoken => $auth);
3717 return $e->event unless $e->checkauth;
3718 $user_id ||= $e->requestor->id;
3720 if($user_id != $e->requestor->id) {
3721 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3722 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3725 return OpenILS::Application::Actor::Friends->retrieve_friends(
3726 $e, $user_id, $options);
3731 __PACKAGE__->register_method (
3732 method => 'apply_friend_perms',
3733 api_name => 'open-ils.actor.friends.perms.apply',
3739 sub apply_friend_perms {
3740 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3741 my $e = new_editor(authtoken => $auth, xact => 1);
3742 return $e->die_event unless $e->checkauth;
3744 if($user_id != $e->requestor->id) {
3745 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3746 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3749 for my $perm (@perms) {
3751 OpenILS::Application::Actor::Friends->apply_friend_perm(
3752 $e, $user_id, $delegate_id, $perm);
3753 return $evt if $evt;
3761 __PACKAGE__->register_method (
3762 method => 'update_user_pending_address',
3763 api_name => 'open-ils.actor.user.address.pending.cud'
3766 sub update_user_pending_address {
3767 my($self, $conn, $auth, $addr) = @_;
3768 my $e = new_editor(authtoken => $auth, xact => 1);
3769 return $e->die_event unless $e->checkauth;
3771 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3772 if($addr->usr != $e->requestor->id) {
3773 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3777 $e->create_actor_user_address($addr) or return $e->die_event;
3778 } elsif($addr->isdeleted) {
3779 $e->delete_actor_user_address($addr) or return $e->die_event;
3781 $e->update_actor_user_address($addr) or return $e->die_event;
3785 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3791 __PACKAGE__->register_method (
3792 method => 'user_events',
3793 api_name => 'open-ils.actor.user.events.circ',
3796 __PACKAGE__->register_method (
3797 method => 'user_events',
3798 api_name => 'open-ils.actor.user.events.ahr',
3803 my($self, $conn, $auth, $user_id, $filters) = @_;
3804 my $e = new_editor(authtoken => $auth);
3805 return $e->event unless $e->checkauth;
3807 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3808 my $user_field = 'usr';
3811 $filters->{target} = {
3812 select => { $obj_type => ['id'] },
3814 where => {usr => $user_id}
3817 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3818 if($e->requestor->id != $user_id) {
3819 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3822 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3823 my $req = $ses->request('open-ils.trigger.events_by_target',
3824 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3826 while(my $resp = $req->recv) {
3827 my $val = $resp->content;
3828 my $tgt = $val->target;
3830 if($obj_type eq 'circ') {
3831 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3833 } elsif($obj_type eq 'ahr') {
3834 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3835 if $tgt->current_copy;
3838 $conn->respond($val) if $val;
3844 __PACKAGE__->register_method (
3845 method => 'copy_events',
3846 api_name => 'open-ils.actor.copy.events.circ',
3849 __PACKAGE__->register_method (
3850 method => 'copy_events',
3851 api_name => 'open-ils.actor.copy.events.ahr',
3856 my($self, $conn, $auth, $copy_id, $filters) = @_;
3857 my $e = new_editor(authtoken => $auth);
3858 return $e->event unless $e->checkauth;
3860 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3862 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3864 my $copy_field = 'target_copy';
3865 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3868 $filters->{target} = {
3869 select => { $obj_type => ['id'] },
3871 where => {$copy_field => $copy_id}
3875 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3876 my $req = $ses->request('open-ils.trigger.events_by_target',
3877 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3879 while(my $resp = $req->recv) {
3880 my $val = $resp->content;
3881 my $tgt = $val->target;
3883 my $user = $e->retrieve_actor_user($tgt->usr);
3884 if($e->requestor->id != $user->id) {
3885 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3888 $tgt->$copy_field($copy);
3891 $conn->respond($val) if $val;
3898 __PACKAGE__->register_method (
3899 method => 'get_itemsout_notices',
3900 api_name => 'open-ils.actor.user.itemsout.notices',
3904 desc => q/Summary counts of circulat notices/,
3906 {desc => 'authtoken', type => 'string'},
3907 {desc => 'circulation identifiers', type => 'array of numbers'}
3909 return => q/Stream of summary objects/
3913 sub get_itemsout_notices {
3914 my ($self, $client, $auth, $circ_ids) = @_;
3916 my $e = new_editor(authtoken => $auth);
3917 return $e->event unless $e->checkauth;
3919 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3921 for my $circ_id (@$circ_ids) {
3922 my $resp = get_itemsout_notices_impl($e, $circ_id);
3924 if ($U->is_event($resp)) {
3925 $client->respond($resp);
3929 $client->respond({circ_id => $circ_id, %$resp});
3937 sub get_itemsout_notices_impl {
3938 my ($e, $circId) = @_;
3940 my $requestorId = $e->requestor->id;
3942 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3944 my $patronId = $circ->usr;
3946 if( $patronId ne $requestorId ){
3947 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3948 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3951 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3952 #my $req = $ses->request('open-ils.trigger.events_by_target',
3953 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3954 # ^ Above removed in favor of faster json_query.
3957 # select complete_time
3958 # from action_trigger.event atev
3959 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3960 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3961 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3964 my $ctx_loc = $e->requestor->ws_ou;
3965 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3966 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3969 select => { atev => ["complete_time"] },
3972 atevdef => { field => "id",fkey => "event_def"}
3976 "+atevdef" => { active => 't', hook => 'checkout.due' },
3977 "+atev" => { target => $circId, state => 'complete' }
3981 if ($exclude_courtesy_notices){
3982 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3985 my %resblob = ( numNotices => 0, lastDt => undef );
3987 my $res = $e->json_query($query);
3988 for my $ndate (@$res) {
3989 $resblob{numNotices}++;
3990 if( !defined $resblob{lastDt}){
3991 $resblob{lastDt} = $$ndate{complete_time};
3994 if ($resblob{lastDt} lt $$ndate{complete_time}){
3995 $resblob{lastDt} = $$ndate{complete_time};
4002 __PACKAGE__->register_method (
4003 method => 'update_events',
4004 api_name => 'open-ils.actor.user.event.cancel.batch',
4007 __PACKAGE__->register_method (
4008 method => 'update_events',
4009 api_name => 'open-ils.actor.user.event.reset.batch',
4014 my($self, $conn, $auth, $event_ids) = @_;
4015 my $e = new_editor(xact => 1, authtoken => $auth);
4016 return $e->die_event unless $e->checkauth;
4019 for my $id (@$event_ids) {
4021 # do a little dance to determine what user we are ultimately affecting
4022 my $event = $e->retrieve_action_trigger_event([
4025 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
4027 ]) or return $e->die_event;
4030 if($event->event_def->hook->core_type eq 'circ') {
4031 $user_id = $e->retrieve_action_circulation($event->target)->usr;
4032 } elsif($event->event_def->hook->core_type eq 'ahr') {
4033 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
4038 my $user = $e->retrieve_actor_user($user_id);
4039 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
4041 if($self->api_name =~ /cancel/) {
4042 $event->state('invalid');
4043 } elsif($self->api_name =~ /reset/) {
4044 $event->clear_start_time;
4045 $event->clear_update_time;
4046 $event->state('pending');
4049 $e->update_action_trigger_event($event) or return $e->die_event;
4050 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
4054 return {complete => 1};
4058 __PACKAGE__->register_method (
4059 method => 'really_delete_user',
4060 api_name => 'open-ils.actor.user.delete.override',
4061 signature => q/@see open-ils.actor.user.delete/
4064 __PACKAGE__->register_method (
4065 method => 'really_delete_user',
4066 api_name => 'open-ils.actor.user.delete',
4068 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
4069 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
4070 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
4071 dest_usr_id is only required when deleting a user that performs staff functions.
4075 sub really_delete_user {
4076 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
4077 my $e = new_editor(authtoken => $auth, xact => 1);
4078 return $e->die_event unless $e->checkauth;
4079 $oargs = { all => 1 } unless defined $oargs;
4081 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
4082 my $open_bills = $e->json_query({
4083 select => { mbts => ['id'] },
4086 xact_finish => { '=' => undef },
4087 usr => { '=' => $user_id },
4089 }) or return $e->die_event;
4091 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
4093 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
4095 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
4096 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
4097 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
4099 # No deleting yourself - UI is supposed to stop you first, though.
4100 return $e->die_event unless $e->requestor->id != $user->id;
4101 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4102 # Check if you are allowed to mess with this patron permission group at all
4103 my $evt = group_perm_failed($e, $e->requestor, $user);
4104 return $e->die_event($evt) if $evt;
4105 my $stat = $e->json_query(
4106 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4107 or return $e->die_event;
4113 __PACKAGE__->register_method (
4114 method => 'user_payments',
4115 api_name => 'open-ils.actor.user.payments.retrieve',
4118 Returns all payments for a given user. Default order is newest payments first.
4119 @param auth Authentication token
4120 @param user_id The user ID
4121 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4126 my($self, $conn, $auth, $user_id, $filters) = @_;
4129 my $e = new_editor(authtoken => $auth);
4130 return $e->die_event unless $e->checkauth;
4132 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4133 return $e->event unless
4134 $e->requestor->id == $user_id or
4135 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4137 # Find all payments for all transactions for user $user_id
4139 select => {mp => ['id']},
4144 select => {mbt => ['id']},
4146 where => {usr => $user_id}
4151 { # by default, order newest payments first
4153 field => 'payment_ts',
4156 # secondary sort in ID as a tie-breaker, since payments created
4157 # within the same transaction will have identical payment_ts's
4164 for (qw/order_by limit offset/) {
4165 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4168 if(defined $filters->{where}) {
4169 foreach (keys %{$filters->{where}}) {
4170 # don't allow the caller to expand the result set to other users
4171 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4175 my $payment_ids = $e->json_query($query);
4176 for my $pid (@$payment_ids) {
4177 my $pay = $e->retrieve_money_payment([
4182 mbt => ['summary', 'circulation', 'grocery'],
4183 circ => ['target_copy'],
4184 acp => ['call_number'],
4192 xact_type => $pay->xact->summary->xact_type,
4193 last_billing_type => $pay->xact->summary->last_billing_type,
4196 if($pay->xact->summary->xact_type eq 'circulation') {
4197 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4198 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4201 $pay->xact($pay->xact->id); # de-flesh
4202 $conn->respond($resp);
4210 __PACKAGE__->register_method (
4211 method => 'negative_balance_users',
4212 api_name => 'open-ils.actor.users.negative_balance',
4215 Returns all users that have an overall negative balance
4216 @param auth Authentication token
4217 @param org_id The context org unit as an ID or list of IDs. This will be the home
4218 library of the user. If no org_unit is specified, no org unit filter is applied
4222 sub negative_balance_users {
4223 my($self, $conn, $auth, $org_id, $options) = @_;
4226 $options->{limit} = 1000 unless $options->{limit};
4227 $options->{offset} = 0 unless $options->{offset};
4229 my $e = new_editor(authtoken => $auth);
4230 return $e->die_event unless $e->checkauth;
4231 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4235 mous => ['usr', 'balance_owed'],
4238 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4239 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4256 where => {'+mous' => {balance_owed => {'<' => 0}}},
4257 offset => $options->{offset},
4258 limit => $options->{limit},
4259 order_by => [{class => 'mous', field => 'usr'}]
4262 $org_id = $U->get_org_descendants($org_id) if $options->{org_descendants};
4264 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4266 my $list = $e->json_query($query, {timeout => 600});
4268 for my $data (@$list) {
4270 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4271 balance_owed => $data->{balance_owed},
4272 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4279 __PACKAGE__->register_method(
4280 method => "request_password_reset",
4281 api_name => "open-ils.actor.patron.password_reset.request",
4283 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4284 "method for changing a user's password. The UUID token is distributed via A/T " .
4285 "templates (i.e. email to the user).",
4287 { desc => 'user_id_type', type => 'string' },
4288 { desc => 'user_id', type => 'string' },
4289 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4291 return => {desc => '1 on success, Event on error'}
4294 sub request_password_reset {
4295 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4297 # Check to see if password reset requests are already being throttled:
4298 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4300 my $e = new_editor(xact => 1);
4303 # Get the user, if any, depending on the input value
4304 if ($user_id_type eq 'username') {
4305 $user = $e->search_actor_user({usrname => $user_id})->[0];
4308 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4310 } elsif ($user_id_type eq 'barcode') {
4311 my $card = $e->search_actor_card([
4312 {barcode => $user_id},
4313 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4316 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4321 # If the user doesn't have an email address, we can't help them
4322 if (!$user->email) {
4324 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4327 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4328 if ($email_must_match) {
4329 if (lc($user->email) ne lc($email)) {
4330 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4334 _reset_password_request($conn, $e, $user);
4337 # Once we have the user, we can issue the password reset request
4338 # XXX Add a wrapper method that accepts barcode + email input
4339 sub _reset_password_request {
4340 my ($conn, $e, $user) = @_;
4342 # 1. Get throttle threshold and time-to-live from OU_settings
4343 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4344 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4346 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4348 # 2. Get time of last request and number of active requests (num_active)
4349 my $active_requests = $e->json_query({
4355 transform => 'COUNT'
4358 column => 'request_time',
4364 has_been_reset => { '=' => 'f' },
4365 request_time => { '>' => $threshold_time }
4369 # Guard against no active requests
4370 if ($active_requests->[0]->{'request_time'}) {
4371 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4372 my $now = DateTime::Format::ISO8601->new();
4374 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4375 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4376 ($last_request->add_duration('1 minute') > $now)) {
4377 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4379 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4383 # TODO Check to see if the user is in a password-reset-restricted group
4385 # Otherwise, go ahead and try to get the user.
4387 # Check the number of active requests for this user
4388 $active_requests = $e->json_query({
4394 transform => 'COUNT'
4399 usr => { '=' => $user->id },
4400 has_been_reset => { '=' => 'f' },
4401 request_time => { '>' => $threshold_time }
4405 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4407 # if less than or equal to per-user threshold, proceed; otherwise, return event
4408 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4409 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4411 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4414 # Create the aupr object and insert into the database
4415 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4416 my $uuid = create_uuid_as_string(UUID_V4);
4417 $reset_request->uuid($uuid);
4418 $reset_request->usr($user->id);
4420 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4423 # Create an event to notify user of the URL to reset their password
4425 # Can we stuff this in the user_data param for trigger autocreate?
4426 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4428 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4429 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4432 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4437 __PACKAGE__->register_method(
4438 method => "commit_password_reset",
4439 api_name => "open-ils.actor.patron.password_reset.commit",
4441 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4442 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4443 "with the supplied password.",
4445 { desc => 'uuid', type => 'string' },
4446 { desc => 'password', type => 'string' },
4448 return => {desc => '1 on success, Event on error'}
4451 sub commit_password_reset {
4452 my($self, $conn, $uuid, $password) = @_;
4454 # Check to see if password reset requests are already being throttled:
4455 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4456 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4457 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4459 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4462 my $e = new_editor(xact => 1);
4464 my $aupr = $e->search_actor_usr_password_reset({
4471 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4473 my $user_id = $aupr->[0]->usr;
4474 my $user = $e->retrieve_actor_user($user_id);
4476 # Ensure we're still within the TTL for the request
4477 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4478 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4479 if ($threshold < DateTime->now(time_zone => 'local')) {
4481 $logger->info("Password reset request needed to be submitted before $threshold");
4482 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4485 # Check complexity of password against OU-defined regex
4486 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4490 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4491 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4492 $is_strong = check_password_strength_custom($password, $pw_regex);
4494 $is_strong = check_password_strength_default($password);
4499 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4502 # All is well; update the password
4503 modify_migrated_user_password($e, $user->id, $password);
4505 # And flag that this password reset request has been honoured
4506 $aupr->[0]->has_been_reset('t');
4507 $e->update_actor_usr_password_reset($aupr->[0]);
4513 sub check_password_strength_default {
4514 my $password = shift;
4515 # Use the default set of checks
4516 if ( (length($password) < 7) or
4517 ($password !~ m/.*\d+.*/) or
4518 ($password !~ m/.*[A-Za-z]+.*/)
4525 sub check_password_strength_custom {
4526 my ($password, $pw_regex) = @_;
4528 $pw_regex = qr/$pw_regex/;
4529 if ($password !~ /$pw_regex/) {
4535 __PACKAGE__->register_method(
4536 method => "fire_test_notification",
4537 api_name => "open-ils.actor.event.test_notification"
4540 sub fire_test_notification {
4541 my($self, $conn, $auth, $args) = @_;
4542 my $e = new_editor(authtoken => $auth);
4543 return $e->event unless $e->checkauth;
4544 if ($e->requestor->id != $$args{target}) {
4545 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4546 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4549 my $event_hook = $$args{hook} or return $e->event;
4550 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4552 my $usr = $e->retrieve_actor_user($$args{target});
4553 return $e->event unless $usr;
4555 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4559 __PACKAGE__->register_method(
4560 method => "event_def_opt_in_settings",
4561 api_name => "open-ils.actor.event_def.opt_in.settings",
4564 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4566 { desc => 'Authentication token', type => 'string'},
4568 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4573 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4580 sub event_def_opt_in_settings {
4581 my($self, $conn, $auth, $org_id) = @_;
4582 my $e = new_editor(authtoken => $auth);
4583 return $e->event unless $e->checkauth;
4585 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4586 return $e->event unless
4587 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4589 $org_id = $e->requestor->home_ou;
4592 # find all config.user_setting_type's related to event_defs for the requested org unit
4593 my $types = $e->json_query({
4594 select => {cust => ['name']},
4595 from => {atevdef => 'cust'},
4598 owner => $U->get_org_ancestors($org_id), # context org plus parents
4605 $conn->respond($_) for
4606 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4613 __PACKAGE__->register_method(
4614 method => "user_circ_history",
4615 api_name => "open-ils.actor.history.circ",
4619 desc => 'Returns user circ history objects for the calling user',
4621 { desc => 'Authentication token', type => 'string'},
4622 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4625 desc => q/Stream of 'auch' circ history objects/,
4631 __PACKAGE__->register_method(
4632 method => "user_circ_history",
4633 api_name => "open-ils.actor.history.circ.clear",
4636 desc => 'Delete all user circ history entries for the calling user',
4638 { desc => 'Authentication token', type => 'string'},
4639 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4642 desc => q/1 on success, event on error/,
4648 __PACKAGE__->register_method(
4649 method => "user_circ_history",
4650 api_name => "open-ils.actor.history.circ.print",
4653 desc => q/Returns printable output for the caller's circ history objects/,
4655 { desc => 'Authentication token', type => 'string'},
4656 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4659 desc => q/An action_trigger.event object or error event./,
4665 __PACKAGE__->register_method(
4666 method => "user_circ_history",
4667 api_name => "open-ils.actor.history.circ.email",
4670 desc => q/Emails the caller's circ history/,
4672 { desc => 'Authentication token', type => 'string'},
4673 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4674 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4677 desc => q/undef, or event on error/
4682 sub user_circ_history {
4683 my ($self, $conn, $auth, $options) = @_;
4686 my $for_print = ($self->api_name =~ /print/);
4687 my $for_email = ($self->api_name =~ /email/);
4688 my $for_clear = ($self->api_name =~ /clear/);
4690 # No perm check is performed. Caller may only access his/her own
4691 # circ history entries.
4692 my $e = new_editor(authtoken => $auth);
4693 return $e->event unless $e->checkauth;
4696 if (!$for_clear) { # clear deletes all
4697 $limits{offset} = $options->{offset} if defined $options->{offset};
4698 $limits{limit} = $options->{limit} if defined $options->{limit};
4701 my %circ_id_filter = $options->{circ_ids} ?
4702 (id => $options->{circ_ids}) : ();
4704 my $circs = $e->search_action_user_circ_history([
4705 { usr => $e->requestor->id,
4708 { # order newest to oldest by default
4709 order_by => {auch => 'xact_start DESC'},
4712 {substream => 1} # could be a large list
4716 return $U->fire_object_event(undef,
4717 'circ.format.history.print', $circs, $e->requestor->home_ou);
4720 $e->xact_begin if $for_clear;
4721 $conn->respond_complete(1) if $for_email; # no sense in waiting
4723 for my $circ (@$circs) {
4726 # events will be fired from action_trigger_runner
4727 $U->create_events_for_hook('circ.format.history.email',
4728 $circ, $e->editor->home_ou, undef, undef, 1);
4730 } elsif ($for_clear) {
4732 $e->delete_action_user_circ_history($circ)
4733 or return $e->die_event;
4736 $conn->respond($circ);
4749 __PACKAGE__->register_method(
4750 method => "user_visible_holds",
4751 api_name => "open-ils.actor.history.hold.visible",
4754 desc => 'Returns the set of opt-in visible holds',
4756 { desc => 'Authentication token', type => 'string'},
4757 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4758 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4761 desc => q/An object with 1 field: "hold"/,
4767 __PACKAGE__->register_method(
4768 method => "user_visible_holds",
4769 api_name => "open-ils.actor.history.hold.visible.print",
4772 desc => 'Returns printable output for the set of opt-in visible holds',
4774 { desc => 'Authentication token', type => 'string'},
4775 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4776 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4779 desc => q/An action_trigger.event object or error event./,
4785 __PACKAGE__->register_method(
4786 method => "user_visible_holds",
4787 api_name => "open-ils.actor.history.hold.visible.email",
4790 desc => 'Emails the set of opt-in visible holds to the requestor',
4792 { desc => 'Authentication token', type => 'string'},
4793 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4794 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4797 desc => q/undef, or event on error/
4802 sub user_visible_holds {
4803 my($self, $conn, $auth, $user_id, $options) = @_;
4806 my $for_print = ($self->api_name =~ /print/);
4807 my $for_email = ($self->api_name =~ /email/);
4808 my $e = new_editor(authtoken => $auth);
4809 return $e->event unless $e->checkauth;
4811 $user_id ||= $e->requestor->id;
4813 $options->{limit} ||= 50;
4814 $options->{offset} ||= 0;
4816 if($user_id != $e->requestor->id) {
4817 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4818 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4819 return $e->event unless $e->allowed($perm, $user->home_ou);
4822 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4824 my $data = $e->json_query({
4825 from => [$db_func, $user_id],
4826 limit => $$options{limit},
4827 offset => $$options{offset}
4829 # TODO: I only want IDs. code below didn't get me there
4830 # {"select":{"au":[{"column":"id", "result_field":"id",
4831 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4836 return undef unless @$data;
4840 # collect the batch of objects
4844 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4845 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4849 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4850 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4853 } elsif ($for_email) {
4855 $conn->respond_complete(1) if $for_email; # no sense in waiting
4863 my $hold = $e->retrieve_action_hold_request($id);
4864 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4865 # events will be fired from action_trigger_runner
4869 my $circ = $e->retrieve_action_circulation($id);
4870 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4871 # events will be fired from action_trigger_runner
4875 } else { # just give me the data please
4883 my $hold = $e->retrieve_action_hold_request($id);
4884 $conn->respond({hold => $hold});
4888 my $circ = $e->retrieve_action_circulation($id);
4891 summary => $U->create_circ_chain_summary($e, $id)
4900 __PACKAGE__->register_method(
4901 method => "user_saved_search_cud",
4902 api_name => "open-ils.actor.user.saved_search.cud",
4905 desc => 'Create/Update/Delete Access to user saved searches',
4907 { desc => 'Authentication token', type => 'string' },
4908 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4911 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4917 __PACKAGE__->register_method(
4918 method => "user_saved_search_cud",
4919 api_name => "open-ils.actor.user.saved_search.retrieve",
4922 desc => 'Retrieve a saved search object',
4924 { desc => 'Authentication token', type => 'string' },
4925 { desc => 'Saved Search ID', type => 'number' }
4928 desc => q/The saved search object, Event on error/,
4934 sub user_saved_search_cud {
4935 my( $self, $client, $auth, $search ) = @_;
4936 my $e = new_editor( authtoken=>$auth );
4937 return $e->die_event unless $e->checkauth;
4939 my $o_search; # prior version of the object, if any
4940 my $res; # to be returned
4942 # branch on the operation type
4944 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4946 # Get the old version, to check ownership
4947 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4948 or return $e->die_event;
4950 # You can't read somebody else's search
4951 return OpenILS::Event->new('BAD_PARAMS')
4952 unless $o_search->owner == $e->requestor->id;
4958 $e->xact_begin; # start an editor transaction
4960 if( $search->isnew ) { # Create
4962 # You can't create a search for somebody else
4963 return OpenILS::Event->new('BAD_PARAMS')
4964 unless $search->owner == $e->requestor->id;
4966 $e->create_actor_usr_saved_search( $search )
4967 or return $e->die_event;
4971 } elsif( $search->ischanged ) { # Update
4973 # You can't change ownership of a search
4974 return OpenILS::Event->new('BAD_PARAMS')
4975 unless $search->owner == $e->requestor->id;
4977 # Get the old version, to check ownership
4978 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4979 or return $e->die_event;
4981 # You can't update somebody else's search
4982 return OpenILS::Event->new('BAD_PARAMS')
4983 unless $o_search->owner == $e->requestor->id;
4986 $e->update_actor_usr_saved_search( $search )
4987 or return $e->die_event;
4991 } elsif( $search->isdeleted ) { # Delete
4993 # Get the old version, to check ownership
4994 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4995 or return $e->die_event;
4997 # You can't delete somebody else's search
4998 return OpenILS::Event->new('BAD_PARAMS')
4999 unless $o_search->owner == $e->requestor->id;
5002 $e->delete_actor_usr_saved_search( $o_search )
5003 or return $e->die_event;
5014 __PACKAGE__->register_method(
5015 method => "get_barcodes",
5016 api_name => "open-ils.actor.get_barcodes"
5020 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
5021 my $e = new_editor(authtoken => $auth);
5022 return $e->event unless $e->checkauth;
5023 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
5025 my $db_result = $e->json_query(
5027 'evergreen.get_barcodes',
5028 $org_id, $context, $barcode,
5032 if($context =~ /actor/) {
5033 my $filter_result = ();
5035 foreach my $result (@$db_result) {
5036 if($result->{type} eq 'actor') {
5037 if($e->requestor->id != $result->{id}) {
5038 $patron = $e->retrieve_actor_user($result->{id});
5040 push(@$filter_result, $e->event);
5043 if($e->allowed('VIEW_USER', $patron->home_ou)) {
5044 push(@$filter_result, $result);
5047 push(@$filter_result, $e->event);
5051 push(@$filter_result, $result);
5055 push(@$filter_result, $result);
5058 return $filter_result;
5064 __PACKAGE__->register_method(
5065 method => 'address_alert_test',
5066 api_name => 'open-ils.actor.address_alert.test',
5068 desc => "Tests a set of address fields to determine if they match with an address_alert",
5070 {desc => 'Authentication token', type => 'string'},
5071 {desc => 'Org Unit', type => 'number'},
5072 {desc => 'Fields', type => 'hash'},
5074 return => {desc => 'List of matching address_alerts'}
5078 sub address_alert_test {
5079 my ($self, $client, $auth, $org_unit, $fields) = @_;
5080 return [] unless $fields and grep {$_} values %$fields;
5082 my $e = new_editor(authtoken => $auth);
5083 return $e->event unless $e->checkauth;
5084 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
5085 $org_unit ||= $e->requestor->ws_ou;
5087 my $alerts = $e->json_query({
5089 'actor.address_alert_matches',
5097 $$fields{post_code},
5098 $$fields{mailing_address},
5099 $$fields{billing_address}
5103 # map the json_query hashes to real objects
5105 map {$e->retrieve_actor_address_alert($_)}
5106 (map {$_->{id}} @$alerts)
5110 __PACKAGE__->register_method(
5111 method => "mark_users_contact_invalid",
5112 api_name => "open-ils.actor.invalidate.email",
5114 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",
5116 {desc => "Authentication token", type => "string"},
5117 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5118 {desc => "Additional note text (optional)", type => "string"},
5119 {desc => "penalty org unit ID (optional)", type => "number"},
5120 {desc => "Email address (optional)", type => "string"}
5122 return => {desc => "Event describing success or failure", type => "object"}
5126 __PACKAGE__->register_method(
5127 method => "mark_users_contact_invalid",
5128 api_name => "open-ils.actor.invalidate.day_phone",
5130 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",
5132 {desc => "Authentication token", type => "string"},
5133 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5134 {desc => "Additional note text (optional)", type => "string"},
5135 {desc => "penalty org unit ID (optional)", type => "number"},
5136 {desc => "Phone Number (optional)", type => "string"}
5138 return => {desc => "Event describing success or failure", type => "object"}
5142 __PACKAGE__->register_method(
5143 method => "mark_users_contact_invalid",
5144 api_name => "open-ils.actor.invalidate.evening_phone",
5146 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",
5148 {desc => "Authentication token", type => "string"},
5149 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5150 {desc => "Additional note text (optional)", type => "string"},
5151 {desc => "penalty org unit ID (optional)", type => "number"},
5152 {desc => "Phone Number (optional)", type => "string"}
5154 return => {desc => "Event describing success or failure", type => "object"}
5158 __PACKAGE__->register_method(
5159 method => "mark_users_contact_invalid",
5160 api_name => "open-ils.actor.invalidate.other_phone",
5162 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",
5164 {desc => "Authentication token", type => "string"},
5165 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5166 {desc => "Additional note text (optional)", type => "string"},
5167 {desc => "penalty org unit ID (optional, default to top of org tree)",
5169 {desc => "Phone Number (optional)", type => "string"}
5171 return => {desc => "Event describing success or failure", type => "object"}
5175 sub mark_users_contact_invalid {
5176 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5178 # This method invalidates an email address or a phone_number which
5179 # removes the bad email address or phone number, copying its contents
5180 # to a patron note, and institutes a standing penalty for "bad email"
5181 # or "bad phone number" which is cleared when the user is saved or
5182 # optionally only when the user is saved with an email address or
5183 # phone number (or staff manually delete the penalty).
5185 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5187 my $e = new_editor(authtoken => $auth, xact => 1);
5188 return $e->die_event unless $e->checkauth;
5191 if (defined $patron_id && $patron_id ne "") {
5192 $howfind = {usr => $patron_id};
5193 } elsif (defined $contact && $contact ne "") {
5194 $howfind = {$contact_type => $contact};
5196 # Error out if no patron id set or no contact is set.
5197 return OpenILS::Event->new('BAD_PARAMS');
5200 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5201 $e, $contact_type, $howfind,
5202 $addl_note, $penalty_ou, $e->requestor->id
5206 # Putting the following method in open-ils.actor is a bad fit, except in that
5207 # it serves an interface that lives under 'actor' in the templates directory,
5208 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5210 __PACKAGE__->register_method(
5211 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5212 method => "get_all_at_reactors_in_use",
5217 { name => 'authtoken', type => 'string' }
5220 desc => 'list of reactor names', type => 'array'
5225 sub get_all_at_reactors_in_use {
5226 my ($self, $conn, $auth) = @_;
5228 my $e = new_editor(authtoken => $auth);
5229 $e->checkauth or return $e->die_event;
5230 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5232 my $reactors = $e->json_query({
5234 atevdef => [{column => "reactor", transform => "distinct"}]
5236 from => {atevdef => {}}
5239 return $e->die_event unless ref $reactors eq "ARRAY";
5242 return [ map { $_->{reactor} } @$reactors ];
5245 __PACKAGE__->register_method(
5246 method => "filter_group_entry_crud",
5247 api_name => "open-ils.actor.filter_group_entry.crud",
5250 Provides CRUD access to filter group entry objects. These are not full accessible
5251 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5252 are not accessible via PCRUD (because they have no fields against which to link perms)
5255 {desc => "Authentication token", type => "string"},
5256 {desc => "Entry ID / Entry Object", type => "number"},
5257 {desc => "Additional note text (optional)", type => "string"},
5258 {desc => "penalty org unit ID (optional, default to top of org tree)",
5262 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5268 sub filter_group_entry_crud {
5269 my ($self, $conn, $auth, $arg) = @_;
5271 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5272 my $e = new_editor(authtoken => $auth, xact => 1);
5273 return $e->die_event unless $e->checkauth;
5279 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5280 or return $e->die_event;
5282 return $e->die_event unless $e->allowed(
5283 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5285 my $query = $arg->query;
5286 $query = $e->create_actor_search_query($query) or return $e->die_event;
5287 $arg->query($query->id);
5288 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5289 $entry->query($query);
5294 } elsif ($arg->ischanged) {
5296 my $entry = $e->retrieve_actor_search_filter_group_entry([
5299 flesh_fields => {asfge => ['grp']}
5301 ]) or return $e->die_event;
5303 return $e->die_event unless $e->allowed(
5304 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5306 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5307 $arg->query($arg->query->id);
5308 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5309 $arg->query($query);
5314 } elsif ($arg->isdeleted) {
5316 my $entry = $e->retrieve_actor_search_filter_group_entry([
5319 flesh_fields => {asfge => ['grp', 'query']}
5321 ]) or return $e->die_event;
5323 return $e->die_event unless $e->allowed(
5324 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5326 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5327 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5340 my $entry = $e->retrieve_actor_search_filter_group_entry([
5343 flesh_fields => {asfge => ['grp', 'query']}
5345 ]) or return $e->die_event;
5347 return $e->die_event unless $e->allowed(
5348 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5349 $entry->grp->owner);
5352 $entry->grp($entry->grp->id); # for consistency