1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenILS::Utils::DateTime qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Carousel;
30 use OpenILS::Application::Actor::Container;
31 use OpenILS::Application::Actor::ClosedDates;
32 use OpenILS::Application::Actor::UserGroups;
33 use OpenILS::Application::Actor::Friends;
34 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Application::Actor::Settings;
37 use OpenILS::Utils::CStoreEditor qw/:funcs/;
38 use OpenILS::Utils::Penalty;
39 use OpenILS::Utils::BadContact;
40 use List::Util qw/max reduce/;
42 use UUID::Tiny qw/:std/;
45 OpenILS::Application::Actor::Container->initialize();
46 OpenILS::Application::Actor::UserGroups->initialize();
47 OpenILS::Application::Actor::ClosedDates->initialize();
50 my $apputils = "OpenILS::Application::AppUtils";
53 sub _d { warn "Patron:\n" . Dumper(shift()); }
56 my $set_user_settings;
60 #__PACKAGE__->register_method(
61 # method => "allowed_test",
62 # api_name => "open-ils.actor.allowed_test",
65 # my($self, $conn, $auth, $orgid, $permcode) = @_;
66 # my $e = new_editor(authtoken => $auth);
67 # return $e->die_event unless $e->checkauth;
71 # permcode => $permcode,
72 # result => $e->allowed($permcode, $orgid)
76 __PACKAGE__->register_method(
77 method => "update_user_setting",
78 api_name => "open-ils.actor.patron.settings.update",
80 sub update_user_setting {
81 my($self, $conn, $auth, $user_id, $settings) = @_;
82 my $e = new_editor(xact => 1, authtoken => $auth);
83 return $e->die_event unless $e->checkauth;
85 $user_id = $e->requestor->id unless defined $user_id;
87 unless($e->requestor->id == $user_id) {
88 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
89 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
92 for my $name (keys %$settings) {
93 my $val = $$settings{$name};
94 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
97 $val = OpenSRF::Utils::JSON->perl2JSON($val);
100 $e->update_actor_user_setting($set) or return $e->die_event;
102 $set = Fieldmapper::actor::user_setting->new;
106 $e->create_actor_user_setting($set) or return $e->die_event;
109 $e->delete_actor_user_setting($set) or return $e->die_event;
118 __PACKAGE__->register_method(
119 method => "update_privacy_waiver",
120 api_name => "open-ils.actor.patron.privacy_waiver.update",
122 desc => "Replaces any existing privacy waiver entries for the patron with the supplied values.",
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'User ID', type => 'number'},
126 {desc => 'Arrayref of privacy waiver entries', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
131 sub update_privacy_waiver {
132 my($self, $conn, $auth, $user_id, $waiver) = @_;
133 my $e = new_editor(xact => 1, authtoken => $auth);
134 return $e->die_event unless $e->checkauth;
136 $user_id = $e->requestor->id unless defined $user_id;
138 unless($e->requestor->id == $user_id) {
139 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
140 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
143 foreach my $w (@$waiver) {
144 $w->{usr} = $user_id unless $w->{usr};
145 if ($w->{id} && $w->{id} ne 'new') {
146 my $existing_rows = $e->search_actor_usr_privacy_waiver({usr => $user_id, id => $w->{id}});
147 if ($existing_rows) {
148 my $existing = $existing_rows->[0];
149 # delete existing if name is empty
150 if (!$w->{name} or $w->{name} =~ /^\s*$/) {
151 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
153 # delete existing if none of the boxes were checked
154 } elsif (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}) {
155 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
157 # otherwise, update existing waiver entry
159 $existing->name($w->{name});
160 $existing->place_holds($w->{place_holds});
161 $existing->pickup_holds($w->{pickup_holds});
162 $existing->checkout_items($w->{checkout_items});
163 $existing->view_history($w->{view_history});
164 $e->update_actor_usr_privacy_waiver($existing) or return $e->die_event;
167 $logger->warn("No privacy waiver entry found for user $user_id with ID " . $w->{id});
171 # ignore new entries with empty name or with no boxes checked
172 next if (!$w->{name} or $w->{name} =~ /^\s*$/);
173 next if (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history});
174 my $new = Fieldmapper::actor::usr_privacy_waiver->new;
175 $new->usr($w->{usr});
176 $new->name($w->{name});
177 $new->place_holds($w->{place_holds});
178 $new->pickup_holds($w->{pickup_holds});
179 $new->checkout_items($w->{checkout_items});
180 $new->view_history($w->{view_history});
181 $e->create_actor_usr_privacy_waiver($new) or return $e->die_event;
190 __PACKAGE__->register_method(
191 method => "set_ou_settings",
192 api_name => "open-ils.actor.org_unit.settings.update",
194 desc => "Updates the value for a given org unit setting. The permission to update " .
195 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
196 "permission specified in the update_perm column of the config.org_unit_setting_type " .
197 "table's row corresponding to the setting being changed." ,
199 {desc => 'Authentication token', type => 'string'},
200 {desc => 'Org unit ID', type => 'number'},
201 {desc => 'Hash of setting name-value pairs', type => 'object'}
203 return => {desc => '1 on success, Event on error'}
207 sub set_ou_settings {
208 my( $self, $client, $auth, $org_id, $settings ) = @_;
210 my $e = new_editor(authtoken => $auth, xact => 1);
211 return $e->die_event unless $e->checkauth;
213 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
215 for my $name (keys %$settings) {
216 my $val = $$settings{$name};
218 my $type = $e->retrieve_config_org_unit_setting_type([
220 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
221 ]) or return $e->die_event;
222 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
224 # If there is no relevant permission, the default assumption will
225 # be, "no, the caller cannot change that value."
226 return $e->die_event unless ($all_allowed ||
227 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
230 $val = OpenSRF::Utils::JSON->perl2JSON($val);
233 $e->update_actor_org_unit_setting($set) or return $e->die_event;
235 $set = Fieldmapper::actor::org_unit_setting->new;
236 $set->org_unit($org_id);
239 $e->create_actor_org_unit_setting($set) or return $e->die_event;
242 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
250 __PACKAGE__->register_method(
251 method => "fetch_visible_ou_settings_log",
252 api_name => "open-ils.actor.org_unit.settings.history.visible.retrieve",
254 desc => "Retrieves the log entries for the specified OU setting. " .
255 "If the setting has a view permission, the results are limited " .
256 "to entries at the OUs that the user has the view permission. ",
258 {desc => 'Authentication token', type => 'string'},
259 {desc => 'Setting name', type => 'string'}
261 return => {desc => 'List of fieldmapper objects of the log entries, Event on error'}
265 sub fetch_visible_ou_settings_log {
266 my( $self, $client, $auth, $setting ) = @_;
268 my $e = new_editor(authtoken => $auth);
269 return $e->event unless $e->checkauth;
270 return $e->die_event unless $e->allowed("STAFF_LOGIN");
271 return OpenILS::Event->new('BAD_PARAMS') unless defined($setting);
273 my $type = $e->retrieve_config_org_unit_setting_type([
275 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
277 return OpenILS::Event->new('BAD_PARAMS', note => 'setting type not found')
280 my $query = { field_name => $setting };
281 if ($type->view_perm) {
282 $query->{org} = $U->user_has_work_perm_at($e, $type->view_perm->code, {descendants => 1});
283 if (scalar @{ $query->{org} } == 0) {
284 # user doesn't have the view permission anywhere, so return nothing
289 my $results = $e->search_config_org_unit_setting_type_log([$query, {'order_by' => 'date_applied ASC'}])
290 or return $e->die_event;
294 __PACKAGE__->register_method(
295 method => "user_settings",
297 api_name => "open-ils.actor.patron.settings.retrieve",
300 my( $self, $client, $auth, $user_id, $setting ) = @_;
302 my $e = new_editor(authtoken => $auth);
303 return $e->event unless $e->checkauth;
304 $user_id = $e->requestor->id unless defined $user_id;
306 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
307 if($e->requestor->id != $user_id) {
308 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
312 my($e, $user_id, $setting) = @_;
313 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
314 return undef unless $val; # XXX this should really return undef, but needs testing
315 return OpenSRF::Utils::JSON->JSON2perl($val->value);
319 if(ref $setting eq 'ARRAY') {
321 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
324 return get_setting($e, $user_id, $setting);
327 my $s = $e->search_actor_user_setting({usr => $user_id});
328 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
333 __PACKAGE__->register_method(
334 method => "ranged_ou_settings",
335 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
337 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
338 "is implied for retrieving OU settings by the authenticated users' permissions.",
340 {desc => 'Authentication token', type => 'string'},
341 {desc => 'Org unit ID', type => 'number'},
343 return => {desc => 'A hashref of "ranged" settings, event on error'}
346 sub ranged_ou_settings {
347 my( $self, $client, $auth, $org_id ) = @_;
349 my $e = new_editor(authtoken => $auth);
350 return $e->event unless $e->checkauth;
353 my $org_list = $U->get_org_ancestors($org_id);
354 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
355 $org_list = [ reverse @$org_list ];
357 # start at the context org and capture the setting value
358 # without clobbering settings we've already captured
359 for my $this_org_id (@$org_list) {
361 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
363 for my $set (@sets) {
364 my $type = $e->retrieve_config_org_unit_setting_type([
366 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
369 # If there is no relevant permission, the default assumption will
370 # be, "yes, the caller can have that value."
371 if ($type && $type->view_perm) {
372 next if not $e->allowed($type->view_perm->code, $org_id);
375 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
376 unless defined $ranged_settings{$set->name};
380 return \%ranged_settings;
385 __PACKAGE__->register_method(
386 api_name => 'open-ils.actor.ou_setting.ancestor_default',
387 method => 'ou_ancestor_setting',
389 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
390 'This method will make sure that the given user has permission to view that setting, if there is a ' .
391 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
392 'the user lacks the permisssion, undef will be returned.' ,
394 { desc => 'Org unit ID', type => 'number' },
395 { desc => 'setting name', type => 'string' },
396 { desc => 'authtoken (optional)', type => 'string' }
398 return => {desc => 'A value for the org unit setting, or undef'}
402 # ------------------------------------------------------------------
403 # Attempts to find the org setting value for a given org. if not
404 # found at the requested org, searches up the org tree until it
405 # finds a parent that has the requested setting.
406 # when found, returns { org => $id, value => $value }
407 # otherwise, returns NULL
408 # ------------------------------------------------------------------
409 sub ou_ancestor_setting {
410 my( $self, $client, $orgid, $name, $auth ) = @_;
411 # Make sure $auth is set to something if not given.
413 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
416 __PACKAGE__->register_method(
417 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
418 method => 'ou_ancestor_setting_batch',
420 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
421 'This method will make sure that the given user has permission to view that setting, if there is a ' .
422 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
423 'the user lacks the permisssion, undef will be returned.' ,
425 { desc => 'Org unit ID', type => 'number' },
426 { desc => 'setting name list', type => 'array' },
427 { desc => 'authtoken (optional)', type => 'string' }
429 return => {desc => 'A hash with name => value pairs for the org unit settings'}
432 sub ou_ancestor_setting_batch {
433 my( $self, $client, $orgid, $name_list, $auth ) = @_;
435 # splitting the list of settings to fetch values
436 # so that ones that *don't* require view_perm checks
437 # can be fetched in one fell swoop, which is
438 # significantly faster in cases where a large
439 # number of settings need to be fetched.
440 my %perm_check_required = ();
441 my @perm_check_not_required = ();
443 # Note that ->ou_ancestor_setting also can check
444 # to see if the setting has a view_perm, but testing
445 # suggests that the redundant checks do not significantly
446 # increase the time it takes to fetch the values of
447 # permission-controlled settings.
448 my $e = new_editor();
449 my $res = $e->search_config_org_unit_setting_type({
451 view_perm => { "!=" => undef },
453 %perm_check_required = map { $_->name() => 1 } @$res;
454 foreach my $setting (@$name_list) {
455 push @perm_check_not_required, $setting
456 unless exists($perm_check_required{$setting});
460 if (@perm_check_not_required) {
461 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
463 $values{$_} = $U->ou_ancestor_setting(
466 ) for keys(%perm_check_required);
472 __PACKAGE__->register_method(
473 method => "update_patron",
474 api_name => "open-ils.actor.patron.update",
477 Update an existing user, or create a new one. Related objects,
478 like cards, addresses, survey responses, and stat cats,
479 can be updated by attaching them to the user object in their
480 respective fields. For examples, the billing address object
481 may be inserted into the 'billing_address' field, etc. For each
482 attached object, indicate if the object should be created,
483 updated, or deleted using the built-in 'isnew', 'ischanged',
484 and 'isdeleted' fields on the object.
487 { desc => 'Authentication token', type => 'string' },
488 { desc => 'Patron data object', type => 'object' }
490 return => {desc => 'A fleshed user object, event on error'}
495 my( $self, $client, $auth, $patron ) = @_;
497 my $e = new_editor(xact => 1, authtoken => $auth);
498 return $e->event unless $e->checkauth;
500 $logger->info($patron->isnew ? "Creating new patron..." :
501 "Updating Patron: " . $patron->id);
503 my $evt = check_group_perm($e, $e->requestor, $patron);
506 # $new_patron is the patron in progress. $patron is the original patron
507 # passed in with the method. new_patron will change as the components
508 # of patron are added/updated.
512 # unflesh the real items on the patron
513 $patron->card( $patron->card->id ) if(ref($patron->card));
514 $patron->billing_address( $patron->billing_address->id )
515 if(ref($patron->billing_address));
516 $patron->mailing_address( $patron->mailing_address->id )
517 if(ref($patron->mailing_address));
519 # create/update the patron first so we can use his id
521 # $patron is the obj from the client (new data) and $new_patron is the
522 # patron object properly built for db insertion, so we need a third variable
523 # if we want to represent the old patron.
526 my $barred_hook = '';
529 if($patron->isnew()) {
530 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
532 if($U->is_true($patron->barred)) {
533 return $e->die_event unless
534 $e->allowed('BAR_PATRON', $patron->home_ou);
536 if(($patron->photo_url)) {
537 return $e->die_event unless
538 $e->allowed('UPDATE_USER_PHOTO_URL', $patron->home_ou);
541 $new_patron = $patron;
543 # Did auth checking above already.
544 $old_patron = $e->retrieve_actor_user($patron->id) or
545 return $e->die_event;
547 $renew_hook = 'au.renewed' if ($old_patron->expire_date ne $new_patron->expire_date);
549 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
550 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
551 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
553 $barred_hook = $U->is_true($new_patron->barred) ?
554 'au.barred' : 'au.unbarred';
557 if($old_patron->photo_url ne $new_patron->photo_url) {
558 my $perm = 'UPDATE_USER_PHOTO_URL';
559 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
562 # update the password by itself to avoid the password protection magic
563 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
564 modify_migrated_user_password($e, $patron->id, $patron->passwd);
565 $new_patron->passwd(''); # subsequent update will set
566 # actor.usr.passwd to MD5('')
570 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
573 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
576 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
579 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
582 # re-update the patron if anything has happened to him during this process
583 if($new_patron->ischanged()) {
584 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
588 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
591 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
594 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
597 $evt = apply_invalid_addr_penalty($e, $patron);
602 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
604 $tses->request('open-ils.trigger.event.autocreate',
605 'au.created', $new_patron, $new_patron->home_ou);
607 $tses->request('open-ils.trigger.event.autocreate',
608 'au.updated', $new_patron, $new_patron->home_ou);
610 $tses->request('open-ils.trigger.event.autocreate', $renew_hook,
611 $new_patron, $new_patron->home_ou) if $renew_hook;
613 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
614 $new_patron, $new_patron->home_ou) if $barred_hook;
617 $e->xact_begin; # $e->rollback is called in new_flesh_user
618 return flesh_user($new_patron->id(), $e);
621 sub apply_invalid_addr_penalty {
625 # grab the invalid address penalty if set
626 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
628 my ($addr_penalty) = grep
629 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
631 # do we enforce invalid address penalty
632 my $enforce = $U->ou_ancestor_setting_value(
633 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
635 my $addrs = $e->search_actor_user_address(
636 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
637 my $addr_count = scalar(@$addrs);
639 if($addr_count == 0 and $addr_penalty) {
641 # regardless of any settings, remove the penalty when the user has no invalid addresses
642 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
645 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
647 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
648 my $depth = $ptype->org_depth;
649 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
650 $ctx_org = $patron->home_ou unless defined $ctx_org;
652 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
653 $penalty->usr($patron->id);
654 $penalty->org_unit($ctx_org);
655 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
657 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
672 "standing_penalties",
682 push @$fields, "home_ou" if $home_ou;
683 return new_flesh_user($id, $fields, $e );
691 # clone and clear stuff that would break the database
695 my $new_patron = $patron->clone;
697 $new_patron->clear_billing_address();
698 $new_patron->clear_mailing_address();
699 $new_patron->clear_addresses();
700 $new_patron->clear_card();
701 $new_patron->clear_cards();
702 $new_patron->clear_id();
703 $new_patron->clear_isnew();
704 $new_patron->clear_ischanged();
705 $new_patron->clear_isdeleted();
706 $new_patron->clear_stat_cat_entries();
707 $new_patron->clear_waiver_entries();
708 $new_patron->clear_permissions();
709 $new_patron->clear_standing_penalties();
720 return (undef, $e->die_event) unless
721 $e->allowed('CREATE_USER', $patron->home_ou);
723 my $ex = $e->search_actor_user(
724 {usrname => $patron->usrname}, {idlist => 1});
725 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
727 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
729 # do a dance to get the password hashed securely
730 my $saved_password = $patron->passwd;
732 $e->create_actor_user($patron) or return (undef, $e->die_event);
733 modify_migrated_user_password($e, $patron->id, $saved_password);
735 my $id = $patron->id; # added by CStoreEditor
737 $logger->info("Successfully created new user [$id] in DB");
738 return ($e->retrieve_actor_user($id), undef);
742 sub check_group_perm {
743 my( $e, $requestor, $patron ) = @_;
746 # first let's see if the requestor has
747 # priveleges to update this user in any way
748 if( ! $patron->isnew ) {
749 my $p = $e->retrieve_actor_user($patron->id);
751 # If we are the requestor (trying to update our own account)
752 # and we are not trying to change our profile, we're good
753 if( $p->id == $requestor->id and
754 $p->profile == $patron->profile ) {
759 $evt = group_perm_failed($e, $requestor, $p);
763 # They are allowed to edit this patron.. can they put the
764 # patron into the group requested?
765 $evt = group_perm_failed($e, $requestor, $patron);
771 sub group_perm_failed {
772 my( $e, $requestor, $patron ) = @_;
776 my $grpid = $patron->profile;
780 $logger->debug("user update looking for group perm for group $grpid");
781 $grp = $e->retrieve_permission_grp_tree($grpid);
783 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
785 $logger->info("user update checking perm $perm on user ".
786 $requestor->id." for update/create on user username=".$patron->usrname);
788 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
794 my( $e, $patron, $noperm) = @_;
796 $logger->info("Updating patron ".$patron->id." in DB");
801 return (undef, $e->die_event)
802 unless $e->allowed('UPDATE_USER', $patron->home_ou);
805 if(!$patron->ident_type) {
806 $patron->clear_ident_type;
807 $patron->clear_ident_value;
810 $evt = verify_last_xact($e, $patron);
811 return (undef, $evt) if $evt;
813 $e->update_actor_user($patron) or return (undef, $e->die_event);
815 # re-fetch the user to pick up the latest last_xact_id value
816 # to avoid collisions.
817 $patron = $e->retrieve_actor_user($patron->id);
822 sub verify_last_xact {
823 my( $e, $patron ) = @_;
824 return undef unless $patron->id and $patron->id > 0;
825 my $p = $e->retrieve_actor_user($patron->id);
826 my $xact = $p->last_xact_id;
827 return undef unless $xact;
828 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
829 return OpenILS::Event->new('XACT_COLLISION')
830 if $xact ne $patron->last_xact_id;
835 sub _check_dup_ident {
836 my( $session, $patron ) = @_;
838 return undef unless $patron->ident_value;
841 ident_type => $patron->ident_type,
842 ident_value => $patron->ident_value,
845 $logger->debug("patron update searching for dup ident values: " .
846 $patron->ident_type . ':' . $patron->ident_value);
848 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
850 my $dups = $session->request(
851 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
854 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
861 sub _add_update_addresses {
865 my $new_patron = shift;
869 my $current_id; # id of the address before creation
871 my $addresses = $patron->addresses();
873 for my $address (@$addresses) {
875 next unless ref $address;
876 $current_id = $address->id();
878 if( $patron->billing_address() and
879 $patron->billing_address() == $current_id ) {
880 $logger->info("setting billing addr to $current_id");
881 $new_patron->billing_address($address->id());
882 $new_patron->ischanged(1);
885 if( $patron->mailing_address() and
886 $patron->mailing_address() == $current_id ) {
887 $new_patron->mailing_address($address->id());
888 $logger->info("setting mailing addr to $current_id");
889 $new_patron->ischanged(1);
893 if($address->isnew()) {
895 $address->usr($new_patron->id());
897 ($address, $evt) = _add_address($e,$address);
898 return (undef, $evt) if $evt;
900 # we need to get the new id
901 if( $patron->billing_address() and
902 $patron->billing_address() == $current_id ) {
903 $new_patron->billing_address($address->id());
904 $logger->info("setting billing addr to $current_id");
905 $new_patron->ischanged(1);
908 if( $patron->mailing_address() and
909 $patron->mailing_address() == $current_id ) {
910 $new_patron->mailing_address($address->id());
911 $logger->info("setting mailing addr to $current_id");
912 $new_patron->ischanged(1);
915 } elsif($address->ischanged() ) {
917 ($address, $evt) = _update_address($e, $address);
918 return (undef, $evt) if $evt;
920 } elsif($address->isdeleted() ) {
922 if( $address->id() == $new_patron->mailing_address() ) {
923 $new_patron->clear_mailing_address();
924 ($new_patron, $evt) = _update_patron($e, $new_patron);
925 return (undef, $evt) if $evt;
928 if( $address->id() == $new_patron->billing_address() ) {
929 $new_patron->clear_billing_address();
930 ($new_patron, $evt) = _update_patron($e, $new_patron);
931 return (undef, $evt) if $evt;
934 $evt = _delete_address($e, $address);
935 return (undef, $evt) if $evt;
939 return ( $new_patron, undef );
943 # adds an address to the db and returns the address with new id
945 my($e, $address) = @_;
946 $address->clear_id();
948 $logger->info("Creating new address at street ".$address->street1);
950 # put the address into the database
951 $e->create_actor_user_address($address) or return (undef, $e->die_event);
952 return ($address, undef);
956 sub _update_address {
957 my( $e, $address ) = @_;
959 $logger->info("Updating address ".$address->id." in the DB");
961 $e->update_actor_user_address($address) or return (undef, $e->die_event);
963 return ($address, undef);
968 sub _add_update_cards {
972 my $new_patron = shift;
976 my $virtual_id; #id of the card before creation
978 my $card_changed = 0;
979 my $cards = $patron->cards();
980 for my $card (@$cards) {
982 $card->usr($new_patron->id());
984 if(ref($card) and $card->isnew()) {
986 $virtual_id = $card->id();
987 ( $card, $evt ) = _add_card($e, $card);
988 return (undef, $evt) if $evt;
990 #if(ref($patron->card)) { $patron->card($patron->card->id); }
991 if($patron->card() == $virtual_id) {
992 $new_patron->card($card->id());
993 $new_patron->ischanged(1);
997 } elsif( ref($card) and $card->ischanged() ) {
998 $evt = _update_card($e, $card);
999 return (undef, $evt) if $evt;
1004 $U->create_events_for_hook('au.barcode_changed', $new_patron, $e->requestor->ws_ou)
1007 return ( $new_patron, undef );
1011 # adds an card to the db and returns the card with new id
1013 my( $e, $card ) = @_;
1016 $logger->info("Adding new patron card ".$card->barcode);
1018 $e->create_actor_card($card) or return (undef, $e->die_event);
1020 return ( $card, undef );
1024 # returns event on error. returns undef otherwise
1026 my( $e, $card ) = @_;
1027 $logger->info("Updating patron card ".$card->id);
1029 $e->update_actor_card($card) or return $e->die_event;
1034 sub _add_update_waiver_entries {
1037 my $new_patron = shift;
1040 my $waiver_entries = $patron->waiver_entries();
1041 for my $waiver (@$waiver_entries) {
1042 next unless ref $waiver;
1043 $waiver->usr($new_patron->id());
1044 if ($waiver->isnew()) {
1045 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
1046 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
1047 $logger->info("Adding new patron waiver entry");
1048 $waiver->clear_id();
1049 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1050 } elsif ($waiver->ischanged()) {
1051 $logger->info("Updating patron waiver entry " . $waiver->id);
1052 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1053 } elsif ($waiver->isdeleted()) {
1054 $logger->info("Deleting patron waiver entry " . $waiver->id);
1055 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1058 return ($new_patron, undef);
1062 # returns event on error. returns undef otherwise
1063 sub _delete_address {
1064 my( $e, $address ) = @_;
1066 $logger->info("Deleting address ".$address->id." from DB");
1068 $e->delete_actor_user_address($address) or return $e->die_event;
1074 sub _add_survey_responses {
1075 my ($e, $patron, $new_patron) = @_;
1077 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1079 my $responses = $patron->survey_responses;
1083 $_->usr($new_patron->id) for (@$responses);
1085 my $evt = $U->simplereq( "open-ils.circ",
1086 "open-ils.circ.survey.submit.user_id", $responses );
1088 return (undef, $evt) if defined($U->event_code($evt));
1092 return ( $new_patron, undef );
1095 sub _clear_badcontact_penalties {
1096 my ($e, $old_patron, $new_patron) = @_;
1098 return ($new_patron, undef) unless $old_patron;
1100 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1102 # This ignores whether the caller of update_patron has any permission
1103 # to remove penalties, but these penalties no longer make sense
1104 # if an email address field (for example) is changed (and the caller must
1105 # have perms to do *that*) so there's no reason not to clear the penalties.
1107 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1109 "+csp" => {"name" => [values(%$PNM)]},
1110 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1112 "join" => {"csp" => {}},
1114 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1116 ]) or return (undef, $e->die_event);
1118 return ($new_patron, undef) unless @$bad_contact_penalties;
1120 my @penalties_to_clear;
1121 my ($field, $penalty_name);
1123 # For each field that might have an associated bad contact penalty,
1124 # check for such penalties and add them to the to-clear list if that
1125 # field has changed.
1126 while (($field, $penalty_name) = each(%$PNM)) {
1127 if ($old_patron->$field ne $new_patron->$field) {
1128 push @penalties_to_clear, grep {
1129 $_->standing_penalty->name eq $penalty_name
1130 } @$bad_contact_penalties;
1134 foreach (@penalties_to_clear) {
1135 # Note that this "archives" penalties, in the terminology of the staff
1136 # client, instead of just deleting them. This may assist reporting,
1137 # or preserving old contact information when it is still potentially
1139 $_->standing_penalty($_->standing_penalty->id); # deflesh
1140 $_->stop_date('now');
1141 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1144 return ($new_patron, undef);
1148 sub _create_stat_maps {
1150 my($e, $patron, $new_patron) = @_;
1152 my $maps = $patron->stat_cat_entries();
1154 for my $map (@$maps) {
1156 my $method = "update_actor_stat_cat_entry_user_map";
1158 if ($map->isdeleted()) {
1159 $method = "delete_actor_stat_cat_entry_user_map";
1161 } elsif ($map->isnew()) {
1162 $method = "create_actor_stat_cat_entry_user_map";
1167 $map->target_usr($new_patron->id);
1169 $logger->info("Updating stat entry with method $method and map $map");
1171 $e->$method($map) or return (undef, $e->die_event);
1174 return ($new_patron, undef);
1177 sub _create_perm_maps {
1179 my($e, $patron, $new_patron) = @_;
1181 my $maps = $patron->permissions;
1183 for my $map (@$maps) {
1185 my $method = "update_permission_usr_perm_map";
1186 if ($map->isdeleted()) {
1187 $method = "delete_permission_usr_perm_map";
1188 } elsif ($map->isnew()) {
1189 $method = "create_permission_usr_perm_map";
1193 $map->usr($new_patron->id);
1195 $logger->info( "Updating permissions with method $method and map $map" );
1197 $e->$method($map) or return (undef, $e->die_event);
1200 return ($new_patron, undef);
1204 __PACKAGE__->register_method(
1205 method => "set_user_work_ous",
1206 api_name => "open-ils.actor.user.work_ous.update",
1209 sub set_user_work_ous {
1215 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1216 return $evt if $evt;
1218 my $session = $apputils->start_db_session();
1219 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1221 for my $map (@$maps) {
1223 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1224 if ($map->isdeleted()) {
1225 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1226 } elsif ($map->isnew()) {
1227 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1231 #warn( "Updating permissions with method $method and session $ses and map $map" );
1232 $logger->info( "Updating work_ou map with method $method and map $map" );
1234 my $stat = $session->request($method, $map)->gather(1);
1235 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1239 $apputils->commit_db_session($session);
1241 return scalar(@$maps);
1245 __PACKAGE__->register_method(
1246 method => "set_user_perms",
1247 api_name => "open-ils.actor.user.permissions.update",
1250 sub set_user_perms {
1256 my $session = $apputils->start_db_session();
1258 my( $user_obj, $evt ) = $U->checkses($ses);
1259 return $evt if $evt;
1260 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1262 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1265 $all = 1 if ($U->is_true($user_obj->super_user()));
1266 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1268 for my $map (@$maps) {
1270 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1271 if ($map->isdeleted()) {
1272 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1273 } elsif ($map->isnew()) {
1274 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1278 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1279 #warn( "Updating permissions with method $method and session $ses and map $map" );
1280 $logger->info( "Updating permissions with method $method and map $map" );
1282 my $stat = $session->request($method, $map)->gather(1);
1283 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1287 $apputils->commit_db_session($session);
1289 return scalar(@$maps);
1293 __PACKAGE__->register_method(
1294 method => "user_retrieve_by_barcode",
1296 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1298 sub user_retrieve_by_barcode {
1299 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1301 my $e = new_editor(authtoken => $auth);
1302 return $e->event unless $e->checkauth;
1304 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1305 or return $e->event;
1307 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1308 return $e->event unless $e->allowed(
1309 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1316 __PACKAGE__->register_method(
1317 method => "get_user_by_id",
1319 api_name => "open-ils.actor.user.retrieve",
1322 sub get_user_by_id {
1323 my ($self, $client, $auth, $id) = @_;
1324 my $e = new_editor(authtoken=>$auth);
1325 return $e->event unless $e->checkauth;
1326 my $user = $e->retrieve_actor_user($id) or return $e->event;
1327 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1332 __PACKAGE__->register_method(
1333 method => "get_org_types",
1334 api_name => "open-ils.actor.org_types.retrieve",
1337 return $U->get_org_types();
1341 __PACKAGE__->register_method(
1342 method => "get_user_ident_types",
1343 api_name => "open-ils.actor.user.ident_types.retrieve",
1346 sub get_user_ident_types {
1347 return $ident_types if $ident_types;
1348 return $ident_types =
1349 new_editor()->retrieve_all_config_identification_type();
1353 __PACKAGE__->register_method(
1354 method => "get_org_unit",
1355 api_name => "open-ils.actor.org_unit.retrieve",
1359 my( $self, $client, $user_session, $org_id ) = @_;
1360 my $e = new_editor(authtoken => $user_session);
1362 return $e->event unless $e->checkauth;
1363 $org_id = $e->requestor->ws_ou;
1365 my $o = $e->retrieve_actor_org_unit($org_id)
1366 or return $e->event;
1370 __PACKAGE__->register_method(
1371 method => "search_org_unit",
1372 api_name => "open-ils.actor.org_unit_list.search",
1375 sub search_org_unit {
1377 my( $self, $client, $field, $value ) = @_;
1379 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1381 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1382 { $field => $value } );
1388 # build the org tree
1390 __PACKAGE__->register_method(
1391 method => "get_org_tree",
1392 api_name => "open-ils.actor.org_tree.retrieve",
1394 note => "Returns the entire org tree structure",
1400 return $U->get_org_tree($client->session->session_locale);
1404 __PACKAGE__->register_method(
1405 method => "get_org_descendants",
1406 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1409 # depth is optional. org_unit is the id
1410 sub get_org_descendants {
1411 my( $self, $client, $org_unit, $depth ) = @_;
1413 if(ref $org_unit eq 'ARRAY') {
1416 for my $i (0..scalar(@$org_unit)-1) {
1417 my $list = $U->simple_scalar_request(
1419 "open-ils.storage.actor.org_unit.descendants.atomic",
1420 $org_unit->[$i], $depth->[$i] );
1421 push(@trees, $U->build_org_tree($list));
1426 my $orglist = $apputils->simple_scalar_request(
1428 "open-ils.storage.actor.org_unit.descendants.atomic",
1429 $org_unit, $depth );
1430 return $U->build_org_tree($orglist);
1435 __PACKAGE__->register_method(
1436 method => "get_org_ancestors",
1437 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1440 # depth is optional. org_unit is the id
1441 sub get_org_ancestors {
1442 my( $self, $client, $org_unit, $depth ) = @_;
1443 my $orglist = $apputils->simple_scalar_request(
1445 "open-ils.storage.actor.org_unit.ancestors.atomic",
1446 $org_unit, $depth );
1447 return $U->build_org_tree($orglist);
1451 __PACKAGE__->register_method(
1452 method => "get_standings",
1453 api_name => "open-ils.actor.standings.retrieve"
1458 return $user_standings if $user_standings;
1459 return $user_standings =
1460 $apputils->simple_scalar_request(
1462 "open-ils.cstore.direct.config.standing.search.atomic",
1463 { id => { "!=" => undef } }
1468 __PACKAGE__->register_method(
1469 method => "get_my_org_path",
1470 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1473 sub get_my_org_path {
1474 my( $self, $client, $auth, $org_id ) = @_;
1475 my $e = new_editor(authtoken=>$auth);
1476 return $e->event unless $e->checkauth;
1477 $org_id = $e->requestor->ws_ou unless defined $org_id;
1479 return $apputils->simple_scalar_request(
1481 "open-ils.storage.actor.org_unit.full_path.atomic",
1485 __PACKAGE__->register_method(
1486 method => "retrieve_coordinates",
1487 api_name => "open-ils.actor.geo.retrieve_coordinates",
1490 {desc => 'Authentication token', type => 'string' },
1491 {type => 'number', desc => 'Context Organizational Unit'},
1492 {type => 'string', desc => 'Address to look-up as a text string'}
1494 return => { desc => 'Hash/object containing latitude and longitude for the provided address.'}
1498 sub retrieve_coordinates {
1499 my( $self, $client, $auth, $org_id, $addr_string ) = @_;
1500 my $e = new_editor(authtoken=>$auth);
1501 return $e->event unless $e->checkauth;
1502 $org_id = $e->requestor->ws_ou unless defined $org_id;
1504 return $apputils->simple_scalar_request(
1506 "open-ils.geo.retrieve_coordinates",
1507 $org_id, $addr_string );
1510 __PACKAGE__->register_method(
1511 method => "get_my_org_ancestor_at_depth",
1512 api_name => "open-ils.actor.org_unit.ancestor_at_depth.retrieve"
1515 sub get_my_org_ancestor_at_depth {
1516 my( $self, $client, $auth, $org_id, $depth ) = @_;
1517 my $e = new_editor(authtoken=>$auth);
1518 return $e->event unless $e->checkauth;
1519 $org_id = $e->requestor->ws_ou unless defined $org_id;
1521 return $apputils->org_unit_ancestor_at_depth( $org_id, $depth );
1524 __PACKAGE__->register_method(
1525 method => "patron_adv_search",
1526 api_name => "open-ils.actor.patron.search.advanced"
1529 __PACKAGE__->register_method(
1530 method => "patron_adv_search",
1531 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1533 # Flush the response stream at most 5 patrons in for UI responsiveness.
1534 max_bundle_count => 5,
1536 desc => q/Returns a stream of fleshed user objects instead of
1537 a pile of identifiers/
1541 sub patron_adv_search {
1542 my( $self, $client, $auth, $search_hash, $search_limit,
1543 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1545 # API params sanity checks.
1546 # Exit early with empty result if no filter exists.
1547 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1548 my $fleshed = ($self->api_name =~ /fleshed/);
1549 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1551 for my $key (keys %$search_hash) {
1552 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1556 return ($fleshed ? undef : []) unless $search_ok;
1558 my $e = new_editor(authtoken=>$auth);
1559 return $e->event unless $e->checkauth;
1560 return $e->event unless $e->allowed('VIEW_USER');
1562 # depth boundary outside of which patrons must opt-in, default to 0
1563 my $opt_boundary = 0;
1564 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1566 if (not defined $search_ou) {
1567 my $depth = $U->ou_ancestor_setting_value(
1568 $e->requestor->ws_ou,
1569 'circ.patron_edit.duplicate_patron_check_depth'
1572 if (defined $depth) {
1573 $search_ou = $U->org_unit_ancestor_at_depth(
1574 $e->requestor->ws_ou, $depth
1579 my $ids = $U->storagereq(
1580 "open-ils.storage.actor.user.crazy_search", $search_hash,
1581 $search_limit, $search_sort, $include_inactive,
1582 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1584 return $ids unless $self->api_name =~ /fleshed/;
1586 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1592 # A migrated (main) password has the form:
1593 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1594 sub modify_migrated_user_password {
1595 my ($e, $user_id, $passwd) = @_;
1597 # new password gets a new salt
1598 my $new_salt = $e->json_query({
1599 from => ['actor.create_salt', 'main']})->[0];
1600 $new_salt = $new_salt->{'actor.create_salt'};
1607 md5_hex($new_salt . md5_hex($passwd)),
1615 __PACKAGE__->register_method(
1616 method => "update_passwd",
1617 api_name => "open-ils.actor.user.password.update",
1619 desc => "Update the operator's password",
1621 { desc => 'Authentication token', type => 'string' },
1622 { desc => 'New password', type => 'string' },
1623 { desc => 'Current password', type => 'string' }
1625 return => {desc => '1 on success, Event on error or incorrect current password'}
1629 __PACKAGE__->register_method(
1630 method => "update_passwd",
1631 api_name => "open-ils.actor.user.username.update",
1633 desc => "Update the operator's username",
1635 { desc => 'Authentication token', type => 'string' },
1636 { desc => 'New username', type => 'string' },
1637 { desc => 'Current password', type => 'string' }
1639 return => {desc => '1 on success, Event on error or incorrect current password'}
1643 __PACKAGE__->register_method(
1644 method => "update_passwd",
1645 api_name => "open-ils.actor.user.email.update",
1647 desc => "Update the operator's email address",
1649 { desc => 'Authentication token', type => 'string' },
1650 { desc => 'New email address', type => 'string' },
1651 { desc => 'Current password', type => 'string' }
1653 return => {desc => '1 on success, Event on error or incorrect current password'}
1658 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1659 my $e = new_editor(xact=>1, authtoken=>$auth);
1660 return $e->die_event unless $e->checkauth;
1662 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1663 or return $e->die_event;
1664 my $api = $self->api_name;
1666 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1668 return new OpenILS::Event('INCORRECT_PASSWORD');
1672 if( $api =~ /password/o ) {
1673 # NOTE: with access to the plain text password we could crypt
1674 # the password without the extra MD5 pre-hashing. Other changes
1675 # would be required. Noting here for future reference.
1676 modify_migrated_user_password($e, $db_user->id, $new_val);
1677 $db_user->passwd('');
1681 # if we don't clear the password, the user will be updated with
1682 # a hashed version of the hashed version of their password
1683 $db_user->clear_passwd;
1685 if( $api =~ /username/o ) {
1687 # make sure no one else has this username
1688 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1691 return new OpenILS::Event('USERNAME_EXISTS');
1693 $db_user->usrname($new_val);
1696 } elsif( $api =~ /email/o ) {
1697 $db_user->email($new_val);
1702 $e->update_actor_user($db_user) or return $e->die_event;
1705 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1708 # update the cached user to pick up these changes
1709 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1715 __PACKAGE__->register_method(
1716 method => "check_user_perms",
1717 api_name => "open-ils.actor.user.perm.check",
1718 notes => <<" NOTES");
1719 Takes a login session, user id, an org id, and an array of perm type strings. For each
1720 perm type, if the user does *not* have the given permission it is added
1721 to a list which is returned from the method. If all permissions
1722 are allowed, an empty list is returned
1723 if the logged in user does not match 'user_id', then the logged in user must
1724 have VIEW_PERMISSION priveleges.
1727 sub check_user_perms {
1728 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1730 my( $staff, $evt ) = $apputils->checkses($login_session);
1731 return $evt if $evt;
1733 if($staff->id ne $user_id) {
1734 if( $evt = $apputils->check_perms(
1735 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1741 for my $perm (@$perm_types) {
1742 if($apputils->check_perms($user_id, $org_id, $perm)) {
1743 push @not_allowed, $perm;
1747 return \@not_allowed
1750 __PACKAGE__->register_method(
1751 method => "check_user_perms2",
1752 api_name => "open-ils.actor.user.perm.check.multi_org",
1754 Checks the permissions on a list of perms and orgs for a user
1755 @param authtoken The login session key
1756 @param user_id The id of the user to check
1757 @param orgs The array of org ids
1758 @param perms The array of permission names
1759 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1760 if the logged in user does not match 'user_id', then the logged in user must
1761 have VIEW_PERMISSION priveleges.
1764 sub check_user_perms2 {
1765 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1767 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1768 $authtoken, $user_id, 'VIEW_PERMISSION' );
1769 return $evt if $evt;
1772 for my $org (@$orgs) {
1773 for my $perm (@$perms) {
1774 if($apputils->check_perms($user_id, $org, $perm)) {
1775 push @not_allowed, [ $org, $perm ];
1780 return \@not_allowed
1784 __PACKAGE__->register_method(
1785 method => 'check_user_perms3',
1786 api_name => 'open-ils.actor.user.perm.highest_org',
1788 Returns the highest org unit id at which a user has a given permission
1789 If the requestor does not match the target user, the requestor must have
1790 'VIEW_PERMISSION' rights at the home org unit of the target user
1791 @param authtoken The login session key
1792 @param userid The id of the user in question
1793 @param perm The permission to check
1794 @return The org unit highest in the org tree within which the user has
1795 the requested permission
1798 sub check_user_perms3 {
1799 my($self, $client, $authtoken, $user_id, $perm) = @_;
1800 my $e = new_editor(authtoken=>$authtoken);
1801 return $e->event unless $e->checkauth;
1803 my $tree = $U->get_org_tree();
1805 unless($e->requestor->id == $user_id) {
1806 my $user = $e->retrieve_actor_user($user_id)
1807 or return $e->event;
1808 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1809 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1812 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1815 __PACKAGE__->register_method(
1816 method => 'user_has_work_perm_at',
1817 api_name => 'open-ils.actor.user.has_work_perm_at',
1821 Returns a set of org unit IDs which represent the highest orgs in
1822 the org tree where the user has the requested permission. The
1823 purpose of this method is to return the smallest set of org units
1824 which represent the full expanse of the user's ability to perform
1825 the requested action. The user whose perms this method should
1826 check is implied by the authtoken. /,
1828 {desc => 'authtoken', type => 'string'},
1829 {desc => 'permission name', type => 'string'},
1830 {desc => q/user id, optional. If present, check perms for
1831 this user instead of the logged in user/, type => 'number'},
1833 return => {desc => 'An array of org IDs'}
1837 sub user_has_work_perm_at {
1838 my($self, $conn, $auth, $perm, $user_id) = @_;
1839 my $e = new_editor(authtoken=>$auth);
1840 return $e->event unless $e->checkauth;
1841 if(defined $user_id) {
1842 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1843 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1845 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1848 __PACKAGE__->register_method(
1849 method => 'user_has_work_perm_at_batch',
1850 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1854 sub user_has_work_perm_at_batch {
1855 my($self, $conn, $auth, $perms, $user_id) = @_;
1856 my $e = new_editor(authtoken=>$auth);
1857 return $e->event unless $e->checkauth;
1858 if(defined $user_id) {
1859 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1860 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1863 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1869 __PACKAGE__->register_method(
1870 method => 'check_user_perms4',
1871 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1873 Returns the highest org unit id at which a user has a given permission
1874 If the requestor does not match the target user, the requestor must have
1875 'VIEW_PERMISSION' rights at the home org unit of the target user
1876 @param authtoken The login session key
1877 @param userid The id of the user in question
1878 @param perms An array of perm names to check
1879 @return An array of orgId's representing the org unit
1880 highest in the org tree within which the user has the requested permission
1881 The arrah of orgId's has matches the order of the perms array
1884 sub check_user_perms4 {
1885 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1887 my( $staff, $target, $org, $evt );
1889 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1890 $authtoken, $userid, 'VIEW_PERMISSION' );
1891 return $evt if $evt;
1894 return [] unless ref($perms);
1895 my $tree = $U->get_org_tree();
1897 for my $p (@$perms) {
1898 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1904 __PACKAGE__->register_method(
1905 method => "user_fines_summary",
1906 api_name => "open-ils.actor.user.fines.summary",
1909 desc => 'Returns a short summary of the users total open fines, ' .
1910 'excluding voided fines Params are login_session, user_id' ,
1912 {desc => 'Authentication token', type => 'string'},
1913 {desc => 'User ID', type => 'string'} # number?
1916 desc => "a 'mous' object, event on error",
1921 sub user_fines_summary {
1922 my( $self, $client, $auth, $user_id ) = @_;
1924 my $e = new_editor(authtoken=>$auth);
1925 return $e->event unless $e->checkauth;
1927 if( $user_id ne $e->requestor->id ) {
1928 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1929 return $e->event unless
1930 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1933 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1937 __PACKAGE__->register_method(
1938 method => "user_opac_vitals",
1939 api_name => "open-ils.actor.user.opac.vital_stats",
1943 desc => 'Returns a short summary of the users vital stats, including ' .
1944 'identification information, accumulated balance, number of holds, ' .
1945 'and current open circulation stats' ,
1947 {desc => 'Authentication token', type => 'string'},
1948 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1951 desc => "An object with four properties: user, fines, checkouts and holds."
1956 sub user_opac_vitals {
1957 my( $self, $client, $auth, $user_id ) = @_;
1959 my $e = new_editor(authtoken=>$auth);
1960 return $e->event unless $e->checkauth;
1962 $user_id ||= $e->requestor->id;
1964 my $user = $e->retrieve_actor_user( $user_id );
1967 ->method_lookup('open-ils.actor.user.fines.summary')
1968 ->run($auth => $user_id);
1969 return $fines if (defined($U->event_code($fines)));
1972 $fines = new Fieldmapper::money::open_user_summary ();
1973 $fines->balance_owed(0.00);
1974 $fines->total_owed(0.00);
1975 $fines->total_paid(0.00);
1976 $fines->usr($user_id);
1980 ->method_lookup('open-ils.actor.user.hold_requests.count')
1981 ->run($auth => $user_id);
1982 return $holds if (defined($U->event_code($holds)));
1985 ->method_lookup('open-ils.actor.user.checked_out.count')
1986 ->run($auth => $user_id);
1987 return $out if (defined($U->event_code($out)));
1989 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1991 my $unread_msgs = $e->search_actor_usr_message([
1992 {usr => $user_id, read_date => undef, deleted => 'f',
1993 'pub' => 't', # this is for the unread message count in the opac
1995 {stop_date => undef},
1996 {stop_date => {'>' => 'now'}}
2004 first_given_name => $user->first_given_name,
2005 second_given_name => $user->second_given_name,
2006 family_name => $user->family_name,
2007 alias => $user->alias,
2008 usrname => $user->usrname
2010 fines => $fines->to_bare_hash,
2013 messages => { unread => scalar(@$unread_msgs) }
2018 ##### a small consolidation of related method registrations
2019 my $common_params = [
2020 { desc => 'Authentication token', type => 'string' },
2021 { desc => 'User ID', type => 'string' },
2022 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
2023 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
2026 'open-ils.actor.user.transactions' => '',
2027 'open-ils.actor.user.transactions.fleshed' => '',
2028 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
2029 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
2030 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
2031 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
2034 foreach (keys %methods) {
2036 method => "user_transactions",
2039 desc => 'For a given user, retrieve a list of '
2040 . (/\.fleshed/ ? 'fleshed ' : '')
2041 . 'transactions' . $methods{$_}
2042 . ' optionally limited to transactions of a given type.',
2043 params => $common_params,
2045 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
2046 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
2050 $args{authoritative} = 1;
2051 __PACKAGE__->register_method(%args);
2054 # Now for the counts
2056 'open-ils.actor.user.transactions.count' => '',
2057 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
2058 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
2061 foreach (keys %methods) {
2063 method => "user_transactions",
2066 desc => 'For a given user, retrieve a count of open '
2067 . 'transactions' . $methods{$_}
2068 . ' optionally limited to transactions of a given type.',
2069 params => $common_params,
2070 return => { desc => "Integer count of transactions, or event on error" }
2073 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2074 __PACKAGE__->register_method(%args);
2077 __PACKAGE__->register_method(
2078 method => "user_transactions",
2079 api_name => "open-ils.actor.user.transactions.have_balance.total",
2082 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2083 . ' optionally limited to transactions of a given type.',
2084 params => $common_params,
2085 return => { desc => "Decimal balance value, or event on error" }
2090 sub user_transactions {
2091 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2094 my $e = new_editor(authtoken => $auth);
2095 return $e->event unless $e->checkauth;
2097 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2099 return $e->event unless
2100 $e->requestor->id == $user_id or
2101 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2103 my $api = $self->api_name();
2105 my $filter = ($api =~ /have_balance/o) ?
2106 { 'balance_owed' => { '<>' => 0 } }:
2107 { 'total_owed' => { '>' => 0 } };
2109 my $method = 'open-ils.actor.user.transactions.history.still_open';
2110 $method = "$method.authoritative" if $api =~ /authoritative/;
2111 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2113 if($api =~ /total/o) {
2115 $total += $_->balance_owed for @$trans;
2119 ($api =~ /count/o ) and return scalar @$trans;
2120 ($api !~ /fleshed/o) and return $trans;
2123 for my $t (@$trans) {
2125 if( $t->xact_type ne 'circulation' ) {
2126 push @resp, {transaction => $t};
2130 my $circ_data = flesh_circ($e, $t->id);
2131 push @resp, {transaction => $t, %$circ_data};
2138 __PACKAGE__->register_method(
2139 method => "user_transaction_retrieve",
2140 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2143 notes => "Returns a fleshed transaction record"
2146 __PACKAGE__->register_method(
2147 method => "user_transaction_retrieve",
2148 api_name => "open-ils.actor.user.transaction.retrieve",
2151 notes => "Returns a transaction record"
2154 sub user_transaction_retrieve {
2155 my($self, $client, $auth, $bill_id) = @_;
2157 my $e = new_editor(authtoken => $auth);
2158 return $e->event unless $e->checkauth;
2160 my $trans = $e->retrieve_money_billable_transaction_summary(
2161 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2163 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2165 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2167 return $trans unless $self->api_name =~ /flesh/;
2168 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2170 my $circ_data = flesh_circ($e, $trans->id, 1);
2172 return {transaction => $trans, %$circ_data};
2177 my $circ_id = shift;
2178 my $flesh_copy = shift;
2180 my $circ = $e->retrieve_action_circulation([
2184 circ => ['target_copy'],
2185 acp => ['call_number'],
2192 my $copy = $circ->target_copy;
2194 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2195 $mods = new Fieldmapper::metabib::virtual_record;
2196 $mods->doc_id(OILS_PRECAT_RECORD);
2197 $mods->title($copy->dummy_title);
2198 $mods->author($copy->dummy_author);
2201 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2205 $circ->target_copy($circ->target_copy->id);
2206 $copy->call_number($copy->call_number->id);
2208 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2212 __PACKAGE__->register_method(
2213 method => "hold_request_count",
2214 api_name => "open-ils.actor.user.hold_requests.count",
2218 Returns hold ready vs. total counts.
2219 If a context org unit is provided, a third value
2220 is returned with key 'behind_desk', which reports
2221 how many holds are ready at the pickup library
2222 with the behind_desk flag set to true.
2226 sub hold_request_count {
2227 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2228 my $e = new_editor(authtoken => $authtoken);
2229 return $e->event unless $e->checkauth;
2231 $user_id = $e->requestor->id unless defined $user_id;
2233 if($e->requestor->id ne $user_id) {
2234 my $user = $e->retrieve_actor_user($user_id);
2235 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2238 my $holds = $e->json_query({
2239 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2243 fulfillment_time => {"=" => undef },
2244 cancel_time => undef,
2249 $_->{current_shelf_lib} and # avoid undef warnings
2250 $_->{pickup_lib} eq $_->{current_shelf_lib}
2254 total => scalar(@$holds),
2255 ready => int(scalar(@ready))
2259 # count of holds ready at pickup lib with behind_desk true.
2260 $resp->{behind_desk} = int(scalar(
2262 $_->{pickup_lib} == $ctx_org and
2263 $U->is_true($_->{behind_desk})
2271 __PACKAGE__->register_method(
2272 method => "checked_out",
2273 api_name => "open-ils.actor.user.checked_out",
2277 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2278 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2279 . "(i.e., outstanding balance or some other pending action on the circ). "
2280 . "The .count method also includes a 'total' field which sums all open circs.",
2282 { desc => 'Authentication Token', type => 'string'},
2283 { desc => 'User ID', type => 'string'},
2286 desc => 'Returns event on error, or an object with ID lists, like: '
2287 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2292 __PACKAGE__->register_method(
2293 method => "checked_out",
2294 api_name => "open-ils.actor.user.checked_out.count",
2297 signature => q/@see open-ils.actor.user.checked_out/
2301 my( $self, $conn, $auth, $userid ) = @_;
2303 my $e = new_editor(authtoken=>$auth);
2304 return $e->event unless $e->checkauth;
2306 if( $userid ne $e->requestor->id ) {
2307 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2308 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2310 # see if there is a friend link allowing circ.view perms
2311 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2312 $e, $userid, $e->requestor->id, 'circ.view');
2313 return $e->event unless $allowed;
2317 my $count = $self->api_name =~ /count/;
2318 return _checked_out( $count, $e, $userid );
2322 my( $iscount, $e, $userid ) = @_;
2328 claims_returned => [],
2331 my $meth = 'retrieve_action_open_circ_';
2339 claims_returned => 0,
2346 my $data = $e->$meth($userid);
2350 $result{$_} += $data->$_() for (keys %result);
2351 $result{total} += $data->$_() for (keys %result);
2353 for my $k (keys %result) {
2354 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2364 __PACKAGE__->register_method(
2365 method => "checked_in_with_fines",
2366 api_name => "open-ils.actor.user.checked_in_with_fines",
2369 signature => q/@see open-ils.actor.user.checked_out/
2372 sub checked_in_with_fines {
2373 my( $self, $conn, $auth, $userid ) = @_;
2375 my $e = new_editor(authtoken=>$auth);
2376 return $e->event unless $e->checkauth;
2378 if( $userid ne $e->requestor->id ) {
2379 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2382 # money is owed on these items and they are checked in
2383 my $open = $e->search_action_circulation(
2386 xact_finish => undef,
2387 checkin_time => { "!=" => undef },
2392 my( @lost, @cr, @lo );
2393 for my $c (@$open) {
2394 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2395 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2396 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2401 claims_returned => \@cr,
2402 long_overdue => \@lo
2408 my ($api, $desc, $auth) = @_;
2409 $desc = $desc ? (" " . $desc) : '';
2410 my $ids = ($api =~ /ids$/) ? 1 : 0;
2413 method => "user_transaction_history",
2414 api_name => "open-ils.actor.user.transactions.$api",
2416 desc => "For a given User ID, returns a list of billable transaction" .
2417 ($ids ? " id" : '') .
2418 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2419 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2421 {desc => 'Authentication token', type => 'string'},
2422 {desc => 'User ID', type => 'number'},
2423 {desc => 'Transaction type (optional)', type => 'number'},
2424 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2427 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2431 $auth and push @sig, (authoritative => 1);
2435 my %auth_hist_methods = (
2437 'history.have_charge' => 'that have an initial charge',
2438 'history.still_open' => 'that are not finished',
2439 'history.have_balance' => 'that have a balance',
2440 'history.have_bill' => 'that have billings',
2441 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2442 'history.have_payment' => 'that have at least 1 payment',
2445 foreach (keys %auth_hist_methods) {
2446 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2447 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2448 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2451 sub user_transaction_history {
2452 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2456 my $e = new_editor(authtoken=>$auth);
2457 return $e->die_event unless $e->checkauth;
2459 if ($e->requestor->id ne $userid) {
2460 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2463 my $api = $self->api_name;
2464 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2466 if(defined($type)) {
2467 $filter->{'xact_type'} = $type;
2470 if($api =~ /have_bill_or_payment/o) {
2472 # transactions that have a non-zero sum across all billings or at least 1 payment
2473 $filter->{'-or'} = {
2474 'balance_owed' => { '<>' => 0 },
2475 'last_payment_ts' => { '<>' => undef }
2478 } elsif($api =~ /have_payment/) {
2480 $filter->{last_payment_ts} ||= {'<>' => undef};
2482 } elsif( $api =~ /have_balance/o) {
2484 # transactions that have a non-zero overall balance
2485 $filter->{'balance_owed'} = { '<>' => 0 };
2487 } elsif( $api =~ /have_charge/o) {
2489 # transactions that have at least 1 billing, regardless of whether it was voided
2490 $filter->{'last_billing_ts'} = { '<>' => undef };
2492 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2494 # transactions that have non-zero sum across all billings. This will exclude
2495 # xacts where all billings have been voided
2496 $filter->{'total_owed'} = { '<>' => 0 };
2499 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2500 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2501 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2503 my $mbts = $e->search_money_billable_transaction_summary(
2504 [ { usr => $userid, @xact_finish, %$filter },
2509 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2510 return $mbts unless $api =~ /fleshed/;
2513 for my $t (@$mbts) {
2515 if( $t->xact_type ne 'circulation' ) {
2516 push @resp, {transaction => $t};
2520 my $circ_data = flesh_circ($e, $t->id);
2521 push @resp, {transaction => $t, %$circ_data};
2529 __PACKAGE__->register_method(
2530 method => "user_perms",
2531 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2533 notes => "Returns a list of permissions"
2537 my( $self, $client, $authtoken, $user ) = @_;
2539 my( $staff, $evt ) = $apputils->checkses($authtoken);
2540 return $evt if $evt;
2542 $user ||= $staff->id;
2544 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2548 return $apputils->simple_scalar_request(
2550 "open-ils.storage.permission.user_perms.atomic",
2554 __PACKAGE__->register_method(
2555 method => "retrieve_perms",
2556 api_name => "open-ils.actor.permissions.retrieve",
2557 notes => "Returns a list of permissions"
2559 sub retrieve_perms {
2560 my( $self, $client ) = @_;
2561 return $apputils->simple_scalar_request(
2563 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2564 { id => { '!=' => undef } }
2568 __PACKAGE__->register_method(
2569 method => "retrieve_groups",
2570 api_name => "open-ils.actor.groups.retrieve",
2571 notes => "Returns a list of user groups"
2573 sub retrieve_groups {
2574 my( $self, $client ) = @_;
2575 return new_editor()->retrieve_all_permission_grp_tree();
2578 __PACKAGE__->register_method(
2579 method => "retrieve_org_address",
2580 api_name => "open-ils.actor.org_unit.address.retrieve",
2581 notes => <<' NOTES');
2582 Returns an org_unit address by ID
2583 @param An org_address ID
2585 sub retrieve_org_address {
2586 my( $self, $client, $id ) = @_;
2587 return $apputils->simple_scalar_request(
2589 "open-ils.cstore.direct.actor.org_address.retrieve",
2594 __PACKAGE__->register_method(
2595 method => "retrieve_groups_tree",
2596 api_name => "open-ils.actor.groups.tree.retrieve",
2597 notes => "Returns a list of user groups"
2600 sub retrieve_groups_tree {
2601 my( $self, $client ) = @_;
2602 return new_editor()->search_permission_grp_tree(
2607 flesh_fields => { pgt => ["children"] },
2608 order_by => { pgt => 'name'}
2615 __PACKAGE__->register_method(
2616 method => "add_user_to_groups",
2617 api_name => "open-ils.actor.user.set_groups",
2618 notes => "Adds a user to one or more permission groups"
2621 sub add_user_to_groups {
2622 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2624 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2625 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2626 return $evt if $evt;
2628 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2629 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2630 return $evt if $evt;
2632 $apputils->simplereq(
2634 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2636 for my $group (@$groups) {
2637 my $link = Fieldmapper::permission::usr_grp_map->new;
2639 $link->usr($userid);
2641 my $id = $apputils->simplereq(
2643 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2649 __PACKAGE__->register_method(
2650 method => "get_user_perm_groups",
2651 api_name => "open-ils.actor.user.get_groups",
2652 notes => "Retrieve a user's permission groups."
2656 sub get_user_perm_groups {
2657 my( $self, $client, $authtoken, $userid ) = @_;
2659 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2660 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2661 return $evt if $evt;
2663 return $apputils->simplereq(
2665 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2669 __PACKAGE__->register_method(
2670 method => "get_user_work_ous",
2671 api_name => "open-ils.actor.user.get_work_ous",
2672 notes => "Retrieve a user's work org units."
2675 __PACKAGE__->register_method(
2676 method => "get_user_work_ous",
2677 api_name => "open-ils.actor.user.get_work_ous.ids",
2678 notes => "Retrieve a user's work org units."
2681 sub get_user_work_ous {
2682 my( $self, $client, $auth, $userid ) = @_;
2683 my $e = new_editor(authtoken=>$auth);
2684 return $e->event unless $e->checkauth;
2685 $userid ||= $e->requestor->id;
2687 if($e->requestor->id != $userid) {
2688 my $user = $e->retrieve_actor_user($userid)
2689 or return $e->event;
2690 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2693 return $e->search_permission_usr_work_ou_map({usr => $userid})
2694 unless $self->api_name =~ /.ids$/;
2696 # client just wants a list of org IDs
2697 return $U->get_user_work_ou_ids($e, $userid);
2702 __PACKAGE__->register_method(
2703 method => 'register_workstation',
2704 api_name => 'open-ils.actor.workstation.register.override',
2705 signature => q/@see open-ils.actor.workstation.register/
2708 __PACKAGE__->register_method(
2709 method => 'register_workstation',
2710 api_name => 'open-ils.actor.workstation.register',
2712 Registers a new workstion in the system
2713 @param authtoken The login session key
2714 @param name The name of the workstation id
2715 @param owner The org unit that owns this workstation
2716 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2717 if the name is already in use.
2721 sub register_workstation {
2722 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2724 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2725 return $e->die_event unless $e->checkauth;
2726 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2727 my $existing = $e->search_actor_workstation({name => $name})->[0];
2728 $oargs = { all => 1 } unless defined $oargs;
2732 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2733 # workstation with the given name exists.
2735 if($owner ne $existing->owning_lib) {
2736 # if necessary, update the owning_lib of the workstation
2738 $logger->info("changing owning lib of workstation ".$existing->id.
2739 " from ".$existing->owning_lib." to $owner");
2740 return $e->die_event unless
2741 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2743 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2745 $existing->owning_lib($owner);
2746 return $e->die_event unless $e->update_actor_workstation($existing);
2752 "attempt to register an existing workstation. returning existing ID");
2755 return $existing->id;
2758 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2762 my $ws = Fieldmapper::actor::workstation->new;
2763 $ws->owning_lib($owner);
2765 $e->create_actor_workstation($ws) or return $e->die_event;
2767 return $ws->id; # note: editor sets the id on the new object for us
2770 __PACKAGE__->register_method(
2771 method => 'workstation_list',
2772 api_name => 'open-ils.actor.workstation.list',
2774 Returns a list of workstations registered at the given location
2775 @param authtoken The login session key
2776 @param ids A list of org_unit.id's for the workstation owners
2780 sub workstation_list {
2781 my( $self, $conn, $authtoken, @orgs ) = @_;
2783 my $e = new_editor(authtoken=>$authtoken);
2784 return $e->event unless $e->checkauth;
2789 unless $e->allowed('REGISTER_WORKSTATION', $o);
2790 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2796 __PACKAGE__->register_method(
2797 method => 'fetch_patron_note',
2798 api_name => 'open-ils.actor.note.retrieve.all',
2801 Returns a list of notes for a given user
2802 Requestor must have VIEW_USER permission if pub==false and
2803 @param authtoken The login session key
2804 @param args Hash of params including
2805 patronid : the patron's id
2806 pub : true if retrieving only public notes
2810 sub fetch_patron_note {
2811 my( $self, $conn, $authtoken, $args ) = @_;
2812 my $patronid = $$args{patronid};
2814 my($reqr, $evt) = $U->checkses($authtoken);
2815 return $evt if $evt;
2818 ($patron, $evt) = $U->fetch_user($patronid);
2819 return $evt if $evt;
2822 if( $patronid ne $reqr->id ) {
2823 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2824 return $evt if $evt;
2826 return $U->cstorereq(
2827 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2828 { usr => $patronid, pub => 't' } );
2831 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2832 return $evt if $evt;
2834 return $U->cstorereq(
2835 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2838 __PACKAGE__->register_method(
2839 method => 'create_user_note',
2840 api_name => 'open-ils.actor.note.create',
2842 Creates a new note for the given user
2843 @param authtoken The login session key
2844 @param note The note object
2847 sub create_user_note {
2848 my( $self, $conn, $authtoken, $note ) = @_;
2849 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2850 return $e->die_event unless $e->checkauth;
2852 my $user = $e->retrieve_actor_user($note->usr)
2853 or return $e->die_event;
2855 return $e->die_event unless
2856 $e->allowed('UPDATE_USER',$user->home_ou);
2858 $note->creator($e->requestor->id);
2859 $e->create_actor_usr_note($note) or return $e->die_event;
2865 __PACKAGE__->register_method(
2866 method => 'delete_user_note',
2867 api_name => 'open-ils.actor.note.delete',
2869 Deletes a note for the given user
2870 @param authtoken The login session key
2871 @param noteid The note id
2874 sub delete_user_note {
2875 my( $self, $conn, $authtoken, $noteid ) = @_;
2877 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2878 return $e->die_event unless $e->checkauth;
2879 my $note = $e->retrieve_actor_usr_note($noteid)
2880 or return $e->die_event;
2881 my $user = $e->retrieve_actor_user($note->usr)
2882 or return $e->die_event;
2883 return $e->die_event unless
2884 $e->allowed('UPDATE_USER', $user->home_ou);
2886 $e->delete_actor_usr_note($note) or return $e->die_event;
2892 __PACKAGE__->register_method(
2893 method => 'update_user_note',
2894 api_name => 'open-ils.actor.note.update',
2896 @param authtoken The login session key
2897 @param note The note
2901 sub update_user_note {
2902 my( $self, $conn, $auth, $note ) = @_;
2903 my $e = new_editor(authtoken=>$auth, xact=>1);
2904 return $e->die_event unless $e->checkauth;
2905 my $patron = $e->retrieve_actor_user($note->usr)
2906 or return $e->die_event;
2907 return $e->die_event unless
2908 $e->allowed('UPDATE_USER', $patron->home_ou);
2909 $e->update_actor_user_note($note)
2910 or return $e->die_event;
2915 __PACKAGE__->register_method(
2916 method => 'fetch_patron_messages',
2917 api_name => 'open-ils.actor.message.retrieve',
2920 Returns a list of notes for a given user, not
2921 including ones marked deleted
2922 @param authtoken The login session key
2923 @param patronid patron ID
2924 @param options hash containing optional limit and offset
2928 sub fetch_patron_messages {
2929 my( $self, $conn, $auth, $patronid, $options ) = @_;
2933 my $e = new_editor(authtoken => $auth);
2934 return $e->die_event unless $e->checkauth;
2936 if ($e->requestor->id ne $patronid) {
2937 return $e->die_event unless $e->allowed('VIEW_USER');
2940 my $select_clause = { usr => $patronid };
2941 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2942 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2943 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2945 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2950 __PACKAGE__->register_method(
2951 method => 'usrname_exists',
2952 api_name => 'open-ils.actor.username.exists',
2954 desc => 'Check if a username is already taken (by an undeleted patron)',
2956 {desc => 'Authentication token', type => 'string'},
2957 {desc => 'Username', type => 'string'}
2960 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2965 sub usrname_exists {
2966 my( $self, $conn, $auth, $usrname ) = @_;
2967 my $e = new_editor(authtoken=>$auth);
2968 return $e->event unless $e->checkauth;
2969 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2970 return $$a[0] if $a and @$a;
2974 __PACKAGE__->register_method(
2975 method => 'barcode_exists',
2976 api_name => 'open-ils.actor.barcode.exists',
2978 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2981 sub barcode_exists {
2982 my( $self, $conn, $auth, $barcode ) = @_;
2983 my $e = new_editor(authtoken=>$auth);
2984 return $e->event unless $e->checkauth;
2985 my $card = $e->search_actor_card({barcode => $barcode});
2991 #return undef unless @$card;
2992 #return $card->[0]->usr;
2996 __PACKAGE__->register_method(
2997 method => 'retrieve_net_levels',
2998 api_name => 'open-ils.actor.net_access_level.retrieve.all',
3001 sub retrieve_net_levels {
3002 my( $self, $conn, $auth ) = @_;
3003 my $e = new_editor(authtoken=>$auth);
3004 return $e->event unless $e->checkauth;
3005 return $e->retrieve_all_config_net_access_level();
3008 # Retain the old typo API name just in case
3009 __PACKAGE__->register_method(
3010 method => 'fetch_org_by_shortname',
3011 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
3013 __PACKAGE__->register_method(
3014 method => 'fetch_org_by_shortname',
3015 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
3017 sub fetch_org_by_shortname {
3018 my( $self, $conn, $sname ) = @_;
3019 my $e = new_editor();
3020 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
3021 return $e->event unless $org;
3026 __PACKAGE__->register_method(
3027 method => 'session_home_lib',
3028 api_name => 'open-ils.actor.session.home_lib',
3031 sub session_home_lib {
3032 my( $self, $conn, $auth ) = @_;
3033 my $e = new_editor(authtoken=>$auth);
3034 return undef unless $e->checkauth;
3035 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
3036 return $org->shortname;
3039 __PACKAGE__->register_method(
3040 method => 'session_safe_token',
3041 api_name => 'open-ils.actor.session.safe_token',
3043 Returns a hashed session ID that is safe for export to the world.
3044 This safe token will expire after 1 hour of non-use.
3045 @param auth Active authentication token
3049 sub session_safe_token {
3050 my( $self, $conn, $auth ) = @_;
3051 my $e = new_editor(authtoken=>$auth);
3052 return undef unless $e->checkauth;
3054 my $safe_token = md5_hex($auth);
3056 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3058 # add more user fields as needed
3060 "safe-token-user-$safe_token", {
3061 id => $e->requestor->id,
3062 home_ou_shortname => $e->retrieve_actor_org_unit(
3063 $e->requestor->home_ou)->shortname,
3072 __PACKAGE__->register_method(
3073 method => 'safe_token_home_lib',
3074 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
3076 Returns the home library shortname from the session
3077 asscociated with a safe token from generated by
3078 open-ils.actor.session.safe_token.
3079 @param safe_token Active safe token
3080 @param who Optional user activity "ewho" value
3084 sub safe_token_home_lib {
3085 my( $self, $conn, $safe_token, $who ) = @_;
3086 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3088 my $blob = $cache->get_cache("safe-token-user-$safe_token");
3089 return unless $blob;
3091 $U->log_user_activity($blob->{id}, $who, 'verify');
3092 return $blob->{home_ou_shortname};
3096 __PACKAGE__->register_method(
3097 method => "update_penalties",
3098 api_name => "open-ils.actor.user.penalties.update"
3101 sub update_penalties {
3102 my($self, $conn, $auth, $user_id) = @_;
3103 my $e = new_editor(authtoken=>$auth, xact => 1);
3104 return $e->die_event unless $e->checkauth;
3105 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3106 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3107 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3108 return $evt if $evt;
3114 __PACKAGE__->register_method(
3115 method => "apply_penalty",
3116 api_name => "open-ils.actor.user.penalty.apply"
3120 my($self, $conn, $auth, $penalty, $msg) = @_;
3124 my $e = new_editor(authtoken=>$auth, xact => 1);
3125 return $e->die_event unless $e->checkauth;
3127 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3128 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3130 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3132 my $ctx_org = $penalty->org_unit; # csp org_depth is now considered in the UI for the org drop-down menu
3134 if (($msg->{title} || $msg->{message}) && ($msg->{title} ne '' || $msg->{message} ne '')) {
3135 my $aum = Fieldmapper::actor::usr_message->new;
3137 $aum->create_date('now');
3138 $aum->sending_lib($e->requestor->ws_ou);
3139 $aum->title($msg->{title});
3140 $aum->usr($penalty->usr);
3141 $aum->message($msg->{message});
3142 $aum->pub($msg->{pub});
3144 $aum = $e->create_actor_usr_message($aum)
3145 or return $e->die_event;
3147 $penalty->usr_message($aum->id);
3150 $penalty->org_unit($ctx_org);
3151 $penalty->staff($e->requestor->id);
3152 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3155 return $penalty->id;
3158 __PACKAGE__->register_method(
3159 method => "modify_penalty",
3160 api_name => "open-ils.actor.user.penalty.modify"
3163 sub modify_penalty {
3164 my($self, $conn, $auth, $penalty, $usr_msg) = @_;
3166 my $e = new_editor(authtoken=>$auth, xact => 1);
3167 return $e->die_event unless $e->checkauth;
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 $usr_msg->editor($e->requestor->id);
3173 $usr_msg->edit_date('now');
3175 if ($usr_msg->isnew) {
3176 $usr_msg = $e->create_actor_usr_message($usr_msg)
3177 or return $e->die_event;
3178 $penalty->usr_message($usr_msg->id);
3180 $usr_msg = $e->update_actor_usr_message($usr_msg)
3181 or return $e->die_event;
3184 if ($penalty->isnew) {
3185 $penalty = $e->create_actor_user_standing_penalty($penalty)
3186 or return $e->die_event;
3188 $penalty = $e->update_actor_user_standing_penalty($penalty)
3189 or return $e->die_event;
3196 __PACKAGE__->register_method(
3197 method => "remove_penalty",
3198 api_name => "open-ils.actor.user.penalty.remove"
3201 sub remove_penalty {
3202 my($self, $conn, $auth, $penalty) = @_;
3203 my $e = new_editor(authtoken=>$auth, xact => 1);
3204 return $e->die_event unless $e->checkauth;
3205 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3206 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3208 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3213 __PACKAGE__->register_method(
3214 method => "update_penalty_note",
3215 api_name => "open-ils.actor.user.penalty.note.update"
3218 sub update_penalty_note {
3219 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3220 my $e = new_editor(authtoken=>$auth, xact => 1);
3221 return $e->die_event unless $e->checkauth;
3222 for my $penalty_id (@$penalty_ids) {
3223 my $penalty = $e->search_actor_user_standing_penalty([
3224 { id => $penalty_id },
3226 flesh_fields => {aum => ['usr_message']}
3229 if (! $penalty ) { return $e->die_event; }
3230 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3231 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3233 my $aum = $penalty->usr_message();
3235 $aum = Fieldmapper::actor::usr_message->new;
3237 $aum->create_date('now');
3238 $aum->sending_lib($e->requestor->ws_ou);
3240 $aum->usr($penalty->usr);
3241 $aum->message($note);
3245 $aum = $e->create_actor_usr_message($aum)
3246 or return $e->die_event;
3248 $penalty->usr_message($aum->id);
3249 $penalty->ischanged(1);
3250 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3252 $aum = $e->retrieve_actor_usr_message($aum) or return $e->die_event;
3253 $aum->message($note); $aum->ischanged(1);
3254 $e->update_actor_usr_message($aum) or return $e->die_event;
3261 __PACKAGE__->register_method(
3262 method => "ranged_penalty_thresholds",
3263 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3267 sub ranged_penalty_thresholds {
3268 my($self, $conn, $auth, $context_org) = @_;
3269 my $e = new_editor(authtoken=>$auth);
3270 return $e->event unless $e->checkauth;
3271 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3272 my $list = $e->search_permission_grp_penalty_threshold([
3273 {org_unit => $U->get_org_ancestors($context_org)},
3274 {order_by => {pgpt => 'id'}}
3276 $conn->respond($_) for @$list;
3282 __PACKAGE__->register_method(
3283 method => "user_retrieve_fleshed_by_id",
3285 api_name => "open-ils.actor.user.fleshed.retrieve",
3288 sub user_retrieve_fleshed_by_id {
3289 my( $self, $client, $auth, $user_id, $fields ) = @_;
3290 my $e = new_editor(authtoken => $auth);
3291 return $e->event unless $e->checkauth;
3293 if( $e->requestor->id != $user_id ) {
3294 return $e->event unless $e->allowed('VIEW_USER');
3301 "standing_penalties",
3309 return new_flesh_user($user_id, $fields, $e);
3313 sub new_flesh_user {
3316 my $fields = shift || [];
3319 my $fetch_penalties = 0;
3320 if(grep {$_ eq 'standing_penalties'} @$fields) {
3321 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3322 $fetch_penalties = 1;
3325 my $fetch_notes = 0;
3326 if(grep {$_ eq 'notes'} @$fields) {
3327 $fields = [grep {$_ ne 'notes'} @$fields];
3331 my $fetch_usr_act = 0;
3332 if(grep {$_ eq 'usr_activity'} @$fields) {
3333 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3337 my $user = $e->retrieve_actor_user(
3342 "flesh_fields" => { "au" => $fields }
3345 ) or return $e->die_event;
3348 if( grep { $_ eq 'addresses' } @$fields ) {
3350 $user->addresses([]) unless @{$user->addresses};
3351 # don't expose "replaced" addresses by default
3352 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3354 if( ref $user->billing_address ) {
3355 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3356 push( @{$user->addresses}, $user->billing_address );
3360 if( ref $user->mailing_address ) {
3361 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3362 push( @{$user->addresses}, $user->mailing_address );
3367 if($fetch_penalties) {
3368 # grab the user penalties ranged for this location
3369 $user->standing_penalties(
3370 $e->search_actor_user_standing_penalty([
3373 {stop_date => undef},
3374 {stop_date => {'>' => 'now'}}
3376 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3379 flesh_fields => {ausp => ['standing_penalty','usr_message']}
3386 # grab notes (now actor.usr_message_penalty) that have not hit their stop_date
3387 # NOTE: This is a view that already filters out deleted messages that are not
3388 # attached to a penalty, but the query is slow if we include deleted=f, so we
3389 # post-filter that. This counts both user messages and standing penalties, but
3390 # linked ones are only counted once.
3392 grep { !$_->deleted or $_->deleted eq 'f' } @{ $e->search_actor_usr_message_penalty([
3395 {stop_date => undef},
3396 {stop_date => {'>' => 'now'}}
3403 # retrieve the most recent usr_activity entry
3404 if ($fetch_usr_act) {
3406 # max number to return for simple patron fleshing
3407 my $limit = $U->ou_ancestor_setting_value(
3408 $e->requestor->ws_ou,
3409 'circ.patron.usr_activity_retrieve.max');
3413 flesh_fields => {auact => ['etype']},
3414 order_by => {auact => 'event_time DESC'},
3417 # 0 == none, <0 == return all
3418 $limit = 1 unless defined $limit;
3419 $opts->{limit} = $limit if $limit > 0;
3421 $user->usr_activity(
3423 [] : # skip the DB call
3424 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3429 $user->clear_passwd();
3436 __PACKAGE__->register_method(
3437 method => "user_retrieve_parts",
3438 api_name => "open-ils.actor.user.retrieve.parts",
3441 sub user_retrieve_parts {
3442 my( $self, $client, $auth, $user_id, $fields ) = @_;
3443 my $e = new_editor(authtoken => $auth);
3444 return $e->event unless $e->checkauth;
3445 $user_id ||= $e->requestor->id;
3446 if( $e->requestor->id != $user_id ) {
3447 return $e->event unless $e->allowed('VIEW_USER');
3450 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3451 push(@resp, $user->$_()) for(@$fields);
3457 __PACKAGE__->register_method(
3458 method => 'user_opt_in_enabled',
3459 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3460 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3463 sub user_opt_in_enabled {
3464 my($self, $conn) = @_;
3465 my $sc = OpenSRF::Utils::SettingsClient->new;
3466 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3471 __PACKAGE__->register_method(
3472 method => 'user_opt_in_at_org',
3473 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3475 @param $auth The auth token
3476 @param user_id The ID of the user to test
3477 @return 1 if the user has opted in at the specified org,
3478 2 if opt-in is disallowed for the user's home org,
3479 event on error, and 0 otherwise. /
3481 sub user_opt_in_at_org {
3482 my($self, $conn, $auth, $user_id) = @_;
3484 # see if we even need to enforce the opt-in value
3485 return 1 unless user_opt_in_enabled($self);
3487 my $e = new_editor(authtoken => $auth);
3488 return $e->event unless $e->checkauth;
3490 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3491 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3493 my $ws_org = $e->requestor->ws_ou;
3494 # user is automatically opted-in if they are from the local org
3495 return 1 if $user->home_ou eq $ws_org;
3497 # get the boundary setting
3498 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3500 # auto opt in if user falls within the opt boundary
3501 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3503 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3505 # check whether opt-in is restricted at the user's home library
3506 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3507 if ($opt_restrict_depth) {
3508 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3509 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3511 # opt-in is disallowed unless the workstation org is within the home
3512 # library's opt-in scope
3513 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3516 my $vals = $e->search_actor_usr_org_unit_opt_in(
3517 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3523 __PACKAGE__->register_method(
3524 method => 'create_user_opt_in_at_org',
3525 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3527 @param $auth The auth token
3528 @param user_id The ID of the user to test
3529 @return The ID of the newly created object, event on error./
3532 sub create_user_opt_in_at_org {
3533 my($self, $conn, $auth, $user_id, $org_id) = @_;
3535 my $e = new_editor(authtoken => $auth, xact=>1);
3536 return $e->die_event unless $e->checkauth;
3538 # if a specific org unit wasn't passed in, get one based on the defaults;
3540 my $wsou = $e->requestor->ws_ou;
3541 # get the default opt depth
3542 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3543 # get the org unit at that depth
3544 my $org = $e->json_query({
3545 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3546 $org_id = $org->{id};
3549 # fall back to the workstation OU, the pre-opt-in-boundary way
3550 $org_id = $e->requestor->ws_ou;
3553 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3554 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3556 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3558 $opt_in->org_unit($org_id);
3559 $opt_in->usr($user_id);
3560 $opt_in->staff($e->requestor->id);
3561 $opt_in->opt_in_ts('now');
3562 $opt_in->opt_in_ws($e->requestor->wsid);
3564 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3565 or return $e->die_event;
3573 __PACKAGE__->register_method (
3574 method => 'retrieve_org_hours',
3575 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3577 Returns the hours of operation for a specified org unit
3578 @param authtoken The login session key
3579 @param org_id The org_unit ID
3583 sub retrieve_org_hours {
3584 my($self, $conn, $auth, $org_id) = @_;
3585 my $e = new_editor(authtoken => $auth);
3586 return $e->die_event unless $e->checkauth;
3587 $org_id ||= $e->requestor->ws_ou;
3588 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3592 __PACKAGE__->register_method (
3593 method => 'verify_user_password',
3594 api_name => 'open-ils.actor.verify_user_password',
3596 Given a barcode or username and the MD5 encoded password,
3597 returns 1 if the password is correct. Returns 0 otherwise.
3601 sub verify_user_password {
3602 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3603 my $e = new_editor(authtoken => $auth);
3604 return $e->die_event unless $e->checkauth;
3606 my $user_by_barcode;
3607 my $user_by_username;
3609 my $card = $e->search_actor_card([
3610 {barcode => $barcode},
3611 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3612 $user_by_barcode = $card->usr;
3613 $user = $user_by_barcode;
3616 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3617 $user = $user_by_username;
3619 return 0 if (!$user || $U->is_true($user->deleted));
3620 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3621 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3622 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3625 __PACKAGE__->register_method (
3626 method => 'retrieve_usr_id_via_barcode_or_usrname',
3627 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3629 Given a barcode or username returns the id for the user or
3634 sub retrieve_usr_id_via_barcode_or_usrname {
3635 my($self, $conn, $auth, $barcode, $username) = @_;
3636 my $e = new_editor(authtoken => $auth);
3637 return $e->die_event unless $e->checkauth;
3638 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3640 my $user_by_barcode;
3641 my $user_by_username;
3642 $logger->info("$id_as_barcode is the ID as BARCODE");
3644 my $card = $e->search_actor_card([
3645 {barcode => $barcode},
3646 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3647 if ($id_as_barcode =~ /^t/i) {
3649 $user = $e->retrieve_actor_user($barcode);
3650 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3652 $user_by_barcode = $card->usr;
3653 $user = $user_by_barcode;
3656 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3657 $user_by_barcode = $card->usr;
3658 $user = $user_by_barcode;
3663 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3665 $user = $user_by_username;
3667 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3668 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3669 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3674 __PACKAGE__->register_method (
3675 method => 'merge_users',
3676 api_name => 'open-ils.actor.user.merge',
3679 Given a list of source users and destination user, transfer all data from the source
3680 to the dest user and delete the source user. All user related data is
3681 transferred, including circulations, holds, bookbags, etc.
3687 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3688 my $e = new_editor(xact => 1, authtoken => $auth);
3689 return $e->die_event unless $e->checkauth;
3691 # disallow the merge if any subordinate accounts are in collections
3692 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3693 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3695 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3696 if $master_id == $e->requestor->id;
3698 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3699 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3700 return $evt if $evt;
3702 my $del_addrs = ($U->ou_ancestor_setting_value(
3703 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3704 my $del_cards = ($U->ou_ancestor_setting_value(
3705 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3706 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3707 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3709 for my $src_id (@$user_ids) {
3711 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3712 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3713 return $evt if $evt;
3715 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3716 if $src_id == $e->requestor->id;
3718 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3719 if($src_user->home_ou ne $master_user->home_ou) {
3720 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3723 return $e->die_event unless
3724 $e->json_query({from => [
3739 __PACKAGE__->register_method (
3740 method => 'approve_user_address',
3741 api_name => 'open-ils.actor.user.pending_address.approve',
3748 sub approve_user_address {
3749 my($self, $conn, $auth, $addr) = @_;
3750 my $e = new_editor(xact => 1, authtoken => $auth);
3751 return $e->die_event unless $e->checkauth;
3753 # if the caller passes an address object, assume they want to
3754 # update it first before approving it
3755 $e->update_actor_user_address($addr) or return $e->die_event;
3757 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3759 my $user = $e->retrieve_actor_user($addr->usr);
3760 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3761 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3762 or return $e->die_event;
3764 return [values %$result]->[0];
3768 __PACKAGE__->register_method (
3769 method => 'retrieve_friends',
3770 api_name => 'open-ils.actor.friends.retrieve',
3773 returns { confirmed: [], pending_out: [], pending_in: []}
3774 pending_out are users I'm requesting friendship with
3775 pending_in are users requesting friendship with me
3780 sub retrieve_friends {
3781 my($self, $conn, $auth, $user_id, $options) = @_;
3782 my $e = new_editor(authtoken => $auth);
3783 return $e->event unless $e->checkauth;
3784 $user_id ||= $e->requestor->id;
3786 if($user_id != $e->requestor->id) {
3787 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3788 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3791 return OpenILS::Application::Actor::Friends->retrieve_friends(
3792 $e, $user_id, $options);
3797 __PACKAGE__->register_method (
3798 method => 'apply_friend_perms',
3799 api_name => 'open-ils.actor.friends.perms.apply',
3805 sub apply_friend_perms {
3806 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3807 my $e = new_editor(authtoken => $auth, xact => 1);
3808 return $e->die_event unless $e->checkauth;
3810 if($user_id != $e->requestor->id) {
3811 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3812 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3815 for my $perm (@perms) {
3817 OpenILS::Application::Actor::Friends->apply_friend_perm(
3818 $e, $user_id, $delegate_id, $perm);
3819 return $evt if $evt;
3827 __PACKAGE__->register_method (
3828 method => 'update_user_pending_address',
3829 api_name => 'open-ils.actor.user.address.pending.cud'
3832 sub update_user_pending_address {
3833 my($self, $conn, $auth, $addr) = @_;
3834 my $e = new_editor(authtoken => $auth, xact => 1);
3835 return $e->die_event unless $e->checkauth;
3837 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3838 if($addr->usr != $e->requestor->id) {
3839 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3843 $e->create_actor_user_address($addr) or return $e->die_event;
3844 } elsif($addr->isdeleted) {
3845 $e->delete_actor_user_address($addr) or return $e->die_event;
3847 $e->update_actor_user_address($addr) or return $e->die_event;
3851 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3857 __PACKAGE__->register_method (
3858 method => 'user_events',
3859 api_name => 'open-ils.actor.user.events.circ',
3862 __PACKAGE__->register_method (
3863 method => 'user_events',
3864 api_name => 'open-ils.actor.user.events.ahr',
3869 my($self, $conn, $auth, $user_id, $filters) = @_;
3870 my $e = new_editor(authtoken => $auth);
3871 return $e->event unless $e->checkauth;
3873 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3874 my $user_field = 'usr';
3877 $filters->{target} = {
3878 select => { $obj_type => ['id'] },
3880 where => {usr => $user_id}
3883 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3884 if($e->requestor->id != $user_id) {
3885 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3888 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3889 my $req = $ses->request('open-ils.trigger.events_by_target',
3890 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3892 while(my $resp = $req->recv) {
3893 my $val = $resp->content;
3894 my $tgt = $val->target;
3896 if($obj_type eq 'circ') {
3897 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3899 } elsif($obj_type eq 'ahr') {
3900 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3901 if $tgt->current_copy;
3904 $conn->respond($val) if $val;
3910 __PACKAGE__->register_method (
3911 method => 'copy_events',
3912 api_name => 'open-ils.actor.copy.events.circ',
3915 __PACKAGE__->register_method (
3916 method => 'copy_events',
3917 api_name => 'open-ils.actor.copy.events.ahr',
3922 my($self, $conn, $auth, $copy_id, $filters) = @_;
3923 my $e = new_editor(authtoken => $auth);
3924 return $e->event unless $e->checkauth;
3926 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3928 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3930 my $copy_field = 'target_copy';
3931 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3934 $filters->{target} = {
3935 select => { $obj_type => ['id'] },
3937 where => {$copy_field => $copy_id}
3941 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3942 my $req = $ses->request('open-ils.trigger.events_by_target',
3943 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3945 while(my $resp = $req->recv) {
3946 my $val = $resp->content;
3947 my $tgt = $val->target;
3949 my $user = $e->retrieve_actor_user($tgt->usr);
3950 if($e->requestor->id != $user->id) {
3951 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3954 $tgt->$copy_field($copy);
3957 $conn->respond($val) if $val;
3964 __PACKAGE__->register_method (
3965 method => 'get_itemsout_notices',
3966 api_name => 'open-ils.actor.user.itemsout.notices',
3970 desc => q/Summary counts of circulat notices/,
3972 {desc => 'authtoken', type => 'string'},
3973 {desc => 'circulation identifiers', type => 'array of numbers'}
3975 return => q/Stream of summary objects/
3979 sub get_itemsout_notices {
3980 my ($self, $client, $auth, $circ_ids) = @_;
3982 my $e = new_editor(authtoken => $auth);
3983 return $e->event unless $e->checkauth;
3985 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3987 for my $circ_id (@$circ_ids) {
3988 my $resp = get_itemsout_notices_impl($e, $circ_id);
3990 if ($U->is_event($resp)) {
3991 $client->respond($resp);
3995 $client->respond({circ_id => $circ_id, %$resp});
4003 sub get_itemsout_notices_impl {
4004 my ($e, $circId) = @_;
4006 my $requestorId = $e->requestor->id;
4008 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
4010 my $patronId = $circ->usr;
4012 if( $patronId ne $requestorId ){
4013 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
4014 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
4017 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4018 #my $req = $ses->request('open-ils.trigger.events_by_target',
4019 # 'circ', {target => [$circId], event=> {state=>'complete'}});
4020 # ^ Above removed in favor of faster json_query.
4023 # select complete_time
4024 # from action_trigger.event atev
4025 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
4026 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
4027 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
4030 my $ctx_loc = $e->requestor->ws_ou;
4031 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
4032 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
4035 select => { atev => ["complete_time"] },
4038 atevdef => { field => "id",fkey => "event_def"}
4042 "+atevdef" => { active => 't', hook => 'checkout.due' },
4043 "+atev" => { target => $circId, state => 'complete' }
4047 if ($exclude_courtesy_notices){
4048 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
4051 my %resblob = ( numNotices => 0, lastDt => undef );
4053 my $res = $e->json_query($query);
4054 for my $ndate (@$res) {
4055 $resblob{numNotices}++;
4056 if( !defined $resblob{lastDt}){
4057 $resblob{lastDt} = $$ndate{complete_time};
4060 if ($resblob{lastDt} lt $$ndate{complete_time}){
4061 $resblob{lastDt} = $$ndate{complete_time};
4068 __PACKAGE__->register_method (
4069 method => 'update_events',
4070 api_name => 'open-ils.actor.user.event.cancel.batch',
4073 __PACKAGE__->register_method (
4074 method => 'update_events',
4075 api_name => 'open-ils.actor.user.event.reset.batch',
4080 my($self, $conn, $auth, $event_ids) = @_;
4081 my $e = new_editor(xact => 1, authtoken => $auth);
4082 return $e->die_event unless $e->checkauth;
4085 for my $id (@$event_ids) {
4087 # do a little dance to determine what user we are ultimately affecting
4088 my $event = $e->retrieve_action_trigger_event([
4091 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
4093 ]) or return $e->die_event;
4096 if($event->event_def->hook->core_type eq 'circ') {
4097 $user_id = $e->retrieve_action_circulation($event->target)->usr;
4098 } elsif($event->event_def->hook->core_type eq 'ahr') {
4099 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
4104 my $user = $e->retrieve_actor_user($user_id);
4105 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
4107 if($self->api_name =~ /cancel/) {
4108 $event->state('invalid');
4109 } elsif($self->api_name =~ /reset/) {
4110 $event->clear_start_time;
4111 $event->clear_update_time;
4112 $event->state('pending');
4115 $e->update_action_trigger_event($event) or return $e->die_event;
4116 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
4120 return {complete => 1};
4124 __PACKAGE__->register_method (
4125 method => 'really_delete_user',
4126 api_name => 'open-ils.actor.user.delete.override',
4127 signature => q/@see open-ils.actor.user.delete/
4130 __PACKAGE__->register_method (
4131 method => 'really_delete_user',
4132 api_name => 'open-ils.actor.user.delete',
4134 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
4135 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
4136 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
4137 dest_usr_id is only required when deleting a user that performs staff functions.
4141 sub really_delete_user {
4142 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
4143 my $e = new_editor(authtoken => $auth, xact => 1);
4144 return $e->die_event unless $e->checkauth;
4145 $oargs = { all => 1 } unless defined $oargs;
4147 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
4148 my $open_bills = $e->json_query({
4149 select => { mbts => ['id'] },
4152 xact_finish => { '=' => undef },
4153 usr => { '=' => $user_id },
4155 }) or return $e->die_event;
4157 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
4159 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
4161 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
4162 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
4163 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
4165 # No deleting yourself - UI is supposed to stop you first, though.
4166 return $e->die_event unless $e->requestor->id != $user->id;
4167 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4168 # Check if you are allowed to mess with this patron permission group at all
4169 my $evt = group_perm_failed($e, $e->requestor, $user);
4170 return $e->die_event($evt) if $evt;
4171 my $stat = $e->json_query(
4172 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4173 or return $e->die_event;
4179 __PACKAGE__->register_method (
4180 method => 'user_payments',
4181 api_name => 'open-ils.actor.user.payments.retrieve',
4184 Returns all payments for a given user. Default order is newest payments first.
4185 @param auth Authentication token
4186 @param user_id The user ID
4187 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4192 my($self, $conn, $auth, $user_id, $filters) = @_;
4195 my $e = new_editor(authtoken => $auth);
4196 return $e->die_event unless $e->checkauth;
4198 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4199 return $e->event unless
4200 $e->requestor->id == $user_id or
4201 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4203 # Find all payments for all transactions for user $user_id
4205 select => {mp => ['id']},
4210 select => {mbt => ['id']},
4212 where => {usr => $user_id}
4217 { # by default, order newest payments first
4219 field => 'payment_ts',
4222 # secondary sort in ID as a tie-breaker, since payments created
4223 # within the same transaction will have identical payment_ts's
4230 for (qw/order_by limit offset/) {
4231 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4234 if(defined $filters->{where}) {
4235 foreach (keys %{$filters->{where}}) {
4236 # don't allow the caller to expand the result set to other users
4237 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4241 my $payment_ids = $e->json_query($query);
4242 for my $pid (@$payment_ids) {
4243 my $pay = $e->retrieve_money_payment([
4248 mbt => ['summary', 'circulation', 'grocery'],
4249 circ => ['target_copy'],
4250 acp => ['call_number'],
4258 xact_type => $pay->xact->summary->xact_type,
4259 last_billing_type => $pay->xact->summary->last_billing_type,
4262 if($pay->xact->summary->xact_type eq 'circulation') {
4263 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4264 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4267 $pay->xact($pay->xact->id); # de-flesh
4268 $conn->respond($resp);
4276 __PACKAGE__->register_method (
4277 method => 'negative_balance_users',
4278 api_name => 'open-ils.actor.users.negative_balance',
4281 Returns all users that have an overall negative balance
4282 @param auth Authentication token
4283 @param org_id The context org unit as an ID or list of IDs. This will be the home
4284 library of the user. If no org_unit is specified, no org unit filter is applied
4288 sub negative_balance_users {
4289 my($self, $conn, $auth, $org_id) = @_;
4291 my $e = new_editor(authtoken => $auth);
4292 return $e->die_event unless $e->checkauth;
4293 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4297 mous => ['usr', 'balance_owed'],
4300 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4301 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4318 where => {'+mous' => {balance_owed => {'<' => 0}}}
4321 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4323 my $list = $e->json_query($query, {timeout => 600});
4325 for my $data (@$list) {
4327 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4328 balance_owed => $data->{balance_owed},
4329 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4336 __PACKAGE__->register_method(
4337 method => "request_password_reset",
4338 api_name => "open-ils.actor.patron.password_reset.request",
4340 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4341 "method for changing a user's password. The UUID token is distributed via A/T " .
4342 "templates (i.e. email to the user).",
4344 { desc => 'user_id_type', type => 'string' },
4345 { desc => 'user_id', type => 'string' },
4346 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4348 return => {desc => '1 on success, Event on error'}
4351 sub request_password_reset {
4352 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4354 # Check to see if password reset requests are already being throttled:
4355 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4357 my $e = new_editor(xact => 1);
4360 # Get the user, if any, depending on the input value
4361 if ($user_id_type eq 'username') {
4362 $user = $e->search_actor_user({usrname => $user_id})->[0];
4365 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4367 } elsif ($user_id_type eq 'barcode') {
4368 my $card = $e->search_actor_card([
4369 {barcode => $user_id},
4370 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4373 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4378 # If the user doesn't have an email address, we can't help them
4379 if (!$user->email) {
4381 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4384 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4385 if ($email_must_match) {
4386 if (lc($user->email) ne lc($email)) {
4387 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4391 _reset_password_request($conn, $e, $user);
4394 # Once we have the user, we can issue the password reset request
4395 # XXX Add a wrapper method that accepts barcode + email input
4396 sub _reset_password_request {
4397 my ($conn, $e, $user) = @_;
4399 # 1. Get throttle threshold and time-to-live from OU_settings
4400 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4401 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4403 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4405 # 2. Get time of last request and number of active requests (num_active)
4406 my $active_requests = $e->json_query({
4412 transform => 'COUNT'
4415 column => 'request_time',
4421 has_been_reset => { '=' => 'f' },
4422 request_time => { '>' => $threshold_time }
4426 # Guard against no active requests
4427 if ($active_requests->[0]->{'request_time'}) {
4428 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4429 my $now = DateTime::Format::ISO8601->new();
4431 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4432 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4433 ($last_request->add_duration('1 minute') > $now)) {
4434 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4436 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4440 # TODO Check to see if the user is in a password-reset-restricted group
4442 # Otherwise, go ahead and try to get the user.
4444 # Check the number of active requests for this user
4445 $active_requests = $e->json_query({
4451 transform => 'COUNT'
4456 usr => { '=' => $user->id },
4457 has_been_reset => { '=' => 'f' },
4458 request_time => { '>' => $threshold_time }
4462 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4464 # if less than or equal to per-user threshold, proceed; otherwise, return event
4465 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4466 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4468 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4471 # Create the aupr object and insert into the database
4472 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4473 my $uuid = create_uuid_as_string(UUID_V4);
4474 $reset_request->uuid($uuid);
4475 $reset_request->usr($user->id);
4477 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4480 # Create an event to notify user of the URL to reset their password
4482 # Can we stuff this in the user_data param for trigger autocreate?
4483 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4485 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4486 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4489 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4494 __PACKAGE__->register_method(
4495 method => "commit_password_reset",
4496 api_name => "open-ils.actor.patron.password_reset.commit",
4498 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4499 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4500 "with the supplied password.",
4502 { desc => 'uuid', type => 'string' },
4503 { desc => 'password', type => 'string' },
4505 return => {desc => '1 on success, Event on error'}
4508 sub commit_password_reset {
4509 my($self, $conn, $uuid, $password) = @_;
4511 # Check to see if password reset requests are already being throttled:
4512 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4513 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4514 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4516 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4519 my $e = new_editor(xact => 1);
4521 my $aupr = $e->search_actor_usr_password_reset({
4528 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4530 my $user_id = $aupr->[0]->usr;
4531 my $user = $e->retrieve_actor_user($user_id);
4533 # Ensure we're still within the TTL for the request
4534 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4535 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4536 if ($threshold < DateTime->now(time_zone => 'local')) {
4538 $logger->info("Password reset request needed to be submitted before $threshold");
4539 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4542 # Check complexity of password against OU-defined regex
4543 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4547 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4548 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4549 $is_strong = check_password_strength_custom($password, $pw_regex);
4551 $is_strong = check_password_strength_default($password);
4556 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4559 # All is well; update the password
4560 modify_migrated_user_password($e, $user->id, $password);
4562 # And flag that this password reset request has been honoured
4563 $aupr->[0]->has_been_reset('t');
4564 $e->update_actor_usr_password_reset($aupr->[0]);
4570 sub check_password_strength_default {
4571 my $password = shift;
4572 # Use the default set of checks
4573 if ( (length($password) < 7) or
4574 ($password !~ m/.*\d+.*/) or
4575 ($password !~ m/.*[A-Za-z]+.*/)
4582 sub check_password_strength_custom {
4583 my ($password, $pw_regex) = @_;
4585 $pw_regex = qr/$pw_regex/;
4586 if ($password !~ /$pw_regex/) {
4592 __PACKAGE__->register_method(
4593 method => "fire_test_notification",
4594 api_name => "open-ils.actor.event.test_notification"
4597 sub fire_test_notification {
4598 my($self, $conn, $auth, $args) = @_;
4599 my $e = new_editor(authtoken => $auth);
4600 return $e->event unless $e->checkauth;
4601 if ($e->requestor->id != $$args{target}) {
4602 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4603 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4606 my $event_hook = $$args{hook} or return $e->event;
4607 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4609 my $usr = $e->retrieve_actor_user($$args{target});
4610 return $e->event unless $usr;
4612 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4616 __PACKAGE__->register_method(
4617 method => "event_def_opt_in_settings",
4618 api_name => "open-ils.actor.event_def.opt_in.settings",
4621 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4623 { desc => 'Authentication token', type => 'string'},
4625 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4630 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4637 sub event_def_opt_in_settings {
4638 my($self, $conn, $auth, $org_id) = @_;
4639 my $e = new_editor(authtoken => $auth);
4640 return $e->event unless $e->checkauth;
4642 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4643 return $e->event unless
4644 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4646 $org_id = $e->requestor->home_ou;
4649 # find all config.user_setting_type's related to event_defs for the requested org unit
4650 my $types = $e->json_query({
4651 select => {cust => ['name']},
4652 from => {atevdef => 'cust'},
4655 owner => $U->get_org_ancestors($org_id), # context org plus parents
4662 $conn->respond($_) for
4663 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4670 __PACKAGE__->register_method(
4671 method => "user_circ_history",
4672 api_name => "open-ils.actor.history.circ",
4676 desc => 'Returns user circ history objects for the calling user',
4678 { desc => 'Authentication token', type => 'string'},
4679 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4682 desc => q/Stream of 'auch' circ history objects/,
4688 __PACKAGE__->register_method(
4689 method => "user_circ_history",
4690 api_name => "open-ils.actor.history.circ.clear",
4693 desc => 'Delete all user circ history entries for the calling user',
4695 { desc => 'Authentication token', type => 'string'},
4696 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4699 desc => q/1 on success, event on error/,
4705 __PACKAGE__->register_method(
4706 method => "user_circ_history",
4707 api_name => "open-ils.actor.history.circ.print",
4710 desc => q/Returns printable output for the caller's circ history objects/,
4712 { desc => 'Authentication token', type => 'string'},
4713 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4716 desc => q/An action_trigger.event object or error event./,
4722 __PACKAGE__->register_method(
4723 method => "user_circ_history",
4724 api_name => "open-ils.actor.history.circ.email",
4727 desc => q/Emails the caller's circ history/,
4729 { desc => 'Authentication token', type => 'string'},
4730 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4731 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4734 desc => q/undef, or event on error/
4739 sub user_circ_history {
4740 my ($self, $conn, $auth, $options) = @_;
4743 my $for_print = ($self->api_name =~ /print/);
4744 my $for_email = ($self->api_name =~ /email/);
4745 my $for_clear = ($self->api_name =~ /clear/);
4747 # No perm check is performed. Caller may only access his/her own
4748 # circ history entries.
4749 my $e = new_editor(authtoken => $auth);
4750 return $e->event unless $e->checkauth;
4753 if (!$for_clear) { # clear deletes all
4754 $limits{offset} = $options->{offset} if defined $options->{offset};
4755 $limits{limit} = $options->{limit} if defined $options->{limit};
4758 my %circ_id_filter = $options->{circ_ids} ?
4759 (id => $options->{circ_ids}) : ();
4761 my $circs = $e->search_action_user_circ_history([
4762 { usr => $e->requestor->id,
4765 { # order newest to oldest by default
4766 order_by => {auch => 'xact_start DESC'},
4769 {substream => 1} # could be a large list
4773 return $U->fire_object_event(undef,
4774 'circ.format.history.print', $circs, $e->requestor->home_ou);
4777 $e->xact_begin if $for_clear;
4778 $conn->respond_complete(1) if $for_email; # no sense in waiting
4780 for my $circ (@$circs) {
4783 # events will be fired from action_trigger_runner
4784 $U->create_events_for_hook('circ.format.history.email',
4785 $circ, $e->editor->home_ou, undef, undef, 1);
4787 } elsif ($for_clear) {
4789 $e->delete_action_user_circ_history($circ)
4790 or return $e->die_event;
4793 $conn->respond($circ);
4806 __PACKAGE__->register_method(
4807 method => "user_visible_holds",
4808 api_name => "open-ils.actor.history.hold.visible",
4811 desc => 'Returns the set of opt-in visible holds',
4813 { desc => 'Authentication token', type => 'string'},
4814 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4815 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4818 desc => q/An object with 1 field: "hold"/,
4824 __PACKAGE__->register_method(
4825 method => "user_visible_holds",
4826 api_name => "open-ils.actor.history.hold.visible.print",
4829 desc => 'Returns printable output for the set of opt-in visible holds',
4831 { desc => 'Authentication token', type => 'string'},
4832 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4833 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4836 desc => q/An action_trigger.event object or error event./,
4842 __PACKAGE__->register_method(
4843 method => "user_visible_holds",
4844 api_name => "open-ils.actor.history.hold.visible.email",
4847 desc => 'Emails the set of opt-in visible holds to the requestor',
4849 { desc => 'Authentication token', type => 'string'},
4850 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4851 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4854 desc => q/undef, or event on error/
4859 sub user_visible_holds {
4860 my($self, $conn, $auth, $user_id, $options) = @_;
4863 my $for_print = ($self->api_name =~ /print/);
4864 my $for_email = ($self->api_name =~ /email/);
4865 my $e = new_editor(authtoken => $auth);
4866 return $e->event unless $e->checkauth;
4868 $user_id ||= $e->requestor->id;
4870 $options->{limit} ||= 50;
4871 $options->{offset} ||= 0;
4873 if($user_id != $e->requestor->id) {
4874 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4875 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4876 return $e->event unless $e->allowed($perm, $user->home_ou);
4879 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4881 my $data = $e->json_query({
4882 from => [$db_func, $user_id],
4883 limit => $$options{limit},
4884 offset => $$options{offset}
4886 # TODO: I only want IDs. code below didn't get me there
4887 # {"select":{"au":[{"column":"id", "result_field":"id",
4888 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4893 return undef unless @$data;
4897 # collect the batch of objects
4901 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4902 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4906 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4907 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4910 } elsif ($for_email) {
4912 $conn->respond_complete(1) if $for_email; # no sense in waiting
4920 my $hold = $e->retrieve_action_hold_request($id);
4921 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4922 # events will be fired from action_trigger_runner
4926 my $circ = $e->retrieve_action_circulation($id);
4927 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4928 # events will be fired from action_trigger_runner
4932 } else { # just give me the data please
4940 my $hold = $e->retrieve_action_hold_request($id);
4941 $conn->respond({hold => $hold});
4945 my $circ = $e->retrieve_action_circulation($id);
4948 summary => $U->create_circ_chain_summary($e, $id)
4957 __PACKAGE__->register_method(
4958 method => "user_saved_search_cud",
4959 api_name => "open-ils.actor.user.saved_search.cud",
4962 desc => 'Create/Update/Delete Access to user saved searches',
4964 { desc => 'Authentication token', type => 'string' },
4965 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4968 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4974 __PACKAGE__->register_method(
4975 method => "user_saved_search_cud",
4976 api_name => "open-ils.actor.user.saved_search.retrieve",
4979 desc => 'Retrieve a saved search object',
4981 { desc => 'Authentication token', type => 'string' },
4982 { desc => 'Saved Search ID', type => 'number' }
4985 desc => q/The saved search object, Event on error/,
4991 sub user_saved_search_cud {
4992 my( $self, $client, $auth, $search ) = @_;
4993 my $e = new_editor( authtoken=>$auth );
4994 return $e->die_event unless $e->checkauth;
4996 my $o_search; # prior version of the object, if any
4997 my $res; # to be returned
4999 # branch on the operation type
5001 if( $self->api_name =~ /retrieve/ ) { # Retrieve
5003 # Get the old version, to check ownership
5004 $o_search = $e->retrieve_actor_usr_saved_search( $search )
5005 or return $e->die_event;
5007 # You can't read somebody else's search
5008 return OpenILS::Event->new('BAD_PARAMS')
5009 unless $o_search->owner == $e->requestor->id;
5015 $e->xact_begin; # start an editor transaction
5017 if( $search->isnew ) { # Create
5019 # You can't create a search for somebody else
5020 return OpenILS::Event->new('BAD_PARAMS')
5021 unless $search->owner == $e->requestor->id;
5023 $e->create_actor_usr_saved_search( $search )
5024 or return $e->die_event;
5028 } elsif( $search->ischanged ) { # Update
5030 # You can't change ownership of a search
5031 return OpenILS::Event->new('BAD_PARAMS')
5032 unless $search->owner == $e->requestor->id;
5034 # Get the old version, to check ownership
5035 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
5036 or return $e->die_event;
5038 # You can't update somebody else's search
5039 return OpenILS::Event->new('BAD_PARAMS')
5040 unless $o_search->owner == $e->requestor->id;
5043 $e->update_actor_usr_saved_search( $search )
5044 or return $e->die_event;
5048 } elsif( $search->isdeleted ) { # Delete
5050 # Get the old version, to check ownership
5051 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
5052 or return $e->die_event;
5054 # You can't delete somebody else's search
5055 return OpenILS::Event->new('BAD_PARAMS')
5056 unless $o_search->owner == $e->requestor->id;
5059 $e->delete_actor_usr_saved_search( $o_search )
5060 or return $e->die_event;
5071 __PACKAGE__->register_method(
5072 method => "get_barcodes",
5073 api_name => "open-ils.actor.get_barcodes"
5077 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
5078 my $e = new_editor(authtoken => $auth);
5079 return $e->event unless $e->checkauth;
5080 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
5082 my $db_result = $e->json_query(
5084 'evergreen.get_barcodes',
5085 $org_id, $context, $barcode,
5089 if($context =~ /actor/) {
5090 my $filter_result = ();
5092 foreach my $result (@$db_result) {
5093 if($result->{type} eq 'actor') {
5094 if($e->requestor->id != $result->{id}) {
5095 $patron = $e->retrieve_actor_user($result->{id});
5097 push(@$filter_result, $e->event);
5100 if($e->allowed('VIEW_USER', $patron->home_ou)) {
5101 push(@$filter_result, $result);
5104 push(@$filter_result, $e->event);
5108 push(@$filter_result, $result);
5112 push(@$filter_result, $result);
5115 return $filter_result;
5121 __PACKAGE__->register_method(
5122 method => 'address_alert_test',
5123 api_name => 'open-ils.actor.address_alert.test',
5125 desc => "Tests a set of address fields to determine if they match with an address_alert",
5127 {desc => 'Authentication token', type => 'string'},
5128 {desc => 'Org Unit', type => 'number'},
5129 {desc => 'Fields', type => 'hash'},
5131 return => {desc => 'List of matching address_alerts'}
5135 sub address_alert_test {
5136 my ($self, $client, $auth, $org_unit, $fields) = @_;
5137 return [] unless $fields and grep {$_} values %$fields;
5139 my $e = new_editor(authtoken => $auth);
5140 return $e->event unless $e->checkauth;
5141 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
5142 $org_unit ||= $e->requestor->ws_ou;
5144 my $alerts = $e->json_query({
5146 'actor.address_alert_matches',
5154 $$fields{post_code},
5155 $$fields{mailing_address},
5156 $$fields{billing_address}
5160 # map the json_query hashes to real objects
5162 map {$e->retrieve_actor_address_alert($_)}
5163 (map {$_->{id}} @$alerts)
5167 __PACKAGE__->register_method(
5168 method => "mark_users_contact_invalid",
5169 api_name => "open-ils.actor.invalidate.email",
5171 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",
5173 {desc => "Authentication token", type => "string"},
5174 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5175 {desc => "Additional note text (optional)", type => "string"},
5176 {desc => "penalty org unit ID (optional)", type => "number"},
5177 {desc => "Email address (optional)", type => "string"}
5179 return => {desc => "Event describing success or failure", type => "object"}
5183 __PACKAGE__->register_method(
5184 method => "mark_users_contact_invalid",
5185 api_name => "open-ils.actor.invalidate.day_phone",
5187 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",
5189 {desc => "Authentication token", type => "string"},
5190 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5191 {desc => "Additional note text (optional)", type => "string"},
5192 {desc => "penalty org unit ID (optional)", type => "number"},
5193 {desc => "Phone Number (optional)", type => "string"}
5195 return => {desc => "Event describing success or failure", type => "object"}
5199 __PACKAGE__->register_method(
5200 method => "mark_users_contact_invalid",
5201 api_name => "open-ils.actor.invalidate.evening_phone",
5203 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",
5205 {desc => "Authentication token", type => "string"},
5206 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5207 {desc => "Additional note text (optional)", type => "string"},
5208 {desc => "penalty org unit ID (optional)", type => "number"},
5209 {desc => "Phone Number (optional)", type => "string"}
5211 return => {desc => "Event describing success or failure", type => "object"}
5215 __PACKAGE__->register_method(
5216 method => "mark_users_contact_invalid",
5217 api_name => "open-ils.actor.invalidate.other_phone",
5219 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",
5221 {desc => "Authentication token", type => "string"},
5222 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5223 {desc => "Additional note text (optional)", type => "string"},
5224 {desc => "penalty org unit ID (optional, default to top of org tree)",
5226 {desc => "Phone Number (optional)", type => "string"}
5228 return => {desc => "Event describing success or failure", type => "object"}
5232 sub mark_users_contact_invalid {
5233 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5235 # This method invalidates an email address or a phone_number which
5236 # removes the bad email address or phone number, copying its contents
5237 # to a patron note, and institutes a standing penalty for "bad email"
5238 # or "bad phone number" which is cleared when the user is saved or
5239 # optionally only when the user is saved with an email address or
5240 # phone number (or staff manually delete the penalty).
5242 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5244 my $e = new_editor(authtoken => $auth, xact => 1);
5245 return $e->die_event unless $e->checkauth;
5248 if (defined $patron_id && $patron_id ne "") {
5249 $howfind = {usr => $patron_id};
5250 } elsif (defined $contact && $contact ne "") {
5251 $howfind = {$contact_type => $contact};
5253 # Error out if no patron id set or no contact is set.
5254 return OpenILS::Event->new('BAD_PARAMS');
5257 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5258 $e, $contact_type, $howfind,
5259 $addl_note, $penalty_ou, $e->requestor->id
5263 # Putting the following method in open-ils.actor is a bad fit, except in that
5264 # it serves an interface that lives under 'actor' in the templates directory,
5265 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5267 __PACKAGE__->register_method(
5268 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5269 method => "get_all_at_reactors_in_use",
5274 { name => 'authtoken', type => 'string' }
5277 desc => 'list of reactor names', type => 'array'
5282 sub get_all_at_reactors_in_use {
5283 my ($self, $conn, $auth) = @_;
5285 my $e = new_editor(authtoken => $auth);
5286 $e->checkauth or return $e->die_event;
5287 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5289 my $reactors = $e->json_query({
5291 atevdef => [{column => "reactor", transform => "distinct"}]
5293 from => {atevdef => {}}
5296 return $e->die_event unless ref $reactors eq "ARRAY";
5299 return [ map { $_->{reactor} } @$reactors ];
5302 __PACKAGE__->register_method(
5303 method => "filter_group_entry_crud",
5304 api_name => "open-ils.actor.filter_group_entry.crud",
5307 Provides CRUD access to filter group entry objects. These are not full accessible
5308 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5309 are not accessible via PCRUD (because they have no fields against which to link perms)
5312 {desc => "Authentication token", type => "string"},
5313 {desc => "Entry ID / Entry Object", type => "number"},
5314 {desc => "Additional note text (optional)", type => "string"},
5315 {desc => "penalty org unit ID (optional, default to top of org tree)",
5319 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5325 sub filter_group_entry_crud {
5326 my ($self, $conn, $auth, $arg) = @_;
5328 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5329 my $e = new_editor(authtoken => $auth, xact => 1);
5330 return $e->die_event unless $e->checkauth;
5336 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5337 or return $e->die_event;
5339 return $e->die_event unless $e->allowed(
5340 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5342 my $query = $arg->query;
5343 $query = $e->create_actor_search_query($query) or return $e->die_event;
5344 $arg->query($query->id);
5345 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5346 $entry->query($query);
5351 } elsif ($arg->ischanged) {
5353 my $entry = $e->retrieve_actor_search_filter_group_entry([
5356 flesh_fields => {asfge => ['grp']}
5358 ]) or return $e->die_event;
5360 return $e->die_event unless $e->allowed(
5361 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5363 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5364 $arg->query($arg->query->id);
5365 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5366 $arg->query($query);
5371 } elsif ($arg->isdeleted) {
5373 my $entry = $e->retrieve_actor_search_filter_group_entry([
5376 flesh_fields => {asfge => ['grp', 'query']}
5378 ]) or return $e->die_event;
5380 return $e->die_event unless $e->allowed(
5381 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5383 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5384 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5397 my $entry = $e->retrieve_actor_search_filter_group_entry([
5400 flesh_fields => {asfge => ['grp', 'query']}
5402 ]) or return $e->die_event;
5404 return $e->die_event unless $e->allowed(
5405 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5406 $entry->grp->owner);
5409 $entry->grp($entry->grp->id); # for consistency