1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenILS::Utils::DateTime qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Carousel;
30 use OpenILS::Application::Actor::Container;
31 use OpenILS::Application::Actor::ClosedDates;
32 use OpenILS::Application::Actor::UserGroups;
33 use OpenILS::Application::Actor::Friends;
34 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Application::Actor::Settings;
37 use OpenILS::Utils::CStoreEditor qw/:funcs/;
38 use OpenILS::Utils::Penalty;
39 use OpenILS::Utils::BadContact;
40 use List::Util qw/max reduce/;
42 use UUID::Tiny qw/:std/;
45 OpenILS::Application::Actor::Container->initialize();
46 OpenILS::Application::Actor::UserGroups->initialize();
47 OpenILS::Application::Actor::ClosedDates->initialize();
50 my $apputils = "OpenILS::Application::AppUtils";
53 sub _d { warn "Patron:\n" . Dumper(shift()); }
56 my $set_user_settings;
60 #__PACKAGE__->register_method(
61 # method => "allowed_test",
62 # api_name => "open-ils.actor.allowed_test",
65 # my($self, $conn, $auth, $orgid, $permcode) = @_;
66 # my $e = new_editor(authtoken => $auth);
67 # return $e->die_event unless $e->checkauth;
71 # permcode => $permcode,
72 # result => $e->allowed($permcode, $orgid)
76 __PACKAGE__->register_method(
77 method => "update_user_setting",
78 api_name => "open-ils.actor.patron.settings.update",
80 sub update_user_setting {
81 my($self, $conn, $auth, $user_id, $settings) = @_;
82 my $e = new_editor(xact => 1, authtoken => $auth);
83 return $e->die_event unless $e->checkauth;
85 $user_id = $e->requestor->id unless defined $user_id;
87 unless($e->requestor->id == $user_id) {
88 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
89 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
92 for my $name (keys %$settings) {
93 my $val = $$settings{$name};
94 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
97 $val = OpenSRF::Utils::JSON->perl2JSON($val);
100 $e->update_actor_user_setting($set) or return $e->die_event;
102 $set = Fieldmapper::actor::user_setting->new;
106 $e->create_actor_user_setting($set) or return $e->die_event;
109 $e->delete_actor_user_setting($set) or return $e->die_event;
118 __PACKAGE__->register_method(
119 method => "update_privacy_waiver",
120 api_name => "open-ils.actor.patron.privacy_waiver.update",
122 desc => "Replaces any existing privacy waiver entries for the patron with the supplied values.",
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'User ID', type => 'number'},
126 {desc => 'Arrayref of privacy waiver entries', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
131 sub update_privacy_waiver {
132 my($self, $conn, $auth, $user_id, $waiver) = @_;
133 my $e = new_editor(xact => 1, authtoken => $auth);
134 return $e->die_event unless $e->checkauth;
136 $user_id = $e->requestor->id unless defined $user_id;
138 unless($e->requestor->id == $user_id) {
139 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
140 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
143 foreach my $w (@$waiver) {
144 $w->{usr} = $user_id unless $w->{usr};
145 if ($w->{id} && $w->{id} ne 'new') {
146 my $existing_rows = $e->search_actor_usr_privacy_waiver({usr => $user_id, id => $w->{id}});
147 if ($existing_rows) {
148 my $existing = $existing_rows->[0];
149 # delete existing if name is empty
150 if (!$w->{name} or $w->{name} =~ /^\s*$/) {
151 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
153 # delete existing if none of the boxes were checked
154 } elsif (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history}) {
155 $e->delete_actor_usr_privacy_waiver($existing) or return $e->die_event;
157 # otherwise, update existing waiver entry
159 $existing->name($w->{name});
160 $existing->place_holds($w->{place_holds});
161 $existing->pickup_holds($w->{pickup_holds});
162 $existing->checkout_items($w->{checkout_items});
163 $existing->view_history($w->{view_history});
164 $e->update_actor_usr_privacy_waiver($existing) or return $e->die_event;
167 $logger->warn("No privacy waiver entry found for user $user_id with ID " . $w->{id});
171 # ignore new entries with empty name or with no boxes checked
172 next if (!$w->{name} or $w->{name} =~ /^\s*$/);
173 next if (!$w->{place_holds} && !$w->{pickup_holds} && !$w->{checkout_items} && !$w->{view_history});
174 my $new = Fieldmapper::actor::usr_privacy_waiver->new;
175 $new->usr($w->{usr});
176 $new->name($w->{name});
177 $new->place_holds($w->{place_holds});
178 $new->pickup_holds($w->{pickup_holds});
179 $new->checkout_items($w->{checkout_items});
180 $new->view_history($w->{view_history});
181 $e->create_actor_usr_privacy_waiver($new) or return $e->die_event;
189 __PACKAGE__->register_method(
190 method => "get_ou_setting_history",
191 api_name => "open-ils.actor.org_unit.settings.history.retrieve",
193 desc => "Retrieves the history of an Org Unit Setting. The permission to retrieve " .
194 "an org unit setting's history is dependant on a specific permission specified " .
195 "in the view_perm column of the config.org_unit_setting_type " .
196 "table's row corresponding to the setting being changed." ,
198 {desc => 'Authentication token', type => 'string'},
199 {desc => 'Org Unit ID', type => 'number'},
200 {desc => 'Setting Type Name', type => 'string'}
202 return => {desc => 'History IDL Object'}
206 sub get_ou_setting_history {
207 my( $self, $client, $auth, $setting, $orgid ) = @_;
208 my $e = new_editor(authtoken => $auth, xact => 1);
209 return $e->die_event unless $e->checkauth;
211 return $U->ou_ancestor_setting_log(
212 $orgid, $setting, $e, $auth
217 __PACKAGE__->register_method(
218 method => "set_ou_settings",
219 api_name => "open-ils.actor.org_unit.settings.update",
221 desc => "Updates the value for a given org unit setting. The permission to update " .
222 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
223 "permission specified in the update_perm column of the config.org_unit_setting_type " .
224 "table's row corresponding to the setting being changed." ,
226 {desc => 'Authentication token', type => 'string'},
227 {desc => 'Org unit ID', type => 'number'},
228 {desc => 'Hash of setting name-value pairs', type => 'object'}
230 return => {desc => '1 on success, Event on error'}
234 sub set_ou_settings {
235 my( $self, $client, $auth, $org_id, $settings ) = @_;
237 my $e = new_editor(authtoken => $auth, xact => 1);
238 return $e->die_event unless $e->checkauth;
240 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
242 for my $name (keys %$settings) {
243 my $val = $$settings{$name};
245 my $type = $e->retrieve_config_org_unit_setting_type([
247 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
248 ]) or return $e->die_event;
249 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
251 # If there is no relevant permission, the default assumption will
252 # be, "no, the caller cannot change that value."
253 return $e->die_event unless ($all_allowed ||
254 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
257 $val = OpenSRF::Utils::JSON->perl2JSON($val);
260 $e->update_actor_org_unit_setting($set) or return $e->die_event;
262 $set = Fieldmapper::actor::org_unit_setting->new;
263 $set->org_unit($org_id);
266 $e->create_actor_org_unit_setting($set) or return $e->die_event;
269 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
277 __PACKAGE__->register_method(
278 method => "fetch_visible_ou_settings_log",
279 api_name => "open-ils.actor.org_unit.settings.history.visible.retrieve",
281 desc => "Retrieves the log entries for the specified OU setting. " .
282 "If the setting has a view permission, the results are limited " .
283 "to entries at the OUs that the user has the view permission. ",
285 {desc => 'Authentication token', type => 'string'},
286 {desc => 'Setting name', type => 'string'}
288 return => {desc => 'List of fieldmapper objects of the log entries, Event on error'}
292 sub fetch_visible_ou_settings_log {
293 my( $self, $client, $auth, $setting ) = @_;
295 my $e = new_editor(authtoken => $auth);
296 return $e->event unless $e->checkauth;
297 return $e->die_event unless $e->allowed("STAFF_LOGIN");
298 return OpenILS::Event->new('BAD_PARAMS') unless defined($setting);
300 my $type = $e->retrieve_config_org_unit_setting_type([
302 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
304 return OpenILS::Event->new('BAD_PARAMS', note => 'setting type not found')
307 my $query = { field_name => $setting };
308 if ($type->view_perm) {
309 $query->{org} = $U->user_has_work_perm_at($e, $type->view_perm->code, {descendants => 1});
310 if (scalar @{ $query->{org} } == 0) {
311 # user doesn't have the view permission anywhere, so return nothing
316 my $results = $e->search_config_org_unit_setting_type_log([$query, {'order_by' => 'date_applied ASC'}])
317 or return $e->die_event;
321 __PACKAGE__->register_method(
322 method => "user_settings",
324 api_name => "open-ils.actor.patron.settings.retrieve",
327 my( $self, $client, $auth, $user_id, $setting ) = @_;
329 my $e = new_editor(authtoken => $auth);
330 return $e->event unless $e->checkauth;
331 $user_id = $e->requestor->id unless defined $user_id;
333 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
334 if($e->requestor->id != $user_id) {
335 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
339 my($e, $user_id, $setting) = @_;
340 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
341 return undef unless $val; # XXX this should really return undef, but needs testing
342 return OpenSRF::Utils::JSON->JSON2perl($val->value);
346 if(ref $setting eq 'ARRAY') {
348 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
351 return get_setting($e, $user_id, $setting);
354 my $s = $e->search_actor_user_setting({usr => $user_id});
355 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
360 __PACKAGE__->register_method(
361 method => "ranged_ou_settings",
362 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
364 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
365 "is implied for retrieving OU settings by the authenticated users' permissions.",
367 {desc => 'Authentication token', type => 'string'},
368 {desc => 'Org unit ID', type => 'number'},
370 return => {desc => 'A hashref of "ranged" settings, event on error'}
373 sub ranged_ou_settings {
374 my( $self, $client, $auth, $org_id ) = @_;
376 my $e = new_editor(authtoken => $auth);
377 return $e->event unless $e->checkauth;
380 my $org_list = $U->get_org_ancestors($org_id);
381 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
382 $org_list = [ reverse @$org_list ];
384 # start at the context org and capture the setting value
385 # without clobbering settings we've already captured
386 for my $this_org_id (@$org_list) {
388 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
390 for my $set (@sets) {
391 my $type = $e->retrieve_config_org_unit_setting_type([
393 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
396 # If there is no relevant permission, the default assumption will
397 # be, "yes, the caller can have that value."
398 if ($type && $type->view_perm) {
399 next if not $e->allowed($type->view_perm->code, $org_id);
402 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
403 unless defined $ranged_settings{$set->name};
407 return \%ranged_settings;
412 __PACKAGE__->register_method(
413 api_name => 'open-ils.actor.ou_setting.ancestor_default',
414 method => 'ou_ancestor_setting',
416 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
417 'This method will make sure that the given user has permission to view that setting, if there is a ' .
418 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
419 'the user lacks the permisssion, undef will be returned.' ,
421 { desc => 'Org unit ID', type => 'number' },
422 { desc => 'setting name', type => 'string' },
423 { desc => 'authtoken (optional)', type => 'string' }
425 return => {desc => 'A value for the org unit setting, or undef'}
429 # ------------------------------------------------------------------
430 # Attempts to find the org setting value for a given org. if not
431 # found at the requested org, searches up the org tree until it
432 # finds a parent that has the requested setting.
433 # when found, returns { org => $id, value => $value }
434 # otherwise, returns NULL
435 # ------------------------------------------------------------------
436 sub ou_ancestor_setting {
437 my( $self, $client, $orgid, $name, $auth ) = @_;
438 # Make sure $auth is set to something if not given.
440 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
443 __PACKAGE__->register_method(
444 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
445 method => 'ou_ancestor_setting_batch',
447 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
448 'This method will make sure that the given user has permission to view that setting, if there is a ' .
449 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
450 'the user lacks the permisssion, undef will be returned.' ,
452 { desc => 'Org unit ID', type => 'number' },
453 { desc => 'setting name list', type => 'array' },
454 { desc => 'authtoken (optional)', type => 'string' }
456 return => {desc => 'A hash with name => value pairs for the org unit settings'}
459 sub ou_ancestor_setting_batch {
460 my( $self, $client, $orgid, $name_list, $auth ) = @_;
462 # splitting the list of settings to fetch values
463 # so that ones that *don't* require view_perm checks
464 # can be fetched in one fell swoop, which is
465 # significantly faster in cases where a large
466 # number of settings need to be fetched.
467 my %perm_check_required = ();
468 my @perm_check_not_required = ();
470 # Note that ->ou_ancestor_setting also can check
471 # to see if the setting has a view_perm, but testing
472 # suggests that the redundant checks do not significantly
473 # increase the time it takes to fetch the values of
474 # permission-controlled settings.
475 my $e = new_editor();
476 my $res = $e->search_config_org_unit_setting_type({
478 view_perm => { "!=" => undef },
480 %perm_check_required = map { $_->name() => 1 } @$res;
481 foreach my $setting (@$name_list) {
482 push @perm_check_not_required, $setting
483 unless exists($perm_check_required{$setting});
487 if (@perm_check_not_required) {
488 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
490 $values{$_} = $U->ou_ancestor_setting(
493 ) for keys(%perm_check_required);
499 __PACKAGE__->register_method(
500 method => "update_patron",
501 api_name => "open-ils.actor.patron.update",
504 Update an existing user, or create a new one. Related objects,
505 like cards, addresses, survey responses, and stat cats,
506 can be updated by attaching them to the user object in their
507 respective fields. For examples, the billing address object
508 may be inserted into the 'billing_address' field, etc. For each
509 attached object, indicate if the object should be created,
510 updated, or deleted using the built-in 'isnew', 'ischanged',
511 and 'isdeleted' fields on the object.
514 { desc => 'Authentication token', type => 'string' },
515 { desc => 'Patron data object', type => 'object' }
517 return => {desc => 'A fleshed user object, event on error'}
522 my( $self, $client, $auth, $patron ) = @_;
524 my $e = new_editor(xact => 1, authtoken => $auth);
525 return $e->event unless $e->checkauth;
527 $logger->info($patron->isnew ? "Creating new patron..." :
528 "Updating Patron: " . $patron->id);
530 my $evt = check_group_perm($e, $e->requestor, $patron);
533 # $new_patron is the patron in progress. $patron is the original patron
534 # passed in with the method. new_patron will change as the components
535 # of patron are added/updated.
539 # unflesh the real items on the patron
540 $patron->card( $patron->card->id ) if(ref($patron->card));
541 $patron->billing_address( $patron->billing_address->id )
542 if(ref($patron->billing_address));
543 $patron->mailing_address( $patron->mailing_address->id )
544 if(ref($patron->mailing_address));
546 # create/update the patron first so we can use his id
548 # $patron is the obj from the client (new data) and $new_patron is the
549 # patron object properly built for db insertion, so we need a third variable
550 # if we want to represent the old patron.
553 my $barred_hook = '';
556 if($patron->isnew()) {
557 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
559 if($U->is_true($patron->barred)) {
560 return $e->die_event unless
561 $e->allowed('BAR_PATRON', $patron->home_ou);
563 if(($patron->photo_url)) {
564 return $e->die_event unless
565 $e->allowed('UPDATE_USER_PHOTO_URL', $patron->home_ou);
568 $new_patron = $patron;
570 # Did auth checking above already.
571 $old_patron = $e->retrieve_actor_user($patron->id) or
572 return $e->die_event;
574 $renew_hook = 'au.renewed' if ($old_patron->expire_date ne $new_patron->expire_date);
576 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
577 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
578 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
580 $barred_hook = $U->is_true($new_patron->barred) ?
581 'au.barred' : 'au.unbarred';
584 if($old_patron->photo_url ne $new_patron->photo_url) {
585 my $perm = 'UPDATE_USER_PHOTO_URL';
586 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
589 # update the password by itself to avoid the password protection magic
590 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
591 modify_migrated_user_password($e, $patron->id, $patron->passwd);
592 $new_patron->passwd(''); # subsequent update will set
593 # actor.usr.passwd to MD5('')
597 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
600 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
603 ( $new_patron, $evt ) = _add_update_waiver_entries($e, $patron, $new_patron);
606 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
609 # re-update the patron if anything has happened to him during this process
610 if($new_patron->ischanged()) {
611 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
615 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
618 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
621 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
624 $evt = apply_invalid_addr_penalty($e, $patron);
629 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
631 $tses->request('open-ils.trigger.event.autocreate',
632 'au.created', $new_patron, $new_patron->home_ou);
634 $tses->request('open-ils.trigger.event.autocreate',
635 'au.updated', $new_patron, $new_patron->home_ou);
637 $tses->request('open-ils.trigger.event.autocreate', $renew_hook,
638 $new_patron, $new_patron->home_ou) if $renew_hook;
640 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
641 $new_patron, $new_patron->home_ou) if $barred_hook;
644 $e->xact_begin; # $e->rollback is called in new_flesh_user
645 return flesh_user($new_patron->id(), $e);
648 sub apply_invalid_addr_penalty {
652 # grab the invalid address penalty if set
653 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
655 my ($addr_penalty) = grep
656 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
658 # do we enforce invalid address penalty
659 my $enforce = $U->ou_ancestor_setting_value(
660 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
662 my $addrs = $e->search_actor_user_address(
663 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
664 my $addr_count = scalar(@$addrs);
666 if($addr_count == 0 and $addr_penalty) {
668 # regardless of any settings, remove the penalty when the user has no invalid addresses
669 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
672 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
674 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
675 my $depth = $ptype->org_depth;
676 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
677 $ctx_org = $patron->home_ou unless defined $ctx_org;
679 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
680 $penalty->usr($patron->id);
681 $penalty->org_unit($ctx_org);
682 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
684 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
699 "standing_penalties",
709 push @$fields, "home_ou" if $home_ou;
710 return new_flesh_user($id, $fields, $e );
718 # clone and clear stuff that would break the database
722 my $new_patron = $patron->clone;
724 $new_patron->clear_billing_address();
725 $new_patron->clear_mailing_address();
726 $new_patron->clear_addresses();
727 $new_patron->clear_card();
728 $new_patron->clear_cards();
729 $new_patron->clear_id();
730 $new_patron->clear_isnew();
731 $new_patron->clear_ischanged();
732 $new_patron->clear_isdeleted();
733 $new_patron->clear_stat_cat_entries();
734 $new_patron->clear_waiver_entries();
735 $new_patron->clear_permissions();
736 $new_patron->clear_standing_penalties();
747 return (undef, $e->die_event) unless
748 $e->allowed('CREATE_USER', $patron->home_ou);
750 my $ex = $e->search_actor_user(
751 {usrname => $patron->usrname}, {idlist => 1});
752 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
754 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
756 # do a dance to get the password hashed securely
757 my $saved_password = $patron->passwd;
759 $e->create_actor_user($patron) or return (undef, $e->die_event);
760 modify_migrated_user_password($e, $patron->id, $saved_password);
762 my $id = $patron->id; # added by CStoreEditor
764 $logger->info("Successfully created new user [$id] in DB");
765 return ($e->retrieve_actor_user($id), undef);
769 sub check_group_perm {
770 my( $e, $requestor, $patron ) = @_;
773 # first let's see if the requestor has
774 # priveleges to update this user in any way
775 if( ! $patron->isnew ) {
776 my $p = $e->retrieve_actor_user($patron->id);
778 # If we are the requestor (trying to update our own account)
779 # and we are not trying to change our profile, we're good
780 if( $p->id == $requestor->id and
781 $p->profile == $patron->profile ) {
786 $evt = group_perm_failed($e, $requestor, $p);
790 # They are allowed to edit this patron.. can they put the
791 # patron into the group requested?
792 $evt = group_perm_failed($e, $requestor, $patron);
798 sub group_perm_failed {
799 my( $e, $requestor, $patron ) = @_;
803 my $grpid = $patron->profile;
807 $logger->debug("user update looking for group perm for group $grpid");
808 $grp = $e->retrieve_permission_grp_tree($grpid);
810 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
812 $logger->info("user update checking perm $perm on user ".
813 $requestor->id." for update/create on user username=".$patron->usrname);
815 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
821 my( $e, $patron, $noperm) = @_;
823 $logger->info("Updating patron ".$patron->id." in DB");
828 return (undef, $e->die_event)
829 unless $e->allowed('UPDATE_USER', $patron->home_ou);
832 if(!$patron->ident_type) {
833 $patron->clear_ident_type;
834 $patron->clear_ident_value;
837 $evt = verify_last_xact($e, $patron);
838 return (undef, $evt) if $evt;
840 $e->update_actor_user($patron) or return (undef, $e->die_event);
842 # re-fetch the user to pick up the latest last_xact_id value
843 # to avoid collisions.
844 $patron = $e->retrieve_actor_user($patron->id);
849 sub verify_last_xact {
850 my( $e, $patron ) = @_;
851 return undef unless $patron->id and $patron->id > 0;
852 my $p = $e->retrieve_actor_user($patron->id);
853 my $xact = $p->last_xact_id;
854 return undef unless $xact;
855 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
856 return OpenILS::Event->new('XACT_COLLISION')
857 if $xact ne $patron->last_xact_id;
862 sub _check_dup_ident {
863 my( $session, $patron ) = @_;
865 return undef unless $patron->ident_value;
868 ident_type => $patron->ident_type,
869 ident_value => $patron->ident_value,
872 $logger->debug("patron update searching for dup ident values: " .
873 $patron->ident_type . ':' . $patron->ident_value);
875 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
877 my $dups = $session->request(
878 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
881 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
888 sub _add_update_addresses {
892 my $new_patron = shift;
896 my $current_id; # id of the address before creation
898 my $addresses = $patron->addresses();
900 for my $address (@$addresses) {
902 next unless ref $address;
903 $current_id = $address->id();
905 if( $patron->billing_address() and
906 $patron->billing_address() == $current_id ) {
907 $logger->info("setting billing addr to $current_id");
908 $new_patron->billing_address($address->id());
909 $new_patron->ischanged(1);
912 if( $patron->mailing_address() and
913 $patron->mailing_address() == $current_id ) {
914 $new_patron->mailing_address($address->id());
915 $logger->info("setting mailing addr to $current_id");
916 $new_patron->ischanged(1);
920 if($address->isnew()) {
922 $address->usr($new_patron->id());
924 ($address, $evt) = _add_address($e,$address);
925 return (undef, $evt) if $evt;
927 # we need to get the new id
928 if( $patron->billing_address() and
929 $patron->billing_address() == $current_id ) {
930 $new_patron->billing_address($address->id());
931 $logger->info("setting billing addr to $current_id");
932 $new_patron->ischanged(1);
935 if( $patron->mailing_address() and
936 $patron->mailing_address() == $current_id ) {
937 $new_patron->mailing_address($address->id());
938 $logger->info("setting mailing addr to $current_id");
939 $new_patron->ischanged(1);
942 } elsif($address->ischanged() ) {
944 ($address, $evt) = _update_address($e, $address);
945 return (undef, $evt) if $evt;
947 } elsif($address->isdeleted() ) {
949 if( $address->id() == $new_patron->mailing_address() ) {
950 $new_patron->clear_mailing_address();
951 ($new_patron, $evt) = _update_patron($e, $new_patron);
952 return (undef, $evt) if $evt;
955 if( $address->id() == $new_patron->billing_address() ) {
956 $new_patron->clear_billing_address();
957 ($new_patron, $evt) = _update_patron($e, $new_patron);
958 return (undef, $evt) if $evt;
961 $evt = _delete_address($e, $address);
962 return (undef, $evt) if $evt;
966 return ( $new_patron, undef );
970 # adds an address to the db and returns the address with new id
972 my($e, $address) = @_;
973 $address->clear_id();
975 $logger->info("Creating new address at street ".$address->street1);
977 # put the address into the database
978 $e->create_actor_user_address($address) or return (undef, $e->die_event);
979 return ($address, undef);
983 sub _update_address {
984 my( $e, $address ) = @_;
986 $logger->info("Updating address ".$address->id." in the DB");
988 $e->update_actor_user_address($address) or return (undef, $e->die_event);
990 return ($address, undef);
995 sub _add_update_cards {
999 my $new_patron = shift;
1003 my $virtual_id; #id of the card before creation
1005 my $card_changed = 0;
1006 my $cards = $patron->cards();
1007 for my $card (@$cards) {
1009 $card->usr($new_patron->id());
1011 if(ref($card) and $card->isnew()) {
1013 $virtual_id = $card->id();
1014 ( $card, $evt ) = _add_card($e, $card);
1015 return (undef, $evt) if $evt;
1017 #if(ref($patron->card)) { $patron->card($patron->card->id); }
1018 if($patron->card() == $virtual_id) {
1019 $new_patron->card($card->id());
1020 $new_patron->ischanged(1);
1024 } elsif( ref($card) and $card->ischanged() ) {
1025 $evt = _update_card($e, $card);
1026 return (undef, $evt) if $evt;
1031 $U->create_events_for_hook('au.barcode_changed', $new_patron, $e->requestor->ws_ou)
1034 return ( $new_patron, undef );
1038 # adds an card to the db and returns the card with new id
1040 my( $e, $card ) = @_;
1043 $logger->info("Adding new patron card ".$card->barcode);
1045 $e->create_actor_card($card) or return (undef, $e->die_event);
1047 return ( $card, undef );
1051 # returns event on error. returns undef otherwise
1053 my( $e, $card ) = @_;
1054 $logger->info("Updating patron card ".$card->id);
1056 $e->update_actor_card($card) or return $e->die_event;
1061 sub _add_update_waiver_entries {
1064 my $new_patron = shift;
1067 my $waiver_entries = $patron->waiver_entries();
1068 for my $waiver (@$waiver_entries) {
1069 next unless ref $waiver;
1070 $waiver->usr($new_patron->id());
1071 if ($waiver->isnew()) {
1072 next if (!$waiver->name() or $waiver->name() =~ /^\s*$/);
1073 next if (!$waiver->place_holds() && !$waiver->pickup_holds() && !$waiver->checkout_items() && !$waiver->view_history());
1074 $logger->info("Adding new patron waiver entry");
1075 $waiver->clear_id();
1076 $e->create_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1077 } elsif ($waiver->ischanged()) {
1078 $logger->info("Updating patron waiver entry " . $waiver->id);
1079 $e->update_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1080 } elsif ($waiver->isdeleted()) {
1081 $logger->info("Deleting patron waiver entry " . $waiver->id);
1082 $e->delete_actor_usr_privacy_waiver($waiver) or return (undef, $e->die_event);
1085 return ($new_patron, undef);
1089 # returns event on error. returns undef otherwise
1090 sub _delete_address {
1091 my( $e, $address ) = @_;
1093 $logger->info("Deleting address ".$address->id." from DB");
1095 $e->delete_actor_user_address($address) or return $e->die_event;
1101 sub _add_survey_responses {
1102 my ($e, $patron, $new_patron) = @_;
1104 $logger->info( "Updating survey responses for patron ".$new_patron->id );
1106 my $responses = $patron->survey_responses;
1110 $_->usr($new_patron->id) for (@$responses);
1112 my $evt = $U->simplereq( "open-ils.circ",
1113 "open-ils.circ.survey.submit.user_id", $responses );
1115 return (undef, $evt) if defined($U->event_code($evt));
1119 return ( $new_patron, undef );
1122 sub _clear_badcontact_penalties {
1123 my ($e, $old_patron, $new_patron) = @_;
1125 return ($new_patron, undef) unless $old_patron;
1127 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
1129 # This ignores whether the caller of update_patron has any permission
1130 # to remove penalties, but these penalties no longer make sense
1131 # if an email address field (for example) is changed (and the caller must
1132 # have perms to do *that*) so there's no reason not to clear the penalties.
1134 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
1136 "+csp" => {"name" => [values(%$PNM)]},
1137 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
1139 "join" => {"csp" => {}},
1141 "flesh_fields" => {"ausp" => ["standing_penalty"]}
1143 ]) or return (undef, $e->die_event);
1145 return ($new_patron, undef) unless @$bad_contact_penalties;
1147 my @penalties_to_clear;
1148 my ($field, $penalty_name);
1150 # For each field that might have an associated bad contact penalty,
1151 # check for such penalties and add them to the to-clear list if that
1152 # field has changed.
1153 while (($field, $penalty_name) = each(%$PNM)) {
1154 if ($old_patron->$field ne $new_patron->$field) {
1155 push @penalties_to_clear, grep {
1156 $_->standing_penalty->name eq $penalty_name
1157 } @$bad_contact_penalties;
1161 foreach (@penalties_to_clear) {
1162 # Note that this "archives" penalties, in the terminology of the staff
1163 # client, instead of just deleting them. This may assist reporting,
1164 # or preserving old contact information when it is still potentially
1166 $_->standing_penalty($_->standing_penalty->id); # deflesh
1167 $_->stop_date('now');
1168 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1171 return ($new_patron, undef);
1175 sub _create_stat_maps {
1177 my($e, $patron, $new_patron) = @_;
1179 my $maps = $patron->stat_cat_entries();
1181 for my $map (@$maps) {
1183 my $method = "update_actor_stat_cat_entry_user_map";
1185 if ($map->isdeleted()) {
1186 $method = "delete_actor_stat_cat_entry_user_map";
1188 } elsif ($map->isnew()) {
1189 $method = "create_actor_stat_cat_entry_user_map";
1194 $map->target_usr($new_patron->id);
1196 $logger->info("Updating stat entry with method $method and map $map");
1198 $e->$method($map) or return (undef, $e->die_event);
1201 return ($new_patron, undef);
1204 sub _create_perm_maps {
1206 my($e, $patron, $new_patron) = @_;
1208 my $maps = $patron->permissions;
1210 for my $map (@$maps) {
1212 my $method = "update_permission_usr_perm_map";
1213 if ($map->isdeleted()) {
1214 $method = "delete_permission_usr_perm_map";
1215 } elsif ($map->isnew()) {
1216 $method = "create_permission_usr_perm_map";
1220 $map->usr($new_patron->id);
1222 $logger->info( "Updating permissions with method $method and map $map" );
1224 $e->$method($map) or return (undef, $e->die_event);
1227 return ($new_patron, undef);
1231 __PACKAGE__->register_method(
1232 method => "set_user_work_ous",
1233 api_name => "open-ils.actor.user.work_ous.update",
1236 sub set_user_work_ous {
1242 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1243 return $evt if $evt;
1245 my $session = $apputils->start_db_session();
1246 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1248 for my $map (@$maps) {
1250 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1251 if ($map->isdeleted()) {
1252 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1253 } elsif ($map->isnew()) {
1254 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1258 #warn( "Updating permissions with method $method and session $ses and map $map" );
1259 $logger->info( "Updating work_ou map with method $method and map $map" );
1261 my $stat = $session->request($method, $map)->gather(1);
1262 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1266 $apputils->commit_db_session($session);
1268 return scalar(@$maps);
1272 __PACKAGE__->register_method(
1273 method => "set_user_perms",
1274 api_name => "open-ils.actor.user.permissions.update",
1277 sub set_user_perms {
1283 my $session = $apputils->start_db_session();
1285 my( $user_obj, $evt ) = $U->checkses($ses);
1286 return $evt if $evt;
1287 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1289 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1292 $all = 1 if ($U->is_true($user_obj->super_user()));
1293 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1295 for my $map (@$maps) {
1297 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1298 if ($map->isdeleted()) {
1299 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1300 } elsif ($map->isnew()) {
1301 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1305 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1306 #warn( "Updating permissions with method $method and session $ses and map $map" );
1307 $logger->info( "Updating permissions with method $method and map $map" );
1309 my $stat = $session->request($method, $map)->gather(1);
1310 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1314 $apputils->commit_db_session($session);
1316 return scalar(@$maps);
1320 __PACKAGE__->register_method(
1321 method => "user_retrieve_by_barcode",
1323 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1325 sub user_retrieve_by_barcode {
1326 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1328 my $e = new_editor(authtoken => $auth);
1329 return $e->event unless $e->checkauth;
1331 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1332 or return $e->event;
1334 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1335 return $e->event unless $e->allowed(
1336 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1343 __PACKAGE__->register_method(
1344 method => "get_user_by_id",
1346 api_name => "open-ils.actor.user.retrieve",
1349 sub get_user_by_id {
1350 my ($self, $client, $auth, $id) = @_;
1351 my $e = new_editor(authtoken=>$auth);
1352 return $e->event unless $e->checkauth;
1353 my $user = $e->retrieve_actor_user($id) or return $e->event;
1354 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1359 __PACKAGE__->register_method(
1360 method => "get_org_types",
1361 api_name => "open-ils.actor.org_types.retrieve",
1364 return $U->get_org_types();
1368 __PACKAGE__->register_method(
1369 method => "get_user_ident_types",
1370 api_name => "open-ils.actor.user.ident_types.retrieve",
1373 sub get_user_ident_types {
1374 return $ident_types if $ident_types;
1375 return $ident_types =
1376 new_editor()->retrieve_all_config_identification_type();
1380 __PACKAGE__->register_method(
1381 method => "get_org_unit",
1382 api_name => "open-ils.actor.org_unit.retrieve",
1386 my( $self, $client, $user_session, $org_id ) = @_;
1387 my $e = new_editor(authtoken => $user_session);
1389 return $e->event unless $e->checkauth;
1390 $org_id = $e->requestor->ws_ou;
1392 my $o = $e->retrieve_actor_org_unit($org_id)
1393 or return $e->event;
1397 __PACKAGE__->register_method(
1398 method => "search_org_unit",
1399 api_name => "open-ils.actor.org_unit_list.search",
1402 sub search_org_unit {
1404 my( $self, $client, $field, $value ) = @_;
1406 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1408 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1409 { $field => $value } );
1415 # build the org tree
1417 __PACKAGE__->register_method(
1418 method => "get_org_tree",
1419 api_name => "open-ils.actor.org_tree.retrieve",
1421 note => "Returns the entire org tree structure",
1427 return $U->get_org_tree($client->session->session_locale);
1431 __PACKAGE__->register_method(
1432 method => "get_org_descendants",
1433 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1436 # depth is optional. org_unit is the id
1437 sub get_org_descendants {
1438 my( $self, $client, $org_unit, $depth ) = @_;
1440 if(ref $org_unit eq 'ARRAY') {
1443 for my $i (0..scalar(@$org_unit)-1) {
1444 my $list = $U->simple_scalar_request(
1446 "open-ils.storage.actor.org_unit.descendants.atomic",
1447 $org_unit->[$i], $depth->[$i] );
1448 push(@trees, $U->build_org_tree($list));
1453 my $orglist = $apputils->simple_scalar_request(
1455 "open-ils.storage.actor.org_unit.descendants.atomic",
1456 $org_unit, $depth );
1457 return $U->build_org_tree($orglist);
1462 __PACKAGE__->register_method(
1463 method => "get_org_ancestors",
1464 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1467 # depth is optional. org_unit is the id
1468 sub get_org_ancestors {
1469 my( $self, $client, $org_unit, $depth ) = @_;
1470 my $orglist = $apputils->simple_scalar_request(
1472 "open-ils.storage.actor.org_unit.ancestors.atomic",
1473 $org_unit, $depth );
1474 return $U->build_org_tree($orglist);
1478 __PACKAGE__->register_method(
1479 method => "get_standings",
1480 api_name => "open-ils.actor.standings.retrieve"
1485 return $user_standings if $user_standings;
1486 return $user_standings =
1487 $apputils->simple_scalar_request(
1489 "open-ils.cstore.direct.config.standing.search.atomic",
1490 { id => { "!=" => undef } }
1495 __PACKAGE__->register_method(
1496 method => "get_my_org_path",
1497 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1500 sub get_my_org_path {
1501 my( $self, $client, $auth, $org_id ) = @_;
1502 my $e = new_editor(authtoken=>$auth);
1503 return $e->event unless $e->checkauth;
1504 $org_id = $e->requestor->ws_ou unless defined $org_id;
1506 return $apputils->simple_scalar_request(
1508 "open-ils.storage.actor.org_unit.full_path.atomic",
1512 __PACKAGE__->register_method(
1513 method => "retrieve_coordinates",
1514 api_name => "open-ils.actor.geo.retrieve_coordinates",
1517 {desc => 'Authentication token', type => 'string' },
1518 {type => 'number', desc => 'Context Organizational Unit'},
1519 {type => 'string', desc => 'Address to look-up as a text string'}
1521 return => { desc => 'Hash/object containing latitude and longitude for the provided address.'}
1525 sub retrieve_coordinates {
1526 my( $self, $client, $auth, $org_id, $addr_string ) = @_;
1527 my $e = new_editor(authtoken=>$auth);
1528 return $e->event unless $e->checkauth;
1529 $org_id = $e->requestor->ws_ou unless defined $org_id;
1531 return $apputils->simple_scalar_request(
1533 "open-ils.geo.retrieve_coordinates",
1534 $org_id, $addr_string );
1537 __PACKAGE__->register_method(
1538 method => "get_my_org_ancestor_at_depth",
1539 api_name => "open-ils.actor.org_unit.ancestor_at_depth.retrieve"
1542 sub get_my_org_ancestor_at_depth {
1543 my( $self, $client, $auth, $org_id, $depth ) = @_;
1544 my $e = new_editor(authtoken=>$auth);
1545 return $e->event unless $e->checkauth;
1546 $org_id = $e->requestor->ws_ou unless defined $org_id;
1548 return $apputils->org_unit_ancestor_at_depth( $org_id, $depth );
1551 __PACKAGE__->register_method(
1552 method => "patron_adv_search",
1553 api_name => "open-ils.actor.patron.search.advanced"
1556 __PACKAGE__->register_method(
1557 method => "patron_adv_search",
1558 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1560 # Flush the response stream at most 5 patrons in for UI responsiveness.
1561 max_bundle_count => 5,
1563 desc => q/Returns a stream of fleshed user objects instead of
1564 a pile of identifiers/
1568 sub patron_adv_search {
1569 my( $self, $client, $auth, $search_hash, $search_limit,
1570 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1572 # API params sanity checks.
1573 # Exit early with empty result if no filter exists.
1574 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1575 my $fleshed = ($self->api_name =~ /fleshed/);
1576 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1578 for my $key (keys %$search_hash) {
1579 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1583 return ($fleshed ? undef : []) unless $search_ok;
1585 my $e = new_editor(authtoken=>$auth);
1586 return $e->event unless $e->checkauth;
1587 return $e->event unless $e->allowed('VIEW_USER');
1589 # depth boundary outside of which patrons must opt-in, default to 0
1590 my $opt_boundary = 0;
1591 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1593 if (not defined $search_ou) {
1594 my $depth = $U->ou_ancestor_setting_value(
1595 $e->requestor->ws_ou,
1596 'circ.patron_edit.duplicate_patron_check_depth'
1599 if (defined $depth) {
1600 $search_ou = $U->org_unit_ancestor_at_depth(
1601 $e->requestor->ws_ou, $depth
1606 my $ids = $U->storagereq(
1607 "open-ils.storage.actor.user.crazy_search", $search_hash,
1608 $search_limit, $search_sort, $include_inactive,
1609 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1611 return $ids unless $self->api_name =~ /fleshed/;
1613 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1619 # A migrated (main) password has the form:
1620 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1621 sub modify_migrated_user_password {
1622 my ($e, $user_id, $passwd) = @_;
1624 # new password gets a new salt
1625 my $new_salt = $e->json_query({
1626 from => ['actor.create_salt', 'main']})->[0];
1627 $new_salt = $new_salt->{'actor.create_salt'};
1634 md5_hex($new_salt . md5_hex($passwd)),
1642 __PACKAGE__->register_method(
1643 method => "update_passwd",
1644 api_name => "open-ils.actor.user.password.update",
1646 desc => "Update the operator's password",
1648 { desc => 'Authentication token', type => 'string' },
1649 { desc => 'New password', type => 'string' },
1650 { desc => 'Current password', type => 'string' }
1652 return => {desc => '1 on success, Event on error or incorrect current password'}
1656 __PACKAGE__->register_method(
1657 method => "update_passwd",
1658 api_name => "open-ils.actor.user.username.update",
1660 desc => "Update the operator's username",
1662 { desc => 'Authentication token', type => 'string' },
1663 { desc => 'New username', type => 'string' },
1664 { desc => 'Current password', type => 'string' }
1666 return => {desc => '1 on success, Event on error or incorrect current password'}
1670 __PACKAGE__->register_method(
1671 method => "update_passwd",
1672 api_name => "open-ils.actor.user.email.update",
1674 desc => "Update the operator's email address",
1676 { desc => 'Authentication token', type => 'string' },
1677 { desc => 'New email address', type => 'string' },
1678 { desc => 'Current password', type => 'string' }
1680 return => {desc => '1 on success, Event on error or incorrect current password'}
1684 __PACKAGE__->register_method(
1685 method => "update_passwd",
1686 api_name => "open-ils.actor.user.locale.update",
1688 desc => "Update the operator's i18n locale",
1690 { desc => 'Authentication token', type => 'string' },
1691 { desc => 'New locale', type => 'string' },
1692 { desc => 'Current password', type => 'string' }
1694 return => {desc => '1 on success, Event on error or incorrect current password'}
1699 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1700 my $e = new_editor(xact=>1, authtoken=>$auth);
1701 return $e->die_event unless $e->checkauth;
1703 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1704 or return $e->die_event;
1705 my $api = $self->api_name;
1707 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1709 return new OpenILS::Event('INCORRECT_PASSWORD');
1713 if( $api =~ /password/o ) {
1714 # NOTE: with access to the plain text password we could crypt
1715 # the password without the extra MD5 pre-hashing. Other changes
1716 # would be required. Noting here for future reference.
1717 modify_migrated_user_password($e, $db_user->id, $new_val);
1718 $db_user->passwd('');
1722 # if we don't clear the password, the user will be updated with
1723 # a hashed version of the hashed version of their password
1724 $db_user->clear_passwd;
1726 if( $api =~ /username/o ) {
1728 # make sure no one else has this username
1729 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1732 return new OpenILS::Event('USERNAME_EXISTS');
1734 $db_user->usrname($new_val);
1737 } elsif( $api =~ /email/o ) {
1738 $db_user->email($new_val);
1741 } elsif( $api =~ /locale/o ) {
1742 $db_user->locale($new_val);
1747 $e->update_actor_user($db_user) or return $e->die_event;
1750 $U->create_events_for_hook('au.updated', $db_user, $e->requestor->ws_ou)
1753 # update the cached user to pick up these changes
1754 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1760 __PACKAGE__->register_method(
1761 method => "check_user_perms",
1762 api_name => "open-ils.actor.user.perm.check",
1763 notes => <<" NOTES");
1764 Takes a login session, user id, an org id, and an array of perm type strings. For each
1765 perm type, if the user does *not* have the given permission it is added
1766 to a list which is returned from the method. If all permissions
1767 are allowed, an empty list is returned
1768 if the logged in user does not match 'user_id', then the logged in user must
1769 have VIEW_PERMISSION priveleges.
1772 sub check_user_perms {
1773 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1775 my( $staff, $evt ) = $apputils->checkses($login_session);
1776 return $evt if $evt;
1778 if($staff->id ne $user_id) {
1779 if( $evt = $apputils->check_perms(
1780 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1786 for my $perm (@$perm_types) {
1787 if($apputils->check_perms($user_id, $org_id, $perm)) {
1788 push @not_allowed, $perm;
1792 return \@not_allowed
1795 __PACKAGE__->register_method(
1796 method => "check_user_perms2",
1797 api_name => "open-ils.actor.user.perm.check.multi_org",
1799 Checks the permissions on a list of perms and orgs for a user
1800 @param authtoken The login session key
1801 @param user_id The id of the user to check
1802 @param orgs The array of org ids
1803 @param perms The array of permission names
1804 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1805 if the logged in user does not match 'user_id', then the logged in user must
1806 have VIEW_PERMISSION priveleges.
1809 sub check_user_perms2 {
1810 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1812 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1813 $authtoken, $user_id, 'VIEW_PERMISSION' );
1814 return $evt if $evt;
1817 for my $org (@$orgs) {
1818 for my $perm (@$perms) {
1819 if($apputils->check_perms($user_id, $org, $perm)) {
1820 push @not_allowed, [ $org, $perm ];
1825 return \@not_allowed
1829 __PACKAGE__->register_method(
1830 method => 'check_user_perms3',
1831 api_name => 'open-ils.actor.user.perm.highest_org',
1833 Returns the highest org unit id at which a user has a given permission
1834 If the requestor does not match the target user, the requestor must have
1835 'VIEW_PERMISSION' rights at the home org unit of the target user
1836 @param authtoken The login session key
1837 @param userid The id of the user in question
1838 @param perm The permission to check
1839 @return The org unit highest in the org tree within which the user has
1840 the requested permission
1843 sub check_user_perms3 {
1844 my($self, $client, $authtoken, $user_id, $perm) = @_;
1845 my $e = new_editor(authtoken=>$authtoken);
1846 return $e->event unless $e->checkauth;
1848 my $tree = $U->get_org_tree();
1850 unless($e->requestor->id == $user_id) {
1851 my $user = $e->retrieve_actor_user($user_id)
1852 or return $e->event;
1853 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1854 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1857 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1860 __PACKAGE__->register_method(
1861 method => 'user_has_work_perm_at',
1862 api_name => 'open-ils.actor.user.has_work_perm_at',
1866 Returns a set of org unit IDs which represent the highest orgs in
1867 the org tree where the user has the requested permission. The
1868 purpose of this method is to return the smallest set of org units
1869 which represent the full expanse of the user's ability to perform
1870 the requested action. The user whose perms this method should
1871 check is implied by the authtoken. /,
1873 {desc => 'authtoken', type => 'string'},
1874 {desc => 'permission name', type => 'string'},
1875 {desc => q/user id, optional. If present, check perms for
1876 this user instead of the logged in user/, type => 'number'},
1878 return => {desc => 'An array of org IDs'}
1882 sub user_has_work_perm_at {
1883 my($self, $conn, $auth, $perm, $user_id) = @_;
1884 my $e = new_editor(authtoken=>$auth);
1885 return $e->event unless $e->checkauth;
1886 if(defined $user_id) {
1887 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1888 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1890 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1893 __PACKAGE__->register_method(
1894 method => 'user_has_work_perm_at_batch',
1895 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1899 sub user_has_work_perm_at_batch {
1900 my($self, $conn, $auth, $perms, $user_id) = @_;
1901 my $e = new_editor(authtoken=>$auth);
1902 return $e->event unless $e->checkauth;
1903 if(defined $user_id) {
1904 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1905 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1908 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1914 __PACKAGE__->register_method(
1915 method => 'check_user_perms4',
1916 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1918 Returns the highest org unit id at which a user has a given permission
1919 If the requestor does not match the target user, the requestor must have
1920 'VIEW_PERMISSION' rights at the home org unit of the target user
1921 @param authtoken The login session key
1922 @param userid The id of the user in question
1923 @param perms An array of perm names to check
1924 @return An array of orgId's representing the org unit
1925 highest in the org tree within which the user has the requested permission
1926 The arrah of orgId's has matches the order of the perms array
1929 sub check_user_perms4 {
1930 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1932 my( $staff, $target, $org, $evt );
1934 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1935 $authtoken, $userid, 'VIEW_PERMISSION' );
1936 return $evt if $evt;
1939 return [] unless ref($perms);
1940 my $tree = $U->get_org_tree();
1942 for my $p (@$perms) {
1943 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1949 __PACKAGE__->register_method(
1950 method => "user_fines_summary",
1951 api_name => "open-ils.actor.user.fines.summary",
1954 desc => 'Returns a short summary of the users total open fines, ' .
1955 'excluding voided fines Params are login_session, user_id' ,
1957 {desc => 'Authentication token', type => 'string'},
1958 {desc => 'User ID', type => 'string'} # number?
1961 desc => "a 'mous' object, event on error",
1966 sub user_fines_summary {
1967 my( $self, $client, $auth, $user_id ) = @_;
1969 my $e = new_editor(authtoken=>$auth);
1970 return $e->event unless $e->checkauth;
1972 if( $user_id ne $e->requestor->id ) {
1973 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1974 return $e->event unless
1975 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1978 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1982 __PACKAGE__->register_method(
1983 method => "user_opac_vitals",
1984 api_name => "open-ils.actor.user.opac.vital_stats",
1988 desc => 'Returns a short summary of the users vital stats, including ' .
1989 'identification information, accumulated balance, number of holds, ' .
1990 'and current open circulation stats' ,
1992 {desc => 'Authentication token', type => 'string'},
1993 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1996 desc => "An object with four properties: user, fines, checkouts and holds."
2001 sub user_opac_vitals {
2002 my( $self, $client, $auth, $user_id ) = @_;
2004 my $e = new_editor(authtoken=>$auth);
2005 return $e->event unless $e->checkauth;
2007 $user_id ||= $e->requestor->id;
2009 my $user = $e->retrieve_actor_user( $user_id );
2012 ->method_lookup('open-ils.actor.user.fines.summary')
2013 ->run($auth => $user_id);
2014 return $fines if (defined($U->event_code($fines)));
2017 $fines = new Fieldmapper::money::open_user_summary ();
2018 $fines->balance_owed(0.00);
2019 $fines->total_owed(0.00);
2020 $fines->total_paid(0.00);
2021 $fines->usr($user_id);
2025 ->method_lookup('open-ils.actor.user.hold_requests.count')
2026 ->run($auth => $user_id);
2027 return $holds if (defined($U->event_code($holds)));
2030 ->method_lookup('open-ils.actor.user.checked_out.count')
2031 ->run($auth => $user_id);
2032 return $out if (defined($U->event_code($out)));
2034 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
2036 my $unread_msgs = $e->search_actor_usr_message([
2037 {usr => $user_id, read_date => undef, deleted => 'f',
2038 'pub' => 't', # this is for the unread message count in the opac
2039 #'-or' => [ # Hiding Archived messages are for staff UI, not this
2040 # {stop_date => undef},
2041 # {stop_date => {'>' => 'now'}}
2049 first_given_name => $user->first_given_name,
2050 second_given_name => $user->second_given_name,
2051 family_name => $user->family_name,
2052 alias => $user->alias,
2053 usrname => $user->usrname
2055 fines => $fines->to_bare_hash,
2058 messages => { unread => scalar(@$unread_msgs) }
2063 ##### a small consolidation of related method registrations
2064 my $common_params = [
2065 { desc => 'Authentication token', type => 'string' },
2066 { desc => 'User ID', type => 'string' },
2067 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
2068 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
2071 'open-ils.actor.user.transactions' => '',
2072 'open-ils.actor.user.transactions.fleshed' => '',
2073 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
2074 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
2075 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
2076 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
2079 foreach (keys %methods) {
2081 method => "user_transactions",
2084 desc => 'For a given user, retrieve a list of '
2085 . (/\.fleshed/ ? 'fleshed ' : '')
2086 . 'transactions' . $methods{$_}
2087 . ' optionally limited to transactions of a given type.',
2088 params => $common_params,
2090 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
2091 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
2095 $args{authoritative} = 1;
2096 __PACKAGE__->register_method(%args);
2099 # Now for the counts
2101 'open-ils.actor.user.transactions.count' => '',
2102 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
2103 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
2106 foreach (keys %methods) {
2108 method => "user_transactions",
2111 desc => 'For a given user, retrieve a count of open '
2112 . 'transactions' . $methods{$_}
2113 . ' optionally limited to transactions of a given type.',
2114 params => $common_params,
2115 return => { desc => "Integer count of transactions, or event on error" }
2118 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
2119 __PACKAGE__->register_method(%args);
2122 __PACKAGE__->register_method(
2123 method => "user_transactions",
2124 api_name => "open-ils.actor.user.transactions.have_balance.total",
2127 desc => 'For a given user, retrieve the total balance owed for open transactions,'
2128 . ' optionally limited to transactions of a given type.',
2129 params => $common_params,
2130 return => { desc => "Decimal balance value, or event on error" }
2135 sub user_transactions {
2136 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
2139 my $e = new_editor(authtoken => $auth);
2140 return $e->event unless $e->checkauth;
2142 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2144 return $e->event unless
2145 $e->requestor->id == $user_id or
2146 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
2148 my $api = $self->api_name();
2150 my $filter = ($api =~ /have_balance/o) ?
2151 { 'balance_owed' => { '<>' => 0 } }:
2152 { 'total_owed' => { '>' => 0 } };
2154 my $method = 'open-ils.actor.user.transactions.history.still_open';
2155 $method = "$method.authoritative" if $api =~ /authoritative/;
2156 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
2158 if($api =~ /total/o) {
2160 $total += $_->balance_owed for @$trans;
2164 ($api =~ /count/o ) and return scalar @$trans;
2165 ($api !~ /fleshed/o) and return $trans;
2168 for my $t (@$trans) {
2170 if( $t->xact_type ne 'circulation' ) {
2171 push @resp, {transaction => $t};
2175 my $circ_data = flesh_circ($e, $t->id);
2176 push @resp, {transaction => $t, %$circ_data};
2183 __PACKAGE__->register_method(
2184 method => "user_transaction_retrieve",
2185 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
2188 notes => "Returns a fleshed transaction record"
2191 __PACKAGE__->register_method(
2192 method => "user_transaction_retrieve",
2193 api_name => "open-ils.actor.user.transaction.retrieve",
2196 notes => "Returns a transaction record"
2199 sub user_transaction_retrieve {
2200 my($self, $client, $auth, $bill_id) = @_;
2202 my $e = new_editor(authtoken => $auth);
2203 return $e->event unless $e->checkauth;
2205 my $trans = $e->retrieve_money_billable_transaction_summary(
2206 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
2208 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
2210 $trans->usr($trans->usr->id); # de-flesh for backwards compat
2212 return $trans unless $self->api_name =~ /flesh/;
2213 return {transaction => $trans} if $trans->xact_type ne 'circulation';
2215 my $circ_data = flesh_circ($e, $trans->id, 1);
2217 return {transaction => $trans, %$circ_data};
2222 my $circ_id = shift;
2223 my $flesh_copy = shift;
2225 my $circ = $e->retrieve_action_circulation([
2229 circ => ['target_copy'],
2230 acp => ['call_number'],
2237 my $copy = $circ->target_copy;
2239 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
2240 $mods = new Fieldmapper::metabib::virtual_record;
2241 $mods->doc_id(OILS_PRECAT_RECORD);
2242 $mods->title($copy->dummy_title);
2243 $mods->author($copy->dummy_author);
2246 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2250 $circ->target_copy($circ->target_copy->id);
2251 $copy->call_number($copy->call_number->id);
2253 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2257 __PACKAGE__->register_method(
2258 method => "hold_request_count",
2259 api_name => "open-ils.actor.user.hold_requests.count",
2263 Returns hold ready vs. total counts.
2264 If a context org unit is provided, a third value
2265 is returned with key 'behind_desk', which reports
2266 how many holds are ready at the pickup library
2267 with the behind_desk flag set to true.
2271 sub hold_request_count {
2272 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2273 my $e = new_editor(authtoken => $authtoken);
2274 return $e->event unless $e->checkauth;
2276 $user_id = $e->requestor->id unless defined $user_id;
2278 if($e->requestor->id ne $user_id) {
2279 my $user = $e->retrieve_actor_user($user_id);
2280 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2283 my $holds = $e->json_query({
2284 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2288 fulfillment_time => {"=" => undef },
2289 cancel_time => undef,
2294 $_->{current_shelf_lib} and # avoid undef warnings
2295 $_->{pickup_lib} eq $_->{current_shelf_lib}
2299 total => scalar(@$holds),
2300 ready => int(scalar(@ready))
2304 # count of holds ready at pickup lib with behind_desk true.
2305 $resp->{behind_desk} = int(scalar(
2307 $_->{pickup_lib} == $ctx_org and
2308 $U->is_true($_->{behind_desk})
2316 __PACKAGE__->register_method(
2317 method => "checked_out",
2318 api_name => "open-ils.actor.user.checked_out",
2322 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2323 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2324 . "(i.e., outstanding balance or some other pending action on the circ). "
2325 . "The .count method also includes a 'total' field which sums all open circs.",
2327 { desc => 'Authentication Token', type => 'string'},
2328 { desc => 'User ID', type => 'string'},
2331 desc => 'Returns event on error, or an object with ID lists, like: '
2332 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2337 __PACKAGE__->register_method(
2338 method => "checked_out",
2339 api_name => "open-ils.actor.user.checked_out.count",
2342 signature => q/@see open-ils.actor.user.checked_out/
2346 my( $self, $conn, $auth, $userid ) = @_;
2348 my $e = new_editor(authtoken=>$auth);
2349 return $e->event unless $e->checkauth;
2351 if( $userid ne $e->requestor->id ) {
2352 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2353 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2355 # see if there is a friend link allowing circ.view perms
2356 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2357 $e, $userid, $e->requestor->id, 'circ.view');
2358 return $e->event unless $allowed;
2362 my $count = $self->api_name =~ /count/;
2363 return _checked_out( $count, $e, $userid );
2367 my( $iscount, $e, $userid ) = @_;
2373 claims_returned => [],
2376 my $meth = 'retrieve_action_open_circ_';
2384 claims_returned => 0,
2391 my $data = $e->$meth($userid);
2395 $result{$_} += $data->$_() for (keys %result);
2396 $result{total} += $data->$_() for (keys %result);
2398 for my $k (keys %result) {
2399 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2409 __PACKAGE__->register_method(
2410 method => "checked_in_with_fines",
2411 api_name => "open-ils.actor.user.checked_in_with_fines",
2414 signature => q/@see open-ils.actor.user.checked_out/
2417 sub checked_in_with_fines {
2418 my( $self, $conn, $auth, $userid ) = @_;
2420 my $e = new_editor(authtoken=>$auth);
2421 return $e->event unless $e->checkauth;
2423 if( $userid ne $e->requestor->id ) {
2424 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2427 # money is owed on these items and they are checked in
2428 my $open = $e->search_action_circulation(
2431 xact_finish => undef,
2432 checkin_time => { "!=" => undef },
2437 my( @lost, @cr, @lo );
2438 for my $c (@$open) {
2439 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2440 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2441 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2446 claims_returned => \@cr,
2447 long_overdue => \@lo
2453 my ($api, $desc, $auth) = @_;
2454 $desc = $desc ? (" " . $desc) : '';
2455 my $ids = ($api =~ /ids$/) ? 1 : 0;
2458 method => "user_transaction_history",
2459 api_name => "open-ils.actor.user.transactions.$api",
2461 desc => "For a given User ID, returns a list of billable transaction" .
2462 ($ids ? " id" : '') .
2463 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2464 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2466 {desc => 'Authentication token', type => 'string'},
2467 {desc => 'User ID', type => 'number'},
2468 {desc => 'Transaction type (optional)', type => 'number'},
2469 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2472 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2476 $auth and push @sig, (authoritative => 1);
2480 my %auth_hist_methods = (
2482 'history.have_charge' => 'that have an initial charge',
2483 'history.still_open' => 'that are not finished',
2484 'history.have_balance' => 'that have a balance',
2485 'history.have_bill' => 'that have billings',
2486 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2487 'history.have_payment' => 'that have at least 1 payment',
2490 foreach (keys %auth_hist_methods) {
2491 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2492 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2493 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2496 sub user_transaction_history {
2497 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2501 my $e = new_editor(authtoken=>$auth);
2502 return $e->die_event unless $e->checkauth;
2504 if ($e->requestor->id ne $userid) {
2505 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2508 my $api = $self->api_name;
2509 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2511 if(defined($type)) {
2512 $filter->{'xact_type'} = $type;
2515 if($api =~ /have_bill_or_payment/o) {
2517 # transactions that have a non-zero sum across all billings or at least 1 payment
2518 $filter->{'-or'} = {
2519 'balance_owed' => { '<>' => 0 },
2520 'last_payment_ts' => { '<>' => undef }
2523 } elsif($api =~ /have_payment/) {
2525 $filter->{last_payment_ts} ||= {'<>' => undef};
2527 } elsif( $api =~ /have_balance/o) {
2529 # transactions that have a non-zero overall balance
2530 $filter->{'balance_owed'} = { '<>' => 0 };
2532 } elsif( $api =~ /have_charge/o) {
2534 # transactions that have at least 1 billing, regardless of whether it was voided
2535 $filter->{'last_billing_ts'} = { '<>' => undef };
2537 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2539 # transactions that have non-zero sum across all billings. This will exclude
2540 # xacts where all billings have been voided
2541 $filter->{'total_owed'} = { '<>' => 0 };
2544 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2545 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2546 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2548 my $mbts = $e->search_money_billable_transaction_summary(
2549 [ { usr => $userid, @xact_finish, %$filter },
2554 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2555 return $mbts unless $api =~ /fleshed/;
2558 for my $t (@$mbts) {
2560 if( $t->xact_type ne 'circulation' ) {
2561 push @resp, {transaction => $t};
2565 my $circ_data = flesh_circ($e, $t->id);
2566 push @resp, {transaction => $t, %$circ_data};
2574 __PACKAGE__->register_method(
2575 method => "user_perms",
2576 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2578 notes => "Returns a list of permissions"
2582 my( $self, $client, $authtoken, $user ) = @_;
2584 my( $staff, $evt ) = $apputils->checkses($authtoken);
2585 return $evt if $evt;
2587 $user ||= $staff->id;
2589 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2593 return $apputils->simple_scalar_request(
2595 "open-ils.storage.permission.user_perms.atomic",
2599 __PACKAGE__->register_method(
2600 method => "retrieve_perms",
2601 api_name => "open-ils.actor.permissions.retrieve",
2602 notes => "Returns a list of permissions"
2604 sub retrieve_perms {
2605 my( $self, $client ) = @_;
2606 return $apputils->simple_scalar_request(
2608 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2609 { id => { '!=' => undef } }
2613 __PACKAGE__->register_method(
2614 method => "retrieve_groups",
2615 api_name => "open-ils.actor.groups.retrieve",
2616 notes => "Returns a list of user groups"
2618 sub retrieve_groups {
2619 my( $self, $client ) = @_;
2620 return new_editor()->retrieve_all_permission_grp_tree();
2623 __PACKAGE__->register_method(
2624 method => "retrieve_org_address",
2625 api_name => "open-ils.actor.org_unit.address.retrieve",
2626 notes => <<' NOTES');
2627 Returns an org_unit address by ID
2628 @param An org_address ID
2630 sub retrieve_org_address {
2631 my( $self, $client, $id ) = @_;
2632 return $apputils->simple_scalar_request(
2634 "open-ils.cstore.direct.actor.org_address.retrieve",
2639 __PACKAGE__->register_method(
2640 method => "retrieve_groups_tree",
2641 api_name => "open-ils.actor.groups.tree.retrieve",
2642 notes => "Returns a list of user groups"
2645 sub retrieve_groups_tree {
2646 my( $self, $client ) = @_;
2647 return new_editor()->search_permission_grp_tree(
2652 flesh_fields => { pgt => ["children"] },
2653 order_by => { pgt => 'name'}
2660 __PACKAGE__->register_method(
2661 method => "add_user_to_groups",
2662 api_name => "open-ils.actor.user.set_groups",
2663 notes => "Adds a user to one or more permission groups"
2666 sub add_user_to_groups {
2667 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2669 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2670 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2671 return $evt if $evt;
2673 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2674 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2675 return $evt if $evt;
2677 $apputils->simplereq(
2679 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2681 for my $group (@$groups) {
2682 my $link = Fieldmapper::permission::usr_grp_map->new;
2684 $link->usr($userid);
2686 my $id = $apputils->simplereq(
2688 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2694 __PACKAGE__->register_method(
2695 method => "get_user_perm_groups",
2696 api_name => "open-ils.actor.user.get_groups",
2697 notes => "Retrieve a user's permission groups."
2701 sub get_user_perm_groups {
2702 my( $self, $client, $authtoken, $userid ) = @_;
2704 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2705 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2706 return $evt if $evt;
2708 return $apputils->simplereq(
2710 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2714 __PACKAGE__->register_method(
2715 method => "get_user_work_ous",
2716 api_name => "open-ils.actor.user.get_work_ous",
2717 notes => "Retrieve a user's work org units."
2720 __PACKAGE__->register_method(
2721 method => "get_user_work_ous",
2722 api_name => "open-ils.actor.user.get_work_ous.ids",
2723 notes => "Retrieve a user's work org units."
2726 sub get_user_work_ous {
2727 my( $self, $client, $auth, $userid ) = @_;
2728 my $e = new_editor(authtoken=>$auth);
2729 return $e->event unless $e->checkauth;
2730 $userid ||= $e->requestor->id;
2732 if($e->requestor->id != $userid) {
2733 my $user = $e->retrieve_actor_user($userid)
2734 or return $e->event;
2735 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2738 return $e->search_permission_usr_work_ou_map({usr => $userid})
2739 unless $self->api_name =~ /.ids$/;
2741 # client just wants a list of org IDs
2742 return $U->get_user_work_ou_ids($e, $userid);
2747 __PACKAGE__->register_method(
2748 method => 'register_workstation',
2749 api_name => 'open-ils.actor.workstation.register.override',
2750 signature => q/@see open-ils.actor.workstation.register/
2753 __PACKAGE__->register_method(
2754 method => 'register_workstation',
2755 api_name => 'open-ils.actor.workstation.register',
2757 Registers a new workstion in the system
2758 @param authtoken The login session key
2759 @param name The name of the workstation id
2760 @param owner The org unit that owns this workstation
2761 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2762 if the name is already in use.
2766 sub register_workstation {
2767 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2769 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2770 return $e->die_event unless $e->checkauth;
2771 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2772 my $existing = $e->search_actor_workstation({name => $name})->[0];
2773 $oargs = { all => 1 } unless defined $oargs;
2777 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2778 # workstation with the given name exists.
2780 if($owner ne $existing->owning_lib) {
2781 # if necessary, update the owning_lib of the workstation
2783 $logger->info("changing owning lib of workstation ".$existing->id.
2784 " from ".$existing->owning_lib." to $owner");
2785 return $e->die_event unless
2786 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2788 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2790 $existing->owning_lib($owner);
2791 return $e->die_event unless $e->update_actor_workstation($existing);
2797 "attempt to register an existing workstation. returning existing ID");
2800 return $existing->id;
2803 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2807 my $ws = Fieldmapper::actor::workstation->new;
2808 $ws->owning_lib($owner);
2810 $e->create_actor_workstation($ws) or return $e->die_event;
2812 return $ws->id; # note: editor sets the id on the new object for us
2815 __PACKAGE__->register_method(
2816 method => 'workstation_list',
2817 api_name => 'open-ils.actor.workstation.list',
2819 Returns a list of workstations registered at the given location
2820 @param authtoken The login session key
2821 @param ids A list of org_unit.id's for the workstation owners
2825 sub workstation_list {
2826 my( $self, $conn, $authtoken, @orgs ) = @_;
2828 my $e = new_editor(authtoken=>$authtoken);
2829 return $e->event unless $e->checkauth;
2834 unless $e->allowed('REGISTER_WORKSTATION', $o);
2835 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2840 __PACKAGE__->register_method(
2841 method => 'fetch_patron_messages',
2842 api_name => 'open-ils.actor.message.retrieve',
2845 Returns a list of notes for a given user, not
2846 including ones marked deleted
2847 @param authtoken The login session key
2848 @param patronid patron ID
2849 @param options hash containing optional limit and offset
2853 sub fetch_patron_messages {
2854 my( $self, $conn, $auth, $patronid, $options ) = @_;
2858 my $e = new_editor(authtoken => $auth);
2859 return $e->die_event unless $e->checkauth;
2861 if ($e->requestor->id ne $patronid) {
2862 return $e->die_event unless $e->allowed('VIEW_USER');
2865 my $select_clause = { usr => $patronid };
2866 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2867 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2868 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2870 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2875 __PACKAGE__->register_method(
2876 method => 'usrname_exists',
2877 api_name => 'open-ils.actor.username.exists',
2879 desc => 'Check if a username is already taken (by an undeleted patron)',
2881 {desc => 'Authentication token', type => 'string'},
2882 {desc => 'Username', type => 'string'}
2885 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2890 sub usrname_exists {
2891 my( $self, $conn, $auth, $usrname ) = @_;
2892 my $e = new_editor(authtoken=>$auth);
2893 return $e->event unless $e->checkauth;
2894 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2895 return $$a[0] if $a and @$a;
2899 __PACKAGE__->register_method(
2900 method => 'barcode_exists',
2901 api_name => 'open-ils.actor.barcode.exists',
2903 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2906 sub barcode_exists {
2907 my( $self, $conn, $auth, $barcode ) = @_;
2908 my $e = new_editor(authtoken=>$auth);
2909 return $e->event unless $e->checkauth;
2910 my $card = $e->search_actor_card({barcode => $barcode});
2916 #return undef unless @$card;
2917 #return $card->[0]->usr;
2921 __PACKAGE__->register_method(
2922 method => 'retrieve_net_levels',
2923 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2926 sub retrieve_net_levels {
2927 my( $self, $conn, $auth ) = @_;
2928 my $e = new_editor(authtoken=>$auth);
2929 return $e->event unless $e->checkauth;
2930 return $e->retrieve_all_config_net_access_level();
2933 # Retain the old typo API name just in case
2934 __PACKAGE__->register_method(
2935 method => 'fetch_org_by_shortname',
2936 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2938 __PACKAGE__->register_method(
2939 method => 'fetch_org_by_shortname',
2940 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2942 sub fetch_org_by_shortname {
2943 my( $self, $conn, $sname ) = @_;
2944 my $e = new_editor();
2945 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2946 return $e->event unless $org;
2951 __PACKAGE__->register_method(
2952 method => 'session_home_lib',
2953 api_name => 'open-ils.actor.session.home_lib',
2956 sub session_home_lib {
2957 my( $self, $conn, $auth ) = @_;
2958 my $e = new_editor(authtoken=>$auth);
2959 return undef unless $e->checkauth;
2960 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2961 return $org->shortname;
2964 __PACKAGE__->register_method(
2965 method => 'session_safe_token',
2966 api_name => 'open-ils.actor.session.safe_token',
2968 Returns a hashed session ID that is safe for export to the world.
2969 This safe token will expire after 1 hour of non-use.
2970 @param auth Active authentication token
2974 sub session_safe_token {
2975 my( $self, $conn, $auth ) = @_;
2976 my $e = new_editor(authtoken=>$auth);
2977 return undef unless $e->checkauth;
2979 my $safe_token = md5_hex($auth);
2981 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2983 # add more user fields as needed
2985 "safe-token-user-$safe_token", {
2986 id => $e->requestor->id,
2987 home_ou_shortname => $e->retrieve_actor_org_unit(
2988 $e->requestor->home_ou)->shortname,
2997 __PACKAGE__->register_method(
2998 method => 'safe_token_home_lib',
2999 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
3001 Returns the home library shortname from the session
3002 asscociated with a safe token from generated by
3003 open-ils.actor.session.safe_token.
3004 @param safe_token Active safe token
3005 @param who Optional user activity "ewho" value
3009 sub safe_token_home_lib {
3010 my( $self, $conn, $safe_token, $who ) = @_;
3011 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3013 my $blob = $cache->get_cache("safe-token-user-$safe_token");
3014 return unless $blob;
3016 $U->log_user_activity($blob->{id}, $who, 'verify');
3017 return $blob->{home_ou_shortname};
3021 __PACKAGE__->register_method(
3022 method => "update_penalties",
3023 api_name => "open-ils.actor.user.penalties.update"
3026 sub update_penalties {
3027 my($self, $conn, $auth, $user_id) = @_;
3028 my $e = new_editor(authtoken=>$auth, xact => 1);
3029 return $e->die_event unless $e->checkauth;
3030 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3031 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3032 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
3033 return $evt if $evt;
3039 __PACKAGE__->register_method(
3040 method => "apply_penalty",
3041 api_name => "open-ils.actor.user.penalty.apply"
3045 my($self, $conn, $auth, $penalty, $msg) = @_;
3049 my $e = new_editor(authtoken=>$auth, xact => 1);
3050 return $e->die_event unless $e->checkauth;
3052 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3053 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3055 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
3057 my $ctx_org = $penalty->org_unit; # csp org_depth is now considered in the UI for the org drop-down menu
3059 if (($msg->{title} || $msg->{message}) && ($msg->{title} ne '' || $msg->{message} ne '')) {
3060 my $aum = Fieldmapper::actor::usr_message->new;
3062 $aum->create_date('now');
3063 $aum->sending_lib($e->requestor->ws_ou);
3064 $aum->title($msg->{title});
3065 $aum->usr($penalty->usr);
3066 $aum->message($msg->{message});
3067 $aum->pub($msg->{pub});
3069 $aum = $e->create_actor_usr_message($aum)
3070 or return $e->die_event;
3072 $penalty->usr_message($aum->id);
3075 $penalty->org_unit($ctx_org);
3076 $penalty->staff($e->requestor->id);
3077 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
3080 return $penalty->id;
3083 __PACKAGE__->register_method(
3084 method => "modify_penalty",
3085 api_name => "open-ils.actor.user.penalty.modify"
3088 sub modify_penalty {
3089 my($self, $conn, $auth, $penalty, $usr_msg) = @_;
3091 my $e = new_editor(authtoken=>$auth, xact => 1);
3092 return $e->die_event unless $e->checkauth;
3094 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3095 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3097 $usr_msg->editor($e->requestor->id);
3098 $usr_msg->edit_date('now');
3100 if ($usr_msg->isnew) {
3101 $usr_msg = $e->create_actor_usr_message($usr_msg)
3102 or return $e->die_event;
3103 $penalty->usr_message($usr_msg->id);
3105 $usr_msg = $e->update_actor_usr_message($usr_msg)
3106 or return $e->die_event;
3109 if ($penalty->isnew) {
3110 $penalty = $e->create_actor_user_standing_penalty($penalty)
3111 or return $e->die_event;
3113 $penalty = $e->update_actor_user_standing_penalty($penalty)
3114 or return $e->die_event;
3121 __PACKAGE__->register_method(
3122 method => "remove_penalty",
3123 api_name => "open-ils.actor.user.penalty.remove"
3126 sub remove_penalty {
3127 my($self, $conn, $auth, $penalty) = @_;
3128 my $e = new_editor(authtoken=>$auth, xact => 1);
3129 return $e->die_event unless $e->checkauth;
3130 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3131 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3133 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3138 __PACKAGE__->register_method(
3139 method => "update_penalty_note",
3140 api_name => "open-ils.actor.user.penalty.note.update"
3143 sub update_penalty_note {
3144 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3145 my $e = new_editor(authtoken=>$auth, xact => 1);
3146 return $e->die_event unless $e->checkauth;
3147 for my $penalty_id (@$penalty_ids) {
3148 my $penalty = $e->search_actor_user_standing_penalty([
3149 { id => $penalty_id },
3151 flesh_fields => {aum => ['usr_message']}
3154 if (! $penalty ) { return $e->die_event; }
3155 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3156 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3158 my $aum = $penalty->usr_message();
3160 $aum = Fieldmapper::actor::usr_message->new;
3162 $aum->create_date('now');
3163 $aum->sending_lib($e->requestor->ws_ou);
3165 $aum->usr($penalty->usr);
3166 $aum->message($note);
3170 $aum = $e->create_actor_usr_message($aum)
3171 or return $e->die_event;
3173 $penalty->usr_message($aum->id);
3174 $penalty->ischanged(1);
3175 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3177 $aum = $e->retrieve_actor_usr_message($aum) or return $e->die_event;
3178 $aum->message($note); $aum->ischanged(1);
3179 $e->update_actor_usr_message($aum) or return $e->die_event;
3186 __PACKAGE__->register_method(
3187 method => "ranged_penalty_thresholds",
3188 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3192 sub ranged_penalty_thresholds {
3193 my($self, $conn, $auth, $context_org) = @_;
3194 my $e = new_editor(authtoken=>$auth);
3195 return $e->event unless $e->checkauth;
3196 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3197 my $list = $e->search_permission_grp_penalty_threshold([
3198 {org_unit => $U->get_org_ancestors($context_org)},
3199 {order_by => {pgpt => 'id'}}
3201 $conn->respond($_) for @$list;
3207 __PACKAGE__->register_method(
3208 method => "user_retrieve_fleshed_by_id",
3210 api_name => "open-ils.actor.user.fleshed.retrieve",
3213 sub user_retrieve_fleshed_by_id {
3214 my( $self, $client, $auth, $user_id, $fields ) = @_;
3215 my $e = new_editor(authtoken => $auth);
3216 return $e->event unless $e->checkauth;
3218 if( $e->requestor->id != $user_id ) {
3219 return $e->event unless $e->allowed('VIEW_USER');
3226 "standing_penalties",
3234 return new_flesh_user($user_id, $fields, $e);
3238 sub new_flesh_user {
3241 my $fields = shift || [];
3244 my $fetch_penalties = 0;
3245 if(grep {$_ eq 'standing_penalties'} @$fields) {
3246 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3247 $fetch_penalties = 1;
3250 my $fetch_notes = 0;
3251 if(grep {$_ eq 'notes'} @$fields) {
3252 $fields = [grep {$_ ne 'notes'} @$fields];
3256 my $fetch_usr_act = 0;
3257 if(grep {$_ eq 'usr_activity'} @$fields) {
3258 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3262 my $user = $e->retrieve_actor_user(
3267 "flesh_fields" => { "au" => $fields }
3270 ) or return $e->die_event;
3273 if( grep { $_ eq 'addresses' } @$fields ) {
3275 $user->addresses([]) unless @{$user->addresses};
3276 # don't expose "replaced" addresses by default
3277 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3279 if( ref $user->billing_address ) {
3280 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3281 push( @{$user->addresses}, $user->billing_address );
3285 if( ref $user->mailing_address ) {
3286 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3287 push( @{$user->addresses}, $user->mailing_address );
3292 if($fetch_penalties) {
3293 # grab the user penalties ranged for this location
3294 $user->standing_penalties(
3295 $e->search_actor_user_standing_penalty([
3298 {stop_date => undef},
3299 {stop_date => {'>' => 'now'}}
3301 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3304 flesh_fields => {ausp => ['standing_penalty','usr_message']}
3311 # grab notes (now actor.usr_message_penalty) that have not hit their stop_date
3312 # NOTE: This is a view that already filters out deleted messages that are not
3313 # attached to a penalty
3315 @{ $e->search_actor_usr_message_penalty([
3318 {stop_date => undef},
3319 {stop_date => {'>' => 'now'}}
3326 # retrieve the most recent usr_activity entry
3327 if ($fetch_usr_act) {
3329 # max number to return for simple patron fleshing
3330 my $limit = $U->ou_ancestor_setting_value(
3331 $e->requestor->ws_ou,
3332 'circ.patron.usr_activity_retrieve.max');
3336 flesh_fields => {auact => ['etype']},
3337 order_by => {auact => 'event_time DESC'},
3340 # 0 == none, <0 == return all
3341 $limit = 1 unless defined $limit;
3342 $opts->{limit} = $limit if $limit > 0;
3344 $user->usr_activity(
3346 [] : # skip the DB call
3347 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3352 $user->clear_passwd();
3359 __PACKAGE__->register_method(
3360 method => "user_retrieve_parts",
3361 api_name => "open-ils.actor.user.retrieve.parts",
3364 sub user_retrieve_parts {
3365 my( $self, $client, $auth, $user_id, $fields ) = @_;
3366 my $e = new_editor(authtoken => $auth);
3367 return $e->event unless $e->checkauth;
3368 $user_id ||= $e->requestor->id;
3369 if( $e->requestor->id != $user_id ) {
3370 return $e->event unless $e->allowed('VIEW_USER');
3373 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3374 push(@resp, $user->$_()) for(@$fields);
3380 __PACKAGE__->register_method(
3381 method => 'user_opt_in_enabled',
3382 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3383 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3386 sub user_opt_in_enabled {
3387 my($self, $conn) = @_;
3388 my $sc = OpenSRF::Utils::SettingsClient->new;
3389 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3394 __PACKAGE__->register_method(
3395 method => 'user_opt_in_at_org',
3396 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3398 @param $auth The auth token
3399 @param user_id The ID of the user to test
3400 @return 1 if the user has opted in at the specified org,
3401 2 if opt-in is disallowed for the user's home org,
3402 event on error, and 0 otherwise. /
3404 sub user_opt_in_at_org {
3405 my($self, $conn, $auth, $user_id) = @_;
3407 # see if we even need to enforce the opt-in value
3408 return 1 unless user_opt_in_enabled($self);
3410 my $e = new_editor(authtoken => $auth);
3411 return $e->event unless $e->checkauth;
3413 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3414 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3416 my $ws_org = $e->requestor->ws_ou;
3417 # user is automatically opted-in if they are from the local org
3418 return 1 if $user->home_ou eq $ws_org;
3420 # get the boundary setting
3421 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3423 # auto opt in if user falls within the opt boundary
3424 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3426 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3428 # check whether opt-in is restricted at the user's home library
3429 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3430 if ($opt_restrict_depth) {
3431 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3432 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3434 # opt-in is disallowed unless the workstation org is within the home
3435 # library's opt-in scope
3436 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3439 my $vals = $e->search_actor_usr_org_unit_opt_in(
3440 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3446 __PACKAGE__->register_method(
3447 method => 'create_user_opt_in_at_org',
3448 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3450 @param $auth The auth token
3451 @param user_id The ID of the user to test
3452 @return The ID of the newly created object, event on error./
3455 sub create_user_opt_in_at_org {
3456 my($self, $conn, $auth, $user_id, $org_id) = @_;
3458 my $e = new_editor(authtoken => $auth, xact=>1);
3459 return $e->die_event unless $e->checkauth;
3461 # if a specific org unit wasn't passed in, get one based on the defaults;
3463 my $wsou = $e->requestor->ws_ou;
3464 # get the default opt depth
3465 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3466 # get the org unit at that depth
3467 my $org = $e->json_query({
3468 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3469 $org_id = $org->{id};
3472 # fall back to the workstation OU, the pre-opt-in-boundary way
3473 $org_id = $e->requestor->ws_ou;
3476 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3477 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3479 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3481 $opt_in->org_unit($org_id);
3482 $opt_in->usr($user_id);
3483 $opt_in->staff($e->requestor->id);
3484 $opt_in->opt_in_ts('now');
3485 $opt_in->opt_in_ws($e->requestor->wsid);
3487 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3488 or return $e->die_event;
3496 __PACKAGE__->register_method (
3497 method => 'retrieve_org_hours',
3498 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3500 Returns the hours of operation for a specified org unit
3501 @param authtoken The login session key
3502 @param org_id The org_unit ID
3506 sub retrieve_org_hours {
3507 my($self, $conn, $auth, $org_id) = @_;
3508 my $e = new_editor(authtoken => $auth);
3509 return $e->die_event unless $e->checkauth;
3510 $org_id ||= $e->requestor->ws_ou;
3511 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3515 __PACKAGE__->register_method (
3516 method => 'verify_user_password',
3517 api_name => 'open-ils.actor.verify_user_password',
3519 Given a barcode or username and the MD5 encoded password,
3520 returns 1 if the password is correct. Returns 0 otherwise.
3524 sub verify_user_password {
3525 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3526 my $e = new_editor(authtoken => $auth);
3527 return $e->die_event unless $e->checkauth;
3529 my $user_by_barcode;
3530 my $user_by_username;
3532 my $card = $e->search_actor_card([
3533 {barcode => $barcode},
3534 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3535 $user_by_barcode = $card->usr;
3536 $user = $user_by_barcode;
3539 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3540 $user = $user_by_username;
3542 return 0 if (!$user || $U->is_true($user->deleted));
3543 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3544 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3545 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3548 __PACKAGE__->register_method (
3549 method => 'retrieve_usr_id_via_barcode_or_usrname',
3550 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3552 Given a barcode or username returns the id for the user or
3557 sub retrieve_usr_id_via_barcode_or_usrname {
3558 my($self, $conn, $auth, $barcode, $username) = @_;
3559 my $e = new_editor(authtoken => $auth);
3560 return $e->die_event unless $e->checkauth;
3561 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3563 my $user_by_barcode;
3564 my $user_by_username;
3565 $logger->info("$id_as_barcode is the ID as BARCODE");
3567 my $card = $e->search_actor_card([
3568 {barcode => $barcode},
3569 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3570 if ($id_as_barcode =~ /^t/i) {
3572 $user = $e->retrieve_actor_user($barcode);
3573 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3575 $user_by_barcode = $card->usr;
3576 $user = $user_by_barcode;
3579 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3580 $user_by_barcode = $card->usr;
3581 $user = $user_by_barcode;
3586 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3588 $user = $user_by_username;
3590 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3591 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3592 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3597 __PACKAGE__->register_method (
3598 method => 'merge_users',
3599 api_name => 'open-ils.actor.user.merge',
3602 Given a list of source users and destination user, transfer all data from the source
3603 to the dest user and delete the source user. All user related data is
3604 transferred, including circulations, holds, bookbags, etc.
3610 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3611 my $e = new_editor(xact => 1, authtoken => $auth);
3612 return $e->die_event unless $e->checkauth;
3614 # disallow the merge if any subordinate accounts are in collections
3615 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3616 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3618 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3619 if $master_id == $e->requestor->id;
3621 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3622 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3623 return $evt if $evt;
3625 my $del_addrs = ($U->ou_ancestor_setting_value(
3626 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3627 my $del_cards = ($U->ou_ancestor_setting_value(
3628 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3629 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3630 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3632 for my $src_id (@$user_ids) {
3634 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3635 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3636 return $evt if $evt;
3638 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3639 if $src_id == $e->requestor->id;
3641 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3642 if($src_user->home_ou ne $master_user->home_ou) {
3643 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3646 return $e->die_event unless
3647 $e->json_query({from => [
3662 __PACKAGE__->register_method (
3663 method => 'approve_user_address',
3664 api_name => 'open-ils.actor.user.pending_address.approve',
3671 sub approve_user_address {
3672 my($self, $conn, $auth, $addr) = @_;
3673 my $e = new_editor(xact => 1, authtoken => $auth);
3674 return $e->die_event unless $e->checkauth;
3676 # if the caller passes an address object, assume they want to
3677 # update it first before approving it
3678 $e->update_actor_user_address($addr) or return $e->die_event;
3680 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3682 my $user = $e->retrieve_actor_user($addr->usr);
3683 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3684 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3685 or return $e->die_event;
3687 return [values %$result]->[0];
3691 __PACKAGE__->register_method (
3692 method => 'retrieve_friends',
3693 api_name => 'open-ils.actor.friends.retrieve',
3696 returns { confirmed: [], pending_out: [], pending_in: []}
3697 pending_out are users I'm requesting friendship with
3698 pending_in are users requesting friendship with me
3703 sub retrieve_friends {
3704 my($self, $conn, $auth, $user_id, $options) = @_;
3705 my $e = new_editor(authtoken => $auth);
3706 return $e->event unless $e->checkauth;
3707 $user_id ||= $e->requestor->id;
3709 if($user_id != $e->requestor->id) {
3710 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3711 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3714 return OpenILS::Application::Actor::Friends->retrieve_friends(
3715 $e, $user_id, $options);
3720 __PACKAGE__->register_method (
3721 method => 'apply_friend_perms',
3722 api_name => 'open-ils.actor.friends.perms.apply',
3728 sub apply_friend_perms {
3729 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3730 my $e = new_editor(authtoken => $auth, xact => 1);
3731 return $e->die_event unless $e->checkauth;
3733 if($user_id != $e->requestor->id) {
3734 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3735 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3738 for my $perm (@perms) {
3740 OpenILS::Application::Actor::Friends->apply_friend_perm(
3741 $e, $user_id, $delegate_id, $perm);
3742 return $evt if $evt;
3750 __PACKAGE__->register_method (
3751 method => 'update_user_pending_address',
3752 api_name => 'open-ils.actor.user.address.pending.cud'
3755 sub update_user_pending_address {
3756 my($self, $conn, $auth, $addr) = @_;
3757 my $e = new_editor(authtoken => $auth, xact => 1);
3758 return $e->die_event unless $e->checkauth;
3760 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3761 if($addr->usr != $e->requestor->id) {
3762 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3766 $e->create_actor_user_address($addr) or return $e->die_event;
3767 } elsif($addr->isdeleted) {
3768 $e->delete_actor_user_address($addr) or return $e->die_event;
3770 $e->update_actor_user_address($addr) or return $e->die_event;
3774 $U->create_events_for_hook('au.updated', $user, $e->requestor->ws_ou);
3780 __PACKAGE__->register_method (
3781 method => 'user_events',
3782 api_name => 'open-ils.actor.user.events.circ',
3785 __PACKAGE__->register_method (
3786 method => 'user_events',
3787 api_name => 'open-ils.actor.user.events.ahr',
3792 my($self, $conn, $auth, $user_id, $filters) = @_;
3793 my $e = new_editor(authtoken => $auth);
3794 return $e->event unless $e->checkauth;
3796 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3797 my $user_field = 'usr';
3800 $filters->{target} = {
3801 select => { $obj_type => ['id'] },
3803 where => {usr => $user_id}
3806 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3807 if($e->requestor->id != $user_id) {
3808 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3811 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3812 my $req = $ses->request('open-ils.trigger.events_by_target',
3813 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3815 while(my $resp = $req->recv) {
3816 my $val = $resp->content;
3817 my $tgt = $val->target;
3819 if($obj_type eq 'circ') {
3820 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3822 } elsif($obj_type eq 'ahr') {
3823 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3824 if $tgt->current_copy;
3827 $conn->respond($val) if $val;
3833 __PACKAGE__->register_method (
3834 method => 'copy_events',
3835 api_name => 'open-ils.actor.copy.events.circ',
3838 __PACKAGE__->register_method (
3839 method => 'copy_events',
3840 api_name => 'open-ils.actor.copy.events.ahr',
3845 my($self, $conn, $auth, $copy_id, $filters) = @_;
3846 my $e = new_editor(authtoken => $auth);
3847 return $e->event unless $e->checkauth;
3849 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3851 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3853 my $copy_field = 'target_copy';
3854 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3857 $filters->{target} = {
3858 select => { $obj_type => ['id'] },
3860 where => {$copy_field => $copy_id}
3864 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3865 my $req = $ses->request('open-ils.trigger.events_by_target',
3866 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3868 while(my $resp = $req->recv) {
3869 my $val = $resp->content;
3870 my $tgt = $val->target;
3872 my $user = $e->retrieve_actor_user($tgt->usr);
3873 if($e->requestor->id != $user->id) {
3874 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3877 $tgt->$copy_field($copy);
3880 $conn->respond($val) if $val;
3887 __PACKAGE__->register_method (
3888 method => 'get_itemsout_notices',
3889 api_name => 'open-ils.actor.user.itemsout.notices',
3893 desc => q/Summary counts of circulat notices/,
3895 {desc => 'authtoken', type => 'string'},
3896 {desc => 'circulation identifiers', type => 'array of numbers'}
3898 return => q/Stream of summary objects/
3902 sub get_itemsout_notices {
3903 my ($self, $client, $auth, $circ_ids) = @_;
3905 my $e = new_editor(authtoken => $auth);
3906 return $e->event unless $e->checkauth;
3908 $circ_ids = [$circ_ids] unless ref $circ_ids eq 'ARRAY';
3910 for my $circ_id (@$circ_ids) {
3911 my $resp = get_itemsout_notices_impl($e, $circ_id);
3913 if ($U->is_event($resp)) {
3914 $client->respond($resp);
3918 $client->respond({circ_id => $circ_id, %$resp});
3926 sub get_itemsout_notices_impl {
3927 my ($e, $circId) = @_;
3929 my $requestorId = $e->requestor->id;
3931 my $circ = $e->retrieve_action_circulation($circId) or return $e->event;
3933 my $patronId = $circ->usr;
3935 if( $patronId ne $requestorId ){
3936 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3937 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3940 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3941 #my $req = $ses->request('open-ils.trigger.events_by_target',
3942 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3943 # ^ Above removed in favor of faster json_query.
3946 # select complete_time
3947 # from action_trigger.event atev
3948 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3949 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3950 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3953 my $ctx_loc = $e->requestor->ws_ou;
3954 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value(
3955 $ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3958 select => { atev => ["complete_time"] },
3961 atevdef => { field => "id",fkey => "event_def"}
3965 "+atevdef" => { active => 't', hook => 'checkout.due' },
3966 "+atev" => { target => $circId, state => 'complete' }
3970 if ($exclude_courtesy_notices){
3971 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3974 my %resblob = ( numNotices => 0, lastDt => undef );
3976 my $res = $e->json_query($query);
3977 for my $ndate (@$res) {
3978 $resblob{numNotices}++;
3979 if( !defined $resblob{lastDt}){
3980 $resblob{lastDt} = $$ndate{complete_time};
3983 if ($resblob{lastDt} lt $$ndate{complete_time}){
3984 $resblob{lastDt} = $$ndate{complete_time};
3991 __PACKAGE__->register_method (
3992 method => 'update_events',
3993 api_name => 'open-ils.actor.user.event.cancel.batch',
3996 __PACKAGE__->register_method (
3997 method => 'update_events',
3998 api_name => 'open-ils.actor.user.event.reset.batch',
4003 my($self, $conn, $auth, $event_ids) = @_;
4004 my $e = new_editor(xact => 1, authtoken => $auth);
4005 return $e->die_event unless $e->checkauth;
4008 for my $id (@$event_ids) {
4010 # do a little dance to determine what user we are ultimately affecting
4011 my $event = $e->retrieve_action_trigger_event([
4014 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
4016 ]) or return $e->die_event;
4019 if($event->event_def->hook->core_type eq 'circ') {
4020 $user_id = $e->retrieve_action_circulation($event->target)->usr;
4021 } elsif($event->event_def->hook->core_type eq 'ahr') {
4022 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
4027 my $user = $e->retrieve_actor_user($user_id);
4028 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
4030 if($self->api_name =~ /cancel/) {
4031 $event->state('invalid');
4032 } elsif($self->api_name =~ /reset/) {
4033 $event->clear_start_time;
4034 $event->clear_update_time;
4035 $event->state('pending');
4038 $e->update_action_trigger_event($event) or return $e->die_event;
4039 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
4043 return {complete => 1};
4047 __PACKAGE__->register_method (
4048 method => 'really_delete_user',
4049 api_name => 'open-ils.actor.user.delete.override',
4050 signature => q/@see open-ils.actor.user.delete/
4053 __PACKAGE__->register_method (
4054 method => 'really_delete_user',
4055 api_name => 'open-ils.actor.user.delete',
4057 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
4058 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
4059 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
4060 dest_usr_id is only required when deleting a user that performs staff functions.
4064 sub really_delete_user {
4065 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
4066 my $e = new_editor(authtoken => $auth, xact => 1);
4067 return $e->die_event unless $e->checkauth;
4068 $oargs = { all => 1 } unless defined $oargs;
4070 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
4071 my $open_bills = $e->json_query({
4072 select => { mbts => ['id'] },
4075 xact_finish => { '=' => undef },
4076 usr => { '=' => $user_id },
4078 }) or return $e->die_event;
4080 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
4082 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
4084 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
4085 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
4086 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
4088 # No deleting yourself - UI is supposed to stop you first, though.
4089 return $e->die_event unless $e->requestor->id != $user->id;
4090 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
4091 # Check if you are allowed to mess with this patron permission group at all
4092 my $evt = group_perm_failed($e, $e->requestor, $user);
4093 return $e->die_event($evt) if $evt;
4094 my $stat = $e->json_query(
4095 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
4096 or return $e->die_event;
4102 __PACKAGE__->register_method (
4103 method => 'user_payments',
4104 api_name => 'open-ils.actor.user.payments.retrieve',
4107 Returns all payments for a given user. Default order is newest payments first.
4108 @param auth Authentication token
4109 @param user_id The user ID
4110 @param filters An optional hash of filters, including limit, offset, and order_by definitions
4115 my($self, $conn, $auth, $user_id, $filters) = @_;
4118 my $e = new_editor(authtoken => $auth);
4119 return $e->die_event unless $e->checkauth;
4121 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4122 return $e->event unless
4123 $e->requestor->id == $user_id or
4124 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
4126 # Find all payments for all transactions for user $user_id
4128 select => {mp => ['id']},
4133 select => {mbt => ['id']},
4135 where => {usr => $user_id}
4140 { # by default, order newest payments first
4142 field => 'payment_ts',
4145 # secondary sort in ID as a tie-breaker, since payments created
4146 # within the same transaction will have identical payment_ts's
4153 for (qw/order_by limit offset/) {
4154 $query->{$_} = $filters->{$_} if defined $filters->{$_};
4157 if(defined $filters->{where}) {
4158 foreach (keys %{$filters->{where}}) {
4159 # don't allow the caller to expand the result set to other users
4160 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
4164 my $payment_ids = $e->json_query($query);
4165 for my $pid (@$payment_ids) {
4166 my $pay = $e->retrieve_money_payment([
4171 mbt => ['summary', 'circulation', 'grocery'],
4172 circ => ['target_copy'],
4173 acp => ['call_number'],
4181 xact_type => $pay->xact->summary->xact_type,
4182 last_billing_type => $pay->xact->summary->last_billing_type,
4185 if($pay->xact->summary->xact_type eq 'circulation') {
4186 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
4187 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
4190 $pay->xact($pay->xact->id); # de-flesh
4191 $conn->respond($resp);
4199 __PACKAGE__->register_method (
4200 method => 'negative_balance_users',
4201 api_name => 'open-ils.actor.users.negative_balance',
4204 Returns all users that have an overall negative balance
4205 @param auth Authentication token
4206 @param org_id The context org unit as an ID or list of IDs. This will be the home
4207 library of the user. If no org_unit is specified, no org unit filter is applied
4211 sub negative_balance_users {
4212 my($self, $conn, $auth, $org_id, $options) = @_;
4215 $options->{limit} = 1000 unless $options->{limit};
4216 $options->{offset} = 0 unless $options->{offset};
4218 my $e = new_editor(authtoken => $auth);
4219 return $e->die_event unless $e->checkauth;
4220 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
4224 mous => ['usr', 'balance_owed'],
4227 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
4228 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
4245 where => {'+mous' => {balance_owed => {'<' => 0}}},
4246 offset => $options->{offset},
4247 limit => $options->{limit},
4248 order_by => [{class => 'mous', field => 'usr'}]
4251 $org_id = $U->get_org_descendants($org_id) if $options->{org_descendants};
4253 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
4255 my $list = $e->json_query($query, {timeout => 600});
4257 for my $data (@$list) {
4259 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
4260 balance_owed => $data->{balance_owed},
4261 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
4268 __PACKAGE__->register_method(
4269 method => "request_password_reset",
4270 api_name => "open-ils.actor.patron.password_reset.request",
4272 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
4273 "method for changing a user's password. The UUID token is distributed via A/T " .
4274 "templates (i.e. email to the user).",
4276 { desc => 'user_id_type', type => 'string' },
4277 { desc => 'user_id', type => 'string' },
4278 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
4280 return => {desc => '1 on success, Event on error'}
4283 sub request_password_reset {
4284 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4286 # Check to see if password reset requests are already being throttled:
4287 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4289 my $e = new_editor(xact => 1);
4292 # Get the user, if any, depending on the input value
4293 if ($user_id_type eq 'username') {
4294 $user = $e->search_actor_user({usrname => $user_id})->[0];
4297 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4299 } elsif ($user_id_type eq 'barcode') {
4300 my $card = $e->search_actor_card([
4301 {barcode => $user_id},
4302 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4305 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4310 # If the user doesn't have an email address, we can't help them
4311 if (!$user->email) {
4313 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4316 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4317 if ($email_must_match) {
4318 if (lc($user->email) ne lc($email)) {
4319 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4323 _reset_password_request($conn, $e, $user);
4326 # Once we have the user, we can issue the password reset request
4327 # XXX Add a wrapper method that accepts barcode + email input
4328 sub _reset_password_request {
4329 my ($conn, $e, $user) = @_;
4331 # 1. Get throttle threshold and time-to-live from OU_settings
4332 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4333 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4335 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4337 # 2. Get time of last request and number of active requests (num_active)
4338 my $active_requests = $e->json_query({
4344 transform => 'COUNT'
4347 column => 'request_time',
4353 has_been_reset => { '=' => 'f' },
4354 request_time => { '>' => $threshold_time }
4358 # Guard against no active requests
4359 if ($active_requests->[0]->{'request_time'}) {
4360 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4361 my $now = DateTime::Format::ISO8601->new();
4363 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4364 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4365 ($last_request->add_duration('1 minute') > $now)) {
4366 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4368 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4372 # TODO Check to see if the user is in a password-reset-restricted group
4374 # Otherwise, go ahead and try to get the user.
4376 # Check the number of active requests for this user
4377 $active_requests = $e->json_query({
4383 transform => 'COUNT'
4388 usr => { '=' => $user->id },
4389 has_been_reset => { '=' => 'f' },
4390 request_time => { '>' => $threshold_time }
4394 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4396 # if less than or equal to per-user threshold, proceed; otherwise, return event
4397 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4398 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4400 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4403 # Create the aupr object and insert into the database
4404 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4405 my $uuid = create_uuid_as_string(UUID_V4);
4406 $reset_request->uuid($uuid);
4407 $reset_request->usr($user->id);
4409 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4412 # Create an event to notify user of the URL to reset their password
4414 # Can we stuff this in the user_data param for trigger autocreate?
4415 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4417 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4418 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4421 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4426 __PACKAGE__->register_method(
4427 method => "commit_password_reset",
4428 api_name => "open-ils.actor.patron.password_reset.commit",
4430 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4431 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4432 "with the supplied password.",
4434 { desc => 'uuid', type => 'string' },
4435 { desc => 'password', type => 'string' },
4437 return => {desc => '1 on success, Event on error'}
4440 sub commit_password_reset {
4441 my($self, $conn, $uuid, $password) = @_;
4443 # Check to see if password reset requests are already being throttled:
4444 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4445 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4446 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4448 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4451 my $e = new_editor(xact => 1);
4453 my $aupr = $e->search_actor_usr_password_reset({
4460 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4462 my $user_id = $aupr->[0]->usr;
4463 my $user = $e->retrieve_actor_user($user_id);
4465 # Ensure we're still within the TTL for the request
4466 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4467 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4468 if ($threshold < DateTime->now(time_zone => 'local')) {
4470 $logger->info("Password reset request needed to be submitted before $threshold");
4471 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4474 # Check complexity of password against OU-defined regex
4475 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4479 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4480 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4481 $is_strong = check_password_strength_custom($password, $pw_regex);
4483 $is_strong = check_password_strength_default($password);
4488 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4491 # All is well; update the password
4492 modify_migrated_user_password($e, $user->id, $password);
4494 # And flag that this password reset request has been honoured
4495 $aupr->[0]->has_been_reset('t');
4496 $e->update_actor_usr_password_reset($aupr->[0]);
4502 sub check_password_strength_default {
4503 my $password = shift;
4504 # Use the default set of checks
4505 if ( (length($password) < 7) or
4506 ($password !~ m/.*\d+.*/) or
4507 ($password !~ m/.*[A-Za-z]+.*/)
4514 sub check_password_strength_custom {
4515 my ($password, $pw_regex) = @_;
4517 $pw_regex = qr/$pw_regex/;
4518 if ($password !~ /$pw_regex/) {
4524 __PACKAGE__->register_method(
4525 method => "fire_test_notification",
4526 api_name => "open-ils.actor.event.test_notification"
4529 sub fire_test_notification {
4530 my($self, $conn, $auth, $args) = @_;
4531 my $e = new_editor(authtoken => $auth);
4532 return $e->event unless $e->checkauth;
4533 if ($e->requestor->id != $$args{target}) {
4534 my $home_ou = $e->retrieve_actor_user($$args{target})->home_ou;
4535 return $e->die_event unless $home_ou && $e->allowed('VIEW_USER', $home_ou);
4538 my $event_hook = $$args{hook} or return $e->event;
4539 return $e->event unless ($event_hook eq 'au.email.test' or $event_hook eq 'au.sms_text.test');
4541 my $usr = $e->retrieve_actor_user($$args{target});
4542 return $e->event unless $usr;
4544 return $U->fire_object_event(undef, $event_hook, $usr, $e->requestor->ws_ou);
4548 __PACKAGE__->register_method(
4549 method => "event_def_opt_in_settings",
4550 api_name => "open-ils.actor.event_def.opt_in.settings",
4553 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4555 { desc => 'Authentication token', type => 'string'},
4557 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4562 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4569 sub event_def_opt_in_settings {
4570 my($self, $conn, $auth, $org_id) = @_;
4571 my $e = new_editor(authtoken => $auth);
4572 return $e->event unless $e->checkauth;
4574 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4575 return $e->event unless
4576 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4578 $org_id = $e->requestor->home_ou;
4581 # find all config.user_setting_type's related to event_defs for the requested org unit
4582 my $types = $e->json_query({
4583 select => {cust => ['name']},
4584 from => {atevdef => 'cust'},
4587 owner => $U->get_org_ancestors($org_id), # context org plus parents
4594 $conn->respond($_) for
4595 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4602 __PACKAGE__->register_method(
4603 method => "user_circ_history",
4604 api_name => "open-ils.actor.history.circ",
4608 desc => 'Returns user circ history objects for the calling user',
4610 { desc => 'Authentication token', type => 'string'},
4611 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4614 desc => q/Stream of 'auch' circ history objects/,
4620 __PACKAGE__->register_method(
4621 method => "user_circ_history",
4622 api_name => "open-ils.actor.history.circ.clear",
4625 desc => 'Delete all user circ history entries for the calling user',
4627 { desc => 'Authentication token', type => 'string'},
4628 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4631 desc => q/1 on success, event on error/,
4637 __PACKAGE__->register_method(
4638 method => "user_circ_history",
4639 api_name => "open-ils.actor.history.circ.print",
4642 desc => q/Returns printable output for the caller's circ history objects/,
4644 { desc => 'Authentication token', type => 'string'},
4645 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4648 desc => q/An action_trigger.event object or error event./,
4654 __PACKAGE__->register_method(
4655 method => "user_circ_history",
4656 api_name => "open-ils.actor.history.circ.email",
4659 desc => q/Emails the caller's circ history/,
4661 { desc => 'Authentication token', type => 'string'},
4662 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4663 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4666 desc => q/undef, or event on error/
4671 sub user_circ_history {
4672 my ($self, $conn, $auth, $options) = @_;
4675 my $for_print = ($self->api_name =~ /print/);
4676 my $for_email = ($self->api_name =~ /email/);
4677 my $for_clear = ($self->api_name =~ /clear/);
4679 # No perm check is performed. Caller may only access his/her own
4680 # circ history entries.
4681 my $e = new_editor(authtoken => $auth);
4682 return $e->event unless $e->checkauth;
4685 if (!$for_clear) { # clear deletes all
4686 $limits{offset} = $options->{offset} if defined $options->{offset};
4687 $limits{limit} = $options->{limit} if defined $options->{limit};
4690 my %circ_id_filter = $options->{circ_ids} ?
4691 (id => $options->{circ_ids}) : ();
4693 my $circs = $e->search_action_user_circ_history([
4694 { usr => $e->requestor->id,
4697 { # order newest to oldest by default
4698 order_by => {auch => 'xact_start DESC'},
4701 {substream => 1} # could be a large list
4705 return $U->fire_object_event(undef,
4706 'circ.format.history.print', $circs, $e->requestor->home_ou);
4709 $e->xact_begin if $for_clear;
4710 $conn->respond_complete(1) if $for_email; # no sense in waiting
4712 for my $circ (@$circs) {
4715 # events will be fired from action_trigger_runner
4716 $U->create_events_for_hook('circ.format.history.email',
4717 $circ, $e->editor->home_ou, undef, undef, 1);
4719 } elsif ($for_clear) {
4721 $e->delete_action_user_circ_history($circ)
4722 or return $e->die_event;
4725 $conn->respond($circ);
4738 __PACKAGE__->register_method(
4739 method => "user_visible_holds",
4740 api_name => "open-ils.actor.history.hold.visible",
4743 desc => 'Returns the set of opt-in visible holds',
4745 { desc => 'Authentication token', type => 'string'},
4746 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4747 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4750 desc => q/An object with 1 field: "hold"/,
4756 __PACKAGE__->register_method(
4757 method => "user_visible_holds",
4758 api_name => "open-ils.actor.history.hold.visible.print",
4761 desc => 'Returns printable output for the set of opt-in visible holds',
4763 { desc => 'Authentication token', type => 'string'},
4764 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4765 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4768 desc => q/An action_trigger.event object or error event./,
4774 __PACKAGE__->register_method(
4775 method => "user_visible_holds",
4776 api_name => "open-ils.actor.history.hold.visible.email",
4779 desc => 'Emails the set of opt-in visible holds to the requestor',
4781 { desc => 'Authentication token', type => 'string'},
4782 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4783 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4786 desc => q/undef, or event on error/
4791 sub user_visible_holds {
4792 my($self, $conn, $auth, $user_id, $options) = @_;
4795 my $for_print = ($self->api_name =~ /print/);
4796 my $for_email = ($self->api_name =~ /email/);
4797 my $e = new_editor(authtoken => $auth);
4798 return $e->event unless $e->checkauth;
4800 $user_id ||= $e->requestor->id;
4802 $options->{limit} ||= 50;
4803 $options->{offset} ||= 0;
4805 if($user_id != $e->requestor->id) {
4806 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4807 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4808 return $e->event unless $e->allowed($perm, $user->home_ou);
4811 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4813 my $data = $e->json_query({
4814 from => [$db_func, $user_id],
4815 limit => $$options{limit},
4816 offset => $$options{offset}
4818 # TODO: I only want IDs. code below didn't get me there
4819 # {"select":{"au":[{"column":"id", "result_field":"id",
4820 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4825 return undef unless @$data;
4829 # collect the batch of objects
4833 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4834 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4838 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4839 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4842 } elsif ($for_email) {
4844 $conn->respond_complete(1) if $for_email; # no sense in waiting
4852 my $hold = $e->retrieve_action_hold_request($id);
4853 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4854 # events will be fired from action_trigger_runner
4858 my $circ = $e->retrieve_action_circulation($id);
4859 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4860 # events will be fired from action_trigger_runner
4864 } else { # just give me the data please
4872 my $hold = $e->retrieve_action_hold_request($id);
4873 $conn->respond({hold => $hold});
4877 my $circ = $e->retrieve_action_circulation($id);
4880 summary => $U->create_circ_chain_summary($e, $id)
4889 __PACKAGE__->register_method(
4890 method => "user_saved_search_cud",
4891 api_name => "open-ils.actor.user.saved_search.cud",
4894 desc => 'Create/Update/Delete Access to user saved searches',
4896 { desc => 'Authentication token', type => 'string' },
4897 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4900 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4906 __PACKAGE__->register_method(
4907 method => "user_saved_search_cud",
4908 api_name => "open-ils.actor.user.saved_search.retrieve",
4911 desc => 'Retrieve a saved search object',
4913 { desc => 'Authentication token', type => 'string' },
4914 { desc => 'Saved Search ID', type => 'number' }
4917 desc => q/The saved search object, Event on error/,
4923 sub user_saved_search_cud {
4924 my( $self, $client, $auth, $search ) = @_;
4925 my $e = new_editor( authtoken=>$auth );
4926 return $e->die_event unless $e->checkauth;
4928 my $o_search; # prior version of the object, if any
4929 my $res; # to be returned
4931 # branch on the operation type
4933 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4935 # Get the old version, to check ownership
4936 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4937 or return $e->die_event;
4939 # You can't read somebody else's search
4940 return OpenILS::Event->new('BAD_PARAMS')
4941 unless $o_search->owner == $e->requestor->id;
4947 $e->xact_begin; # start an editor transaction
4949 if( $search->isnew ) { # Create
4951 # You can't create a search for somebody else
4952 return OpenILS::Event->new('BAD_PARAMS')
4953 unless $search->owner == $e->requestor->id;
4955 $e->create_actor_usr_saved_search( $search )
4956 or return $e->die_event;
4960 } elsif( $search->ischanged ) { # Update
4962 # You can't change ownership of a search
4963 return OpenILS::Event->new('BAD_PARAMS')
4964 unless $search->owner == $e->requestor->id;
4966 # Get the old version, to check ownership
4967 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4968 or return $e->die_event;
4970 # You can't update somebody else's search
4971 return OpenILS::Event->new('BAD_PARAMS')
4972 unless $o_search->owner == $e->requestor->id;
4975 $e->update_actor_usr_saved_search( $search )
4976 or return $e->die_event;
4980 } elsif( $search->isdeleted ) { # Delete
4982 # Get the old version, to check ownership
4983 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4984 or return $e->die_event;
4986 # You can't delete somebody else's search
4987 return OpenILS::Event->new('BAD_PARAMS')
4988 unless $o_search->owner == $e->requestor->id;
4991 $e->delete_actor_usr_saved_search( $o_search )
4992 or return $e->die_event;
5003 __PACKAGE__->register_method(
5004 method => "get_barcodes",
5005 api_name => "open-ils.actor.get_barcodes"
5009 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
5010 my $e = new_editor(authtoken => $auth);
5011 return $e->event unless $e->checkauth;
5012 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
5014 my $db_result = $e->json_query(
5016 'evergreen.get_barcodes',
5017 $org_id, $context, $barcode,
5021 if($context =~ /actor/) {
5022 my $filter_result = ();
5024 foreach my $result (@$db_result) {
5025 if($result->{type} eq 'actor') {
5026 if($e->requestor->id != $result->{id}) {
5027 $patron = $e->retrieve_actor_user($result->{id});
5029 push(@$filter_result, $e->event);
5032 if($e->allowed('VIEW_USER', $patron->home_ou)) {
5033 push(@$filter_result, $result);
5036 push(@$filter_result, $e->event);
5040 push(@$filter_result, $result);
5044 push(@$filter_result, $result);
5047 return $filter_result;
5053 __PACKAGE__->register_method(
5054 method => 'address_alert_test',
5055 api_name => 'open-ils.actor.address_alert.test',
5057 desc => "Tests a set of address fields to determine if they match with an address_alert",
5059 {desc => 'Authentication token', type => 'string'},
5060 {desc => 'Org Unit', type => 'number'},
5061 {desc => 'Fields', type => 'hash'},
5063 return => {desc => 'List of matching address_alerts'}
5067 sub address_alert_test {
5068 my ($self, $client, $auth, $org_unit, $fields) = @_;
5069 return [] unless $fields and grep {$_} values %$fields;
5071 my $e = new_editor(authtoken => $auth);
5072 return $e->event unless $e->checkauth;
5073 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
5074 $org_unit ||= $e->requestor->ws_ou;
5076 my $alerts = $e->json_query({
5078 'actor.address_alert_matches',
5086 $$fields{post_code},
5087 $$fields{mailing_address},
5088 $$fields{billing_address}
5092 # map the json_query hashes to real objects
5094 map {$e->retrieve_actor_address_alert($_)}
5095 (map {$_->{id}} @$alerts)
5099 __PACKAGE__->register_method(
5100 method => "mark_users_contact_invalid",
5101 api_name => "open-ils.actor.invalidate.email",
5103 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",
5105 {desc => "Authentication token", type => "string"},
5106 {desc => "Patron ID (optional if Email address specified)", type => "number"},
5107 {desc => "Additional note text (optional)", type => "string"},
5108 {desc => "penalty org unit ID (optional)", type => "number"},
5109 {desc => "Email address (optional)", type => "string"}
5111 return => {desc => "Event describing success or failure", type => "object"}
5115 __PACKAGE__->register_method(
5116 method => "mark_users_contact_invalid",
5117 api_name => "open-ils.actor.invalidate.day_phone",
5119 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",
5121 {desc => "Authentication token", type => "string"},
5122 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5123 {desc => "Additional note text (optional)", type => "string"},
5124 {desc => "penalty org unit ID (optional)", type => "number"},
5125 {desc => "Phone Number (optional)", type => "string"}
5127 return => {desc => "Event describing success or failure", type => "object"}
5131 __PACKAGE__->register_method(
5132 method => "mark_users_contact_invalid",
5133 api_name => "open-ils.actor.invalidate.evening_phone",
5135 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",
5137 {desc => "Authentication token", type => "string"},
5138 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5139 {desc => "Additional note text (optional)", type => "string"},
5140 {desc => "penalty org unit ID (optional)", type => "number"},
5141 {desc => "Phone Number (optional)", type => "string"}
5143 return => {desc => "Event describing success or failure", type => "object"}
5147 __PACKAGE__->register_method(
5148 method => "mark_users_contact_invalid",
5149 api_name => "open-ils.actor.invalidate.other_phone",
5151 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",
5153 {desc => "Authentication token", type => "string"},
5154 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
5155 {desc => "Additional note text (optional)", type => "string"},
5156 {desc => "penalty org unit ID (optional, default to top of org tree)",
5158 {desc => "Phone Number (optional)", type => "string"}
5160 return => {desc => "Event describing success or failure", type => "object"}
5164 sub mark_users_contact_invalid {
5165 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
5167 # This method invalidates an email address or a phone_number which
5168 # removes the bad email address or phone number, copying its contents
5169 # to a patron note, and institutes a standing penalty for "bad email"
5170 # or "bad phone number" which is cleared when the user is saved or
5171 # optionally only when the user is saved with an email address or
5172 # phone number (or staff manually delete the penalty).
5174 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
5176 my $e = new_editor(authtoken => $auth, xact => 1);
5177 return $e->die_event unless $e->checkauth;
5180 if (defined $patron_id && $patron_id ne "") {
5181 $howfind = {usr => $patron_id};
5182 } elsif (defined $contact && $contact ne "") {
5183 $howfind = {$contact_type => $contact};
5185 # Error out if no patron id set or no contact is set.
5186 return OpenILS::Event->new('BAD_PARAMS');
5189 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
5190 $e, $contact_type, $howfind,
5191 $addl_note, $penalty_ou, $e->requestor->id
5195 # Putting the following method in open-ils.actor is a bad fit, except in that
5196 # it serves an interface that lives under 'actor' in the templates directory,
5197 # and in that there's nowhere else obvious to put it (open-ils.trigger is
5199 __PACKAGE__->register_method(
5200 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
5201 method => "get_all_at_reactors_in_use",
5206 { name => 'authtoken', type => 'string' }
5209 desc => 'list of reactor names', type => 'array'
5214 sub get_all_at_reactors_in_use {
5215 my ($self, $conn, $auth) = @_;
5217 my $e = new_editor(authtoken => $auth);
5218 $e->checkauth or return $e->die_event;
5219 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
5221 my $reactors = $e->json_query({
5223 atevdef => [{column => "reactor", transform => "distinct"}]
5225 from => {atevdef => {}}
5228 return $e->die_event unless ref $reactors eq "ARRAY";
5231 return [ map { $_->{reactor} } @$reactors ];
5234 __PACKAGE__->register_method(
5235 method => "filter_group_entry_crud",
5236 api_name => "open-ils.actor.filter_group_entry.crud",
5239 Provides CRUD access to filter group entry objects. These are not full accessible
5240 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
5241 are not accessible via PCRUD (because they have no fields against which to link perms)
5244 {desc => "Authentication token", type => "string"},
5245 {desc => "Entry ID / Entry Object", type => "number"},
5246 {desc => "Additional note text (optional)", type => "string"},
5247 {desc => "penalty org unit ID (optional, default to top of org tree)",
5251 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
5257 sub filter_group_entry_crud {
5258 my ($self, $conn, $auth, $arg) = @_;
5260 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
5261 my $e = new_editor(authtoken => $auth, xact => 1);
5262 return $e->die_event unless $e->checkauth;
5268 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
5269 or return $e->die_event;
5271 return $e->die_event unless $e->allowed(
5272 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
5274 my $query = $arg->query;
5275 $query = $e->create_actor_search_query($query) or return $e->die_event;
5276 $arg->query($query->id);
5277 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
5278 $entry->query($query);
5283 } elsif ($arg->ischanged) {
5285 my $entry = $e->retrieve_actor_search_filter_group_entry([
5288 flesh_fields => {asfge => ['grp']}
5290 ]) or return $e->die_event;
5292 return $e->die_event unless $e->allowed(
5293 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5295 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
5296 $arg->query($arg->query->id);
5297 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
5298 $arg->query($query);
5303 } elsif ($arg->isdeleted) {
5305 my $entry = $e->retrieve_actor_search_filter_group_entry([
5308 flesh_fields => {asfge => ['grp', 'query']}
5310 ]) or return $e->die_event;
5312 return $e->die_event unless $e->allowed(
5313 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5315 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5316 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5329 my $entry = $e->retrieve_actor_search_filter_group_entry([
5332 flesh_fields => {asfge => ['grp', 'query']}
5334 ]) or return $e->die_event;
5336 return $e->die_event unless $e->allowed(
5337 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5338 $entry->grp->owner);
5341 $entry->grp($entry->grp->id); # for consistency
5347 __PACKAGE__->register_method(
5348 method => 'user_billing_xacts',
5349 api_name => 'open-ils.actor.user.transactions.for_billing',
5351 desc => q/Returns a stream of user billing data appropriate for
5352 display in the user bills UI. API is natively "authoritative"./,
5354 {desc => 'Authentication token', type => 'string'},
5355 {desc => 'User ID', type => 'number'}
5358 desc => q/First response is the user money summary, following
5359 responses are fleshed billable transactions/
5364 sub user_billing_xacts {
5365 my ($self, $client, $auth, $user_id) = @_;
5367 my $e = new_editor(authtoken => $auth, xact => 1);
5368 return $e->die_event unless $e->checkauth;
5370 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
5372 return $e->die_event unless
5373 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
5375 # Start with the user summary.
5376 $client->respond($e->retrieve_money_user_summary($user_id));
5378 my $xact_ids = $e->json_query({
5379 select => {mbts => ['id']},
5383 balance_owed => {'<>' => 0}
5385 order_by => {mbts => {xact_start => 'asc'}}
5388 for my $xact_id (map { $_->{id} } @$xact_ids) {
5390 my $xact = $e->retrieve_money_billable_transaction([
5394 mbt => [qw/summary circulation grocery/],
5411 acn => [qw/record owning_lib prefix suffix/],
5412 bre => [qw/wide_display_entry/]
5414 # Avoid adding the MARXML
5415 # Fleshed fields are implicitly included.
5416 select => {bre => ['id']}
5420 $client->respond($xact);