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/;
46 OpenILS::Application::Actor::Container->initialize();
47 OpenILS::Application::Actor::UserGroups->initialize();
48 OpenILS::Application::Actor::ClosedDates->initialize();
51 my $apputils = "OpenILS::Application::AppUtils";
54 sub _d { warn "Patron:\n" . Dumper(shift()); }
57 my $set_user_settings;
61 #__PACKAGE__->register_method(
62 # method => "allowed_test",
63 # api_name => "open-ils.actor.allowed_test",
66 # my($self, $conn, $auth, $orgid, $permcode) = @_;
67 # my $e = new_editor(authtoken => $auth);
68 # return $e->die_event unless $e->checkauth;
72 # permcode => $permcode,
73 # result => $e->allowed($permcode, $orgid)
77 __PACKAGE__->register_method(
78 method => "update_user_setting",
79 api_name => "open-ils.actor.patron.settings.update",
81 sub update_user_setting {
82 my($self, $conn, $auth, $user_id, $settings) = @_;
83 my $e = new_editor(xact => 1, authtoken => $auth);
84 return $e->die_event unless $e->checkauth;
86 $user_id = $e->requestor->id unless defined $user_id;
88 unless($e->requestor->id == $user_id) {
89 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
90 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
93 for my $name (keys %$settings) {
94 my $val = $$settings{$name};
95 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
98 $val = OpenSRF::Utils::JSON->perl2JSON($val);
101 $e->update_actor_user_setting($set) or return $e->die_event;
103 $set = Fieldmapper::actor::user_setting->new;
107 $e->create_actor_user_setting($set) or return $e->die_event;
110 $e->delete_actor_user_setting($set) or return $e->die_event;
119 __PACKAGE__->register_method(
120 method => "update_privacy_waiver",
121 api_name => "open-ils.actor.patron.privacy_waiver.update",
123 desc => "Replaces any existing privacy waiver entries for the patron with the supplied values.",
125 {desc => 'Authentication token', type => 'string'},
126 {desc => 'User ID', type => 'number'},
127 {desc => 'Arrayref of privacy waiver entries', type => 'object'}
129 return => {desc => '1 on success, Event on error'}
132 sub update_privacy_waiver {
133 my($self, $conn, $auth, $user_id, $waiver) = @_;
134 my $e = new_editor(xact => 1, authtoken => $auth);
135 return $e->die_event unless $e->checkauth;
137 $user_id = $e->requestor->id unless defined $user_id;
139 unless($e->requestor->id == $user_id) {
140 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
141 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
144 foreach my $w (@$waiver) {
145 $w->{usr} = $user_id unless $w->{usr};
146 if ($w->{id} && $w->{id} ne 'new') {
147 my $existing_rows = $e->search_actor_usr_privacy_waiver({usr => $user_id, id => $w->{id}});
148 if ($existing_rows) {
149 my $existing = $existing_rows->[0];
150 # delete existing if name is empty
151 if (!$w->{name} or $w->{name} =~ /^\s*$/) {
152 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
154 # delete existing if none of the boxes were checked
155 } elsif (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}) {
156 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
158 # otherwise, update existing waiver entry
160 $existing->name($w->{name});
161 $existing->place_holds($w->{place_holds});
162 $existing->pickup_holds($w->{pickup_holds});
163 $existing->checkout_items($w->{checkout_items});
164 $existing->view_history($w->{view_history});
165 $e->update_actor_usr_privacy_waiver($existing) or return $e->die_event;
168 $logger->warn("No privacy waiver entry found for user $user_id with ID " . $w->{id});
172 # ignore new entries with empty name or with no boxes checked
173 next if (!$w->{name} or $w->{name} =~ /^\s*$/);
174 next if (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history});
175 my $new = Fieldmapper::actor::usr_privacy_waiver->new;
176 $new->usr($w->{usr});
177 $new->name($w->{name});
178 $new->place_holds($w->{place_holds});
179 $new->pickup_holds($w->{pickup_holds});
180 $new->checkout_items($w->{checkout_items});
181 $new->view_history($w->{view_history});
182 $e->create_actor_usr_privacy_waiver($new) or return $e->die_event;
190 __PACKAGE__->register_method(
191 method => "get_ou_setting_history",
192 api_name => "open-ils.actor.org_unit.settings.history.retrieve",
194 desc => "Retrieves the history of an Org Unit Setting. The permission to retrieve " .
195 "an org unit setting's history is dependant on a specific permission specified " .
196 "in the view_perm column of the config.org_unit_setting_type " .
197 "table's row corresponding to the setting being changed." ,
199 {desc => 'Authentication token', type => 'string'},
200 {desc => 'Org Unit ID', type => 'number'},
201 {desc => 'Setting Type Name', type => 'string'}
203 return => {desc => 'History IDL Object'}
207 sub get_ou_setting_history {
208 my( $self, $client, $auth, $setting, $orgid ) = @_;
209 my $e = new_editor(authtoken => $auth, xact => 1);
210 return $e->die_event unless $e->checkauth;
212 return $U->ou_ancestor_setting_log(
213 $orgid, $setting, $e, $auth
218 __PACKAGE__->register_method(
219 method => "set_ou_settings",
220 api_name => "open-ils.actor.org_unit.settings.update",
222 desc => "Updates the value for a given org unit setting. The permission to update " .
223 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
224 "permission specified in the update_perm column of the config.org_unit_setting_type " .
225 "table's row corresponding to the setting being changed." ,
227 {desc => 'Authentication token', type => 'string'},
228 {desc => 'Org unit ID', type => 'number'},
229 {desc => 'Hash of setting name-value pairs', type => 'object'}
231 return => {desc => '1 on success, Event on error'}
235 sub set_ou_settings {
236 my( $self, $client, $auth, $org_id, $settings ) = @_;
238 my $e = new_editor(authtoken => $auth, xact => 1);
239 return $e->die_event unless $e->checkauth;
240 my $defang = HTML::Defang->new;
242 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
244 for my $name (keys %$settings) {
245 my $val = $$settings{$name};
246 if ($name eq 'opac.patron.custom_css') { $val = $defang->defang($val); }
248 my $type = $e->retrieve_config_org_unit_setting_type([
250 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
251 ]) or return $e->die_event;
252 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
254 # If there is no relevant permission, the default assumption will
255 # be, "no, the caller cannot change that value."
256 return $e->die_event unless ($all_allowed ||
257 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
260 $val = OpenSRF::Utils::JSON->perl2JSON($val);
263 $e->update_actor_org_unit_setting($set) or return $e->die_event;
265 $set = Fieldmapper::actor::org_unit_setting->new;
266 $set->org_unit($org_id);
269 $e->create_actor_org_unit_setting($set) or return $e->die_event;
272 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
280 __PACKAGE__->register_method(
281 method => "fetch_visible_ou_settings_log",
282 api_name => "open-ils.actor.org_unit.settings.history.visible.retrieve",
284 desc => "Retrieves the log entries for the specified OU setting. " .
285 "If the setting has a view permission, the results are limited " .
286 "to entries at the OUs that the user has the view permission. ",
288 {desc => 'Authentication token', type => 'string'},
289 {desc => 'Setting name', type => 'string'}
291 return => {desc => 'List of fieldmapper objects of the log entries, Event on error'}
295 sub fetch_visible_ou_settings_log {
296 my( $self, $client, $auth, $setting ) = @_;
298 my $e = new_editor(authtoken => $auth);
299 return $e->event unless $e->checkauth;
300 return $e->die_event unless $e->allowed("STAFF_LOGIN");
301 return OpenILS::Event->new('BAD_PARAMS') unless defined($setting);
303 my $type = $e->retrieve_config_org_unit_setting_type([
305 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
307 return OpenILS::Event->new('BAD_PARAMS', note => 'setting type not found')
310 my $query = { field_name => $setting };
311 if ($type->view_perm) {
312 $query->{org} = $U->user_has_work_perm_at($e, $type->view_perm->code, {descendants => 1});
313 if (scalar @{ $query->{org} } == 0) {
314 # user doesn't have the view permission anywhere, so return nothing
319 my $results = $e->search_config_org_unit_setting_type_log([$query, {'order_by' => 'date_applied ASC'}])
320 or return $e->die_event;
324 __PACKAGE__->register_method(
325 method => "user_settings",
327 api_name => "open-ils.actor.patron.settings.retrieve",
330 my( $self, $client, $auth, $user_id, $setting ) = @_;
332 my $e = new_editor(authtoken => $auth);
333 return $e->event unless $e->checkauth;
334 $user_id = $e->requestor->id unless defined $user_id;
336 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
337 if($e->requestor->id != $user_id) {
338 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
342 my($e, $user_id, $setting) = @_;
343 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
344 return undef unless $val; # XXX this should really return undef, but needs testing
345 return OpenSRF::Utils::JSON->JSON2perl($val->value);
349 if(ref $setting eq 'ARRAY') {
351 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
354 return get_setting($e, $user_id, $setting);
357 my $s = $e->search_actor_user_setting({usr => $user_id});
358 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
363 __PACKAGE__->register_method(
364 method => "ranged_ou_settings",
365 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
367 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
368 "is implied for retrieving OU settings by the authenticated users' permissions.",
370 {desc => 'Authentication token', type => 'string'},
371 {desc => 'Org unit ID', type => 'number'},
373 return => {desc => 'A hashref of "ranged" settings, event on error'}
376 sub ranged_ou_settings {
377 my( $self, $client, $auth, $org_id ) = @_;
379 my $e = new_editor(authtoken => $auth);
380 return $e->event unless $e->checkauth;
383 my $org_list = $U->get_org_ancestors($org_id);
384 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
385 $org_list = [ reverse @$org_list ];
387 # start at the context org and capture the setting value
388 # without clobbering settings we've already captured
389 for my $this_org_id (@$org_list) {
391 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
393 for my $set (@sets) {
394 my $type = $e->retrieve_config_org_unit_setting_type([
396 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
399 # If there is no relevant permission, the default assumption will
400 # be, "yes, the caller can have that value."
401 if ($type && $type->view_perm) {
402 next if not $e->allowed($type->view_perm->code, $org_id);
405 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
406 unless defined $ranged_settings{$set->name};
410 return \%ranged_settings;
415 __PACKAGE__->register_method(
416 api_name => 'open-ils.actor.ou_setting.ancestor_default',
417 method => 'ou_ancestor_setting',
419 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
420 'This method will make sure that the given user has permission to view that setting, if there is a ' .
421 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
422 'the user lacks the permisssion, undef will be returned.' ,
424 { desc => 'Org unit ID', type => 'number' },
425 { desc => 'setting name', type => 'string' },
426 { desc => 'authtoken (optional)', type => 'string' }
428 return => {desc => 'A value for the org unit setting, or undef'}
432 # ------------------------------------------------------------------
433 # Attempts to find the org setting value for a given org. if not
434 # found at the requested org, searches up the org tree until it
435 # finds a parent that has the requested setting.
436 # when found, returns { org => $id, value => $value }
437 # otherwise, returns NULL
438 # ------------------------------------------------------------------
439 sub ou_ancestor_setting {
440 my( $self, $client, $orgid, $name, $auth ) = @_;
441 # Make sure $auth is set to something if not given.
443 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
446 __PACKAGE__->register_method(
447 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
448 method => 'ou_ancestor_setting_batch',
450 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
451 'This method will make sure that the given user has permission to view that setting, if there is a ' .
452 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
453 'the user lacks the permisssion, undef will be returned.' ,
455 { desc => 'Org unit ID', type => 'number' },
456 { desc => 'setting name list', type => 'array' },
457 { desc => 'authtoken (optional)', type => 'string' }
459 return => {desc => 'A hash with name => value pairs for the org unit settings'}
462 sub ou_ancestor_setting_batch {
463 my( $self, $client, $orgid, $name_list, $auth ) = @_;
465 # splitting the list of settings to fetch values
466 # so that ones that *don't* require view_perm checks
467 # can be fetched in one fell swoop, which is
468 # significantly faster in cases where a large
469 # number of settings need to be fetched.
470 my %perm_check_required = ();
471 my @perm_check_not_required = ();
473 # Note that ->ou_ancestor_setting also can check
474 # to see if the setting has a view_perm, but testing
475 # suggests that the redundant checks do not significantly
476 # increase the time it takes to fetch the values of
477 # permission-controlled settings.
478 my $e = new_editor();
479 my $res = $e->search_config_org_unit_setting_type({
481 view_perm => { "!=" => undef },
483 %perm_check_required = map { $_->name() => 1 } @$res;
484 foreach my $setting (@$name_list) {
485 push @perm_check_not_required, $setting
486 unless exists($perm_check_required{$setting});
490 if (@perm_check_not_required) {
491 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
493 $values{$_} = $U->ou_ancestor_setting(
496 ) for keys(%perm_check_required);
502 __PACKAGE__->register_method(
503 method => "update_patron",
504 api_name => "open-ils.actor.patron.update",
507 Update an existing user, or create a new one. Related objects,
508 like cards, addresses, survey responses, and stat cats,
509 can be updated by attaching them to the user object in their
510 respective fields. For examples, the billing address object
511 may be inserted into the 'billing_address' field, etc. For each
512 attached object, indicate if the object should be created,
513 updated, or deleted using the built-in 'isnew', 'ischanged',
514 and 'isdeleted' fields on the object.
515 This method intentionally does not handle updates to patron
516 notes, user activity, and standing penalties; if any values
517 are supplied for those fields in the patron data object,
518 they will be ignored. Please refer to bug 1976126 before
522 { desc => 'Authentication token', type => 'string' },
523 { desc => 'Patron data object', type => 'object' }
525 return => {desc => 'A fleshed user object, event on error'}
530 my( $self, $client, $auth, $patron ) = @_;
532 my $e = new_editor(xact => 1, authtoken => $auth);
533 return $e->event unless $e->checkauth;
535 $logger->info($patron->isnew ? "Creating new patron..." :
536 "Updating Patron: " . $patron->id);
538 my $evt = check_group_perm($e, $e->requestor, $patron);
541 # $new_patron is the patron in progress. $patron is the original patron
542 # passed in with the method. new_patron will change as the components
543 # of patron are added/updated.
547 # unflesh the real items on the patron
548 $patron->card( $patron->card->id ) if(ref($patron->card));
549 $patron->billing_address( $patron->billing_address->id )
550 if(ref($patron->billing_address));
551 $patron->mailing_address( $patron->mailing_address->id )
552 if(ref($patron->mailing_address));
554 # create/update the patron first so we can use his id
556 # $patron is the obj from the client (new data) and $new_patron is the
557 # patron object properly built for db insertion, so we need a third variable
558 # if we want to represent the old patron.
561 my $barred_hook = '';
564 if($patron->isnew()) {
565 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
567 if($U->is_true($patron->barred)) {
568 return $e->die_event unless
569 $e->allowed('BAR_PATRON', $patron->home_ou);
571 if(($patron->photo_url)) {
572 return $e->die_event unless
573 $e->allowed('UPDATE_USER_PHOTO_URL', $patron->home_ou);
576 $new_patron = $patron;
578 # Did auth checking above already.
579 $old_patron = $e->retrieve_actor_user($patron->id) or
580 return $e->die_event;
582 $renew_hook = 'au.renewed' if ($old_patron->expire_date ne $new_patron->expire_date);
584 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
585 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
586 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
588 $barred_hook = $U->is_true($new_patron->barred) ?
589 'au.barred' : 'au.unbarred';
592 if($old_patron->photo_url ne $new_patron->photo_url) {
593 my $perm = 'UPDATE_USER_PHOTO_URL';
594 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
597 # update the password by itself to avoid the password protection magic
598 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
599 modify_migrated_user_password($e, $patron->id, $patron->passwd);
600 $new_patron->passwd(''); # subsequent update will set
601 # actor.usr.passwd to MD5('')
605 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
608 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
611 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
614 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
617 # re-update the patron if anything has happened to him during this process
618 if($new_patron->ischanged()) {
619 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
623 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
626 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
629 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
632 $evt = apply_invalid_addr_penalty($e, $patron);
637 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
639 $tses->request('open-ils.trigger.event.autocreate',
640 'au.created', $new_patron, $new_patron->home_ou);
642 $tses->request('open-ils.trigger.event.autocreate',
643 'au.updated', $new_patron, $new_patron->home_ou);
645 $tses->request('open-ils.trigger.event.autocreate', $renew_hook,
646 $new_patron, $new_patron->home_ou) if $renew_hook;
648 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
649 $new_patron, $new_patron->home_ou) if $barred_hook;
652 $e->xact_begin; # $e->rollback is called in new_flesh_user
653 return flesh_user($new_patron->id(), $e);
656 sub apply_invalid_addr_penalty {
660 # grab the invalid address penalty if set
661 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
663 my ($addr_penalty) = grep
664 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
666 # do we enforce invalid address penalty
667 my $enforce = $U->ou_ancestor_setting_value(
668 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
670 my $addrs = $e->search_actor_user_address(
671 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
672 my $addr_count = scalar(@$addrs);
674 if($addr_count == 0 and $addr_penalty) {
676 # regardless of any settings, remove the penalty when the user has no invalid addresses
677 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
680 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
682 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
683 my $depth = $ptype->org_depth;
684 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
685 $ctx_org = $patron->home_ou unless defined $ctx_org;
687 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
688 $penalty->usr($patron->id);
689 $penalty->org_unit($ctx_org);
690 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
692 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
707 "standing_penalties",
717 push @$fields, "home_ou" if $home_ou;
718 return new_flesh_user($id, $fields, $e );
726 # clone and clear stuff that would break the database
730 my $new_patron = $patron->clone;
732 $new_patron->clear_billing_address();
733 $new_patron->clear_mailing_address();
734 $new_patron->clear_addresses();
735 $new_patron->clear_card();
736 $new_patron->clear_cards();
737 $new_patron->clear_id();
738 $new_patron->clear_isnew();
739 $new_patron->clear_ischanged();
740 $new_patron->clear_isdeleted();
741 $new_patron->clear_stat_cat_entries();
742 $new_patron->clear_waiver_entries();
743 $new_patron->clear_permissions();
744 $new_patron->clear_standing_penalties();
755 return (undef, $e->die_event) unless
756 $e->allowed('CREATE_USER', $patron->home_ou);
758 my $ex = $e->search_actor_user(
759 {usrname => $patron->usrname}, {idlist => 1});
760 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
762 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
764 # do a dance to get the password hashed securely
765 my $saved_password = $patron->passwd;
767 $e->create_actor_user($patron) or return (undef, $e->die_event);
768 modify_migrated_user_password($e, $patron->id, $saved_password);
770 my $id = $patron->id; # added by CStoreEditor
772 $logger->info("Successfully created new user [$id] in DB");
773 return ($e->retrieve_actor_user($id), undef);
777 sub check_group_perm {
778 my( $e, $requestor, $patron ) = @_;
781 # first let's see if the requestor has
782 # priveleges to update this user in any way
783 if( ! $patron->isnew ) {
784 my $p = $e->retrieve_actor_user($patron->id);
786 # If we are the requestor (trying to update our own account)
787 # and we are not trying to change our profile, we're good
788 if( $p->id == $requestor->id and
789 $p->profile == $patron->profile ) {
794 $evt = group_perm_failed($e, $requestor, $p);
798 # They are allowed to edit this patron.. can they put the
799 # patron into the group requested?
800 $evt = group_perm_failed($e, $requestor, $patron);
806 sub group_perm_failed {
807 my( $e, $requestor, $patron ) = @_;
811 my $grpid = $patron->profile;
815 $logger->debug("user update looking for group perm for group $grpid");
816 $grp = $e->retrieve_permission_grp_tree($grpid);
818 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
820 $logger->info("user update checking perm $perm on user ".
821 $requestor->id." for update/create on user username=".$patron->usrname);
823 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
829 my( $e, $patron, $noperm) = @_;
831 $logger->info("Updating patron ".$patron->id." in DB");
836 return (undef, $e->die_event)
837 unless $e->allowed('UPDATE_USER', $patron->home_ou);
840 if(!$patron->ident_type) {
841 $patron->clear_ident_type;
842 $patron->clear_ident_value;
845 $evt = verify_last_xact($e, $patron);
846 return (undef, $evt) if $evt;
848 $e->update_actor_user($patron) or return (undef, $e->die_event);
850 # re-fetch the user to pick up the latest last_xact_id value
851 # to avoid collisions.
852 $patron = $e->retrieve_actor_user($patron->id);
857 sub verify_last_xact {
858 my( $e, $patron ) = @_;
859 return undef unless $patron->id and $patron->id > 0;
860 my $p = $e->retrieve_actor_user($patron->id);
861 my $xact = $p->last_xact_id;
862 return undef unless $xact;
863 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
864 return OpenILS::Event->new('XACT_COLLISION')
865 if $xact ne $patron->last_xact_id;
870 sub _check_dup_ident {
871 my( $session, $patron ) = @_;
873 return undef unless $patron->ident_value;
876 ident_type => $patron->ident_type,
877 ident_value => $patron->ident_value,
880 $logger->debug("patron update searching for dup ident values: " .
881 $patron->ident_type . ':' . $patron->ident_value);
883 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
885 my $dups = $session->request(
886 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
889 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
896 sub _add_update_addresses {
900 my $new_patron = shift;
904 my $current_id; # id of the address before creation
906 my $addresses = $patron->addresses();
908 for my $address (@$addresses) {
910 next unless ref $address;
911 $current_id = $address->id();
913 if( $patron->billing_address() and
914 $patron->billing_address() == $current_id ) {
915 $logger->info("setting billing addr to $current_id");
916 $new_patron->billing_address($address->id());
917 $new_patron->ischanged(1);
920 if( $patron->mailing_address() and
921 $patron->mailing_address() == $current_id ) {
922 $new_patron->mailing_address($address->id());
923 $logger->info("setting mailing addr to $current_id");
924 $new_patron->ischanged(1);
928 if($address->isnew()) {
930 $address->usr($new_patron->id());
932 ($address, $evt) = _add_address($e,$address);
933 return (undef, $evt) if $evt;
935 # we need to get the new id
936 if( $patron->billing_address() and
937 $patron->billing_address() == $current_id ) {
938 $new_patron->billing_address($address->id());
939 $logger->info("setting billing addr to $current_id");
940 $new_patron->ischanged(1);
943 if( $patron->mailing_address() and
944 $patron->mailing_address() == $current_id ) {
945 $new_patron->mailing_address($address->id());
946 $logger->info("setting mailing addr to $current_id");
947 $new_patron->ischanged(1);
950 } elsif($address->ischanged() ) {
952 ($address, $evt) = _update_address($e, $address);
953 return (undef, $evt) if $evt;
955 } elsif($address->isdeleted() ) {
957 if( $address->id() == $new_patron->mailing_address() ) {
958 $new_patron->clear_mailing_address();
959 ($new_patron, $evt) = _update_patron($e, $new_patron);
960 return (undef, $evt) if $evt;
963 if( $address->id() == $new_patron->billing_address() ) {
964 $new_patron->clear_billing_address();
965 ($new_patron, $evt) = _update_patron($e, $new_patron);
966 return (undef, $evt) if $evt;
969 $evt = _delete_address($e, $address);
970 return (undef, $evt) if $evt;
974 return ( $new_patron, undef );
978 # adds an address to the db and returns the address with new id
980 my($e, $address) = @_;
981 $address->clear_id();
983 $logger->info("Creating new address at street ".$address->street1);
985 # put the address into the database
986 $e->create_actor_user_address($address) or return (undef, $e->die_event);
987 return ($address, undef);
991 sub _update_address {
992 my( $e, $address ) = @_;
994 $logger->info("Updating address ".$address->id." in the DB");
996 $e->update_actor_user_address($address) or return (undef, $e->die_event);
998 return ($address, undef);
1003 sub _add_update_cards {
1007 my $new_patron = shift;
1011 my $virtual_id; #id of the card before creation
1013 my $card_changed = 0;
1014 my $cards = $patron->cards();
1015 for my $card (@$cards) {
1017 $card->usr($new_patron->id());
1019 if(ref($card) and $card->isnew()) {
1021 $virtual_id = $card->id();
1022 ( $card, $evt ) = _add_card($e, $card);
1023 return (undef, $evt) if $evt;
1025 #if(ref($patron->card)) { $patron->card($patron->card->id); }
1026 if($patron->card() == $virtual_id) {
1027 $new_patron->card($card->id());
1028 $new_patron->ischanged(1);
1032 } elsif( ref($card) and $card->ischanged() ) {
1033 $evt = _update_card($e, $card);
1034 return (undef, $evt) if $evt;
1039 $U->create_events_for_hook('au.barcode_changed', $new_patron, $e->requestor->ws_ou)
1042 return ( $new_patron, undef );
1046 # adds an card to the db and returns the card with new id
1048 my( $e, $card ) = @_;
1051 $logger->info("Adding new patron card ".$card->barcode);
1053 $e->create_actor_card($card) or return (undef, $e->die_event);
1055 return ( $card, undef );
1059 # returns event on error. returns undef otherwise
1061 my( $e, $card ) = @_;
1062 $logger->info("Updating patron card ".$card->id);
1064 $e->update_actor_card($card) or return $e->die_event;
1069 sub _add_update_waiver_entries {
1072 my $new_patron = shift;
1075 my $waiver_entries = $patron->waiver_entries();
1076 for my $waiver (@$waiver_entries) {
1077 next unless ref $waiver;
1078 $waiver->usr($new_patron->id());
1079 if ($waiver->isnew()) {
1080 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
1081 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
1082 $logger->info("Adding new patron waiver entry");
1083 $waiver->clear_id();
1084 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1085 } elsif ($waiver->ischanged()) {
1086 $logger->info("Updating patron waiver entry " . $waiver->id);
1087 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1088 } elsif ($waiver->isdeleted()) {
1089 $logger->info("Deleting patron waiver entry " . $waiver->id);
1090 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1093 return ($new_patron, undef);
1097 # returns event on error. returns undef otherwise
1098 sub _delete_address {
1099 my( $e, $address ) = @_;
1101 $logger->info("Deleting address ".$address->id." from DB");
1103 $e->delete_actor_user_address($address) or return $e->die_event;
1109 sub _add_survey_responses {
1110 my ($e, $patron, $new_patron) = @_;
1112 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1114 my $responses = $patron->survey_responses;
1118 $_->usr($new_patron->id) for (@$responses);
1120 my $evt = $U->simplereq( "open-ils.circ",
1121 "open-ils.circ.survey.submit.user_id", $responses );
1123 return (undef, $evt) if defined($U->event_code($evt));
1127 return ( $new_patron, undef );
1130 sub _clear_badcontact_penalties {
1131 my ($e, $old_patron, $new_patron) = @_;
1133 return ($new_patron, undef) unless $old_patron;
1135 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1137 # This ignores whether the caller of update_patron has any permission
1138 # to remove penalties, but these penalties no longer make sense
1139 # if an email address field (for example) is changed (and the caller must
1140 # have perms to do *that*) so there's no reason not to clear the penalties.
1142 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1144 "+csp" => {"name" => [values(%$PNM)]},
1145 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1147 "join" => {"csp" => {}},
1149 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1151 ]) or return (undef, $e->die_event);
1153 return ($new_patron, undef) unless @$bad_contact_penalties;
1155 my @penalties_to_clear;
1156 my ($field, $penalty_name);
1158 # For each field that might have an associated bad contact penalty,
1159 # check for such penalties and add them to the to-clear list if that
1160 # field has changed.
1161 while (($field, $penalty_name) = each(%$PNM)) {
1162 if ($old_patron->$field ne $new_patron->$field) {
1163 push @penalties_to_clear, grep {
1164 $_->standing_penalty->name eq $penalty_name
1165 } @$bad_contact_penalties;
1169 foreach (@penalties_to_clear) {
1170 # Note that this "archives" penalties, in the terminology of the staff
1171 # client, instead of just deleting them. This may assist reporting,
1172 # or preserving old contact information when it is still potentially
1174 $_->standing_penalty($_->standing_penalty->id); # deflesh
1175 $_->stop_date('now');
1176 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1179 return ($new_patron, undef);
1183 sub _create_stat_maps {
1185 my($e, $patron, $new_patron) = @_;
1187 my $maps = $patron->stat_cat_entries();
1189 for my $map (@$maps) {
1191 my $method = "update_actor_stat_cat_entry_user_map";
1193 if ($map->isdeleted()) {
1194 $method = "delete_actor_stat_cat_entry_user_map";
1196 } elsif ($map->isnew()) {
1197 $method = "create_actor_stat_cat_entry_user_map";
1202 $map->target_usr($new_patron->id);
1204 $logger->info("Updating stat entry with method $method and map $map");
1206 $e->$method($map) or return (undef, $e->die_event);
1209 return ($new_patron, undef);
1212 sub _create_perm_maps {
1214 my($e, $patron, $new_patron) = @_;
1216 my $maps = $patron->permissions;
1218 for my $map (@$maps) {
1220 my $method = "update_permission_usr_perm_map";
1221 if ($map->isdeleted()) {
1222 $method = "delete_permission_usr_perm_map";
1223 } elsif ($map->isnew()) {
1224 $method = "create_permission_usr_perm_map";
1228 $map->usr($new_patron->id);
1230 $logger->info( "Updating permissions with method $method and map $map" );
1232 $e->$method($map) or return (undef, $e->die_event);
1235 return ($new_patron, undef);
1239 __PACKAGE__->register_method(
1240 method => "set_user_work_ous",
1241 api_name => "open-ils.actor.user.work_ous.update",
1244 sub set_user_work_ous {
1250 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1251 return $evt if $evt;
1253 my $session = $apputils->start_db_session();
1254 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1256 for my $map (@$maps) {
1258 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1259 if ($map->isdeleted()) {
1260 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1261 } elsif ($map->isnew()) {
1262 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1266 #warn( "Updating permissions with method $method and session $ses and map $map" );
1267 $logger->info( "Updating work_ou map with method $method and map $map" );
1269 my $stat = $session->request($method, $map)->gather(1);
1270 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1274 $apputils->commit_db_session($session);
1276 return scalar(@$maps);
1280 __PACKAGE__->register_method(
1281 method => "set_user_perms",
1282 api_name => "open-ils.actor.user.permissions.update",
1285 sub set_user_perms {
1291 my $session = $apputils->start_db_session();
1293 my( $user_obj, $evt ) = $U->checkses($ses);
1294 return $evt if $evt;
1295 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1297 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1300 $all = 1 if ($U->is_true($user_obj->super_user()));
1301 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1303 for my $map (@$maps) {
1305 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1306 if ($map->isdeleted()) {
1307 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1308 } elsif ($map->isnew()) {
1309 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1313 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1314 #warn( "Updating permissions with method $method and session $ses and map $map" );
1315 $logger->info( "Updating permissions with method $method and map $map" );
1317 my $stat = $session->request($method, $map)->gather(1);
1318 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1322 $apputils->commit_db_session($session);
1324 return scalar(@$maps);
1328 __PACKAGE__->register_method(
1329 method => "user_retrieve_by_barcode",
1331 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1333 sub user_retrieve_by_barcode {
1334 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1336 my $e = new_editor(authtoken => $auth);
1337 return $e->event unless $e->checkauth;
1339 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1340 or return $e->event;
1342 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1343 return $e->event unless $e->allowed(
1344 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1351 __PACKAGE__->register_method(
1352 method => "get_user_by_id",
1354 api_name => "open-ils.actor.user.retrieve",
1357 sub get_user_by_id {
1358 my ($self, $client, $auth, $id) = @_;
1359 my $e = new_editor(authtoken=>$auth);
1360 return $e->event unless $e->checkauth;
1361 my $user = $e->retrieve_actor_user($id) or return $e->event;
1362 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1367 __PACKAGE__->register_method(
1368 method => "get_org_types",
1369 api_name => "open-ils.actor.org_types.retrieve",
1372 return $U->get_org_types();
1376 __PACKAGE__->register_method(
1377 method => "get_user_ident_types",
1378 api_name => "open-ils.actor.user.ident_types.retrieve",
1381 sub get_user_ident_types {
1382 return $ident_types if $ident_types;
1383 return $ident_types =
1384 new_editor()->retrieve_all_config_identification_type();
1388 __PACKAGE__->register_method(
1389 method => "get_org_unit",
1390 api_name => "open-ils.actor.org_unit.retrieve",
1394 my( $self, $client, $user_session, $org_id ) = @_;
1395 my $e = new_editor(authtoken => $user_session);
1397 return $e->event unless $e->checkauth;
1398 $org_id = $e->requestor->ws_ou;
1400 my $o = $e->retrieve_actor_org_unit($org_id)
1401 or return $e->event;
1405 __PACKAGE__->register_method(
1406 method => "search_org_unit",
1407 api_name => "open-ils.actor.org_unit_list.search",
1410 sub search_org_unit {
1412 my( $self, $client, $field, $value ) = @_;
1414 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1416 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1417 { $field => $value } );
1423 # build the org tree
1425 __PACKAGE__->register_method(
1426 method => "get_org_tree",
1427 api_name => "open-ils.actor.org_tree.retrieve",
1429 note => "Returns the entire org tree structure",
1435 return $U->get_org_tree($client->session->session_locale);
1439 __PACKAGE__->register_method(
1440 method => "get_org_descendants",
1441 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1444 # depth is optional. org_unit is the id
1445 sub get_org_descendants {
1446 my( $self, $client, $org_unit, $depth ) = @_;
1448 if(ref $org_unit eq 'ARRAY') {
1451 for my $i (0..scalar(@$org_unit)-1) {
1452 my $list = $U->simple_scalar_request(
1454 "open-ils.storage.actor.org_unit.descendants.atomic",
1455 $org_unit->[$i], $depth->[$i] );
1456 push(@trees, $U->build_org_tree($list));
1461 my $orglist = $apputils->simple_scalar_request(
1463 "open-ils.storage.actor.org_unit.descendants.atomic",
1464 $org_unit, $depth );
1465 return $U->build_org_tree($orglist);
1470 __PACKAGE__->register_method(
1471 method => "get_org_ancestors",
1472 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1475 # depth is optional. org_unit is the id
1476 sub get_org_ancestors {
1477 my( $self, $client, $org_unit, $depth ) = @_;
1478 my $orglist = $apputils->simple_scalar_request(
1480 "open-ils.storage.actor.org_unit.ancestors.atomic",
1481 $org_unit, $depth );
1482 return $U->build_org_tree($orglist);
1486 __PACKAGE__->register_method(
1487 method => "get_standings",
1488 api_name => "open-ils.actor.standings.retrieve"
1493 return $user_standings if $user_standings;
1494 return $user_standings =
1495 $apputils->simple_scalar_request(
1497 "open-ils.cstore.direct.config.standing.search.atomic",
1498 { id => { "!=" => undef } }
1503 __PACKAGE__->register_method(
1504 method => "get_my_org_path",
1505 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1508 sub get_my_org_path {
1509 my( $self, $client, $auth, $org_id ) = @_;
1510 my $e = new_editor(authtoken=>$auth);
1511 return $e->event unless $e->checkauth;
1512 $org_id = $e->requestor->ws_ou unless defined $org_id;
1514 return $apputils->simple_scalar_request(
1516 "open-ils.storage.actor.org_unit.full_path.atomic",
1520 __PACKAGE__->register_method(
1521 method => "retrieve_coordinates",
1522 api_name => "open-ils.actor.geo.retrieve_coordinates",
1525 {desc => 'Authentication token', type => 'string' },
1526 {type => 'number', desc => 'Context Organizational Unit'},
1527 {type => 'string', desc => 'Address to look-up as a text string'}
1529 return => { desc => 'Hash/object containing latitude and longitude for the provided address.'}
1533 sub retrieve_coordinates {
1534 my( $self, $client, $auth, $org_id, $addr_string ) = @_;
1535 my $e = new_editor(authtoken=>$auth);
1536 return $e->event unless $e->checkauth;
1537 $org_id = $e->requestor->ws_ou unless defined $org_id;
1539 return $apputils->simple_scalar_request(
1541 "open-ils.geo.retrieve_coordinates",
1542 $org_id, $addr_string );
1545 __PACKAGE__->register_method(
1546 method => "get_my_org_ancestor_at_depth",
1547 api_name => "open-ils.actor.org_unit.ancestor_at_depth.retrieve"
1550 sub get_my_org_ancestor_at_depth {
1551 my( $self, $client, $auth, $org_id, $depth ) = @_;
1552 my $e = new_editor(authtoken=>$auth);
1553 return $e->event unless $e->checkauth;
1554 $org_id = $e->requestor->ws_ou unless defined $org_id;
1556 return $apputils->org_unit_ancestor_at_depth( $org_id, $depth );
1559 __PACKAGE__->register_method(
1560 method => "patron_adv_search",
1561 api_name => "open-ils.actor.patron.search.advanced"
1564 __PACKAGE__->register_method(
1565 method => "patron_adv_search",
1566 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1568 # Flush the response stream at most 5 patrons in for UI responsiveness.
1569 max_bundle_count => 5,
1571 desc => q/Returns a stream of fleshed user objects instead of
1572 a pile of identifiers/
1576 sub patron_adv_search {
1577 my( $self, $client, $auth, $search_hash, $search_limit,
1578 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1580 # API params sanity checks.
1581 # Exit early with empty result if no filter exists.
1582 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1583 my $fleshed = ($self->api_name =~ /fleshed/);
1584 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1586 for my $key (keys %$search_hash) {
1587 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1591 return ($fleshed ? undef : []) unless $search_ok;
1593 my $e = new_editor(authtoken=>$auth);
1594 return $e->event unless $e->checkauth;
1595 return $e->event unless $e->allowed('VIEW_USER');
1597 # depth boundary outside of which patrons must opt-in, default to 0
1598 my $opt_boundary = 0;
1599 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1601 if (not defined $search_ou) {
1602 my $depth = $U->ou_ancestor_setting_value(
1603 $e->requestor->ws_ou,
1604 'circ.patron_edit.duplicate_patron_check_depth'
1607 if (defined $depth) {
1608 $search_ou = $U->org_unit_ancestor_at_depth(
1609 $e->requestor->ws_ou, $depth
1614 my $ids = $U->storagereq(
1615 "open-ils.storage.actor.user.crazy_search", $search_hash,
1616 $search_limit, $search_sort, $include_inactive,
1617 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1619 return $ids unless $self->api_name =~ /fleshed/;
1621 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1627 # A migrated (main) password has the form:
1628 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1629 sub modify_migrated_user_password {
1630 my ($e, $user_id, $passwd) = @_;
1632 # new password gets a new salt
1633 my $new_salt = $e->json_query({
1634 from => ['actor.create_salt', 'main']})->[0];
1635 $new_salt = $new_salt->{'actor.create_salt'};
1642 md5_hex($new_salt . md5_hex($passwd)),
1650 __PACKAGE__->register_method(
1651 method => "update_passwd",
1652 api_name => "open-ils.actor.user.password.update",
1654 desc => "Update the operator's password",
1656 { desc => 'Authentication token', type => 'string' },
1657 { desc => 'New password', type => 'string' },
1658 { desc => 'Current password', type => 'string' }
1660 return => {desc => '1 on success, Event on error or incorrect current password'}
1664 __PACKAGE__->register_method(
1665 method => "update_passwd",
1666 api_name => "open-ils.actor.user.username.update",
1668 desc => "Update the operator's username",
1670 { desc => 'Authentication token', type => 'string' },
1671 { desc => 'New username', type => 'string' },
1672 { desc => 'Current password', type => 'string' }
1674 return => {desc => '1 on success, Event on error or incorrect current password'}
1678 __PACKAGE__->register_method(
1679 method => "update_passwd",
1680 api_name => "open-ils.actor.user.email.update",
1682 desc => "Update the operator's email address",
1684 { desc => 'Authentication token', type => 'string' },
1685 { desc => 'New email address', type => 'string' },
1686 { desc => 'Current password', type => 'string' }
1688 return => {desc => '1 on success, Event on error or incorrect current password'}
1692 __PACKAGE__->register_method(
1693 method => "update_passwd",
1694 api_name => "open-ils.actor.user.locale.update",
1696 desc => "Update the operator's i18n locale",
1698 { desc => 'Authentication token', type => 'string' },
1699 { desc => 'New locale', type => 'string' },
1700 { desc => 'Current password', type => 'string' }
1702 return => {desc => '1 on success, Event on error or incorrect current password'}
1707 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1708 my $e = new_editor(xact=>1, authtoken=>$auth);
1709 return $e->die_event unless $e->checkauth;
1711 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1712 or return $e->die_event;
1713 my $api = $self->api_name;
1715 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1717 return new OpenILS::Event('INCORRECT_PASSWORD');
1721 if( $api =~ /password/o ) {
1722 # NOTE: with access to the plain text password we could crypt
1723 # the password without the extra MD5 pre-hashing. Other changes
1724 # would be required. Noting here for future reference.
1725 modify_migrated_user_password($e, $db_user->id, $new_val);
1726 $db_user->passwd('');
1730 # if we don't clear the password, the user will be updated with
1731 # a hashed version of the hashed version of their password
1732 $db_user->clear_passwd;
1734 if( $api =~ /username/o ) {
1736 # make sure no one else has this username
1737 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1740 return new OpenILS::Event('USERNAME_EXISTS');
1742 $db_user->usrname($new_val);
1745 } elsif( $api =~ /email/o ) {
1746 $db_user->email($new_val);
1749 } elsif( $api =~ /locale/o ) {
1750 $db_user->locale($new_val);
1755 $e->update_actor_user($db_user) or return $e->die_event;
1758 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1761 # update the cached user to pick up these changes
1762 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1768 __PACKAGE__->register_method(
1769 method => "check_user_perms",
1770 api_name => "open-ils.actor.user.perm.check",
1771 notes => <<" NOTES");
1772 Takes a login session, user id, an org id, and an array of perm type strings. For each
1773 perm type, if the user does *not* have the given permission it is added
1774 to a list which is returned from the method. If all permissions
1775 are allowed, an empty list is returned
1776 if the logged in user does not match 'user_id', then the logged in user must
1777 have VIEW_PERMISSION priveleges.
1780 sub check_user_perms {
1781 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1783 my( $staff, $evt ) = $apputils->checkses($login_session);
1784 return $evt if $evt;
1786 if($staff->id ne $user_id) {
1787 if( $evt = $apputils->check_perms(
1788 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1794 for my $perm (@$perm_types) {
1795 if($apputils->check_perms($user_id, $org_id, $perm)) {
1796 push @not_allowed, $perm;
1800 return \@not_allowed
1803 __PACKAGE__->register_method(
1804 method => "check_user_perms2",
1805 api_name => "open-ils.actor.user.perm.check.multi_org",
1807 Checks the permissions on a list of perms and orgs for a user
1808 @param authtoken The login session key
1809 @param user_id The id of the user to check
1810 @param orgs The array of org ids
1811 @param perms The array of permission names
1812 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1813 if the logged in user does not match 'user_id', then the logged in user must
1814 have VIEW_PERMISSION priveleges.
1817 sub check_user_perms2 {
1818 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1820 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1821 $authtoken, $user_id, 'VIEW_PERMISSION' );
1822 return $evt if $evt;
1825 for my $org (@$orgs) {
1826 for my $perm (@$perms) {
1827 if($apputils->check_perms($user_id, $org, $perm)) {
1828 push @not_allowed, [ $org, $perm ];
1833 return \@not_allowed
1837 __PACKAGE__->register_method(
1838 method => 'check_user_perms3',
1839 api_name => 'open-ils.actor.user.perm.highest_org',
1841 Returns the highest org unit id at which a user has a given permission
1842 If the requestor does not match the target user, the requestor must have
1843 'VIEW_PERMISSION' rights at the home org unit of the target user
1844 @param authtoken The login session key
1845 @param userid The id of the user in question
1846 @param perm The permission to check
1847 @return The org unit highest in the org tree within which the user has
1848 the requested permission
1851 sub check_user_perms3 {
1852 my($self, $client, $authtoken, $user_id, $perm) = @_;
1853 my $e = new_editor(authtoken=>$authtoken);
1854 return $e->event unless $e->checkauth;
1856 my $tree = $U->get_org_tree();
1858 unless($e->requestor->id == $user_id) {
1859 my $user = $e->retrieve_actor_user($user_id)
1860 or return $e->event;
1861 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1862 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1865 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1868 __PACKAGE__->register_method(
1869 method => 'user_has_work_perm_at',
1870 api_name => 'open-ils.actor.user.has_work_perm_at',
1874 Returns a set of org unit IDs which represent the highest orgs in
1875 the org tree where the user has the requested permission. The
1876 purpose of this method is to return the smallest set of org units
1877 which represent the full expanse of the user's ability to perform
1878 the requested action. The user whose perms this method should
1879 check is implied by the authtoken. /,
1881 {desc => 'authtoken', type => 'string'},
1882 {desc => 'permission name', type => 'string'},
1883 {desc => q/user id, optional. If present, check perms for
1884 this user instead of the logged in user/, type => 'number'},
1886 return => {desc => 'An array of org IDs'}
1890 sub user_has_work_perm_at {
1891 my($self, $conn, $auth, $perm, $user_id) = @_;
1892 my $e = new_editor(authtoken=>$auth);
1893 return $e->event unless $e->checkauth;
1894 if(defined $user_id) {
1895 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1896 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1898 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1901 __PACKAGE__->register_method(
1902 method => 'user_has_work_perm_at_batch',
1903 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1907 sub user_has_work_perm_at_batch {
1908 my($self, $conn, $auth, $perms, $user_id) = @_;
1909 my $e = new_editor(authtoken=>$auth);
1910 return $e->event unless $e->checkauth;
1911 if(defined $user_id) {
1912 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1913 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1916 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1922 __PACKAGE__->register_method(
1923 method => 'check_user_perms4',
1924 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1926 Returns the highest org unit id at which a user has a given permission
1927 If the requestor does not match the target user, the requestor must have
1928 'VIEW_PERMISSION' rights at the home org unit of the target user
1929 @param authtoken The login session key
1930 @param userid The id of the user in question
1931 @param perms An array of perm names to check
1932 @return An array of orgId's representing the org unit
1933 highest in the org tree within which the user has the requested permission
1934 The arrah of orgId's has matches the order of the perms array
1937 sub check_user_perms4 {
1938 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1940 my( $staff, $target, $org, $evt );
1942 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1943 $authtoken, $userid, 'VIEW_PERMISSION' );
1944 return $evt if $evt;
1947 return [] unless ref($perms);
1948 my $tree = $U->get_org_tree();
1950 for my $p (@$perms) {
1951 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1957 __PACKAGE__->register_method(
1958 method => "user_fines_summary",
1959 api_name => "open-ils.actor.user.fines.summary",
1962 desc => 'Returns a short summary of the users total open fines, ' .
1963 'excluding voided fines Params are login_session, user_id' ,
1965 {desc => 'Authentication token', type => 'string'},
1966 {desc => 'User ID', type => 'string'} # number?
1969 desc => "a 'mous' object, event on error",
1974 sub user_fines_summary {
1975 my( $self, $client, $auth, $user_id ) = @_;
1977 my $e = new_editor(authtoken=>$auth);
1978 return $e->event unless $e->checkauth;
1980 if( $user_id ne $e->requestor->id ) {
1981 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1982 return $e->event unless
1983 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1986 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1990 __PACKAGE__->register_method(
1991 method => "user_opac_vitals",
1992 api_name => "open-ils.actor.user.opac.vital_stats",
1996 desc => 'Returns a short summary of the users vital stats, including ' .
1997 'identification information, accumulated balance, number of holds, ' .
1998 'and current open circulation stats' ,
2000 {desc => 'Authentication token', type => 'string'},
2001 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
2004 desc => "An object with four properties: user, fines, checkouts and holds."
2009 sub user_opac_vitals {
2010 my( $self, $client, $auth, $user_id ) = @_;
2012 my $e = new_editor(authtoken=>$auth);
2013 return $e->event unless $e->checkauth;
2015 $user_id ||= $e->requestor->id;
2017 my $user = $e->retrieve_actor_user( $user_id );
2020 ->method_lookup('open-ils.actor.user.fines.summary')
2021 ->run($auth => $user_id);
2022 return $fines if (defined($U->event_code($fines)));
2025 $fines = new Fieldmapper::money::open_user_summary ();
2026 $fines->balance_owed(0.00);
2027 $fines->total_owed(0.00);
2028 $fines->total_paid(0.00);
2029 $fines->usr($user_id);
2033 ->method_lookup('open-ils.actor.user.hold_requests.count')
2034 ->run($auth => $user_id);
2035 return $holds if (defined($U->event_code($holds)));
2038 ->method_lookup('open-ils.actor.user.checked_out.count')
2039 ->run($auth => $user_id);
2040 return $out if (defined($U->event_code($out)));
2042 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
2044 my $unread_msgs = $e->search_actor_usr_message([
2045 {usr => $user_id, read_date => undef, deleted => 'f',
2046 'pub' => 't', # this is for the unread message count in the opac
2047 #'-or' => [ # Hiding Archived messages are for staff UI, not this
2048 # {stop_date => undef},
2049 # {stop_date => {'>' => 'now'}}
2057 first_given_name => $user->first_given_name,
2058 second_given_name => $user->second_given_name,
2059 family_name => $user->family_name,
2060 alias => $user->alias,
2061 usrname => $user->usrname
2063 fines => $fines->to_bare_hash,
2066 messages => { unread => scalar(@$unread_msgs) }
2071 ##### a small consolidation of related method registrations
2072 my $common_params = [
2073 { desc => 'Authentication token', type => 'string' },
2074 { desc => 'User ID', type => 'string' },
2075 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
2076 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
2079 'open-ils.actor.user.transactions' => '',
2080 'open-ils.actor.user.transactions.fleshed' => '',
2081 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
2082 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
2083 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
2084 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
2087 foreach (keys %methods) {
2089 method => "user_transactions",
2092 desc => 'For a given user, retrieve a list of '
2093 . (/\.fleshed/ ? 'fleshed ' : '')
2094 . 'transactions' . $methods{$_}
2095 . ' optionally limited to transactions of a given type.',
2096 params => $common_params,
2098 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
2099 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
2103 $args{authoritative} = 1;
2104 __PACKAGE__->register_method(%args);
2107 # Now for the counts
2109 'open-ils.actor.user.transactions.count' => '',
2110 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
2111 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
2114 foreach (keys %methods) {
2116 method => "user_transactions",
2119 desc => 'For a given user, retrieve a count of open '
2120 . 'transactions' . $methods{$_}
2121 . ' optionally limited to transactions of a given type.',
2122 params => $common_params,
2123 return => { desc => "Integer count of transactions, or event on error" }
2126 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2127 __PACKAGE__->register_method(%args);
2130 __PACKAGE__->register_method(
2131 method => "user_transactions",
2132 api_name => "open-ils.actor.user.transactions.have_balance.total",
2135 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2136 . ' optionally limited to transactions of a given type.',
2137 params => $common_params,
2138 return => { desc => "Decimal balance value, or event on error" }
2143 sub user_transactions {
2144 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2147 my $e = new_editor(authtoken => $auth);
2148 return $e->event unless $e->checkauth;
2150 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2152 return $e->event unless
2153 $e->requestor->id == $user_id or
2154 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2156 my $api = $self->api_name();
2158 my $filter = ($api =~ /have_balance/o) ?
2159 { 'balance_owed' => { '<>' => 0 } }:
2160 { 'total_owed' => { '>' => 0 } };
2162 my $method = 'open-ils.actor.user.transactions.history.still_open';
2163 $method = "$method.authoritative" if $api =~ /authoritative/;
2164 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2166 if($api =~ /total/o) {
2168 $total += $_->balance_owed for @$trans;
2172 ($api =~ /count/o ) and return scalar @$trans;
2173 ($api !~ /fleshed/o) and return $trans;
2176 for my $t (@$trans) {
2178 if( $t->xact_type ne 'circulation' ) {
2179 push @resp, {transaction => $t};
2183 my $circ_data = flesh_circ($e, $t->id);
2184 push @resp, {transaction => $t, %$circ_data};
2191 __PACKAGE__->register_method(
2192 method => "user_transaction_retrieve",
2193 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2196 notes => "Returns a fleshed transaction record"
2199 __PACKAGE__->register_method(
2200 method => "user_transaction_retrieve",
2201 api_name => "open-ils.actor.user.transaction.retrieve",
2204 notes => "Returns a transaction record"
2207 sub user_transaction_retrieve {
2208 my($self, $client, $auth, $bill_id) = @_;
2210 my $e = new_editor(authtoken => $auth);
2211 return $e->event unless $e->checkauth;
2213 my $trans = $e->retrieve_money_billable_transaction_summary(
2214 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2216 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2218 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2220 return $trans unless $self->api_name =~ /flesh/;
2221 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2223 my $circ_data = flesh_circ($e, $trans->id, 1);
2225 return {transaction => $trans, %$circ_data};
2230 my $circ_id = shift;
2231 my $flesh_copy = shift;
2233 my $circ = $e->retrieve_action_circulation([
2237 circ => ['target_copy'],
2238 acp => ['call_number'],
2245 my $copy = $circ->target_copy;
2247 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2248 $mods = new Fieldmapper::metabib::virtual_record;
2249 $mods->doc_id(OILS_PRECAT_RECORD);
2250 $mods->title($copy->dummy_title);
2251 $mods->author($copy->dummy_author);
2254 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2258 $circ->target_copy($circ->target_copy->id);
2259 $copy->call_number($copy->call_number->id);
2261 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2265 __PACKAGE__->register_method(
2266 method => "hold_request_count",
2267 api_name => "open-ils.actor.user.hold_requests.count",
2271 Returns hold ready vs. total counts.
2272 If a context org unit is provided, a third value
2273 is returned with key 'behind_desk', which reports
2274 how many holds are ready at the pickup library
2275 with the behind_desk flag set to true.
2279 sub hold_request_count {
2280 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2281 my $e = new_editor(authtoken => $authtoken);
2282 return $e->event unless $e->checkauth;
2284 $user_id = $e->requestor->id unless defined $user_id;
2286 if($e->requestor->id ne $user_id) {
2287 my $user = $e->retrieve_actor_user($user_id);
2288 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2291 my $holds = $e->json_query({
2292 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2296 fulfillment_time => {"=" => undef },
2297 cancel_time => undef,
2302 $_->{current_shelf_lib} and # avoid undef warnings
2303 $_->{pickup_lib} eq $_->{current_shelf_lib}
2307 total => scalar(@$holds),
2308 ready => int(scalar(@ready))
2312 # count of holds ready at pickup lib with behind_desk true.
2313 $resp->{behind_desk} = int(scalar(
2315 $_->{pickup_lib} == $ctx_org and
2316 $U->is_true($_->{behind_desk})
2324 __PACKAGE__->register_method(
2325 method => "checked_out",
2326 api_name => "open-ils.actor.user.checked_out",
2330 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2331 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2332 . "(i.e., outstanding balance or some other pending action on the circ). "
2333 . "The .count method also includes a 'total' field which sums all open circs.",
2335 { desc => 'Authentication Token', type => 'string'},
2336 { desc => 'User ID', type => 'string'},
2339 desc => 'Returns event on error, or an object with ID lists, like: '
2340 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2345 __PACKAGE__->register_method(
2346 method => "checked_out",
2347 api_name => "open-ils.actor.user.checked_out.count",
2350 signature => q/@see open-ils.actor.user.checked_out/
2354 my( $self, $conn, $auth, $userid ) = @_;
2356 my $e = new_editor(authtoken=>$auth);
2357 return $e->event unless $e->checkauth;
2359 if( $userid ne $e->requestor->id ) {
2360 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2361 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2363 # see if there is a friend link allowing circ.view perms
2364 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2365 $e, $userid, $e->requestor->id, 'circ.view');
2366 return $e->event unless $allowed;
2370 my $count = $self->api_name =~ /count/;
2371 return _checked_out( $count, $e, $userid );
2375 my( $iscount, $e, $userid ) = @_;
2381 claims_returned => [],
2384 my $meth = 'retrieve_action_open_circ_';
2392 claims_returned => 0,
2399 my $data = $e->$meth($userid);
2403 $result{$_} += $data->$_() for (keys %result);
2404 $result{total} += $data->$_() for (keys %result);
2406 for my $k (keys %result) {
2407 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2417 __PACKAGE__->register_method(
2418 method => "checked_in_with_fines",
2419 api_name => "open-ils.actor.user.checked_in_with_fines",
2422 signature => q/@see open-ils.actor.user.checked_out/
2425 sub checked_in_with_fines {
2426 my( $self, $conn, $auth, $userid ) = @_;
2428 my $e = new_editor(authtoken=>$auth);
2429 return $e->event unless $e->checkauth;
2431 if( $userid ne $e->requestor->id ) {
2432 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2435 # money is owed on these items and they are checked in
2436 my $open = $e->search_action_circulation(
2439 xact_finish => undef,
2440 checkin_time => { "!=" => undef },
2445 my( @lost, @cr, @lo );
2446 for my $c (@$open) {
2447 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2448 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2449 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2454 claims_returned => \@cr,
2455 long_overdue => \@lo
2461 my ($api, $desc, $auth) = @_;
2462 $desc = $desc ? (" " . $desc) : '';
2463 my $ids = ($api =~ /ids$/) ? 1 : 0;
2466 method => "user_transaction_history",
2467 api_name => "open-ils.actor.user.transactions.$api",
2469 desc => "For a given User ID, returns a list of billable transaction" .
2470 ($ids ? " id" : '') .
2471 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2472 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2474 {desc => 'Authentication token', type => 'string'},
2475 {desc => 'User ID', type => 'number'},
2476 {desc => 'Transaction type (optional)', type => 'number'},
2477 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2480 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2484 $auth and push @sig, (authoritative => 1);
2488 my %auth_hist_methods = (
2490 'history.have_charge' => 'that have an initial charge',
2491 'history.still_open' => 'that are not finished',
2492 'history.have_balance' => 'that have a balance',
2493 'history.have_bill' => 'that have billings',
2494 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2495 'history.have_payment' => 'that have at least 1 payment',
2498 foreach (keys %auth_hist_methods) {
2499 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2500 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2501 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2504 sub user_transaction_history {
2505 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2509 my $e = new_editor(authtoken=>$auth);
2510 return $e->die_event unless $e->checkauth;
2512 if ($e->requestor->id ne $userid) {
2513 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2516 my $api = $self->api_name;
2517 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2519 if(defined($type)) {
2520 $filter->{'xact_type'} = $type;
2523 if($api =~ /have_bill_or_payment/o) {
2525 # transactions that have a non-zero sum across all billings or at least 1 payment
2526 $filter->{'-or'} = {
2527 'balance_owed' => { '<>' => 0 },
2528 'last_payment_ts' => { '<>' => undef }
2531 } elsif($api =~ /have_payment/) {
2533 $filter->{last_payment_ts} ||= {'<>' => undef};
2535 } elsif( $api =~ /have_balance/o) {
2537 # transactions that have a non-zero overall balance
2538 $filter->{'balance_owed'} = { '<>' => 0 };
2540 } elsif( $api =~ /have_charge/o) {
2542 # transactions that have at least 1 billing, regardless of whether it was voided
2543 $filter->{'last_billing_ts'} = { '<>' => undef };
2545 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2547 # transactions that have non-zero sum across all billings. This will exclude
2548 # xacts where all billings have been voided
2549 $filter->{'total_owed'} = { '<>' => 0 };
2552 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2553 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2554 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2556 my $mbts = $e->search_money_billable_transaction_summary(
2557 [ { usr => $userid, @xact_finish, %$filter },
2562 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2563 return $mbts unless $api =~ /fleshed/;
2566 for my $t (@$mbts) {
2568 if( $t->xact_type ne 'circulation' ) {
2569 push @resp, {transaction => $t};
2573 my $circ_data = flesh_circ($e, $t->id);
2574 push @resp, {transaction => $t, %$circ_data};
2582 __PACKAGE__->register_method(
2583 method => "user_perms",
2584 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2586 notes => "Returns a list of permissions"
2590 my( $self, $client, $authtoken, $user ) = @_;
2592 my( $staff, $evt ) = $apputils->checkses($authtoken);
2593 return $evt if $evt;
2595 $user ||= $staff->id;
2597 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2601 return $apputils->simple_scalar_request(
2603 "open-ils.storage.permission.user_perms.atomic",
2607 __PACKAGE__->register_method(
2608 method => "retrieve_perms",
2609 api_name => "open-ils.actor.permissions.retrieve",
2610 notes => "Returns a list of permissions"
2612 sub retrieve_perms {
2613 my( $self, $client ) = @_;
2614 return $apputils->simple_scalar_request(
2616 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2617 { id => { '!=' => undef } }
2621 __PACKAGE__->register_method(
2622 method => "retrieve_groups",
2623 api_name => "open-ils.actor.groups.retrieve",
2624 notes => "Returns a list of user groups"
2626 sub retrieve_groups {
2627 my( $self, $client ) = @_;
2628 return new_editor()->retrieve_all_permission_grp_tree();
2631 __PACKAGE__->register_method(
2632 method => "retrieve_org_address",
2633 api_name => "open-ils.actor.org_unit.address.retrieve",
2634 notes => <<' NOTES');
2635 Returns an org_unit address by ID
2636 @param An org_address ID
2638 sub retrieve_org_address {
2639 my( $self, $client, $id ) = @_;
2640 return $apputils->simple_scalar_request(
2642 "open-ils.cstore.direct.actor.org_address.retrieve",
2647 __PACKAGE__->register_method(
2648 method => "retrieve_groups_tree",
2649 api_name => "open-ils.actor.groups.tree.retrieve",
2650 notes => "Returns a list of user groups"
2653 sub retrieve_groups_tree {
2654 my( $self, $client ) = @_;
2655 return new_editor()->search_permission_grp_tree(
2660 flesh_fields => { pgt => ["children"] },
2661 order_by => { pgt => 'name'}
2668 __PACKAGE__->register_method(
2669 method => "add_user_to_groups",
2670 api_name => "open-ils.actor.user.set_groups",
2671 notes => "Adds a user to one or more permission groups"
2674 sub add_user_to_groups {
2675 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2677 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2678 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2679 return $evt if $evt;
2681 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2682 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2683 return $evt if $evt;
2685 $apputils->simplereq(
2687 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2689 for my $group (@$groups) {
2690 my $link = Fieldmapper::permission::usr_grp_map->new;
2692 $link->usr($userid);
2694 my $id = $apputils->simplereq(
2696 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2702 __PACKAGE__->register_method(
2703 method => "get_user_perm_groups",
2704 api_name => "open-ils.actor.user.get_groups",
2705 notes => "Retrieve a user's permission groups."
2709 sub get_user_perm_groups {
2710 my( $self, $client, $authtoken, $userid ) = @_;
2712 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2713 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2714 return $evt if $evt;
2716 return $apputils->simplereq(
2718 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2722 __PACKAGE__->register_method(
2723 method => "get_user_work_ous",
2724 api_name => "open-ils.actor.user.get_work_ous",
2725 notes => "Retrieve a user's work org units."
2728 __PACKAGE__->register_method(
2729 method => "get_user_work_ous",
2730 api_name => "open-ils.actor.user.get_work_ous.ids",
2731 notes => "Retrieve a user's work org units."
2734 sub get_user_work_ous {
2735 my( $self, $client, $auth, $userid ) = @_;
2736 my $e = new_editor(authtoken=>$auth);
2737 return $e->event unless $e->checkauth;
2738 $userid ||= $e->requestor->id;
2740 if($e->requestor->id != $userid) {
2741 my $user = $e->retrieve_actor_user($userid)
2742 or return $e->event;
2743 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2746 return $e->search_permission_usr_work_ou_map({usr => $userid})
2747 unless $self->api_name =~ /.ids$/;
2749 # client just wants a list of org IDs
2750 return $U->get_user_work_ou_ids($e, $userid);
2755 __PACKAGE__->register_method(
2756 method => 'register_workstation',
2757 api_name => 'open-ils.actor.workstation.register.override',
2758 signature => q/@see open-ils.actor.workstation.register/
2761 __PACKAGE__->register_method(
2762 method => 'register_workstation',
2763 api_name => 'open-ils.actor.workstation.register',
2765 Registers a new workstion in the system
2766 @param authtoken The login session key
2767 @param name The name of the workstation id
2768 @param owner The org unit that owns this workstation
2769 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2770 if the name is already in use.
2774 sub register_workstation {
2775 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2777 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2778 return $e->die_event unless $e->checkauth;
2779 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2780 my $existing = $e->search_actor_workstation({name => $name})->[0];
2781 $oargs = { all => 1 } unless defined $oargs;
2785 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2786 # workstation with the given name exists.
2788 if($owner ne $existing->owning_lib) {
2789 # if necessary, update the owning_lib of the workstation
2791 $logger->info("changing owning lib of workstation ".$existing->id.
2792 " from ".$existing->owning_lib." to $owner");
2793 return $e->die_event unless
2794 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2796 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2798 $existing->owning_lib($owner);
2799 return $e->die_event unless $e->update_actor_workstation($existing);
2805 "attempt to register an existing workstation. returning existing ID");
2808 return $existing->id;
2811 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2815 my $ws = Fieldmapper::actor::workstation->new;
2816 $ws->owning_lib($owner);
2818 $e->create_actor_workstation($ws) or return $e->die_event;
2820 return $ws->id; # note: editor sets the id on the new object for us
2823 __PACKAGE__->register_method(
2824 method => 'workstation_list',
2825 api_name => 'open-ils.actor.workstation.list',
2827 Returns a list of workstations registered at the given location
2828 @param authtoken The login session key
2829 @param ids A list of org_unit.id's for the workstation owners
2833 sub workstation_list {
2834 my( $self, $conn, $authtoken, @orgs ) = @_;
2836 my $e = new_editor(authtoken=>$authtoken);
2837 return $e->event unless $e->checkauth;
2842 unless $e->allowed('REGISTER_WORKSTATION', $o);
2843 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2848 __PACKAGE__->register_method(
2849 method => 'fetch_patron_messages',
2850 api_name => 'open-ils.actor.message.retrieve',
2853 Returns a list of notes for a given user, not
2854 including ones marked deleted
2855 @param authtoken The login session key
2856 @param patronid patron ID
2857 @param options hash containing optional limit and offset
2861 sub fetch_patron_messages {
2862 my( $self, $conn, $auth, $patronid, $options ) = @_;
2866 my $e = new_editor(authtoken => $auth);
2867 return $e->die_event unless $e->checkauth;
2869 if ($e->requestor->id ne $patronid) {
2870 return $e->die_event unless $e->allowed('VIEW_USER');
2873 my $select_clause = { usr => $patronid };
2874 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2875 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2876 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2878 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2883 __PACKAGE__->register_method(
2884 method => 'usrname_exists',
2885 api_name => 'open-ils.actor.username.exists',
2887 desc => 'Check if a username is already taken (by an undeleted patron)',
2889 {desc => 'Authentication token', type => 'string'},
2890 {desc => 'Username', type => 'string'}
2893 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2898 sub usrname_exists {
2899 my( $self, $conn, $auth, $usrname ) = @_;
2900 my $e = new_editor(authtoken=>$auth);
2901 return $e->event unless $e->checkauth;
2902 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2903 return $$a[0] if $a and @$a;
2907 __PACKAGE__->register_method(
2908 method => 'barcode_exists',
2909 api_name => 'open-ils.actor.barcode.exists',
2911 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2914 sub barcode_exists {
2915 my( $self, $conn, $auth, $barcode ) = @_;
2916 my $e = new_editor(authtoken=>$auth);
2917 return $e->event unless $e->checkauth;
2918 my $card = $e->search_actor_card({barcode => $barcode});
2924 #return undef unless @$card;
2925 #return $card->[0]->usr;
2929 __PACKAGE__->register_method(
2930 method => 'retrieve_net_levels',
2931 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2934 sub retrieve_net_levels {
2935 my( $self, $conn, $auth ) = @_;
2936 my $e = new_editor(authtoken=>$auth);
2937 return $e->event unless $e->checkauth;
2938 return $e->retrieve_all_config_net_access_level();
2941 # Retain the old typo API name just in case
2942 __PACKAGE__->register_method(
2943 method => 'fetch_org_by_shortname',
2944 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2946 __PACKAGE__->register_method(
2947 method => 'fetch_org_by_shortname',
2948 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2950 sub fetch_org_by_shortname {
2951 my( $self, $conn, $sname ) = @_;
2952 my $e = new_editor();
2953 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2954 return $e->event unless $org;
2959 __PACKAGE__->register_method(
2960 method => 'session_home_lib',
2961 api_name => 'open-ils.actor.session.home_lib',
2964 sub session_home_lib {
2965 my( $self, $conn, $auth ) = @_;
2966 my $e = new_editor(authtoken=>$auth);
2967 return undef unless $e->checkauth;
2968 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2969 return $org->shortname;
2972 __PACKAGE__->register_method(
2973 method => 'session_safe_token',
2974 api_name => 'open-ils.actor.session.safe_token',
2976 Returns a hashed session ID that is safe for export to the world.
2977 This safe token will expire after 1 hour of non-use.
2978 @param auth Active authentication token
2982 sub session_safe_token {
2983 my( $self, $conn, $auth ) = @_;
2984 my $e = new_editor(authtoken=>$auth);
2985 return undef unless $e->checkauth;
2987 my $safe_token = md5_hex($auth);
2989 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2991 # add more user fields as needed
2993 "safe-token-user-$safe_token", {
2994 id => $e->requestor->id,
2995 home_ou_shortname => $e->retrieve_actor_org_unit(
2996 $e->requestor->home_ou)->shortname,
3005 __PACKAGE__->register_method(
3006 method => 'safe_token_home_lib',
3007 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
3009 Returns the home library shortname from the session
3010 asscociated with a safe token from generated by
3011 open-ils.actor.session.safe_token.
3012 @param safe_token Active safe token
3013 @param who Optional user activity "ewho" value
3017 sub safe_token_home_lib {
3018 my( $self, $conn, $safe_token, $who ) = @_;
3019 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3021 my $blob = $cache->get_cache("safe-token-user-$safe_token");
3022 return unless $blob;
3024 $U->log_user_activity($blob->{id}, $who, 'verify');
3025 return $blob->{home_ou_shortname};
3029 __PACKAGE__->register_method(
3030 method => "update_penalties",
3031 api_name => "open-ils.actor.user.penalties.update"
3034 __PACKAGE__->register_method(
3035 method => "update_penalties",
3036 api_name => "open-ils.actor.user.penalties.update_at_home"
3039 sub update_penalties {
3040 my($self, $conn, $auth, $user_id, @penalties) = @_;
3041 my $e = new_editor(authtoken=>$auth, xact => 1);
3042 return $e->die_event unless $e->checkauth;
3043 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3044 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3045 my $context_org = ($self->api_name =~ /_at_home$/) ? $user->home_ou : $e->requestor->ws_ou;
3046 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $context_org, @penalties);
3047 return $evt if $evt;
3053 __PACKAGE__->register_method(
3054 method => "apply_penalty",
3055 api_name => "open-ils.actor.user.penalty.apply"
3059 my($self, $conn, $auth, $penalty, $msg) = @_;
3063 my $e = new_editor(authtoken=>$auth, xact => 1);
3064 return $e->die_event unless $e->checkauth;
3066 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3067 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3069 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3071 my $ctx_org = $penalty->org_unit; # csp org_depth is now considered in the UI for the org drop-down menu
3073 if (($msg->{title} || $msg->{message}) && ($msg->{title} ne '' || $msg->{message} ne '')) {
3074 my $aum = Fieldmapper::actor::usr_message->new;
3076 $aum->create_date('now');
3077 $aum->sending_lib($e->requestor->ws_ou);
3078 $aum->title($msg->{title});
3079 $aum->usr($penalty->usr);
3080 $aum->message($msg->{message});
3081 $aum->pub($msg->{pub});
3083 $aum = $e->create_actor_usr_message($aum)
3084 or return $e->die_event;
3086 $penalty->usr_message($aum->id);
3089 $penalty->org_unit($ctx_org);
3090 $penalty->staff($e->requestor->id);
3091 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3094 return $penalty->id;
3097 __PACKAGE__->register_method(
3098 method => "modify_penalty",
3099 api_name => "open-ils.actor.user.penalty.modify"
3102 sub modify_penalty {
3103 my($self, $conn, $auth, $penalty, $usr_msg) = @_;
3105 my $e = new_editor(authtoken=>$auth, xact => 1);
3106 return $e->die_event unless $e->checkauth;
3108 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3109 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3111 $usr_msg->editor($e->requestor->id);
3112 $usr_msg->edit_date('now');
3114 if ($usr_msg->isnew) {
3115 $usr_msg = $e->create_actor_usr_message($usr_msg)
3116 or return $e->die_event;
3117 $penalty->usr_message($usr_msg->id);
3119 $usr_msg = $e->update_actor_usr_message($usr_msg)
3120 or return $e->die_event;
3123 if ($penalty->isnew) {
3124 $penalty = $e->create_actor_user_standing_penalty($penalty)
3125 or return $e->die_event;
3127 $penalty = $e->update_actor_user_standing_penalty($penalty)
3128 or return $e->die_event;
3135 __PACKAGE__->register_method(
3136 method => "remove_penalty",
3137 api_name => "open-ils.actor.user.penalty.remove"
3140 sub remove_penalty {
3141 my($self, $conn, $auth, $penalty) = @_;
3142 my $e = new_editor(authtoken=>$auth, xact => 1);
3143 return $e->die_event unless $e->checkauth;
3144 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3145 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3147 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3152 __PACKAGE__->register_method(
3153 method => "update_penalty_note",
3154 api_name => "open-ils.actor.user.penalty.note.update"
3157 sub update_penalty_note {
3158 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3159 my $e = new_editor(authtoken=>$auth, xact => 1);
3160 return $e->die_event unless $e->checkauth;
3161 for my $penalty_id (@$penalty_ids) {
3162 my $penalty = $e->search_actor_user_standing_penalty([
3163 { id => $penalty_id },
3165 flesh_fields => {aum => ['usr_message']}
3168 if (! $penalty ) { return $e->die_event; }
3169 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3170 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3172 my $aum = $penalty->usr_message();
3174 $aum = Fieldmapper::actor::usr_message->new;
3176 $aum->create_date('now');
3177 $aum->sending_lib($e->requestor->ws_ou);
3179 $aum->usr($penalty->usr);
3180 $aum->message($note);
3184 $aum = $e->create_actor_usr_message($aum)
3185 or return $e->die_event;
3187 $penalty->usr_message($aum->id);
3188 $penalty->ischanged(1);
3189 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3191 $aum = $e->retrieve_actor_usr_message($aum) or return $e->die_event;
3192 $aum->message($note); $aum->ischanged(1);
3193 $e->update_actor_usr_message($aum) or return $e->die_event;
3200 __PACKAGE__->register_method(
3201 method => "ranged_penalty_thresholds",
3202 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3206 sub ranged_penalty_thresholds {
3207 my($self, $conn, $auth, $context_org) = @_;
3208 my $e = new_editor(authtoken=>$auth);
3209 return $e->event unless $e->checkauth;
3210 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3211 my $list = $e->search_permission_grp_penalty_threshold([
3212 {org_unit => $U->get_org_ancestors($context_org)},
3213 {order_by => {pgpt => 'id'}}
3215 $conn->respond($_) for @$list;
3221 __PACKAGE__->register_method(
3222 method => "user_retrieve_fleshed_by_id",
3224 api_name => "open-ils.actor.user.fleshed.retrieve",
3227 sub user_retrieve_fleshed_by_id {
3228 my( $self, $client, $auth, $user_id, $fields ) = @_;
3229 my $e = new_editor(authtoken => $auth);
3230 return $e->event unless $e->checkauth;
3232 if( $e->requestor->id != $user_id ) {
3233 return $e->event unless $e->allowed('VIEW_USER');
3240 "standing_penalties",
3248 return new_flesh_user($user_id, $fields, $e);
3252 sub new_flesh_user {
3255 my $fields = shift || [];
3258 my $fetch_penalties = 0;
3259 if(grep {$_ eq 'standing_penalties'} @$fields) {
3260 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3261 $fetch_penalties = 1;
3264 my $fetch_notes = 0;
3265 if(grep {$_ eq 'notes'} @$fields) {
3266 $fields = [grep {$_ ne 'notes'} @$fields];
3270 my $fetch_usr_act = 0;
3271 if(grep {$_ eq 'usr_activity'} @$fields) {
3272 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3276 my $user = $e->retrieve_actor_user(
3281 "flesh_fields" => { "au" => $fields }
3284 ) or return $e->die_event;
3287 if( grep { $_ eq 'addresses' } @$fields ) {
3289 $user->addresses([]) unless @{$user->addresses};
3290 # don't expose "replaced" addresses by default
3291 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3293 if( ref $user->billing_address ) {
3294 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3295 push( @{$user->addresses}, $user->billing_address );
3299 if( ref $user->mailing_address ) {
3300 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3301 push( @{$user->addresses}, $user->mailing_address );
3306 if($fetch_penalties) {
3307 # grab the user penalties ranged for this location
3308 $user->standing_penalties(
3309 $e->search_actor_user_standing_penalty([
3312 {stop_date => undef},
3313 {stop_date => {'>' => 'now'}}
3315 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3318 flesh_fields => {ausp => ['standing_penalty','usr_message']}
3325 # grab notes (now actor.usr_message_penalty) that have not hit their stop_date
3326 # NOTE: This is a view that already filters out deleted messages that are not
3327 # attached to a penalty
3329 @{ $e->search_actor_usr_message_penalty([
3332 {stop_date => undef},
3333 {stop_date => {'>' => 'now'}}
3340 # retrieve the most recent usr_activity entry
3341 if ($fetch_usr_act) {
3343 # max number to return for simple patron fleshing
3344 my $limit = $U->ou_ancestor_setting_value(
3345 $e->requestor->ws_ou,
3346 'circ.patron.usr_activity_retrieve.max');
3350 flesh_fields => {auact => ['etype']},
3351 order_by => {auact => 'event_time DESC'},
3354 # 0 == none, <0 == return all
3355 $limit = 1 unless defined $limit;
3356 $opts->{limit} = $limit if $limit > 0;
3358 $user->usr_activity(
3360 [] : # skip the DB call
3361 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3366 $user->clear_passwd();
3373 __PACKAGE__->register_method(
3374 method => "user_retrieve_parts",
3375 api_name => "open-ils.actor.user.retrieve.parts",
3378 sub user_retrieve_parts {
3379 my( $self, $client, $auth, $user_id, $fields ) = @_;
3380 my $e = new_editor(authtoken => $auth);
3381 return $e->event unless $e->checkauth;
3382 $user_id ||= $e->requestor->id;
3383 if( $e->requestor->id != $user_id ) {
3384 return $e->event unless $e->allowed('VIEW_USER');
3387 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3388 push(@resp, $user->$_()) for(@$fields);
3394 __PACKAGE__->register_method(
3395 method => 'user_opt_in_enabled',
3396 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3397 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3400 sub user_opt_in_enabled {
3401 my($self, $conn) = @_;
3402 my $sc = OpenSRF::Utils::SettingsClient->new;
3403 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3408 __PACKAGE__->register_method(
3409 method => 'user_opt_in_at_org',
3410 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3412 @param $auth The auth token
3413 @param user_id The ID of the user to test
3414 @return 1 if the user has opted in at the specified org,
3415 2 if opt-in is disallowed for the user's home org,
3416 event on error, and 0 otherwise. /
3418 sub user_opt_in_at_org {
3419 my($self, $conn, $auth, $user_id) = @_;
3421 # see if we even need to enforce the opt-in value
3422 return 1 unless user_opt_in_enabled($self);
3424 my $e = new_editor(authtoken => $auth);
3425 return $e->event unless $e->checkauth;
3427 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3428 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3430 my $ws_org = $e->requestor->ws_ou;
3431 # user is automatically opted-in if they are from the local org
3432 return 1 if $user->home_ou eq $ws_org;
3434 # get the boundary setting
3435 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3437 # auto opt in if user falls within the opt boundary
3438 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3440 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3442 # check whether opt-in is restricted at the user's home library
3443 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3444 if ($opt_restrict_depth) {
3445 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3446 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3448 # opt-in is disallowed unless the workstation org is within the home
3449 # library's opt-in scope
3450 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3453 my $vals = $e->search_actor_usr_org_unit_opt_in(
3454 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3460 __PACKAGE__->register_method(
3461 method => 'create_user_opt_in_at_org',
3462 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3464 @param $auth The auth token
3465 @param user_id The ID of the user to test
3466 @return The ID of the newly created object, event on error./
3469 sub create_user_opt_in_at_org {
3470 my($self, $conn, $auth, $user_id, $org_id) = @_;
3472 my $e = new_editor(authtoken => $auth, xact=>1);
3473 return $e->die_event unless $e->checkauth;
3475 # if a specific org unit wasn't passed in, get one based on the defaults;
3477 my $wsou = $e->requestor->ws_ou;
3478 # get the default opt depth
3479 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3480 # get the org unit at that depth
3481 my $org = $e->json_query({
3482 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3483 $org_id = $org->{id};
3486 # fall back to the workstation OU, the pre-opt-in-boundary way
3487 $org_id = $e->requestor->ws_ou;
3490 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3491 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3493 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3495 $opt_in->org_unit($org_id);
3496 $opt_in->usr($user_id);
3497 $opt_in->staff($e->requestor->id);
3498 $opt_in->opt_in_ts('now');
3499 $opt_in->opt_in_ws($e->requestor->wsid);
3501 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3502 or return $e->die_event;
3510 __PACKAGE__->register_method (
3511 method => 'retrieve_org_hours',
3512 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3514 Returns the hours of operation for a specified org unit
3515 @param authtoken The login session key
3516 @param org_id The org_unit ID
3520 sub retrieve_org_hours {
3521 my($self, $conn, $auth, $org_id) = @_;
3522 my $e = new_editor(authtoken => $auth);
3523 return $e->die_event unless $e->checkauth;
3524 $org_id ||= $e->requestor->ws_ou;
3525 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3529 __PACKAGE__->register_method (
3530 method => 'verify_user_password',
3531 api_name => 'open-ils.actor.verify_user_password',
3533 Given a barcode or username and the MD5 encoded password,
3534 The password can also be passed without the MD5 hashing.
3535 returns 1 if the password is correct. Returns 0 otherwise.
3539 sub verify_user_password {
3540 my($self, $conn, $auth, $barcode, $username, $password, $pass_nohash) = @_;
3541 my $e = new_editor(authtoken => $auth);
3542 return $e->die_event unless $e->checkauth;
3544 my $user_by_barcode;
3545 my $user_by_username;
3547 my $card = $e->search_actor_card([
3548 {barcode => $barcode},
3549 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3550 $user_by_barcode = $card->usr;
3551 $user = $user_by_barcode;
3554 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3555 $user = $user_by_username;
3557 return 0 if (!$user || $U->is_true($user->deleted));
3558 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3559 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3562 return $U->verify_migrated_user_password($e, $user->id, $pass_nohash);
3564 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3568 __PACKAGE__->register_method (
3569 method => 'retrieve_usr_id_via_barcode_or_usrname',
3570 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3572 Given a barcode or username returns the id for the user or
3577 sub retrieve_usr_id_via_barcode_or_usrname {
3578 my($self, $conn, $auth, $barcode, $username) = @_;
3579 my $e = new_editor(authtoken => $auth);
3580 return $e->die_event unless $e->checkauth;
3581 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3583 my $user_by_barcode;
3584 my $user_by_username;
3585 $logger->info("$id_as_barcode is the ID as BARCODE");
3587 my $card = $e->search_actor_card([
3588 {barcode => $barcode},
3589 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3590 if ($id_as_barcode =~ /^t/i) {
3592 $user = $e->retrieve_actor_user($barcode);
3593 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3595 $user_by_barcode = $card->usr;
3596 $user = $user_by_barcode;
3599 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3600 $user_by_barcode = $card->usr;
3601 $user = $user_by_barcode;
3606 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3608 $user = $user_by_username;
3610 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3611 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3612 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3617 __PACKAGE__->register_method (
3618 method => 'merge_users',
3619 api_name => 'open-ils.actor.user.merge',
3622 Given a list of source users and destination user, transfer all data from the source
3623 to the dest user and delete the source user. All user related data is
3624 transferred, including circulations, holds, bookbags, etc.
3630 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3631 my $e = new_editor(xact => 1, authtoken => $auth);
3632 return $e->die_event unless $e->checkauth;
3634 # disallow the merge if any subordinate accounts are in collections
3635 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3636 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3638 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3639 if $master_id == $e->requestor->id;
3641 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3642 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3643 return $evt if $evt;
3645 my $del_addrs = ($U->ou_ancestor_setting_value(
3646 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3647 my $del_cards = ($U->ou_ancestor_setting_value(
3648 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3649 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3650 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3652 for my $src_id (@$user_ids) {
3654 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3655 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3656 return $evt if $evt;
3658 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3659 if $src_id == $e->requestor->id;
3661 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3662 if($src_user->home_ou ne $master_user->home_ou) {
3663 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3666 return $e->die_event unless
3667 $e->json_query({from => [
3682 __PACKAGE__->register_method (
3683 method => 'approve_user_address',
3684 api_name => 'open-ils.actor.user.pending_address.approve',
3691 sub approve_user_address {
3692 my($self, $conn, $auth, $addr) = @_;
3693 my $e = new_editor(xact => 1, authtoken => $auth);
3694 return $e->die_event unless $e->checkauth;
3696 # if the caller passes an address object, assume they want to
3697 # update it first before approving it
3698 $e->update_actor_user_address($addr) or return $e->die_event;
3700 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3702 my $user = $e->retrieve_actor_user($addr->usr);
3703 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3704 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3705 or return $e->die_event;
3707 return [values %$result]->[0];
3711 __PACKAGE__->register_method (
3712 method => 'retrieve_friends',
3713 api_name => 'open-ils.actor.friends.retrieve',
3716 returns { confirmed: [], pending_out: [], pending_in: []}
3717 pending_out are users I'm requesting friendship with
3718 pending_in are users requesting friendship with me
3723 sub retrieve_friends {
3724 my($self, $conn, $auth, $user_id, $options) = @_;
3725 my $e = new_editor(authtoken => $auth);
3726 return $e->event unless $e->checkauth;
3727 $user_id ||= $e->requestor->id;
3729 if($user_id != $e->requestor->id) {
3730 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3731 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3734 return OpenILS::Application::Actor::Friends->retrieve_friends(
3735 $e, $user_id, $options);
3740 __PACKAGE__->register_method (
3741 method => 'apply_friend_perms',
3742 api_name => 'open-ils.actor.friends.perms.apply',
3748 sub apply_friend_perms {
3749 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3750 my $e = new_editor(authtoken => $auth, xact => 1);
3751 return $e->die_event unless $e->checkauth;
3753 if($user_id != $e->requestor->id) {
3754 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3755 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3758 for my $perm (@perms) {
3760 OpenILS::Application::Actor::Friends->apply_friend_perm(
3761 $e, $user_id, $delegate_id, $perm);
3762 return $evt if $evt;
3770 __PACKAGE__->register_method (
3771 method => 'update_user_pending_address',
3772 api_name => 'open-ils.actor.user.address.pending.cud'
3775 sub update_user_pending_address {
3776 my($self, $conn, $auth, $addr) = @_;
3777 my $e = new_editor(authtoken => $auth, xact => 1);
3778 return $e->die_event unless $e->checkauth;
3780 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3781 if($addr->usr != $e->requestor->id) {
3782 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3786 $e->create_actor_user_address($addr) or return $e->die_event;
3787 } elsif($addr->isdeleted) {
3788 $e->delete_actor_user_address($addr) or return $e->die_event;
3790 $e->update_actor_user_address($addr) or return $e->die_event;
3794 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3800 __PACKAGE__->register_method (
3801 method => 'user_events',
3802 api_name => 'open-ils.actor.user.events.circ',
3805 __PACKAGE__->register_method (
3806 method => 'user_events',
3807 api_name => 'open-ils.actor.user.events.ahr',
3812 my($self, $conn, $auth, $user_id, $filters) = @_;
3813 my $e = new_editor(authtoken => $auth);
3814 return $e->event unless $e->checkauth;
3816 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3817 my $user_field = 'usr';
3820 $filters->{target} = {
3821 select => { $obj_type => ['id'] },
3823 where => {usr => $user_id}
3826 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3827 if($e->requestor->id != $user_id) {
3828 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3831 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3832 my $req = $ses->request('open-ils.trigger.events_by_target',
3833 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3835 while(my $resp = $req->recv) {
3836 my $val = $resp->content;
3837 my $tgt = $val->target;
3839 if($obj_type eq 'circ') {
3840 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3842 } elsif($obj_type eq 'ahr') {
3843 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3844 if $tgt->current_copy;
3847 $conn->respond($val) if $val;
3853 __PACKAGE__->register_method (
3854 method => 'copy_events',
3855 api_name => 'open-ils.actor.copy.events.circ',
3858 __PACKAGE__->register_method (
3859 method => 'copy_events',
3860 api_name => 'open-ils.actor.copy.events.ahr',
3865 my($self, $conn, $auth, $copy_id, $filters) = @_;
3866 my $e = new_editor(authtoken => $auth);
3867 return $e->event unless $e->checkauth;
3869 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3871 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3873 my $copy_field = 'target_copy';
3874 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3877 $filters->{target} = {
3878 select => { $obj_type => ['id'] },
3880 where => {$copy_field => $copy_id}
3884 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3885 my $req = $ses->request('open-ils.trigger.events_by_target',
3886 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3888 while(my $resp = $req->recv) {
3889 my $val = $resp->content;
3890 my $tgt = $val->target;
3892 my $user = $e->retrieve_actor_user($tgt->usr);
3893 if($e->requestor->id != $user->id) {
3894 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3897 $tgt->$copy_field($copy);
3900 $conn->respond($val) if $val;
3907 __PACKAGE__->register_method (
3908 method => 'get_itemsout_notices',
3909 api_name => 'open-ils.actor.user.itemsout.notices',
3913 desc => q/Summary counts of circulat notices/,
3915 {desc => 'authtoken', type => 'string'},
3916 {desc => 'circulation identifiers', type => 'array of numbers'}
3918 return => q/Stream of summary objects/
3922 sub get_itemsout_notices {
3923 my ($self, $client, $auth, $circ_ids) = @_;
3925 my $e = new_editor(authtoken => $auth);
3926 return $e->event unless $e->checkauth;
3928 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3930 for my $circ_id (@$circ_ids) {
3931 my $resp = get_itemsout_notices_impl($e, $circ_id);
3933 if ($U->is_event($resp)) {
3934 $client->respond($resp);
3938 $client->respond({circ_id => $circ_id, %$resp});
3946 sub get_itemsout_notices_impl {
3947 my ($e, $circId) = @_;
3949 my $requestorId = $e->requestor->id;
3951 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3953 my $patronId = $circ->usr;
3955 if( $patronId ne $requestorId ){
3956 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3957 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3960 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3961 #my $req = $ses->request('open-ils.trigger.events_by_target',
3962 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3963 # ^ Above removed in favor of faster json_query.
3966 # select complete_time
3967 # from action_trigger.event atev
3968 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3969 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3970 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3973 my $ctx_loc = $e->requestor->ws_ou;
3974 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3975 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3978 select => { atev => ["complete_time"] },
3981 atevdef => { field => "id",fkey => "event_def"}
3985 "+atevdef" => { active => 't', hook => 'checkout.due' },
3986 "+atev" => { target => $circId, state => 'complete' }
3990 if ($exclude_courtesy_notices){
3991 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3994 my %resblob = ( numNotices => 0, lastDt => undef );
3996 my $res = $e->json_query($query);
3997 for my $ndate (@$res) {
3998 $resblob{numNotices}++;
3999 if( !defined $resblob{lastDt}){
4000 $resblob{lastDt} = $$ndate{complete_time};
4003 if ($resblob{lastDt} lt $$ndate{complete_time}){
4004 $resblob{lastDt} = $$ndate{complete_time};
4011 __PACKAGE__->register_method (
4012 method => 'update_events',
4013 api_name => 'open-ils.actor.user.event.cancel.batch',
4016 __PACKAGE__->register_method (
4017 method => 'update_events',
4018 api_name => 'open-ils.actor.user.event.reset.batch',
4023 my($self, $conn, $auth, $event_ids) = @_;
4024 my $e = new_editor(xact => 1, authtoken => $auth);
4025 return $e->die_event unless $e->checkauth;
4028 for my $id (@$event_ids) {
4030 # do a little dance to determine what user we are ultimately affecting
4031 my $event = $e->retrieve_action_trigger_event([
4034 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
4036 ]) or return $e->die_event;
4039 if($event->event_def->hook->core_type eq 'circ') {
4040 $user_id = $e->retrieve_action_circulation($event->target)->usr;
4041 } elsif($event->event_def->hook->core_type eq 'ahr') {
4042 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
4047 my $user = $e->retrieve_actor_user($user_id);
4048 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
4050 if($self->api_name =~ /cancel/) {
4051 $event->state('invalid');
4052 } elsif($self->api_name =~ /reset/) {
4053 $event->clear_start_time;
4054 $event->clear_update_time;
4055 $event->state('pending');
4058 $e->update_action_trigger_event($event) or return $e->die_event;
4059 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
4063 return {complete => 1};
4067 __PACKAGE__->register_method (
4068 method => 'really_delete_user',
4069 api_name => 'open-ils.actor.user.delete.override',
4070 signature => q/@see open-ils.actor.user.delete/
4073 __PACKAGE__->register_method (
4074 method => 'really_delete_user',
4075 api_name => 'open-ils.actor.user.delete',
4077 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
4078 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
4079 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
4080 dest_usr_id is only required when deleting a user that performs staff functions.
4084 sub really_delete_user {
4085 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
4086 my $e = new_editor(authtoken => $auth, xact => 1);
4087 return $e->die_event unless $e->checkauth;
4088 $oargs = { all => 1 } unless defined $oargs;
4090 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
4091 my $open_bills = $e->json_query({
4092 select => { mbts => ['id'] },
4095 xact_finish => { '=' => undef },
4096 usr => { '=' => $user_id },
4098 }) or return $e->die_event;
4100 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
4102 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
4104 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
4105 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
4106 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
4108 # No deleting yourself - UI is supposed to stop you first, though.
4109 return $e->die_event unless $e->requestor->id != $user->id;
4110 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4111 # Check if you are allowed to mess with this patron permission group at all
4112 my $evt = group_perm_failed($e, $e->requestor, $user);
4113 return $e->die_event($evt) if $evt;
4114 my $stat = $e->json_query(
4115 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4116 or return $e->die_event;
4122 __PACKAGE__->register_method (
4123 method => 'user_payments',
4124 api_name => 'open-ils.actor.user.payments.retrieve',
4127 Returns all payments for a given user. Default order is newest payments first.
4128 @param auth Authentication token
4129 @param user_id The user ID
4130 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4135 my($self, $conn, $auth, $user_id, $filters) = @_;
4138 my $e = new_editor(authtoken => $auth);
4139 return $e->die_event unless $e->checkauth;
4141 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4142 return $e->event unless
4143 $e->requestor->id == $user_id or
4144 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4146 # Find all payments for all transactions for user $user_id
4148 select => {mp => ['id']},
4153 select => {mbt => ['id']},
4155 where => {usr => $user_id}
4160 { # by default, order newest payments first
4162 field => 'payment_ts',
4165 # secondary sort in ID as a tie-breaker, since payments created
4166 # within the same transaction will have identical payment_ts's
4173 for (qw/order_by limit offset/) {
4174 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4177 if(defined $filters->{where}) {
4178 foreach (keys %{$filters->{where}}) {
4179 # don't allow the caller to expand the result set to other users
4180 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4184 my $payment_ids = $e->json_query($query);
4185 for my $pid (@$payment_ids) {
4186 my $pay = $e->retrieve_money_payment([
4191 mbt => ['summary', 'circulation', 'grocery'],
4192 circ => ['target_copy'],
4193 acp => ['call_number'],
4201 xact_type => $pay->xact->summary->xact_type,
4202 last_billing_type => $pay->xact->summary->last_billing_type,
4205 if($pay->xact->summary->xact_type eq 'circulation') {
4206 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4207 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4210 $pay->xact($pay->xact->id); # de-flesh
4211 $conn->respond($resp);
4219 __PACKAGE__->register_method (
4220 method => 'negative_balance_users',
4221 api_name => 'open-ils.actor.users.negative_balance',
4224 Returns all users that have an overall negative balance
4225 @param auth Authentication token
4226 @param org_id The context org unit as an ID or list of IDs. This will be the home
4227 library of the user. If no org_unit is specified, no org unit filter is applied
4231 sub negative_balance_users {
4232 my($self, $conn, $auth, $org_id, $options) = @_;
4235 $options->{limit} = 1000 unless $options->{limit};
4236 $options->{offset} = 0 unless $options->{offset};
4238 my $e = new_editor(authtoken => $auth);
4239 return $e->die_event unless $e->checkauth;
4240 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4244 mous => ['usr', 'balance_owed'],
4247 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4248 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4265 where => {'+mous' => {balance_owed => {'<' => 0}}, '+au' => {deleted => 'f'}},
4266 offset => $options->{offset},
4267 limit => $options->{limit},
4268 order_by => [{class => 'mous', field => 'usr'}]
4271 $org_id = $U->get_org_descendants($org_id) if $options->{org_descendants};
4273 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4275 my $list = $e->json_query($query, {timeout => 600});
4277 for my $data (@$list) {
4279 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4280 balance_owed => $data->{balance_owed},
4281 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4288 __PACKAGE__->register_method(
4289 method => "request_password_reset",
4290 api_name => "open-ils.actor.patron.password_reset.request",
4292 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4293 "method for changing a user's password. The UUID token is distributed via A/T " .
4294 "templates (i.e. email to the user).",
4296 { desc => 'user_id_type', type => 'string' },
4297 { desc => 'user_id', type => 'string' },
4298 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4300 return => {desc => '1 on success, Event on error'}
4303 sub request_password_reset {
4304 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4306 # Check to see if password reset requests are already being throttled:
4307 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4309 my $e = new_editor(xact => 1);
4312 # Get the user, if any, depending on the input value
4313 if ($user_id_type eq 'username') {
4314 $user = $e->search_actor_user({usrname => $user_id})->[0];
4317 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4319 } elsif ($user_id_type eq 'barcode') {
4320 my $card = $e->search_actor_card([
4321 {barcode => $user_id},
4322 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4325 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4330 # If the user doesn't have an email address, we can't help them
4331 if (!$user->email) {
4333 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4336 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4337 if ($email_must_match) {
4338 if (lc($user->email) ne lc($email)) {
4339 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4343 _reset_password_request($conn, $e, $user);
4346 # Once we have the user, we can issue the password reset request
4347 # XXX Add a wrapper method that accepts barcode + email input
4348 sub _reset_password_request {
4349 my ($conn, $e, $user) = @_;
4351 # 1. Get throttle threshold and time-to-live from OU_settings
4352 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4353 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4355 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4357 # 2. Get time of last request and number of active requests (num_active)
4358 my $active_requests = $e->json_query({
4364 transform => 'COUNT'
4367 column => 'request_time',
4373 has_been_reset => { '=' => 'f' },
4374 request_time => { '>' => $threshold_time }
4378 # Guard against no active requests
4379 if ($active_requests->[0]->{'request_time'}) {
4380 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4381 my $now = DateTime::Format::ISO8601->new();
4383 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4384 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4385 ($last_request->add_duration('1 minute') > $now)) {
4386 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4388 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4392 # TODO Check to see if the user is in a password-reset-restricted group
4394 # Otherwise, go ahead and try to get the user.
4396 # Check the number of active requests for this user
4397 $active_requests = $e->json_query({
4403 transform => 'COUNT'
4408 usr => { '=' => $user->id },
4409 has_been_reset => { '=' => 'f' },
4410 request_time => { '>' => $threshold_time }
4414 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4416 # if less than or equal to per-user threshold, proceed; otherwise, return event
4417 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4418 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4420 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4423 # Create the aupr object and insert into the database
4424 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4425 my $uuid = create_uuid_as_string(UUID_V4);
4426 $reset_request->uuid($uuid);
4427 $reset_request->usr($user->id);
4429 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4432 # Create an event to notify user of the URL to reset their password
4434 # Can we stuff this in the user_data param for trigger autocreate?
4435 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4437 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4438 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4441 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4446 __PACKAGE__->register_method(
4447 method => "commit_password_reset",
4448 api_name => "open-ils.actor.patron.password_reset.commit",
4450 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4451 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4452 "with the supplied password.",
4454 { desc => 'uuid', type => 'string' },
4455 { desc => 'password', type => 'string' },
4457 return => {desc => '1 on success, Event on error'}
4460 sub commit_password_reset {
4461 my($self, $conn, $uuid, $password) = @_;
4463 # Check to see if password reset requests are already being throttled:
4464 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4465 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4466 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4468 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4471 my $e = new_editor(xact => 1);
4473 my $aupr = $e->search_actor_usr_password_reset({
4480 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4482 my $user_id = $aupr->[0]->usr;
4483 my $user = $e->retrieve_actor_user($user_id);
4485 # Ensure we're still within the TTL for the request
4486 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4487 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4488 if ($threshold < DateTime->now(time_zone => 'local')) {
4490 $logger->info("Password reset request needed to be submitted before $threshold");
4491 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4494 # Check complexity of password against OU-defined regex
4495 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4499 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4500 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4501 $is_strong = check_password_strength_custom($password, $pw_regex);
4503 $is_strong = check_password_strength_default($password);
4508 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4511 # All is well; update the password
4512 modify_migrated_user_password($e, $user->id, $password);
4514 # And flag that this password reset request has been honoured
4515 $aupr->[0]->has_been_reset('t');
4516 $e->update_actor_usr_password_reset($aupr->[0]);
4522 sub check_password_strength_default {
4523 my $password = shift;
4524 # Use the default set of checks
4525 if ( (length($password) < 7) or
4526 ($password !~ m/.*\d+.*/) or
4527 ($password !~ m/.*[A-Za-z]+.*/)
4534 sub check_password_strength_custom {
4535 my ($password, $pw_regex) = @_;
4537 $pw_regex = qr/$pw_regex/;
4538 if ($password !~ /$pw_regex/) {
4544 __PACKAGE__->register_method(
4545 method => "fire_test_notification",
4546 api_name => "open-ils.actor.event.test_notification"
4549 sub fire_test_notification {
4550 my($self, $conn, $auth, $args) = @_;
4551 my $e = new_editor(authtoken => $auth);
4552 return $e->event unless $e->checkauth;
4553 if ($e->requestor->id != $$args{target}) {
4554 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4555 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4558 my $event_hook = $$args{hook} or return $e->event;
4559 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4561 my $usr = $e->retrieve_actor_user($$args{target});
4562 return $e->event unless $usr;
4564 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4568 __PACKAGE__->register_method(
4569 method => "event_def_opt_in_settings",
4570 api_name => "open-ils.actor.event_def.opt_in.settings",
4573 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4575 { desc => 'Authentication token', type => 'string'},
4577 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4582 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4589 sub event_def_opt_in_settings {
4590 my($self, $conn, $auth, $org_id) = @_;
4591 my $e = new_editor(authtoken => $auth);
4592 return $e->event unless $e->checkauth;
4594 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4595 return $e->event unless
4596 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4598 $org_id = $e->requestor->home_ou;
4601 # find all config.user_setting_type's related to event_defs for the requested org unit
4602 my $types = $e->json_query({
4603 select => {cust => ['name']},
4604 from => {atevdef => 'cust'},
4607 owner => $U->get_org_ancestors($org_id), # context org plus parents
4614 $conn->respond($_) for
4615 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4622 __PACKAGE__->register_method(
4623 method => "user_circ_history",
4624 api_name => "open-ils.actor.history.circ",
4628 desc => 'Returns user circ history objects for the calling user',
4630 { desc => 'Authentication token', type => 'string'},
4631 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4634 desc => q/Stream of 'auch' circ history objects/,
4640 __PACKAGE__->register_method(
4641 method => "user_circ_history",
4642 api_name => "open-ils.actor.history.circ.clear",
4645 desc => 'Delete all user circ history entries for the calling user',
4647 { desc => 'Authentication token', type => 'string'},
4648 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4651 desc => q/1 on success, event on error/,
4657 __PACKAGE__->register_method(
4658 method => "user_circ_history",
4659 api_name => "open-ils.actor.history.circ.print",
4662 desc => q/Returns printable output for the caller's circ history objects/,
4664 { desc => 'Authentication token', type => 'string'},
4665 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4668 desc => q/An action_trigger.event object or error event./,
4674 __PACKAGE__->register_method(
4675 method => "user_circ_history",
4676 api_name => "open-ils.actor.history.circ.email",
4679 desc => q/Emails the caller's circ history/,
4681 { desc => 'Authentication token', type => 'string'},
4682 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4683 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4686 desc => q/undef, or event on error/
4691 sub user_circ_history {
4692 my ($self, $conn, $auth, $options) = @_;
4695 my $for_print = ($self->api_name =~ /print/);
4696 my $for_email = ($self->api_name =~ /email/);
4697 my $for_clear = ($self->api_name =~ /clear/);
4699 # No perm check is performed. Caller may only access his/her own
4700 # circ history entries.
4701 my $e = new_editor(authtoken => $auth);
4702 return $e->event unless $e->checkauth;
4705 if (!$for_clear) { # clear deletes all
4706 $limits{offset} = $options->{offset} if defined $options->{offset};
4707 $limits{limit} = $options->{limit} if defined $options->{limit};
4710 my %circ_id_filter = $options->{circ_ids} ?
4711 (id => $options->{circ_ids}) : ();
4713 my $circs = $e->search_action_user_circ_history([
4714 { usr => $e->requestor->id,
4717 { # order newest to oldest by default
4718 order_by => {auch => 'xact_start DESC'},
4721 {substream => 1} # could be a large list
4725 return $U->fire_object_event(undef,
4726 'circ.format.history.print', $circs, $e->requestor->home_ou);
4729 $e->xact_begin if $for_clear;
4730 $conn->respond_complete(1) if $for_email; # no sense in waiting
4732 for my $circ (@$circs) {
4735 # events will be fired from action_trigger_runner
4736 $U->create_events_for_hook('circ.format.history.email',
4737 $circ, $e->editor->home_ou, undef, undef, 1);
4739 } elsif ($for_clear) {
4741 $e->delete_action_user_circ_history($circ)
4742 or return $e->die_event;
4745 $conn->respond($circ);
4758 __PACKAGE__->register_method(
4759 method => "user_visible_holds",
4760 api_name => "open-ils.actor.history.hold.visible",
4763 desc => 'Returns the set of opt-in visible holds',
4765 { desc => 'Authentication token', type => 'string'},
4766 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4767 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4770 desc => q/An object with 1 field: "hold"/,
4776 __PACKAGE__->register_method(
4777 method => "user_visible_holds",
4778 api_name => "open-ils.actor.history.hold.visible.print",
4781 desc => 'Returns printable output for the set of opt-in visible holds',
4783 { desc => 'Authentication token', type => 'string'},
4784 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4785 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4788 desc => q/An action_trigger.event object or error event./,
4794 __PACKAGE__->register_method(
4795 method => "user_visible_holds",
4796 api_name => "open-ils.actor.history.hold.visible.email",
4799 desc => 'Emails the set of opt-in visible holds to the requestor',
4801 { desc => 'Authentication token', type => 'string'},
4802 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4803 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4806 desc => q/undef, or event on error/
4811 sub user_visible_holds {
4812 my($self, $conn, $auth, $user_id, $options) = @_;
4815 my $for_print = ($self->api_name =~ /print/);
4816 my $for_email = ($self->api_name =~ /email/);
4817 my $e = new_editor(authtoken => $auth);
4818 return $e->event unless $e->checkauth;
4820 $user_id ||= $e->requestor->id;
4822 $options->{limit} ||= 50;
4823 $options->{offset} ||= 0;
4825 if($user_id != $e->requestor->id) {
4826 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4827 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4828 return $e->event unless $e->allowed($perm, $user->home_ou);
4831 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4833 my $data = $e->json_query({
4834 from => [$db_func, $user_id],
4835 limit => $$options{limit},
4836 offset => $$options{offset}
4838 # TODO: I only want IDs. code below didn't get me there
4839 # {"select":{"au":[{"column":"id", "result_field":"id",
4840 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4845 return undef unless @$data;
4849 # collect the batch of objects
4853 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4854 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4858 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4859 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4862 } elsif ($for_email) {
4864 $conn->respond_complete(1) if $for_email; # no sense in waiting
4872 my $hold = $e->retrieve_action_hold_request($id);
4873 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4874 # events will be fired from action_trigger_runner
4878 my $circ = $e->retrieve_action_circulation($id);
4879 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4880 # events will be fired from action_trigger_runner
4884 } else { # just give me the data please
4892 my $hold = $e->retrieve_action_hold_request($id);
4893 $conn->respond({hold => $hold});
4897 my $circ = $e->retrieve_action_circulation($id);
4900 summary => $U->create_circ_chain_summary($e, $id)
4909 __PACKAGE__->register_method(
4910 method => "user_saved_search_cud",
4911 api_name => "open-ils.actor.user.saved_search.cud",
4914 desc => 'Create/Update/Delete Access to user saved searches',
4916 { desc => 'Authentication token', type => 'string' },
4917 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4920 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4926 __PACKAGE__->register_method(
4927 method => "user_saved_search_cud",
4928 api_name => "open-ils.actor.user.saved_search.retrieve",
4931 desc => 'Retrieve a saved search object',
4933 { desc => 'Authentication token', type => 'string' },
4934 { desc => 'Saved Search ID', type => 'number' }
4937 desc => q/The saved search object, Event on error/,
4943 sub user_saved_search_cud {
4944 my( $self, $client, $auth, $search ) = @_;
4945 my $e = new_editor( authtoken=>$auth );
4946 return $e->die_event unless $e->checkauth;
4948 my $o_search; # prior version of the object, if any
4949 my $res; # to be returned
4951 # branch on the operation type
4953 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4955 # Get the old version, to check ownership
4956 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4957 or return $e->die_event;
4959 # You can't read somebody else's search
4960 return OpenILS::Event->new('BAD_PARAMS')
4961 unless $o_search->owner == $e->requestor->id;
4967 $e->xact_begin; # start an editor transaction
4969 if( $search->isnew ) { # Create
4971 # You can't create a search for somebody else
4972 return OpenILS::Event->new('BAD_PARAMS')
4973 unless $search->owner == $e->requestor->id;
4975 $e->create_actor_usr_saved_search( $search )
4976 or return $e->die_event;
4980 } elsif( $search->ischanged ) { # Update
4982 # You can't change ownership of a search
4983 return OpenILS::Event->new('BAD_PARAMS')
4984 unless $search->owner == $e->requestor->id;
4986 # Get the old version, to check ownership
4987 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4988 or return $e->die_event;
4990 # You can't update somebody else's search
4991 return OpenILS::Event->new('BAD_PARAMS')
4992 unless $o_search->owner == $e->requestor->id;
4995 $e->update_actor_usr_saved_search( $search )
4996 or return $e->die_event;
5000 } elsif( $search->isdeleted ) { # Delete
5002 # Get the old version, to check ownership
5003 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
5004 or return $e->die_event;
5006 # You can't delete somebody else's search
5007 return OpenILS::Event->new('BAD_PARAMS')
5008 unless $o_search->owner == $e->requestor->id;
5011 $e->delete_actor_usr_saved_search( $o_search )
5012 or return $e->die_event;
5023 __PACKAGE__->register_method(
5024 method => "get_barcodes",
5025 api_name => "open-ils.actor.get_barcodes"
5029 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
5030 my $e = new_editor(authtoken => $auth);
5031 return $e->event unless $e->checkauth;
5032 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
5034 my $db_result = $e->json_query(
5036 'evergreen.get_barcodes',
5037 $org_id, $context, $barcode,
5041 if($context =~ /actor/) {
5042 my $filter_result = ();
5044 foreach my $result (@$db_result) {
5045 if($result->{type} eq 'actor') {
5046 if($e->requestor->id != $result->{id}) {
5047 $patron = $e->retrieve_actor_user($result->{id});
5049 push(@$filter_result, $e->event);
5052 if($e->allowed('VIEW_USER', $patron->home_ou)) {
5053 push(@$filter_result, $result);
5056 push(@$filter_result, $e->event);
5060 push(@$filter_result, $result);
5064 push(@$filter_result, $result);
5067 return $filter_result;
5073 __PACKAGE__->register_method(
5074 method => 'address_alert_test',
5075 api_name => 'open-ils.actor.address_alert.test',
5077 desc => "Tests a set of address fields to determine if they match with an address_alert",
5079 {desc => 'Authentication token', type => 'string'},
5080 {desc => 'Org Unit', type => 'number'},
5081 {desc => 'Fields', type => 'hash'},
5083 return => {desc => 'List of matching address_alerts'}
5087 sub address_alert_test {
5088 my ($self, $client, $auth, $org_unit, $fields) = @_;
5089 return [] unless $fields and grep {$_} values %$fields;
5091 my $e = new_editor(authtoken => $auth);
5092 return $e->event unless $e->checkauth;
5093 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
5094 $org_unit ||= $e->requestor->ws_ou;
5096 my $alerts = $e->json_query({
5098 'actor.address_alert_matches',
5106 $$fields{post_code},
5107 $$fields{mailing_address},
5108 $$fields{billing_address}
5112 # map the json_query hashes to real objects
5114 map {$e->retrieve_actor_address_alert($_)}
5115 (map {$_->{id}} @$alerts)
5119 __PACKAGE__->register_method(
5120 method => "mark_users_contact_invalid",
5121 api_name => "open-ils.actor.invalidate.email",
5123 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",
5125 {desc => "Authentication token", type => "string"},
5126 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5127 {desc => "Additional note text (optional)", type => "string"},
5128 {desc => "penalty org unit ID (optional)", type => "number"},
5129 {desc => "Email address (optional)", type => "string"}
5131 return => {desc => "Event describing success or failure", type => "object"}
5135 __PACKAGE__->register_method(
5136 method => "mark_users_contact_invalid",
5137 api_name => "open-ils.actor.invalidate.day_phone",
5139 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",
5141 {desc => "Authentication token", type => "string"},
5142 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5143 {desc => "Additional note text (optional)", type => "string"},
5144 {desc => "penalty org unit ID (optional)", type => "number"},
5145 {desc => "Phone Number (optional)", type => "string"}
5147 return => {desc => "Event describing success or failure", type => "object"}
5151 __PACKAGE__->register_method(
5152 method => "mark_users_contact_invalid",
5153 api_name => "open-ils.actor.invalidate.evening_phone",
5155 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",
5157 {desc => "Authentication token", type => "string"},
5158 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5159 {desc => "Additional note text (optional)", type => "string"},
5160 {desc => "penalty org unit ID (optional)", type => "number"},
5161 {desc => "Phone Number (optional)", type => "string"}
5163 return => {desc => "Event describing success or failure", type => "object"}
5167 __PACKAGE__->register_method(
5168 method => "mark_users_contact_invalid",
5169 api_name => "open-ils.actor.invalidate.other_phone",
5171 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",
5173 {desc => "Authentication token", type => "string"},
5174 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5175 {desc => "Additional note text (optional)", type => "string"},
5176 {desc => "penalty org unit ID (optional, default to top of org tree)",
5178 {desc => "Phone Number (optional)", type => "string"}
5180 return => {desc => "Event describing success or failure", type => "object"}
5184 sub mark_users_contact_invalid {
5185 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5187 # This method invalidates an email address or a phone_number which
5188 # removes the bad email address or phone number, copying its contents
5189 # to a patron note, and institutes a standing penalty for "bad email"
5190 # or "bad phone number" which is cleared when the user is saved or
5191 # optionally only when the user is saved with an email address or
5192 # phone number (or staff manually delete the penalty).
5194 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5196 my $e = new_editor(authtoken => $auth, xact => 1);
5197 return $e->die_event unless $e->checkauth;
5200 if (defined $patron_id && $patron_id ne "") {
5201 $howfind = {usr => $patron_id};
5202 } elsif (defined $contact && $contact ne "") {
5203 $howfind = {$contact_type => $contact};
5205 # Error out if no patron id set or no contact is set.
5206 return OpenILS::Event->new('BAD_PARAMS');
5209 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5210 $e, $contact_type, $howfind,
5211 $addl_note, $penalty_ou, $e->requestor->id
5215 # Putting the following method in open-ils.actor is a bad fit, except in that
5216 # it serves an interface that lives under 'actor' in the templates directory,
5217 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5219 __PACKAGE__->register_method(
5220 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5221 method => "get_all_at_reactors_in_use",
5226 { name => 'authtoken', type => 'string' }
5229 desc => 'list of reactor names', type => 'array'
5234 sub get_all_at_reactors_in_use {
5235 my ($self, $conn, $auth) = @_;
5237 my $e = new_editor(authtoken => $auth);
5238 $e->checkauth or return $e->die_event;
5239 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5241 my $reactors = $e->json_query({
5243 atevdef => [{column => "reactor", transform => "distinct"}]
5245 from => {atevdef => {}}
5248 return $e->die_event unless ref $reactors eq "ARRAY";
5251 return [ map { $_->{reactor} } @$reactors ];
5254 __PACKAGE__->register_method(
5255 method => "filter_group_entry_crud",
5256 api_name => "open-ils.actor.filter_group_entry.crud",
5259 Provides CRUD access to filter group entry objects. These are not full accessible
5260 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5261 are not accessible via PCRUD (because they have no fields against which to link perms)
5264 {desc => "Authentication token", type => "string"},
5265 {desc => "Entry ID / Entry Object", type => "number"},
5266 {desc => "Additional note text (optional)", type => "string"},
5267 {desc => "penalty org unit ID (optional, default to top of org tree)",
5271 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5277 sub filter_group_entry_crud {
5278 my ($self, $conn, $auth, $arg) = @_;
5280 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5281 my $e = new_editor(authtoken => $auth, xact => 1);
5282 return $e->die_event unless $e->checkauth;
5288 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5289 or return $e->die_event;
5291 return $e->die_event unless $e->allowed(
5292 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5294 my $query = $arg->query;
5295 $query = $e->create_actor_search_query($query) or return $e->die_event;
5296 $arg->query($query->id);
5297 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5298 $entry->query($query);
5303 } elsif ($arg->ischanged) {
5305 my $entry = $e->retrieve_actor_search_filter_group_entry([
5308 flesh_fields => {asfge => ['grp']}
5310 ]) or return $e->die_event;
5312 return $e->die_event unless $e->allowed(
5313 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5315 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5316 $arg->query($arg->query->id);
5317 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5318 $arg->query($query);
5323 } elsif ($arg->isdeleted) {
5325 my $entry = $e->retrieve_actor_search_filter_group_entry([
5328 flesh_fields => {asfge => ['grp', 'query']}
5330 ]) or return $e->die_event;
5332 return $e->die_event unless $e->allowed(
5333 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5335 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5336 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5349 my $entry = $e->retrieve_actor_search_filter_group_entry([
5352 flesh_fields => {asfge => ['grp', 'query']}
5354 ]) or return $e->die_event;
5356 return $e->die_event unless $e->allowed(
5357 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5358 $entry->grp->owner);
5361 $entry->grp($entry->grp->id); # for consistency