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 OpenSRF::Utils 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::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use OpenILS::Utils::BadContact;
38 use List::Util qw/max reduce/;
40 use UUID::Tiny qw/:std/;
43 OpenILS::Application::Actor::Container->initialize();
44 OpenILS::Application::Actor::UserGroups->initialize();
45 OpenILS::Application::Actor::ClosedDates->initialize();
48 my $apputils = "OpenILS::Application::AppUtils";
51 sub _d { warn "Patron:\n" . Dumper(shift()); }
54 my $set_user_settings;
58 #__PACKAGE__->register_method(
59 # method => "allowed_test",
60 # api_name => "open-ils.actor.allowed_test",
63 # my($self, $conn, $auth, $orgid, $permcode) = @_;
64 # my $e = new_editor(authtoken => $auth);
65 # return $e->die_event unless $e->checkauth;
69 # permcode => $permcode,
70 # result => $e->allowed($permcode, $orgid)
74 __PACKAGE__->register_method(
75 method => "update_user_setting",
76 api_name => "open-ils.actor.patron.settings.update",
78 sub update_user_setting {
79 my($self, $conn, $auth, $user_id, $settings) = @_;
80 my $e = new_editor(xact => 1, authtoken => $auth);
81 return $e->die_event unless $e->checkauth;
83 $user_id = $e->requestor->id unless defined $user_id;
85 unless($e->requestor->id == $user_id) {
86 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
87 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
90 for my $name (keys %$settings) {
91 my $val = $$settings{$name};
92 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
95 $val = OpenSRF::Utils::JSON->perl2JSON($val);
98 $e->update_actor_user_setting($set) or return $e->die_event;
100 $set = Fieldmapper::actor::user_setting->new;
104 $e->create_actor_user_setting($set) or return $e->die_event;
107 $e->delete_actor_user_setting($set) or return $e->die_event;
116 __PACKAGE__->register_method(
117 method => "set_ou_settings",
118 api_name => "open-ils.actor.org_unit.settings.update",
120 desc => "Updates the value for a given org unit setting. The permission to update " .
121 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
122 "permission specified in the update_perm column of the config.org_unit_setting_type " .
123 "table's row corresponding to the setting being changed." ,
125 {desc => 'Authentication token', type => 'string'},
126 {desc => 'Org unit ID', type => 'number'},
127 {desc => 'Hash of setting name-value pairs', type => 'object'}
129 return => {desc => '1 on success, Event on error'}
133 sub set_ou_settings {
134 my( $self, $client, $auth, $org_id, $settings ) = @_;
136 my $e = new_editor(authtoken => $auth, xact => 1);
137 return $e->die_event unless $e->checkauth;
139 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
141 for my $name (keys %$settings) {
142 my $val = $$settings{$name};
144 my $type = $e->retrieve_config_org_unit_setting_type([
146 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
147 ]) or return $e->die_event;
148 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
150 # If there is no relevant permission, the default assumption will
151 # be, "no, the caller cannot change that value."
152 return $e->die_event unless ($all_allowed ||
153 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
156 $val = OpenSRF::Utils::JSON->perl2JSON($val);
159 $e->update_actor_org_unit_setting($set) or return $e->die_event;
161 $set = Fieldmapper::actor::org_unit_setting->new;
162 $set->org_unit($org_id);
165 $e->create_actor_org_unit_setting($set) or return $e->die_event;
168 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
176 __PACKAGE__->register_method(
177 method => "user_settings",
179 api_name => "open-ils.actor.patron.settings.retrieve",
182 my( $self, $client, $auth, $user_id, $setting ) = @_;
184 my $e = new_editor(authtoken => $auth);
185 return $e->event unless $e->checkauth;
186 $user_id = $e->requestor->id unless defined $user_id;
188 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
189 if($e->requestor->id != $user_id) {
190 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
194 my($e, $user_id, $setting) = @_;
195 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
196 return undef unless $val; # XXX this should really return undef, but needs testing
197 return OpenSRF::Utils::JSON->JSON2perl($val->value);
201 if(ref $setting eq 'ARRAY') {
203 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
206 return get_setting($e, $user_id, $setting);
209 my $s = $e->search_actor_user_setting({usr => $user_id});
210 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
215 __PACKAGE__->register_method(
216 method => "ranged_ou_settings",
217 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
219 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
220 "is implied for retrieving OU settings by the authenticated users' permissions.",
222 {desc => 'Authentication token', type => 'string'},
223 {desc => 'Org unit ID', type => 'number'},
225 return => {desc => 'A hashref of "ranged" settings, event on error'}
228 sub ranged_ou_settings {
229 my( $self, $client, $auth, $org_id ) = @_;
231 my $e = new_editor(authtoken => $auth);
232 return $e->event unless $e->checkauth;
235 my $org_list = $U->get_org_ancestors($org_id);
236 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
237 $org_list = [ reverse @$org_list ];
239 # start at the context org and capture the setting value
240 # without clobbering settings we've already captured
241 for my $this_org_id (@$org_list) {
243 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
245 for my $set (@sets) {
246 my $type = $e->retrieve_config_org_unit_setting_type([
248 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
251 # If there is no relevant permission, the default assumption will
252 # be, "yes, the caller can have that value."
253 if ($type && $type->view_perm) {
254 next if not $e->allowed($type->view_perm->code, $org_id);
257 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
258 unless defined $ranged_settings{$set->name};
262 return \%ranged_settings;
267 __PACKAGE__->register_method(
268 api_name => 'open-ils.actor.ou_setting.ancestor_default',
269 method => 'ou_ancestor_setting',
271 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
272 'This method will make sure that the given user has permission to view that setting, if there is a ' .
273 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
274 'the user lacks the permisssion, undef will be returned.' ,
276 { desc => 'Org unit ID', type => 'number' },
277 { desc => 'setting name', type => 'string' },
278 { desc => 'authtoken (optional)', type => 'string' }
280 return => {desc => 'A value for the org unit setting, or undef'}
284 # ------------------------------------------------------------------
285 # Attempts to find the org setting value for a given org. if not
286 # found at the requested org, searches up the org tree until it
287 # finds a parent that has the requested setting.
288 # when found, returns { org => $id, value => $value }
289 # otherwise, returns NULL
290 # ------------------------------------------------------------------
291 sub ou_ancestor_setting {
292 my( $self, $client, $orgid, $name, $auth ) = @_;
293 # Make sure $auth is set to something if not given.
295 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
298 __PACKAGE__->register_method(
299 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
300 method => 'ou_ancestor_setting_batch',
302 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
303 'This method will make sure that the given user has permission to view that setting, if there is a ' .
304 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
305 'the user lacks the permisssion, undef will be returned.' ,
307 { desc => 'Org unit ID', type => 'number' },
308 { desc => 'setting name list', type => 'array' },
309 { desc => 'authtoken (optional)', type => 'string' }
311 return => {desc => 'A hash with name => value pairs for the org unit settings'}
314 sub ou_ancestor_setting_batch {
315 my( $self, $client, $orgid, $name_list, $auth ) = @_;
317 # splitting the list of settings to fetch values
318 # so that ones that *don't* require view_perm checks
319 # can be fetched in one fell swoop, which is
320 # significantly faster in cases where a large
321 # number of settings need to be fetched.
322 my %perm_check_required = ();
323 my @perm_check_not_required = ();
325 # Note that ->ou_ancestor_setting also can check
326 # to see if the setting has a view_perm, but testing
327 # suggests that the redundant checks do not significantly
328 # increase the time it takes to fetch the values of
329 # permission-controlled settings.
330 my $e = new_editor();
331 my $res = $e->search_config_org_unit_setting_type({
333 view_perm => { "!=" => undef },
335 %perm_check_required = map { $_->name() => 1 } @$res;
336 foreach my $setting (@$name_list) {
337 push @perm_check_not_required, $setting
338 unless exists($perm_check_required{$setting});
342 if (@perm_check_not_required) {
343 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
345 $values{$_} = $U->ou_ancestor_setting(
348 ) for keys(%perm_check_required);
354 __PACKAGE__->register_method(
355 method => "update_patron",
356 api_name => "open-ils.actor.patron.update",
359 Update an existing user, or create a new one. Related objects,
360 like cards, addresses, survey responses, and stat cats,
361 can be updated by attaching them to the user object in their
362 respective fields. For examples, the billing address object
363 may be inserted into the 'billing_address' field, etc. For each
364 attached object, indicate if the object should be created,
365 updated, or deleted using the built-in 'isnew', 'ischanged',
366 and 'isdeleted' fields on the object.
369 { desc => 'Authentication token', type => 'string' },
370 { desc => 'Patron data object', type => 'object' }
372 return => {desc => 'A fleshed user object, event on error'}
377 my( $self, $client, $auth, $patron ) = @_;
379 my $e = new_editor(xact => 1, authtoken => $auth);
380 return $e->event unless $e->checkauth;
382 $logger->info($patron->isnew ? "Creating new patron..." :
383 "Updating Patron: " . $patron->id);
385 my $evt = check_group_perm($e, $e->requestor, $patron);
388 # $new_patron is the patron in progress. $patron is the original patron
389 # passed in with the method. new_patron will change as the components
390 # of patron are added/updated.
394 # unflesh the real items on the patron
395 $patron->card( $patron->card->id ) if(ref($patron->card));
396 $patron->billing_address( $patron->billing_address->id )
397 if(ref($patron->billing_address));
398 $patron->mailing_address( $patron->mailing_address->id )
399 if(ref($patron->mailing_address));
401 # create/update the patron first so we can use his id
403 # $patron is the obj from the client (new data) and $new_patron is the
404 # patron object properly built for db insertion, so we need a third variable
405 # if we want to represent the old patron.
408 my $barred_hook = '';
410 if($patron->isnew()) {
411 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
413 if($U->is_true($patron->barred)) {
414 return $e->die_event unless
415 $e->allowed('BAR_PATRON', $patron->home_ou);
418 $new_patron = $patron;
420 # Did auth checking above already.
421 $old_patron = $e->retrieve_actor_user($patron->id) or
422 return $e->die_event;
424 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
425 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
426 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
428 $barred_hook = $U->is_true($new_patron->barred) ?
429 'au.barred' : 'au.unbarred';
432 # update the password by itself to avoid the password protection magic
433 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
434 modify_migrated_user_password($e, $patron->id, $patron->passwd);
435 $new_patron->passwd(''); # subsequent update will set
436 # actor.usr.passwd to MD5('')
440 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
443 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
446 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
449 # re-update the patron if anything has happened to him during this process
450 if($new_patron->ischanged()) {
451 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
455 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
458 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
461 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
464 $evt = apply_invalid_addr_penalty($e, $patron);
469 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
471 $tses->request('open-ils.trigger.event.autocreate',
472 'au.create', $new_patron, $new_patron->home_ou);
474 $tses->request('open-ils.trigger.event.autocreate',
475 'au.update', $new_patron, $new_patron->home_ou);
477 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
478 $new_patron, $new_patron->home_ou) if $barred_hook;
481 $e->xact_begin; # $e->rollback is called in new_flesh_user
482 return flesh_user($new_patron->id(), $e);
485 sub apply_invalid_addr_penalty {
489 # grab the invalid address penalty if set
490 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
492 my ($addr_penalty) = grep
493 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
495 # do we enforce invalid address penalty
496 my $enforce = $U->ou_ancestor_setting_value(
497 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
499 my $addrs = $e->search_actor_user_address(
500 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
501 my $addr_count = scalar(@$addrs);
503 if($addr_count == 0 and $addr_penalty) {
505 # regardless of any settings, remove the penalty when the user has no invalid addresses
506 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
509 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
511 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
512 my $depth = $ptype->org_depth;
513 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
514 $ctx_org = $patron->home_ou unless defined $ctx_org;
516 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
517 $penalty->usr($patron->id);
518 $penalty->org_unit($ctx_org);
519 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
521 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
536 "standing_penalties",
544 push @$fields, "home_ou" if $home_ou;
545 return new_flesh_user($id, $fields, $e );
553 # clone and clear stuff that would break the database
557 my $new_patron = $patron->clone;
559 $new_patron->clear_billing_address();
560 $new_patron->clear_mailing_address();
561 $new_patron->clear_addresses();
562 $new_patron->clear_card();
563 $new_patron->clear_cards();
564 $new_patron->clear_id();
565 $new_patron->clear_isnew();
566 $new_patron->clear_ischanged();
567 $new_patron->clear_isdeleted();
568 $new_patron->clear_stat_cat_entries();
569 $new_patron->clear_permissions();
570 $new_patron->clear_standing_penalties();
581 return (undef, $e->die_event) unless
582 $e->allowed('CREATE_USER', $patron->home_ou);
584 my $ex = $e->search_actor_user(
585 {usrname => $patron->usrname}, {idlist => 1});
586 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
588 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
590 # do a dance to get the password hashed securely
591 my $saved_password = $patron->passwd;
593 $e->create_actor_user($patron) or return $e->die_event;
594 modify_migrated_user_password($e, $patron->id, $saved_password);
596 my $id = $patron->id; # added by CStoreEditor
598 $logger->info("Successfully created new user [$id] in DB");
599 return ($e->retrieve_actor_user($id), undef);
603 sub check_group_perm {
604 my( $e, $requestor, $patron ) = @_;
607 # first let's see if the requestor has
608 # priveleges to update this user in any way
609 if( ! $patron->isnew ) {
610 my $p = $e->retrieve_actor_user($patron->id);
612 # If we are the requestor (trying to update our own account)
613 # and we are not trying to change our profile, we're good
614 if( $p->id == $requestor->id and
615 $p->profile == $patron->profile ) {
620 $evt = group_perm_failed($e, $requestor, $p);
624 # They are allowed to edit this patron.. can they put the
625 # patron into the group requested?
626 $evt = group_perm_failed($e, $requestor, $patron);
632 sub group_perm_failed {
633 my( $e, $requestor, $patron ) = @_;
637 my $grpid = $patron->profile;
641 $logger->debug("user update looking for group perm for group $grpid");
642 $grp = $e->retrieve_permission_grp_tree($grpid);
644 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
646 $logger->info("user update checking perm $perm on user ".
647 $requestor->id." for update/create on user username=".$patron->usrname);
649 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
655 my( $e, $patron, $noperm) = @_;
657 $logger->info("Updating patron ".$patron->id." in DB");
662 return (undef, $e->die_event)
663 unless $e->allowed('UPDATE_USER', $patron->home_ou);
666 if(!$patron->ident_type) {
667 $patron->clear_ident_type;
668 $patron->clear_ident_value;
671 $evt = verify_last_xact($e, $patron);
672 return (undef, $evt) if $evt;
674 $e->update_actor_user($patron) or return (undef, $e->die_event);
676 # re-fetch the user to pick up the latest last_xact_id value
677 # to avoid collisions.
678 $patron = $e->retrieve_actor_user($patron->id);
683 sub verify_last_xact {
684 my( $e, $patron ) = @_;
685 return undef unless $patron->id and $patron->id > 0;
686 my $p = $e->retrieve_actor_user($patron->id);
687 my $xact = $p->last_xact_id;
688 return undef unless $xact;
689 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
690 return OpenILS::Event->new('XACT_COLLISION')
691 if $xact ne $patron->last_xact_id;
696 sub _check_dup_ident {
697 my( $session, $patron ) = @_;
699 return undef unless $patron->ident_value;
702 ident_type => $patron->ident_type,
703 ident_value => $patron->ident_value,
706 $logger->debug("patron update searching for dup ident values: " .
707 $patron->ident_type . ':' . $patron->ident_value);
709 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
711 my $dups = $session->request(
712 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
715 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
722 sub _add_update_addresses {
726 my $new_patron = shift;
730 my $current_id; # id of the address before creation
732 my $addresses = $patron->addresses();
734 for my $address (@$addresses) {
736 next unless ref $address;
737 $current_id = $address->id();
739 if( $patron->billing_address() and
740 $patron->billing_address() == $current_id ) {
741 $logger->info("setting billing addr to $current_id");
742 $new_patron->billing_address($address->id());
743 $new_patron->ischanged(1);
746 if( $patron->mailing_address() and
747 $patron->mailing_address() == $current_id ) {
748 $new_patron->mailing_address($address->id());
749 $logger->info("setting mailing addr to $current_id");
750 $new_patron->ischanged(1);
754 if($address->isnew()) {
756 $address->usr($new_patron->id());
758 ($address, $evt) = _add_address($e,$address);
759 return (undef, $evt) if $evt;
761 # we need to get the new id
762 if( $patron->billing_address() and
763 $patron->billing_address() == $current_id ) {
764 $new_patron->billing_address($address->id());
765 $logger->info("setting billing addr to $current_id");
766 $new_patron->ischanged(1);
769 if( $patron->mailing_address() and
770 $patron->mailing_address() == $current_id ) {
771 $new_patron->mailing_address($address->id());
772 $logger->info("setting mailing addr to $current_id");
773 $new_patron->ischanged(1);
776 } elsif($address->ischanged() ) {
778 ($address, $evt) = _update_address($e, $address);
779 return (undef, $evt) if $evt;
781 } elsif($address->isdeleted() ) {
783 if( $address->id() == $new_patron->mailing_address() ) {
784 $new_patron->clear_mailing_address();
785 ($new_patron, $evt) = _update_patron($e, $new_patron);
786 return (undef, $evt) if $evt;
789 if( $address->id() == $new_patron->billing_address() ) {
790 $new_patron->clear_billing_address();
791 ($new_patron, $evt) = _update_patron($e, $new_patron);
792 return (undef, $evt) if $evt;
795 $evt = _delete_address($e, $address);
796 return (undef, $evt) if $evt;
800 return ( $new_patron, undef );
804 # adds an address to the db and returns the address with new id
806 my($e, $address) = @_;
807 $address->clear_id();
809 $logger->info("Creating new address at street ".$address->street1);
811 # put the address into the database
812 $e->create_actor_user_address($address) or return (undef, $e->die_event);
813 return ($address, undef);
817 sub _update_address {
818 my( $e, $address ) = @_;
820 $logger->info("Updating address ".$address->id." in the DB");
822 $e->update_actor_user_address($address) or return (undef, $e->die_event);
824 return ($address, undef);
829 sub _add_update_cards {
833 my $new_patron = shift;
837 my $virtual_id; #id of the card before creation
839 my $cards = $patron->cards();
840 for my $card (@$cards) {
842 $card->usr($new_patron->id());
844 if(ref($card) and $card->isnew()) {
846 $virtual_id = $card->id();
847 ( $card, $evt ) = _add_card($e, $card);
848 return (undef, $evt) if $evt;
850 #if(ref($patron->card)) { $patron->card($patron->card->id); }
851 if($patron->card() == $virtual_id) {
852 $new_patron->card($card->id());
853 $new_patron->ischanged(1);
856 } elsif( ref($card) and $card->ischanged() ) {
857 $evt = _update_card($e, $card);
858 return (undef, $evt) if $evt;
862 return ( $new_patron, undef );
866 # adds an card to the db and returns the card with new id
868 my( $e, $card ) = @_;
871 $logger->info("Adding new patron card ".$card->barcode);
873 $e->create_actor_card($card) or return (undef, $e->die_event);
875 return ( $card, undef );
879 # returns event on error. returns undef otherwise
881 my( $e, $card ) = @_;
882 $logger->info("Updating patron card ".$card->id);
884 $e->update_actor_card($card) or return $e->die_event;
891 # returns event on error. returns undef otherwise
892 sub _delete_address {
893 my( $e, $address ) = @_;
895 $logger->info("Deleting address ".$address->id." from DB");
897 $e->delete_actor_user_address($address) or return $e->die_event;
903 sub _add_survey_responses {
904 my ($e, $patron, $new_patron) = @_;
906 $logger->info( "Updating survey responses for patron ".$new_patron->id );
908 my $responses = $patron->survey_responses;
912 $_->usr($new_patron->id) for (@$responses);
914 my $evt = $U->simplereq( "open-ils.circ",
915 "open-ils.circ.survey.submit.user_id", $responses );
917 return (undef, $evt) if defined($U->event_code($evt));
921 return ( $new_patron, undef );
924 sub _clear_badcontact_penalties {
925 my ($e, $old_patron, $new_patron) = @_;
927 return ($new_patron, undef) unless $old_patron;
929 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
931 # This ignores whether the caller of update_patron has any permission
932 # to remove penalties, but these penalties no longer make sense
933 # if an email address field (for example) is changed (and the caller must
934 # have perms to do *that*) so there's no reason not to clear the penalties.
936 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
938 "+csp" => {"name" => [values(%$PNM)]},
939 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
941 "join" => {"csp" => {}},
943 "flesh_fields" => {"ausp" => ["standing_penalty"]}
945 ]) or return (undef, $e->die_event);
947 return ($new_patron, undef) unless @$bad_contact_penalties;
949 my @penalties_to_clear;
950 my ($field, $penalty_name);
952 # For each field that might have an associated bad contact penalty,
953 # check for such penalties and add them to the to-clear list if that
955 while (($field, $penalty_name) = each(%$PNM)) {
956 if ($old_patron->$field ne $new_patron->$field) {
957 push @penalties_to_clear, grep {
958 $_->standing_penalty->name eq $penalty_name
959 } @$bad_contact_penalties;
963 foreach (@penalties_to_clear) {
964 # Note that this "archives" penalties, in the terminology of the staff
965 # client, instead of just deleting them. This may assist reporting,
966 # or preserving old contact information when it is still potentially
968 $_->standing_penalty($_->standing_penalty->id); # deflesh
969 $_->stop_date('now');
970 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
973 return ($new_patron, undef);
977 sub _create_stat_maps {
979 my($e, $patron, $new_patron) = @_;
981 my $maps = $patron->stat_cat_entries();
983 for my $map (@$maps) {
985 my $method = "update_actor_stat_cat_entry_user_map";
987 if ($map->isdeleted()) {
988 $method = "delete_actor_stat_cat_entry_user_map";
990 } elsif ($map->isnew()) {
991 $method = "create_actor_stat_cat_entry_user_map";
996 $map->target_usr($new_patron->id);
998 $logger->info("Updating stat entry with method $method and map $map");
1000 $e->$method($map) or return (undef, $e->die_event);
1003 return ($new_patron, undef);
1006 sub _create_perm_maps {
1008 my($e, $patron, $new_patron) = @_;
1010 my $maps = $patron->permissions;
1012 for my $map (@$maps) {
1014 my $method = "update_permission_usr_perm_map";
1015 if ($map->isdeleted()) {
1016 $method = "delete_permission_usr_perm_map";
1017 } elsif ($map->isnew()) {
1018 $method = "create_permission_usr_perm_map";
1022 $map->usr($new_patron->id);
1024 $logger->info( "Updating permissions with method $method and map $map" );
1026 $e->$method($map) or return (undef, $e->die_event);
1029 return ($new_patron, undef);
1033 __PACKAGE__->register_method(
1034 method => "set_user_work_ous",
1035 api_name => "open-ils.actor.user.work_ous.update",
1038 sub set_user_work_ous {
1044 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1045 return $evt if $evt;
1047 my $session = $apputils->start_db_session();
1048 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1050 for my $map (@$maps) {
1052 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1053 if ($map->isdeleted()) {
1054 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1055 } elsif ($map->isnew()) {
1056 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1060 #warn( "Updating permissions with method $method and session $ses and map $map" );
1061 $logger->info( "Updating work_ou map with method $method and map $map" );
1063 my $stat = $session->request($method, $map)->gather(1);
1064 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1068 $apputils->commit_db_session($session);
1070 return scalar(@$maps);
1074 __PACKAGE__->register_method(
1075 method => "set_user_perms",
1076 api_name => "open-ils.actor.user.permissions.update",
1079 sub set_user_perms {
1085 my $session = $apputils->start_db_session();
1087 my( $user_obj, $evt ) = $U->checkses($ses);
1088 return $evt if $evt;
1089 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1091 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1094 $all = 1 if ($U->is_true($user_obj->super_user()));
1095 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1097 for my $map (@$maps) {
1099 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1100 if ($map->isdeleted()) {
1101 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1102 } elsif ($map->isnew()) {
1103 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1107 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1108 #warn( "Updating permissions with method $method and session $ses and map $map" );
1109 $logger->info( "Updating permissions with method $method and map $map" );
1111 my $stat = $session->request($method, $map)->gather(1);
1112 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1116 $apputils->commit_db_session($session);
1118 return scalar(@$maps);
1122 __PACKAGE__->register_method(
1123 method => "user_retrieve_by_barcode",
1125 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1127 sub user_retrieve_by_barcode {
1128 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1130 my $e = new_editor(authtoken => $auth);
1131 return $e->event unless $e->checkauth;
1133 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1134 or return $e->event;
1136 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1137 return $e->event unless $e->allowed(
1138 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1145 __PACKAGE__->register_method(
1146 method => "get_user_by_id",
1148 api_name => "open-ils.actor.user.retrieve",
1151 sub get_user_by_id {
1152 my ($self, $client, $auth, $id) = @_;
1153 my $e = new_editor(authtoken=>$auth);
1154 return $e->event unless $e->checkauth;
1155 my $user = $e->retrieve_actor_user($id) or return $e->event;
1156 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1161 __PACKAGE__->register_method(
1162 method => "get_org_types",
1163 api_name => "open-ils.actor.org_types.retrieve",
1166 return $U->get_org_types();
1170 __PACKAGE__->register_method(
1171 method => "get_user_ident_types",
1172 api_name => "open-ils.actor.user.ident_types.retrieve",
1175 sub get_user_ident_types {
1176 return $ident_types if $ident_types;
1177 return $ident_types =
1178 new_editor()->retrieve_all_config_identification_type();
1182 __PACKAGE__->register_method(
1183 method => "get_org_unit",
1184 api_name => "open-ils.actor.org_unit.retrieve",
1188 my( $self, $client, $user_session, $org_id ) = @_;
1189 my $e = new_editor(authtoken => $user_session);
1191 return $e->event unless $e->checkauth;
1192 $org_id = $e->requestor->ws_ou;
1194 my $o = $e->retrieve_actor_org_unit($org_id)
1195 or return $e->event;
1199 __PACKAGE__->register_method(
1200 method => "search_org_unit",
1201 api_name => "open-ils.actor.org_unit_list.search",
1204 sub search_org_unit {
1206 my( $self, $client, $field, $value ) = @_;
1208 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1210 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1211 { $field => $value } );
1217 # build the org tree
1219 __PACKAGE__->register_method(
1220 method => "get_org_tree",
1221 api_name => "open-ils.actor.org_tree.retrieve",
1223 note => "Returns the entire org tree structure",
1229 return $U->get_org_tree($client->session->session_locale);
1233 __PACKAGE__->register_method(
1234 method => "get_org_descendants",
1235 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1238 # depth is optional. org_unit is the id
1239 sub get_org_descendants {
1240 my( $self, $client, $org_unit, $depth ) = @_;
1242 if(ref $org_unit eq 'ARRAY') {
1245 for my $i (0..scalar(@$org_unit)-1) {
1246 my $list = $U->simple_scalar_request(
1248 "open-ils.storage.actor.org_unit.descendants.atomic",
1249 $org_unit->[$i], $depth->[$i] );
1250 push(@trees, $U->build_org_tree($list));
1255 my $orglist = $apputils->simple_scalar_request(
1257 "open-ils.storage.actor.org_unit.descendants.atomic",
1258 $org_unit, $depth );
1259 return $U->build_org_tree($orglist);
1264 __PACKAGE__->register_method(
1265 method => "get_org_ancestors",
1266 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1269 # depth is optional. org_unit is the id
1270 sub get_org_ancestors {
1271 my( $self, $client, $org_unit, $depth ) = @_;
1272 my $orglist = $apputils->simple_scalar_request(
1274 "open-ils.storage.actor.org_unit.ancestors.atomic",
1275 $org_unit, $depth );
1276 return $U->build_org_tree($orglist);
1280 __PACKAGE__->register_method(
1281 method => "get_standings",
1282 api_name => "open-ils.actor.standings.retrieve"
1287 return $user_standings if $user_standings;
1288 return $user_standings =
1289 $apputils->simple_scalar_request(
1291 "open-ils.cstore.direct.config.standing.search.atomic",
1292 { id => { "!=" => undef } }
1297 __PACKAGE__->register_method(
1298 method => "get_my_org_path",
1299 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1302 sub get_my_org_path {
1303 my( $self, $client, $auth, $org_id ) = @_;
1304 my $e = new_editor(authtoken=>$auth);
1305 return $e->event unless $e->checkauth;
1306 $org_id = $e->requestor->ws_ou unless defined $org_id;
1308 return $apputils->simple_scalar_request(
1310 "open-ils.storage.actor.org_unit.full_path.atomic",
1315 __PACKAGE__->register_method(
1316 method => "patron_adv_search",
1317 api_name => "open-ils.actor.patron.search.advanced"
1320 __PACKAGE__->register_method(
1321 method => "patron_adv_search",
1322 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1324 # TODO: change when opensrf 'bundling' is merged.
1325 # set a relatively small bundle size so the caller can start
1326 # seeing results fairly quickly
1327 max_chunk_size => 4096, # bundling
1330 # pending opensrf work -- also, not sure if needed since we're not
1331 # actaully creating an alternate vesrion, only offering to return a
1335 desc => q/Returns a stream of fleshed user objects instead of
1336 a pile of identifiers/
1340 sub patron_adv_search {
1341 my( $self, $client, $auth, $search_hash, $search_limit,
1342 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1344 # API params sanity checks.
1345 # Exit early with empty result if no filter exists.
1346 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1347 my $fleshed = ($self->api_name =~ /fleshed/);
1348 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1350 for my $key (keys %$search_hash) {
1351 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1355 return ($fleshed ? undef : []) unless $search_ok;
1357 my $e = new_editor(authtoken=>$auth);
1358 return $e->event unless $e->checkauth;
1359 return $e->event unless $e->allowed('VIEW_USER');
1361 # depth boundary outside of which patrons must opt-in, default to 0
1362 my $opt_boundary = 0;
1363 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1365 if (not defined $search_ou) {
1366 my $depth = $U->ou_ancestor_setting_value(
1367 $e->requestor->ws_ou,
1368 'circ.patron_edit.duplicate_patron_check_depth'
1371 if (defined $depth) {
1372 $search_ou = $U->org_unit_ancestor_at_depth(
1373 $e->requestor->ws_ou, $depth
1378 my $ids = $U->storagereq(
1379 "open-ils.storage.actor.user.crazy_search", $search_hash,
1380 $search_limit, $search_sort, $include_inactive,
1381 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1383 return $ids unless $self->api_name =~ /fleshed/;
1385 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1391 # A migrated (main) password has the form:
1392 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1393 sub modify_migrated_user_password {
1394 my ($e, $user_id, $passwd) = @_;
1396 # new password gets a new salt
1397 my $new_salt = $e->json_query({
1398 from => ['actor.create_salt', 'main']})->[0];
1399 $new_salt = $new_salt->{'actor.create_salt'};
1406 md5_hex($new_salt . md5_hex($passwd)),
1414 __PACKAGE__->register_method(
1415 method => "update_passwd",
1416 api_name => "open-ils.actor.user.password.update",
1418 desc => "Update the operator's password",
1420 { desc => 'Authentication token', type => 'string' },
1421 { desc => 'New password', type => 'string' },
1422 { desc => 'Current password', type => 'string' }
1424 return => {desc => '1 on success, Event on error or incorrect current password'}
1428 __PACKAGE__->register_method(
1429 method => "update_passwd",
1430 api_name => "open-ils.actor.user.username.update",
1432 desc => "Update the operator's username",
1434 { desc => 'Authentication token', type => 'string' },
1435 { desc => 'New username', type => 'string' },
1436 { desc => 'Current password', type => 'string' }
1438 return => {desc => '1 on success, Event on error or incorrect current password'}
1442 __PACKAGE__->register_method(
1443 method => "update_passwd",
1444 api_name => "open-ils.actor.user.email.update",
1446 desc => "Update the operator's email address",
1448 { desc => 'Authentication token', type => 'string' },
1449 { desc => 'New email address', type => 'string' },
1450 { desc => 'Current password', type => 'string' }
1452 return => {desc => '1 on success, Event on error or incorrect current password'}
1457 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1458 my $e = new_editor(xact=>1, authtoken=>$auth);
1459 return $e->die_event unless $e->checkauth;
1461 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1462 or return $e->die_event;
1463 my $api = $self->api_name;
1465 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1467 return new OpenILS::Event('INCORRECT_PASSWORD');
1470 if( $api =~ /password/o ) {
1471 # NOTE: with access to the plain text password we could crypt
1472 # the password without the extra MD5 pre-hashing. Other changes
1473 # would be required. Noting here for future reference.
1474 modify_migrated_user_password($e, $db_user->id, $new_val);
1475 $db_user->passwd('');
1479 # if we don't clear the password, the user will be updated with
1480 # a hashed version of the hashed version of their password
1481 $db_user->clear_passwd;
1483 if( $api =~ /username/o ) {
1485 # make sure no one else has this username
1486 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1489 return new OpenILS::Event('USERNAME_EXISTS');
1491 $db_user->usrname($new_val);
1493 } elsif( $api =~ /email/o ) {
1494 $db_user->email($new_val);
1498 $e->update_actor_user($db_user) or return $e->die_event;
1501 # update the cached user to pick up these changes
1502 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1508 __PACKAGE__->register_method(
1509 method => "check_user_perms",
1510 api_name => "open-ils.actor.user.perm.check",
1511 notes => <<" NOTES");
1512 Takes a login session, user id, an org id, and an array of perm type strings. For each
1513 perm type, if the user does *not* have the given permission it is added
1514 to a list which is returned from the method. If all permissions
1515 are allowed, an empty list is returned
1516 if the logged in user does not match 'user_id', then the logged in user must
1517 have VIEW_PERMISSION priveleges.
1520 sub check_user_perms {
1521 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1523 my( $staff, $evt ) = $apputils->checkses($login_session);
1524 return $evt if $evt;
1526 if($staff->id ne $user_id) {
1527 if( $evt = $apputils->check_perms(
1528 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1534 for my $perm (@$perm_types) {
1535 if($apputils->check_perms($user_id, $org_id, $perm)) {
1536 push @not_allowed, $perm;
1540 return \@not_allowed
1543 __PACKAGE__->register_method(
1544 method => "check_user_perms2",
1545 api_name => "open-ils.actor.user.perm.check.multi_org",
1547 Checks the permissions on a list of perms and orgs for a user
1548 @param authtoken The login session key
1549 @param user_id The id of the user to check
1550 @param orgs The array of org ids
1551 @param perms The array of permission names
1552 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1553 if the logged in user does not match 'user_id', then the logged in user must
1554 have VIEW_PERMISSION priveleges.
1557 sub check_user_perms2 {
1558 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1560 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1561 $authtoken, $user_id, 'VIEW_PERMISSION' );
1562 return $evt if $evt;
1565 for my $org (@$orgs) {
1566 for my $perm (@$perms) {
1567 if($apputils->check_perms($user_id, $org, $perm)) {
1568 push @not_allowed, [ $org, $perm ];
1573 return \@not_allowed
1577 __PACKAGE__->register_method(
1578 method => 'check_user_perms3',
1579 api_name => 'open-ils.actor.user.perm.highest_org',
1581 Returns the highest org unit id at which a user has a given permission
1582 If the requestor does not match the target user, the requestor must have
1583 'VIEW_PERMISSION' rights at the home org unit of the target user
1584 @param authtoken The login session key
1585 @param userid The id of the user in question
1586 @param perm The permission to check
1587 @return The org unit highest in the org tree within which the user has
1588 the requested permission
1591 sub check_user_perms3 {
1592 my($self, $client, $authtoken, $user_id, $perm) = @_;
1593 my $e = new_editor(authtoken=>$authtoken);
1594 return $e->event unless $e->checkauth;
1596 my $tree = $U->get_org_tree();
1598 unless($e->requestor->id == $user_id) {
1599 my $user = $e->retrieve_actor_user($user_id)
1600 or return $e->event;
1601 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1602 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1605 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1608 __PACKAGE__->register_method(
1609 method => 'user_has_work_perm_at',
1610 api_name => 'open-ils.actor.user.has_work_perm_at',
1614 Returns a set of org unit IDs which represent the highest orgs in
1615 the org tree where the user has the requested permission. The
1616 purpose of this method is to return the smallest set of org units
1617 which represent the full expanse of the user's ability to perform
1618 the requested action. The user whose perms this method should
1619 check is implied by the authtoken. /,
1621 {desc => 'authtoken', type => 'string'},
1622 {desc => 'permission name', type => 'string'},
1623 {desc => q/user id, optional. If present, check perms for
1624 this user instead of the logged in user/, type => 'number'},
1626 return => {desc => 'An array of org IDs'}
1630 sub user_has_work_perm_at {
1631 my($self, $conn, $auth, $perm, $user_id) = @_;
1632 my $e = new_editor(authtoken=>$auth);
1633 return $e->event unless $e->checkauth;
1634 if(defined $user_id) {
1635 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1636 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1638 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1641 __PACKAGE__->register_method(
1642 method => 'user_has_work_perm_at_batch',
1643 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1647 sub user_has_work_perm_at_batch {
1648 my($self, $conn, $auth, $perms, $user_id) = @_;
1649 my $e = new_editor(authtoken=>$auth);
1650 return $e->event unless $e->checkauth;
1651 if(defined $user_id) {
1652 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1653 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1656 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1662 __PACKAGE__->register_method(
1663 method => 'check_user_perms4',
1664 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1666 Returns the highest org unit id at which a user has a given permission
1667 If the requestor does not match the target user, the requestor must have
1668 'VIEW_PERMISSION' rights at the home org unit of the target user
1669 @param authtoken The login session key
1670 @param userid The id of the user in question
1671 @param perms An array of perm names to check
1672 @return An array of orgId's representing the org unit
1673 highest in the org tree within which the user has the requested permission
1674 The arrah of orgId's has matches the order of the perms array
1677 sub check_user_perms4 {
1678 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1680 my( $staff, $target, $org, $evt );
1682 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1683 $authtoken, $userid, 'VIEW_PERMISSION' );
1684 return $evt if $evt;
1687 return [] unless ref($perms);
1688 my $tree = $U->get_org_tree();
1690 for my $p (@$perms) {
1691 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1697 __PACKAGE__->register_method(
1698 method => "user_fines_summary",
1699 api_name => "open-ils.actor.user.fines.summary",
1702 desc => 'Returns a short summary of the users total open fines, ' .
1703 'excluding voided fines Params are login_session, user_id' ,
1705 {desc => 'Authentication token', type => 'string'},
1706 {desc => 'User ID', type => 'string'} # number?
1709 desc => "a 'mous' object, event on error",
1714 sub user_fines_summary {
1715 my( $self, $client, $auth, $user_id ) = @_;
1717 my $e = new_editor(authtoken=>$auth);
1718 return $e->event unless $e->checkauth;
1720 if( $user_id ne $e->requestor->id ) {
1721 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1722 return $e->event unless
1723 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1726 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1730 __PACKAGE__->register_method(
1731 method => "user_opac_vitals",
1732 api_name => "open-ils.actor.user.opac.vital_stats",
1736 desc => 'Returns a short summary of the users vital stats, including ' .
1737 'identification information, accumulated balance, number of holds, ' .
1738 'and current open circulation stats' ,
1740 {desc => 'Authentication token', type => 'string'},
1741 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1744 desc => "An object with four properties: user, fines, checkouts and holds."
1749 sub user_opac_vitals {
1750 my( $self, $client, $auth, $user_id ) = @_;
1752 my $e = new_editor(authtoken=>$auth);
1753 return $e->event unless $e->checkauth;
1755 $user_id ||= $e->requestor->id;
1757 my $user = $e->retrieve_actor_user( $user_id );
1760 ->method_lookup('open-ils.actor.user.fines.summary')
1761 ->run($auth => $user_id);
1762 return $fines if (defined($U->event_code($fines)));
1765 $fines = new Fieldmapper::money::open_user_summary ();
1766 $fines->balance_owed(0.00);
1767 $fines->total_owed(0.00);
1768 $fines->total_paid(0.00);
1769 $fines->usr($user_id);
1773 ->method_lookup('open-ils.actor.user.hold_requests.count')
1774 ->run($auth => $user_id);
1775 return $holds if (defined($U->event_code($holds)));
1778 ->method_lookup('open-ils.actor.user.checked_out.count')
1779 ->run($auth => $user_id);
1780 return $out if (defined($U->event_code($out)));
1782 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1784 my $unread_msgs = $e->search_actor_usr_message([
1785 {usr => $user_id, read_date => undef, deleted => 'f'},
1791 first_given_name => $user->first_given_name,
1792 second_given_name => $user->second_given_name,
1793 family_name => $user->family_name,
1794 alias => $user->alias,
1795 usrname => $user->usrname
1797 fines => $fines->to_bare_hash,
1800 messages => { unread => scalar(@$unread_msgs) }
1805 ##### a small consolidation of related method registrations
1806 my $common_params = [
1807 { desc => 'Authentication token', type => 'string' },
1808 { desc => 'User ID', type => 'string' },
1809 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1810 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1813 'open-ils.actor.user.transactions' => '',
1814 'open-ils.actor.user.transactions.fleshed' => '',
1815 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1816 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1817 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1818 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1821 foreach (keys %methods) {
1823 method => "user_transactions",
1826 desc => 'For a given user, retrieve a list of '
1827 . (/\.fleshed/ ? 'fleshed ' : '')
1828 . 'transactions' . $methods{$_}
1829 . ' optionally limited to transactions of a given type.',
1830 params => $common_params,
1832 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1833 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1837 $args{authoritative} = 1;
1838 __PACKAGE__->register_method(%args);
1841 # Now for the counts
1843 'open-ils.actor.user.transactions.count' => '',
1844 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1845 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1848 foreach (keys %methods) {
1850 method => "user_transactions",
1853 desc => 'For a given user, retrieve a count of open '
1854 . 'transactions' . $methods{$_}
1855 . ' optionally limited to transactions of a given type.',
1856 params => $common_params,
1857 return => { desc => "Integer count of transactions, or event on error" }
1860 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1861 __PACKAGE__->register_method(%args);
1864 __PACKAGE__->register_method(
1865 method => "user_transactions",
1866 api_name => "open-ils.actor.user.transactions.have_balance.total",
1869 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1870 . ' optionally limited to transactions of a given type.',
1871 params => $common_params,
1872 return => { desc => "Decimal balance value, or event on error" }
1877 sub user_transactions {
1878 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1881 my $e = new_editor(authtoken => $auth);
1882 return $e->event unless $e->checkauth;
1884 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1886 return $e->event unless
1887 $e->requestor->id == $user_id or
1888 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1890 my $api = $self->api_name();
1892 my $filter = ($api =~ /have_balance/o) ?
1893 { 'balance_owed' => { '<>' => 0 } }:
1894 { 'total_owed' => { '>' => 0 } };
1896 my $method = 'open-ils.actor.user.transactions.history.still_open';
1897 $method = "$method.authoritative" if $api =~ /authoritative/;
1898 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1900 if($api =~ /total/o) {
1902 $total += $_->balance_owed for @$trans;
1906 ($api =~ /count/o ) and return scalar @$trans;
1907 ($api !~ /fleshed/o) and return $trans;
1910 for my $t (@$trans) {
1912 if( $t->xact_type ne 'circulation' ) {
1913 push @resp, {transaction => $t};
1917 my $circ_data = flesh_circ($e, $t->id);
1918 push @resp, {transaction => $t, %$circ_data};
1925 __PACKAGE__->register_method(
1926 method => "user_transaction_retrieve",
1927 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1930 notes => "Returns a fleshed transaction record"
1933 __PACKAGE__->register_method(
1934 method => "user_transaction_retrieve",
1935 api_name => "open-ils.actor.user.transaction.retrieve",
1938 notes => "Returns a transaction record"
1941 sub user_transaction_retrieve {
1942 my($self, $client, $auth, $bill_id) = @_;
1944 my $e = new_editor(authtoken => $auth);
1945 return $e->event unless $e->checkauth;
1947 my $trans = $e->retrieve_money_billable_transaction_summary(
1948 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1950 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1952 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1954 return $trans unless $self->api_name =~ /flesh/;
1955 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1957 my $circ_data = flesh_circ($e, $trans->id, 1);
1959 return {transaction => $trans, %$circ_data};
1964 my $circ_id = shift;
1965 my $flesh_copy = shift;
1967 my $circ = $e->retrieve_action_circulation([
1971 circ => ['target_copy'],
1972 acp => ['call_number'],
1979 my $copy = $circ->target_copy;
1981 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1982 $mods = new Fieldmapper::metabib::virtual_record;
1983 $mods->doc_id(OILS_PRECAT_RECORD);
1984 $mods->title($copy->dummy_title);
1985 $mods->author($copy->dummy_author);
1988 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1992 $circ->target_copy($circ->target_copy->id);
1993 $copy->call_number($copy->call_number->id);
1995 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1999 __PACKAGE__->register_method(
2000 method => "hold_request_count",
2001 api_name => "open-ils.actor.user.hold_requests.count",
2005 Returns hold ready vs. total counts.
2006 If a context org unit is provided, a third value
2007 is returned with key 'behind_desk', which reports
2008 how many holds are ready at the pickup library
2009 with the behind_desk flag set to true.
2013 sub hold_request_count {
2014 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2015 my $e = new_editor(authtoken => $authtoken);
2016 return $e->event unless $e->checkauth;
2018 $user_id = $e->requestor->id unless defined $user_id;
2020 if($e->requestor->id ne $user_id) {
2021 my $user = $e->retrieve_actor_user($user_id);
2022 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2025 my $holds = $e->json_query({
2026 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2030 fulfillment_time => {"=" => undef },
2031 cancel_time => undef,
2036 $_->{current_shelf_lib} and # avoid undef warnings
2037 $_->{pickup_lib} eq $_->{current_shelf_lib}
2041 total => scalar(@$holds),
2042 ready => scalar(@ready)
2046 # count of holds ready at pickup lib with behind_desk true.
2047 $resp->{behind_desk} = scalar(
2049 $_->{pickup_lib} == $ctx_org and
2050 $U->is_true($_->{behind_desk})
2058 __PACKAGE__->register_method(
2059 method => "checked_out",
2060 api_name => "open-ils.actor.user.checked_out",
2064 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2065 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2066 . "(i.e., outstanding balance or some other pending action on the circ). "
2067 . "The .count method also includes a 'total' field which sums all open circs.",
2069 { desc => 'Authentication Token', type => 'string'},
2070 { desc => 'User ID', type => 'string'},
2073 desc => 'Returns event on error, or an object with ID lists, like: '
2074 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2079 __PACKAGE__->register_method(
2080 method => "checked_out",
2081 api_name => "open-ils.actor.user.checked_out.count",
2084 signature => q/@see open-ils.actor.user.checked_out/
2088 my( $self, $conn, $auth, $userid ) = @_;
2090 my $e = new_editor(authtoken=>$auth);
2091 return $e->event unless $e->checkauth;
2093 if( $userid ne $e->requestor->id ) {
2094 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2095 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2097 # see if there is a friend link allowing circ.view perms
2098 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2099 $e, $userid, $e->requestor->id, 'circ.view');
2100 return $e->event unless $allowed;
2104 my $count = $self->api_name =~ /count/;
2105 return _checked_out( $count, $e, $userid );
2109 my( $iscount, $e, $userid ) = @_;
2115 claims_returned => [],
2118 my $meth = 'retrieve_action_open_circ_';
2126 claims_returned => 0,
2133 my $data = $e->$meth($userid);
2137 $result{$_} += $data->$_() for (keys %result);
2138 $result{total} += $data->$_() for (keys %result);
2140 for my $k (keys %result) {
2141 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2151 __PACKAGE__->register_method(
2152 method => "checked_in_with_fines",
2153 api_name => "open-ils.actor.user.checked_in_with_fines",
2156 signature => q/@see open-ils.actor.user.checked_out/
2159 sub checked_in_with_fines {
2160 my( $self, $conn, $auth, $userid ) = @_;
2162 my $e = new_editor(authtoken=>$auth);
2163 return $e->event unless $e->checkauth;
2165 if( $userid ne $e->requestor->id ) {
2166 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2169 # money is owed on these items and they are checked in
2170 my $open = $e->search_action_circulation(
2173 xact_finish => undef,
2174 checkin_time => { "!=" => undef },
2179 my( @lost, @cr, @lo );
2180 for my $c (@$open) {
2181 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2182 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2183 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2188 claims_returned => \@cr,
2189 long_overdue => \@lo
2195 my ($api, $desc, $auth) = @_;
2196 $desc = $desc ? (" " . $desc) : '';
2197 my $ids = ($api =~ /ids$/) ? 1 : 0;
2200 method => "user_transaction_history",
2201 api_name => "open-ils.actor.user.transactions.$api",
2203 desc => "For a given User ID, returns a list of billable transaction" .
2204 ($ids ? " id" : '') .
2205 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2206 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2208 {desc => 'Authentication token', type => 'string'},
2209 {desc => 'User ID', type => 'number'},
2210 {desc => 'Transaction type (optional)', type => 'number'},
2211 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2214 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2218 $auth and push @sig, (authoritative => 1);
2222 my %auth_hist_methods = (
2224 'history.have_charge' => 'that have an initial charge',
2225 'history.still_open' => 'that are not finished',
2226 'history.have_balance' => 'that have a balance',
2227 'history.have_bill' => 'that have billings',
2228 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2229 'history.have_payment' => 'that have at least 1 payment',
2232 foreach (keys %auth_hist_methods) {
2233 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2234 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2235 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2238 sub user_transaction_history {
2239 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2243 my $e = new_editor(authtoken=>$auth);
2244 return $e->die_event unless $e->checkauth;
2246 if ($e->requestor->id ne $userid) {
2247 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2250 my $api = $self->api_name;
2251 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2253 if(defined($type)) {
2254 $filter->{'xact_type'} = $type;
2257 if($api =~ /have_bill_or_payment/o) {
2259 # transactions that have a non-zero sum across all billings or at least 1 payment
2260 $filter->{'-or'} = {
2261 'balance_owed' => { '<>' => 0 },
2262 'last_payment_ts' => { '<>' => undef }
2265 } elsif($api =~ /have_payment/) {
2267 $filter->{last_payment_ts} ||= {'<>' => undef};
2269 } elsif( $api =~ /have_balance/o) {
2271 # transactions that have a non-zero overall balance
2272 $filter->{'balance_owed'} = { '<>' => 0 };
2274 } elsif( $api =~ /have_charge/o) {
2276 # transactions that have at least 1 billing, regardless of whether it was voided
2277 $filter->{'last_billing_ts'} = { '<>' => undef };
2279 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2281 # transactions that have non-zero sum across all billings. This will exclude
2282 # xacts where all billings have been voided
2283 $filter->{'total_owed'} = { '<>' => 0 };
2286 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2287 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2288 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2290 my $mbts = $e->search_money_billable_transaction_summary(
2291 [ { usr => $userid, @xact_finish, %$filter },
2296 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2297 return $mbts unless $api =~ /fleshed/;
2300 for my $t (@$mbts) {
2302 if( $t->xact_type ne 'circulation' ) {
2303 push @resp, {transaction => $t};
2307 my $circ_data = flesh_circ($e, $t->id);
2308 push @resp, {transaction => $t, %$circ_data};
2316 __PACKAGE__->register_method(
2317 method => "user_perms",
2318 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2320 notes => "Returns a list of permissions"
2324 my( $self, $client, $authtoken, $user ) = @_;
2326 my( $staff, $evt ) = $apputils->checkses($authtoken);
2327 return $evt if $evt;
2329 $user ||= $staff->id;
2331 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2335 return $apputils->simple_scalar_request(
2337 "open-ils.storage.permission.user_perms.atomic",
2341 __PACKAGE__->register_method(
2342 method => "retrieve_perms",
2343 api_name => "open-ils.actor.permissions.retrieve",
2344 notes => "Returns a list of permissions"
2346 sub retrieve_perms {
2347 my( $self, $client ) = @_;
2348 return $apputils->simple_scalar_request(
2350 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2351 { id => { '!=' => undef } }
2355 __PACKAGE__->register_method(
2356 method => "retrieve_groups",
2357 api_name => "open-ils.actor.groups.retrieve",
2358 notes => "Returns a list of user groups"
2360 sub retrieve_groups {
2361 my( $self, $client ) = @_;
2362 return new_editor()->retrieve_all_permission_grp_tree();
2365 __PACKAGE__->register_method(
2366 method => "retrieve_org_address",
2367 api_name => "open-ils.actor.org_unit.address.retrieve",
2368 notes => <<' NOTES');
2369 Returns an org_unit address by ID
2370 @param An org_address ID
2372 sub retrieve_org_address {
2373 my( $self, $client, $id ) = @_;
2374 return $apputils->simple_scalar_request(
2376 "open-ils.cstore.direct.actor.org_address.retrieve",
2381 __PACKAGE__->register_method(
2382 method => "retrieve_groups_tree",
2383 api_name => "open-ils.actor.groups.tree.retrieve",
2384 notes => "Returns a list of user groups"
2387 sub retrieve_groups_tree {
2388 my( $self, $client ) = @_;
2389 return new_editor()->search_permission_grp_tree(
2394 flesh_fields => { pgt => ["children"] },
2395 order_by => { pgt => 'name'}
2402 __PACKAGE__->register_method(
2403 method => "add_user_to_groups",
2404 api_name => "open-ils.actor.user.set_groups",
2405 notes => "Adds a user to one or more permission groups"
2408 sub add_user_to_groups {
2409 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2411 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2412 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2413 return $evt if $evt;
2415 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2416 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2417 return $evt if $evt;
2419 $apputils->simplereq(
2421 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2423 for my $group (@$groups) {
2424 my $link = Fieldmapper::permission::usr_grp_map->new;
2426 $link->usr($userid);
2428 my $id = $apputils->simplereq(
2430 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2436 __PACKAGE__->register_method(
2437 method => "get_user_perm_groups",
2438 api_name => "open-ils.actor.user.get_groups",
2439 notes => "Retrieve a user's permission groups."
2443 sub get_user_perm_groups {
2444 my( $self, $client, $authtoken, $userid ) = @_;
2446 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2447 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2448 return $evt if $evt;
2450 return $apputils->simplereq(
2452 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2456 __PACKAGE__->register_method(
2457 method => "get_user_work_ous",
2458 api_name => "open-ils.actor.user.get_work_ous",
2459 notes => "Retrieve a user's work org units."
2462 __PACKAGE__->register_method(
2463 method => "get_user_work_ous",
2464 api_name => "open-ils.actor.user.get_work_ous.ids",
2465 notes => "Retrieve a user's work org units."
2468 sub get_user_work_ous {
2469 my( $self, $client, $auth, $userid ) = @_;
2470 my $e = new_editor(authtoken=>$auth);
2471 return $e->event unless $e->checkauth;
2472 $userid ||= $e->requestor->id;
2474 if($e->requestor->id != $userid) {
2475 my $user = $e->retrieve_actor_user($userid)
2476 or return $e->event;
2477 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2480 return $e->search_permission_usr_work_ou_map({usr => $userid})
2481 unless $self->api_name =~ /.ids$/;
2483 # client just wants a list of org IDs
2484 return $U->get_user_work_ou_ids($e, $userid);
2489 __PACKAGE__->register_method(
2490 method => 'register_workstation',
2491 api_name => 'open-ils.actor.workstation.register.override',
2492 signature => q/@see open-ils.actor.workstation.register/
2495 __PACKAGE__->register_method(
2496 method => 'register_workstation',
2497 api_name => 'open-ils.actor.workstation.register',
2499 Registers a new workstion in the system
2500 @param authtoken The login session key
2501 @param name The name of the workstation id
2502 @param owner The org unit that owns this workstation
2503 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2504 if the name is already in use.
2508 sub register_workstation {
2509 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2511 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2512 return $e->die_event unless $e->checkauth;
2513 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2514 my $existing = $e->search_actor_workstation({name => $name})->[0];
2515 $oargs = { all => 1 } unless defined $oargs;
2519 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2520 # workstation with the given name exists.
2522 if($owner ne $existing->owning_lib) {
2523 # if necessary, update the owning_lib of the workstation
2525 $logger->info("changing owning lib of workstation ".$existing->id.
2526 " from ".$existing->owning_lib." to $owner");
2527 return $e->die_event unless
2528 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2530 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2532 $existing->owning_lib($owner);
2533 return $e->die_event unless $e->update_actor_workstation($existing);
2539 "attempt to register an existing workstation. returning existing ID");
2542 return $existing->id;
2545 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2549 my $ws = Fieldmapper::actor::workstation->new;
2550 $ws->owning_lib($owner);
2552 $e->create_actor_workstation($ws) or return $e->die_event;
2554 return $ws->id; # note: editor sets the id on the new object for us
2557 __PACKAGE__->register_method(
2558 method => 'workstation_list',
2559 api_name => 'open-ils.actor.workstation.list',
2561 Returns a list of workstations registered at the given location
2562 @param authtoken The login session key
2563 @param ids A list of org_unit.id's for the workstation owners
2567 sub workstation_list {
2568 my( $self, $conn, $authtoken, @orgs ) = @_;
2570 my $e = new_editor(authtoken=>$authtoken);
2571 return $e->event unless $e->checkauth;
2576 unless $e->allowed('REGISTER_WORKSTATION', $o);
2577 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2583 __PACKAGE__->register_method(
2584 method => 'fetch_patron_note',
2585 api_name => 'open-ils.actor.note.retrieve.all',
2588 Returns a list of notes for a given user
2589 Requestor must have VIEW_USER permission if pub==false and
2590 @param authtoken The login session key
2591 @param args Hash of params including
2592 patronid : the patron's id
2593 pub : true if retrieving only public notes
2597 sub fetch_patron_note {
2598 my( $self, $conn, $authtoken, $args ) = @_;
2599 my $patronid = $$args{patronid};
2601 my($reqr, $evt) = $U->checkses($authtoken);
2602 return $evt if $evt;
2605 ($patron, $evt) = $U->fetch_user($patronid);
2606 return $evt if $evt;
2609 if( $patronid ne $reqr->id ) {
2610 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2611 return $evt if $evt;
2613 return $U->cstorereq(
2614 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2615 { usr => $patronid, pub => 't' } );
2618 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2619 return $evt if $evt;
2621 return $U->cstorereq(
2622 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2625 __PACKAGE__->register_method(
2626 method => 'create_user_note',
2627 api_name => 'open-ils.actor.note.create',
2629 Creates a new note for the given user
2630 @param authtoken The login session key
2631 @param note The note object
2634 sub create_user_note {
2635 my( $self, $conn, $authtoken, $note ) = @_;
2636 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2637 return $e->die_event unless $e->checkauth;
2639 my $user = $e->retrieve_actor_user($note->usr)
2640 or return $e->die_event;
2642 return $e->die_event unless
2643 $e->allowed('UPDATE_USER',$user->home_ou);
2645 $note->creator($e->requestor->id);
2646 $e->create_actor_usr_note($note) or return $e->die_event;
2652 __PACKAGE__->register_method(
2653 method => 'delete_user_note',
2654 api_name => 'open-ils.actor.note.delete',
2656 Deletes a note for the given user
2657 @param authtoken The login session key
2658 @param noteid The note id
2661 sub delete_user_note {
2662 my( $self, $conn, $authtoken, $noteid ) = @_;
2664 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2665 return $e->die_event unless $e->checkauth;
2666 my $note = $e->retrieve_actor_usr_note($noteid)
2667 or return $e->die_event;
2668 my $user = $e->retrieve_actor_user($note->usr)
2669 or return $e->die_event;
2670 return $e->die_event unless
2671 $e->allowed('UPDATE_USER', $user->home_ou);
2673 $e->delete_actor_usr_note($note) or return $e->die_event;
2679 __PACKAGE__->register_method(
2680 method => 'update_user_note',
2681 api_name => 'open-ils.actor.note.update',
2683 @param authtoken The login session key
2684 @param note The note
2688 sub update_user_note {
2689 my( $self, $conn, $auth, $note ) = @_;
2690 my $e = new_editor(authtoken=>$auth, xact=>1);
2691 return $e->die_event unless $e->checkauth;
2692 my $patron = $e->retrieve_actor_user($note->usr)
2693 or return $e->die_event;
2694 return $e->die_event unless
2695 $e->allowed('UPDATE_USER', $patron->home_ou);
2696 $e->update_actor_user_note($note)
2697 or return $e->die_event;
2702 __PACKAGE__->register_method(
2703 method => 'fetch_patron_messages',
2704 api_name => 'open-ils.actor.message.retrieve',
2707 Returns a list of notes for a given user, not
2708 including ones marked deleted
2709 @param authtoken The login session key
2710 @param patronid patron ID
2711 @param options hash containing optional limit and offset
2715 sub fetch_patron_messages {
2716 my( $self, $conn, $auth, $patronid, $options ) = @_;
2720 my $e = new_editor(authtoken => $auth);
2721 return $e->die_event unless $e->checkauth;
2723 if ($e->requestor->id ne $patronid) {
2724 return $e->die_event unless $e->allowed('VIEW_USER');
2727 my $select_clause = { usr => $patronid };
2728 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2729 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2730 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2732 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2737 __PACKAGE__->register_method(
2738 method => 'usrname_exists',
2739 api_name => 'open-ils.actor.username.exists',
2741 desc => 'Check if a username is already taken (by an undeleted patron)',
2743 {desc => 'Authentication token', type => 'string'},
2744 {desc => 'Username', type => 'string'}
2747 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2752 sub usrname_exists {
2753 my( $self, $conn, $auth, $usrname ) = @_;
2754 my $e = new_editor(authtoken=>$auth);
2755 return $e->event unless $e->checkauth;
2756 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2757 return $$a[0] if $a and @$a;
2761 __PACKAGE__->register_method(
2762 method => 'barcode_exists',
2763 api_name => 'open-ils.actor.barcode.exists',
2765 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2768 sub barcode_exists {
2769 my( $self, $conn, $auth, $barcode ) = @_;
2770 my $e = new_editor(authtoken=>$auth);
2771 return $e->event unless $e->checkauth;
2772 my $card = $e->search_actor_card({barcode => $barcode});
2778 #return undef unless @$card;
2779 #return $card->[0]->usr;
2783 __PACKAGE__->register_method(
2784 method => 'retrieve_net_levels',
2785 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2788 sub retrieve_net_levels {
2789 my( $self, $conn, $auth ) = @_;
2790 my $e = new_editor(authtoken=>$auth);
2791 return $e->event unless $e->checkauth;
2792 return $e->retrieve_all_config_net_access_level();
2795 # Retain the old typo API name just in case
2796 __PACKAGE__->register_method(
2797 method => 'fetch_org_by_shortname',
2798 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2800 __PACKAGE__->register_method(
2801 method => 'fetch_org_by_shortname',
2802 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2804 sub fetch_org_by_shortname {
2805 my( $self, $conn, $sname ) = @_;
2806 my $e = new_editor();
2807 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2808 return $e->event unless $org;
2813 __PACKAGE__->register_method(
2814 method => 'session_home_lib',
2815 api_name => 'open-ils.actor.session.home_lib',
2818 sub session_home_lib {
2819 my( $self, $conn, $auth ) = @_;
2820 my $e = new_editor(authtoken=>$auth);
2821 return undef unless $e->checkauth;
2822 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2823 return $org->shortname;
2826 __PACKAGE__->register_method(
2827 method => 'session_safe_token',
2828 api_name => 'open-ils.actor.session.safe_token',
2830 Returns a hashed session ID that is safe for export to the world.
2831 This safe token will expire after 1 hour of non-use.
2832 @param auth Active authentication token
2836 sub session_safe_token {
2837 my( $self, $conn, $auth ) = @_;
2838 my $e = new_editor(authtoken=>$auth);
2839 return undef unless $e->checkauth;
2841 my $safe_token = md5_hex($auth);
2843 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2845 # add more user fields as needed
2847 "safe-token-user-$safe_token", {
2848 id => $e->requestor->id,
2849 home_ou_shortname => $e->retrieve_actor_org_unit(
2850 $e->requestor->home_ou)->shortname,
2859 __PACKAGE__->register_method(
2860 method => 'safe_token_home_lib',
2861 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2863 Returns the home library shortname from the session
2864 asscociated with a safe token from generated by
2865 open-ils.actor.session.safe_token.
2866 @param safe_token Active safe token
2867 @param who Optional user activity "ewho" value
2871 sub safe_token_home_lib {
2872 my( $self, $conn, $safe_token, $who ) = @_;
2873 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2875 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2876 return unless $blob;
2878 $U->log_user_activity($blob->{id}, $who, 'verify');
2879 return $blob->{home_ou_shortname};
2883 __PACKAGE__->register_method(
2884 method => "update_penalties",
2885 api_name => "open-ils.actor.user.penalties.update"
2888 sub update_penalties {
2889 my($self, $conn, $auth, $user_id) = @_;
2890 my $e = new_editor(authtoken=>$auth, xact => 1);
2891 return $e->die_event unless $e->checkauth;
2892 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2893 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2894 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2895 return $evt if $evt;
2901 __PACKAGE__->register_method(
2902 method => "apply_penalty",
2903 api_name => "open-ils.actor.user.penalty.apply"
2907 my($self, $conn, $auth, $penalty) = @_;
2909 my $e = new_editor(authtoken=>$auth, xact => 1);
2910 return $e->die_event unless $e->checkauth;
2912 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2913 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2915 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2918 (defined $ptype->org_depth) ?
2919 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2922 $penalty->org_unit($ctx_org);
2923 $penalty->staff($e->requestor->id);
2924 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2927 return $penalty->id;
2930 __PACKAGE__->register_method(
2931 method => "remove_penalty",
2932 api_name => "open-ils.actor.user.penalty.remove"
2935 sub remove_penalty {
2936 my($self, $conn, $auth, $penalty) = @_;
2937 my $e = new_editor(authtoken=>$auth, xact => 1);
2938 return $e->die_event unless $e->checkauth;
2939 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2940 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2942 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2947 __PACKAGE__->register_method(
2948 method => "update_penalty_note",
2949 api_name => "open-ils.actor.user.penalty.note.update"
2952 sub update_penalty_note {
2953 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2954 my $e = new_editor(authtoken=>$auth, xact => 1);
2955 return $e->die_event unless $e->checkauth;
2956 for my $penalty_id (@$penalty_ids) {
2957 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2958 if (! $penalty ) { return $e->die_event; }
2959 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2960 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2962 $penalty->note( $note ); $penalty->ischanged( 1 );
2964 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2970 __PACKAGE__->register_method(
2971 method => "ranged_penalty_thresholds",
2972 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2976 sub ranged_penalty_thresholds {
2977 my($self, $conn, $auth, $context_org) = @_;
2978 my $e = new_editor(authtoken=>$auth);
2979 return $e->event unless $e->checkauth;
2980 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2981 my $list = $e->search_permission_grp_penalty_threshold([
2982 {org_unit => $U->get_org_ancestors($context_org)},
2983 {order_by => {pgpt => 'id'}}
2985 $conn->respond($_) for @$list;
2991 __PACKAGE__->register_method(
2992 method => "user_retrieve_fleshed_by_id",
2994 api_name => "open-ils.actor.user.fleshed.retrieve",
2997 sub user_retrieve_fleshed_by_id {
2998 my( $self, $client, $auth, $user_id, $fields ) = @_;
2999 my $e = new_editor(authtoken => $auth);
3000 return $e->event unless $e->checkauth;
3002 if( $e->requestor->id != $user_id ) {
3003 return $e->event unless $e->allowed('VIEW_USER');
3010 "standing_penalties",
3016 return new_flesh_user($user_id, $fields, $e);
3020 sub new_flesh_user {
3023 my $fields = shift || [];
3026 my $fetch_penalties = 0;
3027 if(grep {$_ eq 'standing_penalties'} @$fields) {
3028 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3029 $fetch_penalties = 1;
3032 my $fetch_usr_act = 0;
3033 if(grep {$_ eq 'usr_activity'} @$fields) {
3034 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3038 my $user = $e->retrieve_actor_user(
3043 "flesh_fields" => { "au" => $fields }
3046 ) or return $e->die_event;
3049 if( grep { $_ eq 'addresses' } @$fields ) {
3051 $user->addresses([]) unless @{$user->addresses};
3052 # don't expose "replaced" addresses by default
3053 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3055 if( ref $user->billing_address ) {
3056 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3057 push( @{$user->addresses}, $user->billing_address );
3061 if( ref $user->mailing_address ) {
3062 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3063 push( @{$user->addresses}, $user->mailing_address );
3068 if($fetch_penalties) {
3069 # grab the user penalties ranged for this location
3070 $user->standing_penalties(
3071 $e->search_actor_user_standing_penalty([
3074 {stop_date => undef},
3075 {stop_date => {'>' => 'now'}}
3077 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3080 flesh_fields => {ausp => ['standing_penalty']}
3086 # retrieve the most recent usr_activity entry
3087 if ($fetch_usr_act) {
3089 # max number to return for simple patron fleshing
3090 my $limit = $U->ou_ancestor_setting_value(
3091 $e->requestor->ws_ou,
3092 'circ.patron.usr_activity_retrieve.max');
3096 flesh_fields => {auact => ['etype']},
3097 order_by => {auact => 'event_time DESC'},
3100 # 0 == none, <0 == return all
3101 $limit = 1 unless defined $limit;
3102 $opts->{limit} = $limit if $limit > 0;
3104 $user->usr_activity(
3106 [] : # skip the DB call
3107 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3112 $user->clear_passwd();
3119 __PACKAGE__->register_method(
3120 method => "user_retrieve_parts",
3121 api_name => "open-ils.actor.user.retrieve.parts",
3124 sub user_retrieve_parts {
3125 my( $self, $client, $auth, $user_id, $fields ) = @_;
3126 my $e = new_editor(authtoken => $auth);
3127 return $e->event unless $e->checkauth;
3128 $user_id ||= $e->requestor->id;
3129 if( $e->requestor->id != $user_id ) {
3130 return $e->event unless $e->allowed('VIEW_USER');
3133 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3134 push(@resp, $user->$_()) for(@$fields);
3140 __PACKAGE__->register_method(
3141 method => 'user_opt_in_enabled',
3142 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3143 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3146 sub user_opt_in_enabled {
3147 my($self, $conn) = @_;
3148 my $sc = OpenSRF::Utils::SettingsClient->new;
3149 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3154 __PACKAGE__->register_method(
3155 method => 'user_opt_in_at_org',
3156 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3158 @param $auth The auth token
3159 @param user_id The ID of the user to test
3160 @return 1 if the user has opted in at the specified org,
3161 2 if opt-in is disallowed for the user's home org,
3162 event on error, and 0 otherwise. /
3164 sub user_opt_in_at_org {
3165 my($self, $conn, $auth, $user_id) = @_;
3167 # see if we even need to enforce the opt-in value
3168 return 1 unless user_opt_in_enabled($self);
3170 my $e = new_editor(authtoken => $auth);
3171 return $e->event unless $e->checkauth;
3173 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3174 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3176 my $ws_org = $e->requestor->ws_ou;
3177 # user is automatically opted-in if they are from the local org
3178 return 1 if $user->home_ou eq $ws_org;
3180 # get the boundary setting
3181 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3183 # auto opt in if user falls within the opt boundary
3184 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3186 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3188 # check whether opt-in is restricted at the user's home library
3189 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3190 if ($opt_restrict_depth) {
3191 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3192 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3194 # opt-in is disallowed unless the workstation org is within the home
3195 # library's opt-in scope
3196 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3199 my $vals = $e->search_actor_usr_org_unit_opt_in(
3200 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3206 __PACKAGE__->register_method(
3207 method => 'create_user_opt_in_at_org',
3208 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3210 @param $auth The auth token
3211 @param user_id The ID of the user to test
3212 @return The ID of the newly created object, event on error./
3215 sub create_user_opt_in_at_org {
3216 my($self, $conn, $auth, $user_id, $org_id) = @_;
3218 my $e = new_editor(authtoken => $auth, xact=>1);
3219 return $e->die_event unless $e->checkauth;
3221 # if a specific org unit wasn't passed in, get one based on the defaults;
3223 my $wsou = $e->requestor->ws_ou;
3224 # get the default opt depth
3225 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3226 # get the org unit at that depth
3227 my $org = $e->json_query({
3228 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3229 $org_id = $org->{id};
3232 # fall back to the workstation OU, the pre-opt-in-boundary way
3233 $org_id = $e->requestor->ws_ou;
3236 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3237 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3239 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3241 $opt_in->org_unit($org_id);
3242 $opt_in->usr($user_id);
3243 $opt_in->staff($e->requestor->id);
3244 $opt_in->opt_in_ts('now');
3245 $opt_in->opt_in_ws($e->requestor->wsid);
3247 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3248 or return $e->die_event;
3256 __PACKAGE__->register_method (
3257 method => 'retrieve_org_hours',
3258 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3260 Returns the hours of operation for a specified org unit
3261 @param authtoken The login session key
3262 @param org_id The org_unit ID
3266 sub retrieve_org_hours {
3267 my($self, $conn, $auth, $org_id) = @_;
3268 my $e = new_editor(authtoken => $auth);
3269 return $e->die_event unless $e->checkauth;
3270 $org_id ||= $e->requestor->ws_ou;
3271 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3275 __PACKAGE__->register_method (
3276 method => 'verify_user_password',
3277 api_name => 'open-ils.actor.verify_user_password',
3279 Given a barcode or username and the MD5 encoded password,
3280 returns 1 if the password is correct. Returns 0 otherwise.
3284 sub verify_user_password {
3285 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3286 my $e = new_editor(authtoken => $auth);
3287 return $e->die_event unless $e->checkauth;
3289 my $user_by_barcode;
3290 my $user_by_username;
3292 my $card = $e->search_actor_card([
3293 {barcode => $barcode},
3294 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3295 $user_by_barcode = $card->usr;
3296 $user = $user_by_barcode;
3299 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3300 $user = $user_by_username;
3302 return 0 if (!$user || $U->is_true($user->deleted));
3303 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3304 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3305 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3308 __PACKAGE__->register_method (
3309 method => 'retrieve_usr_id_via_barcode_or_usrname',
3310 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3312 Given a barcode or username returns the id for the user or
3317 sub retrieve_usr_id_via_barcode_or_usrname {
3318 my($self, $conn, $auth, $barcode, $username) = @_;
3319 my $e = new_editor(authtoken => $auth);
3320 return $e->die_event unless $e->checkauth;
3321 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3323 my $user_by_barcode;
3324 my $user_by_username;
3325 $logger->info("$id_as_barcode is the ID as BARCODE");
3327 my $card = $e->search_actor_card([
3328 {barcode => $barcode},
3329 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3330 if ($id_as_barcode =~ /^t/i) {
3332 $user = $e->retrieve_actor_user($barcode);
3333 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3335 $user_by_barcode = $card->usr;
3336 $user = $user_by_barcode;
3339 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3340 $user_by_barcode = $card->usr;
3341 $user = $user_by_barcode;
3346 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3348 $user = $user_by_username;
3350 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3351 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3352 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3357 __PACKAGE__->register_method (
3358 method => 'merge_users',
3359 api_name => 'open-ils.actor.user.merge',
3362 Given a list of source users and destination user, transfer all data from the source
3363 to the dest user and delete the source user. All user related data is
3364 transferred, including circulations, holds, bookbags, etc.
3370 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3371 my $e = new_editor(xact => 1, authtoken => $auth);
3372 return $e->die_event unless $e->checkauth;
3374 # disallow the merge if any subordinate accounts are in collections
3375 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3376 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3378 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3379 my $del_addrs = ($U->ou_ancestor_setting_value(
3380 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3381 my $del_cards = ($U->ou_ancestor_setting_value(
3382 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3383 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3384 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3386 for my $src_id (@$user_ids) {
3387 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3389 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3390 if($src_user->home_ou ne $master_user->home_ou) {
3391 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3394 return $e->die_event unless
3395 $e->json_query({from => [
3410 __PACKAGE__->register_method (
3411 method => 'approve_user_address',
3412 api_name => 'open-ils.actor.user.pending_address.approve',
3419 sub approve_user_address {
3420 my($self, $conn, $auth, $addr) = @_;
3421 my $e = new_editor(xact => 1, authtoken => $auth);
3422 return $e->die_event unless $e->checkauth;
3424 # if the caller passes an address object, assume they want to
3425 # update it first before approving it
3426 $e->update_actor_user_address($addr) or return $e->die_event;
3428 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3430 my $user = $e->retrieve_actor_user($addr->usr);
3431 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3432 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3433 or return $e->die_event;
3435 return [values %$result]->[0];
3439 __PACKAGE__->register_method (
3440 method => 'retrieve_friends',
3441 api_name => 'open-ils.actor.friends.retrieve',
3444 returns { confirmed: [], pending_out: [], pending_in: []}
3445 pending_out are users I'm requesting friendship with
3446 pending_in are users requesting friendship with me
3451 sub retrieve_friends {
3452 my($self, $conn, $auth, $user_id, $options) = @_;
3453 my $e = new_editor(authtoken => $auth);
3454 return $e->event unless $e->checkauth;
3455 $user_id ||= $e->requestor->id;
3457 if($user_id != $e->requestor->id) {
3458 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3459 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3462 return OpenILS::Application::Actor::Friends->retrieve_friends(
3463 $e, $user_id, $options);
3468 __PACKAGE__->register_method (
3469 method => 'apply_friend_perms',
3470 api_name => 'open-ils.actor.friends.perms.apply',
3476 sub apply_friend_perms {
3477 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3478 my $e = new_editor(authtoken => $auth, xact => 1);
3479 return $e->die_event unless $e->checkauth;
3481 if($user_id != $e->requestor->id) {
3482 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3483 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3486 for my $perm (@perms) {
3488 OpenILS::Application::Actor::Friends->apply_friend_perm(
3489 $e, $user_id, $delegate_id, $perm);
3490 return $evt if $evt;
3498 __PACKAGE__->register_method (
3499 method => 'update_user_pending_address',
3500 api_name => 'open-ils.actor.user.address.pending.cud'
3503 sub update_user_pending_address {
3504 my($self, $conn, $auth, $addr) = @_;
3505 my $e = new_editor(authtoken => $auth, xact => 1);
3506 return $e->die_event unless $e->checkauth;
3508 if($addr->usr != $e->requestor->id) {
3509 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3510 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3514 $e->create_actor_user_address($addr) or return $e->die_event;
3515 } elsif($addr->isdeleted) {
3516 $e->delete_actor_user_address($addr) or return $e->die_event;
3518 $e->update_actor_user_address($addr) or return $e->die_event;
3526 __PACKAGE__->register_method (
3527 method => 'user_events',
3528 api_name => 'open-ils.actor.user.events.circ',
3531 __PACKAGE__->register_method (
3532 method => 'user_events',
3533 api_name => 'open-ils.actor.user.events.ahr',
3538 my($self, $conn, $auth, $user_id, $filters) = @_;
3539 my $e = new_editor(authtoken => $auth);
3540 return $e->event unless $e->checkauth;
3542 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3543 my $user_field = 'usr';
3546 $filters->{target} = {
3547 select => { $obj_type => ['id'] },
3549 where => {usr => $user_id}
3552 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3553 if($e->requestor->id != $user_id) {
3554 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3557 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3558 my $req = $ses->request('open-ils.trigger.events_by_target',
3559 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3561 while(my $resp = $req->recv) {
3562 my $val = $resp->content;
3563 my $tgt = $val->target;
3565 if($obj_type eq 'circ') {
3566 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3568 } elsif($obj_type eq 'ahr') {
3569 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3570 if $tgt->current_copy;
3573 $conn->respond($val) if $val;
3579 __PACKAGE__->register_method (
3580 method => 'copy_events',
3581 api_name => 'open-ils.actor.copy.events.circ',
3584 __PACKAGE__->register_method (
3585 method => 'copy_events',
3586 api_name => 'open-ils.actor.copy.events.ahr',
3591 my($self, $conn, $auth, $copy_id, $filters) = @_;
3592 my $e = new_editor(authtoken => $auth);
3593 return $e->event unless $e->checkauth;
3595 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3597 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3599 my $copy_field = 'target_copy';
3600 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3603 $filters->{target} = {
3604 select => { $obj_type => ['id'] },
3606 where => {$copy_field => $copy_id}
3610 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3611 my $req = $ses->request('open-ils.trigger.events_by_target',
3612 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3614 while(my $resp = $req->recv) {
3615 my $val = $resp->content;
3616 my $tgt = $val->target;
3618 my $user = $e->retrieve_actor_user($tgt->usr);
3619 if($e->requestor->id != $user->id) {
3620 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3623 $tgt->$copy_field($copy);
3626 $conn->respond($val) if $val;
3635 __PACKAGE__->register_method (
3636 method => 'update_events',
3637 api_name => 'open-ils.actor.user.event.cancel.batch',
3640 __PACKAGE__->register_method (
3641 method => 'update_events',
3642 api_name => 'open-ils.actor.user.event.reset.batch',
3647 my($self, $conn, $auth, $event_ids) = @_;
3648 my $e = new_editor(xact => 1, authtoken => $auth);
3649 return $e->die_event unless $e->checkauth;
3652 for my $id (@$event_ids) {
3654 # do a little dance to determine what user we are ultimately affecting
3655 my $event = $e->retrieve_action_trigger_event([
3658 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3660 ]) or return $e->die_event;
3663 if($event->event_def->hook->core_type eq 'circ') {
3664 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3665 } elsif($event->event_def->hook->core_type eq 'ahr') {
3666 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3671 my $user = $e->retrieve_actor_user($user_id);
3672 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3674 if($self->api_name =~ /cancel/) {
3675 $event->state('invalid');
3676 } elsif($self->api_name =~ /reset/) {
3677 $event->clear_start_time;
3678 $event->clear_update_time;
3679 $event->state('pending');
3682 $e->update_action_trigger_event($event) or return $e->die_event;
3683 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3687 return {complete => 1};
3691 __PACKAGE__->register_method (
3692 method => 'really_delete_user',
3693 api_name => 'open-ils.actor.user.delete.override',
3694 signature => q/@see open-ils.actor.user.delete/
3697 __PACKAGE__->register_method (
3698 method => 'really_delete_user',
3699 api_name => 'open-ils.actor.user.delete',
3701 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3702 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3703 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3704 dest_usr_id is only required when deleting a user that performs staff functions.
3708 sub really_delete_user {
3709 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3710 my $e = new_editor(authtoken => $auth, xact => 1);
3711 return $e->die_event unless $e->checkauth;
3712 $oargs = { all => 1 } unless defined $oargs;
3714 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3715 my $open_bills = $e->json_query({
3716 select => { mbts => ['id'] },
3719 xact_finish => { '=' => undef },
3720 usr => { '=' => $user_id },
3722 }) or return $e->die_event;
3724 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3726 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3728 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3729 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3730 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3732 # No deleting yourself - UI is supposed to stop you first, though.
3733 return $e->die_event unless $e->requestor->id != $user->id;
3734 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3735 # Check if you are allowed to mess with this patron permission group at all
3736 my $evt = group_perm_failed($e, $e->requestor, $user);
3737 return $e->die_event($evt) if $evt;
3738 my $stat = $e->json_query(
3739 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3740 or return $e->die_event;
3746 __PACKAGE__->register_method (
3747 method => 'user_payments',
3748 api_name => 'open-ils.actor.user.payments.retrieve',
3751 Returns all payments for a given user. Default order is newest payments first.
3752 @param auth Authentication token
3753 @param user_id The user ID
3754 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3759 my($self, $conn, $auth, $user_id, $filters) = @_;
3762 my $e = new_editor(authtoken => $auth);
3763 return $e->die_event unless $e->checkauth;
3765 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3766 return $e->event unless
3767 $e->requestor->id == $user_id or
3768 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3770 # Find all payments for all transactions for user $user_id
3772 select => {mp => ['id']},
3777 select => {mbt => ['id']},
3779 where => {usr => $user_id}
3784 { # by default, order newest payments first
3786 field => 'payment_ts',
3789 # secondary sort in ID as a tie-breaker, since payments created
3790 # within the same transaction will have identical payment_ts's
3797 for (qw/order_by limit offset/) {
3798 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3801 if(defined $filters->{where}) {
3802 foreach (keys %{$filters->{where}}) {
3803 # don't allow the caller to expand the result set to other users
3804 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3808 my $payment_ids = $e->json_query($query);
3809 for my $pid (@$payment_ids) {
3810 my $pay = $e->retrieve_money_payment([
3815 mbt => ['summary', 'circulation', 'grocery'],
3816 circ => ['target_copy'],
3817 acp => ['call_number'],
3825 xact_type => $pay->xact->summary->xact_type,
3826 last_billing_type => $pay->xact->summary->last_billing_type,
3829 if($pay->xact->summary->xact_type eq 'circulation') {
3830 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3831 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3834 $pay->xact($pay->xact->id); # de-flesh
3835 $conn->respond($resp);
3843 __PACKAGE__->register_method (
3844 method => 'negative_balance_users',
3845 api_name => 'open-ils.actor.users.negative_balance',
3848 Returns all users that have an overall negative balance
3849 @param auth Authentication token
3850 @param org_id The context org unit as an ID or list of IDs. This will be the home
3851 library of the user. If no org_unit is specified, no org unit filter is applied
3855 sub negative_balance_users {
3856 my($self, $conn, $auth, $org_id) = @_;
3858 my $e = new_editor(authtoken => $auth);
3859 return $e->die_event unless $e->checkauth;
3860 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3864 mous => ['usr', 'balance_owed'],
3867 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3868 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3885 where => {'+mous' => {balance_owed => {'<' => 0}}}
3888 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3890 my $list = $e->json_query($query, {timeout => 600});
3892 for my $data (@$list) {
3894 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3895 balance_owed => $data->{balance_owed},
3896 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3903 __PACKAGE__->register_method(
3904 method => "request_password_reset",
3905 api_name => "open-ils.actor.patron.password_reset.request",
3907 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3908 "method for changing a user's password. The UUID token is distributed via A/T " .
3909 "templates (i.e. email to the user).",
3911 { desc => 'user_id_type', type => 'string' },
3912 { desc => 'user_id', type => 'string' },
3913 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3915 return => {desc => '1 on success, Event on error'}
3918 sub request_password_reset {
3919 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3921 # Check to see if password reset requests are already being throttled:
3922 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3924 my $e = new_editor(xact => 1);
3927 # Get the user, if any, depending on the input value
3928 if ($user_id_type eq 'username') {
3929 $user = $e->search_actor_user({usrname => $user_id})->[0];
3932 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3934 } elsif ($user_id_type eq 'barcode') {
3935 my $card = $e->search_actor_card([
3936 {barcode => $user_id},
3937 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3940 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3945 # If the user doesn't have an email address, we can't help them
3946 if (!$user->email) {
3948 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3951 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3952 if ($email_must_match) {
3953 if (lc($user->email) ne lc($email)) {
3954 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3958 _reset_password_request($conn, $e, $user);
3961 # Once we have the user, we can issue the password reset request
3962 # XXX Add a wrapper method that accepts barcode + email input
3963 sub _reset_password_request {
3964 my ($conn, $e, $user) = @_;
3966 # 1. Get throttle threshold and time-to-live from OU_settings
3967 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3968 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3970 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3972 # 2. Get time of last request and number of active requests (num_active)
3973 my $active_requests = $e->json_query({
3979 transform => 'COUNT'
3982 column => 'request_time',
3988 has_been_reset => { '=' => 'f' },
3989 request_time => { '>' => $threshold_time }
3993 # Guard against no active requests
3994 if ($active_requests->[0]->{'request_time'}) {
3995 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3996 my $now = DateTime::Format::ISO8601->new();
3998 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3999 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4000 ($last_request->add_duration('1 minute') > $now)) {
4001 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4003 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4007 # TODO Check to see if the user is in a password-reset-restricted group
4009 # Otherwise, go ahead and try to get the user.
4011 # Check the number of active requests for this user
4012 $active_requests = $e->json_query({
4018 transform => 'COUNT'
4023 usr => { '=' => $user->id },
4024 has_been_reset => { '=' => 'f' },
4025 request_time => { '>' => $threshold_time }
4029 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4031 # if less than or equal to per-user threshold, proceed; otherwise, return event
4032 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4033 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4035 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4038 # Create the aupr object and insert into the database
4039 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4040 my $uuid = create_uuid_as_string(UUID_V4);
4041 $reset_request->uuid($uuid);
4042 $reset_request->usr($user->id);
4044 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4047 # Create an event to notify user of the URL to reset their password
4049 # Can we stuff this in the user_data param for trigger autocreate?
4050 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4052 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4053 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4056 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4061 __PACKAGE__->register_method(
4062 method => "commit_password_reset",
4063 api_name => "open-ils.actor.patron.password_reset.commit",
4065 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4066 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4067 "with the supplied password.",
4069 { desc => 'uuid', type => 'string' },
4070 { desc => 'password', type => 'string' },
4072 return => {desc => '1 on success, Event on error'}
4075 sub commit_password_reset {
4076 my($self, $conn, $uuid, $password) = @_;
4078 # Check to see if password reset requests are already being throttled:
4079 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4080 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4081 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4083 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4086 my $e = new_editor(xact => 1);
4088 my $aupr = $e->search_actor_usr_password_reset({
4095 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4097 my $user_id = $aupr->[0]->usr;
4098 my $user = $e->retrieve_actor_user($user_id);
4100 # Ensure we're still within the TTL for the request
4101 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4102 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4103 if ($threshold < DateTime->now(time_zone => 'local')) {
4105 $logger->info("Password reset request needed to be submitted before $threshold");
4106 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4109 # Check complexity of password against OU-defined regex
4110 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4114 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4115 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4116 $is_strong = check_password_strength_custom($password, $pw_regex);
4118 $is_strong = check_password_strength_default($password);
4123 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4126 # All is well; update the password
4127 modify_migrated_user_password($e, $user->id, $password);
4129 # And flag that this password reset request has been honoured
4130 $aupr->[0]->has_been_reset('t');
4131 $e->update_actor_usr_password_reset($aupr->[0]);
4137 sub check_password_strength_default {
4138 my $password = shift;
4139 # Use the default set of checks
4140 if ( (length($password) < 7) or
4141 ($password !~ m/.*\d+.*/) or
4142 ($password !~ m/.*[A-Za-z]+.*/)
4149 sub check_password_strength_custom {
4150 my ($password, $pw_regex) = @_;
4152 $pw_regex = qr/$pw_regex/;
4153 if ($password !~ /$pw_regex/) {
4161 __PACKAGE__->register_method(
4162 method => "event_def_opt_in_settings",
4163 api_name => "open-ils.actor.event_def.opt_in.settings",
4166 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4168 { desc => 'Authentication token', type => 'string'},
4170 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4175 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4182 sub event_def_opt_in_settings {
4183 my($self, $conn, $auth, $org_id) = @_;
4184 my $e = new_editor(authtoken => $auth);
4185 return $e->event unless $e->checkauth;
4187 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4188 return $e->event unless
4189 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4191 $org_id = $e->requestor->home_ou;
4194 # find all config.user_setting_type's related to event_defs for the requested org unit
4195 my $types = $e->json_query({
4196 select => {cust => ['name']},
4197 from => {atevdef => 'cust'},
4200 owner => $U->get_org_ancestors($org_id), # context org plus parents
4207 $conn->respond($_) for
4208 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4215 __PACKAGE__->register_method(
4216 method => "user_circ_history",
4217 api_name => "open-ils.actor.history.circ",
4221 desc => 'Returns user circ history objects for the calling user',
4223 { desc => 'Authentication token', type => 'string'},
4224 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4227 desc => q/Stream of 'auch' circ history objects/,
4233 __PACKAGE__->register_method(
4234 method => "user_circ_history",
4235 api_name => "open-ils.actor.history.circ.clear",
4238 desc => 'Delete all user circ history entries for the calling user',
4240 { desc => 'Authentication token', type => 'string'},
4241 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4244 desc => q/1 on success, event on error/,
4250 __PACKAGE__->register_method(
4251 method => "user_circ_history",
4252 api_name => "open-ils.actor.history.circ.print",
4255 desc => q/Returns printable output for the caller's circ history objects/,
4257 { desc => 'Authentication token', type => 'string'},
4258 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4261 desc => q/An action_trigger.event object or error event./,
4267 __PACKAGE__->register_method(
4268 method => "user_circ_history",
4269 api_name => "open-ils.actor.history.circ.email",
4272 desc => q/Emails the caller's circ history/,
4274 { desc => 'Authentication token', type => 'string'},
4275 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4276 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4279 desc => q/undef, or event on error/
4284 sub user_circ_history {
4285 my ($self, $conn, $auth, $options) = @_;
4288 my $for_print = ($self->api_name =~ /print/);
4289 my $for_email = ($self->api_name =~ /email/);
4290 my $for_clear = ($self->api_name =~ /clear/);
4292 # No perm check is performed. Caller may only access his/her own
4293 # circ history entries.
4294 my $e = new_editor(authtoken => $auth);
4295 return $e->event unless $e->checkauth;
4298 if (!$for_clear) { # clear deletes all
4299 $limits{offset} = $options->{offset} if defined $options->{offset};
4300 $limits{limit} = $options->{limit} if defined $options->{limit};
4303 my %circ_id_filter = $options->{circ_ids} ?
4304 (id => $options->{circ_ids}) : ();
4306 my $circs = $e->search_action_user_circ_history([
4307 { usr => $e->requestor->id,
4310 { # order newest to oldest by default
4311 order_by => {auch => 'xact_start DESC'},
4314 {substream => 1} # could be a large list
4318 return $U->fire_object_event(undef,
4319 'circ.format.history.print', $circs, $e->requestor->home_ou);
4322 $e->xact_begin if $for_clear;
4323 $conn->respond_complete(1) if $for_email; # no sense in waiting
4325 for my $circ (@$circs) {
4328 # events will be fired from action_trigger_runner
4329 $U->create_events_for_hook('circ.format.history.email',
4330 $circ, $e->editor->home_ou, undef, undef, 1);
4332 } elsif ($for_clear) {
4334 $e->delete_action_user_circ_history($circ)
4335 or return $e->die_event;
4338 $conn->respond($circ);
4351 __PACKAGE__->register_method(
4352 method => "user_visible_holds",
4353 api_name => "open-ils.actor.history.hold.visible",
4356 desc => 'Returns the set of opt-in visible holds',
4358 { desc => 'Authentication token', type => 'string'},
4359 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4360 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4363 desc => q/An object with 1 field: "hold"/,
4369 __PACKAGE__->register_method(
4370 method => "user_visible_holds",
4371 api_name => "open-ils.actor.history.hold.visible.print",
4374 desc => 'Returns printable output for the set of opt-in visible holds',
4376 { desc => 'Authentication token', type => 'string'},
4377 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4378 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4381 desc => q/An action_trigger.event object or error event./,
4387 __PACKAGE__->register_method(
4388 method => "user_visible_holds",
4389 api_name => "open-ils.actor.history.hold.visible.email",
4392 desc => 'Emails the set of opt-in visible holds to the requestor',
4394 { desc => 'Authentication token', type => 'string'},
4395 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4396 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4399 desc => q/undef, or event on error/
4404 sub user_visible_holds {
4405 my($self, $conn, $auth, $user_id, $options) = @_;
4408 my $for_print = ($self->api_name =~ /print/);
4409 my $for_email = ($self->api_name =~ /email/);
4410 my $e = new_editor(authtoken => $auth);
4411 return $e->event unless $e->checkauth;
4413 $user_id ||= $e->requestor->id;
4415 $options->{limit} ||= 50;
4416 $options->{offset} ||= 0;
4418 if($user_id != $e->requestor->id) {
4419 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4420 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4421 return $e->event unless $e->allowed($perm, $user->home_ou);
4424 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4426 my $data = $e->json_query({
4427 from => [$db_func, $user_id],
4428 limit => $$options{limit},
4429 offset => $$options{offset}
4431 # TODO: I only want IDs. code below didn't get me there
4432 # {"select":{"au":[{"column":"id", "result_field":"id",
4433 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4438 return undef unless @$data;
4442 # collect the batch of objects
4446 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4447 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4451 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4452 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4455 } elsif ($for_email) {
4457 $conn->respond_complete(1) if $for_email; # no sense in waiting
4465 my $hold = $e->retrieve_action_hold_request($id);
4466 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4467 # events will be fired from action_trigger_runner
4471 my $circ = $e->retrieve_action_circulation($id);
4472 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4473 # events will be fired from action_trigger_runner
4477 } else { # just give me the data please
4485 my $hold = $e->retrieve_action_hold_request($id);
4486 $conn->respond({hold => $hold});
4490 my $circ = $e->retrieve_action_circulation($id);
4493 summary => $U->create_circ_chain_summary($e, $id)
4502 __PACKAGE__->register_method(
4503 method => "user_saved_search_cud",
4504 api_name => "open-ils.actor.user.saved_search.cud",
4507 desc => 'Create/Update/Delete Access to user saved searches',
4509 { desc => 'Authentication token', type => 'string' },
4510 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4513 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4519 __PACKAGE__->register_method(
4520 method => "user_saved_search_cud",
4521 api_name => "open-ils.actor.user.saved_search.retrieve",
4524 desc => 'Retrieve a saved search object',
4526 { desc => 'Authentication token', type => 'string' },
4527 { desc => 'Saved Search ID', type => 'number' }
4530 desc => q/The saved search object, Event on error/,
4536 sub user_saved_search_cud {
4537 my( $self, $client, $auth, $search ) = @_;
4538 my $e = new_editor( authtoken=>$auth );
4539 return $e->die_event unless $e->checkauth;
4541 my $o_search; # prior version of the object, if any
4542 my $res; # to be returned
4544 # branch on the operation type
4546 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4548 # Get the old version, to check ownership
4549 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4550 or return $e->die_event;
4552 # You can't read somebody else's search
4553 return OpenILS::Event->new('BAD_PARAMS')
4554 unless $o_search->owner == $e->requestor->id;
4560 $e->xact_begin; # start an editor transaction
4562 if( $search->isnew ) { # Create
4564 # You can't create a search for somebody else
4565 return OpenILS::Event->new('BAD_PARAMS')
4566 unless $search->owner == $e->requestor->id;
4568 $e->create_actor_usr_saved_search( $search )
4569 or return $e->die_event;
4573 } elsif( $search->ischanged ) { # Update
4575 # You can't change ownership of a search
4576 return OpenILS::Event->new('BAD_PARAMS')
4577 unless $search->owner == $e->requestor->id;
4579 # Get the old version, to check ownership
4580 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4581 or return $e->die_event;
4583 # You can't update somebody else's search
4584 return OpenILS::Event->new('BAD_PARAMS')
4585 unless $o_search->owner == $e->requestor->id;
4588 $e->update_actor_usr_saved_search( $search )
4589 or return $e->die_event;
4593 } elsif( $search->isdeleted ) { # Delete
4595 # Get the old version, to check ownership
4596 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4597 or return $e->die_event;
4599 # You can't delete somebody else's search
4600 return OpenILS::Event->new('BAD_PARAMS')
4601 unless $o_search->owner == $e->requestor->id;
4604 $e->delete_actor_usr_saved_search( $o_search )
4605 or return $e->die_event;
4616 __PACKAGE__->register_method(
4617 method => "get_barcodes",
4618 api_name => "open-ils.actor.get_barcodes"
4622 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4623 my $e = new_editor(authtoken => $auth);
4624 return $e->event unless $e->checkauth;
4625 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4627 my $db_result = $e->json_query(
4629 'evergreen.get_barcodes',
4630 $org_id, $context, $barcode,
4634 if($context =~ /actor/) {
4635 my $filter_result = ();
4637 foreach my $result (@$db_result) {
4638 if($result->{type} eq 'actor') {
4639 if($e->requestor->id != $result->{id}) {
4640 $patron = $e->retrieve_actor_user($result->{id});
4642 push(@$filter_result, $e->event);
4645 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4646 push(@$filter_result, $result);
4649 push(@$filter_result, $e->event);
4653 push(@$filter_result, $result);
4657 push(@$filter_result, $result);
4660 return $filter_result;
4666 __PACKAGE__->register_method(
4667 method => 'address_alert_test',
4668 api_name => 'open-ils.actor.address_alert.test',
4670 desc => "Tests a set of address fields to determine if they match with an address_alert",
4672 {desc => 'Authentication token', type => 'string'},
4673 {desc => 'Org Unit', type => 'number'},
4674 {desc => 'Fields', type => 'hash'},
4676 return => {desc => 'List of matching address_alerts'}
4680 sub address_alert_test {
4681 my ($self, $client, $auth, $org_unit, $fields) = @_;
4682 return [] unless $fields and grep {$_} values %$fields;
4684 my $e = new_editor(authtoken => $auth);
4685 return $e->event unless $e->checkauth;
4686 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4687 $org_unit ||= $e->requestor->ws_ou;
4689 my $alerts = $e->json_query({
4691 'actor.address_alert_matches',
4699 $$fields{post_code},
4700 $$fields{mailing_address},
4701 $$fields{billing_address}
4705 # map the json_query hashes to real objects
4707 map {$e->retrieve_actor_address_alert($_)}
4708 (map {$_->{id}} @$alerts)
4712 __PACKAGE__->register_method(
4713 method => "mark_users_contact_invalid",
4714 api_name => "open-ils.actor.invalidate.email",
4716 desc => "Given a patron, clear the email field and put the old email address into a note and/or create a standing penalty, depending on OU settings",
4718 {desc => "Authentication token", type => "string"},
4719 {desc => "Patron ID", type => "number"},
4720 {desc => "Additional note text (optional)", type => "string"},
4721 {desc => "penalty org unit ID (optional)", type => "number"}
4723 return => {desc => "Event describing success or failure", type => "object"}
4727 __PACKAGE__->register_method(
4728 method => "mark_users_contact_invalid",
4729 api_name => "open-ils.actor.invalidate.day_phone",
4731 desc => "Given a patron, clear the day_phone field and put the old day_phone into a note and/or create a standing penalty, depending on OU settings",
4733 {desc => "Authentication token", type => "string"},
4734 {desc => "Patron ID", type => "number"},
4735 {desc => "Additional note text (optional)", type => "string"},
4736 {desc => "penalty org unit ID (optional)", type => "number"}
4738 return => {desc => "Event describing success or failure", type => "object"}
4742 __PACKAGE__->register_method(
4743 method => "mark_users_contact_invalid",
4744 api_name => "open-ils.actor.invalidate.evening_phone",
4746 desc => "Given a patron, clear the evening_phone field and put the old evening_phone into a note and/or create a standing penalty, depending on OU settings",
4748 {desc => "Authentication token", type => "string"},
4749 {desc => "Patron ID", type => "number"},
4750 {desc => "Additional note text (optional)", type => "string"},
4751 {desc => "penalty org unit ID (optional)", type => "number"}
4753 return => {desc => "Event describing success or failure", type => "object"}
4757 __PACKAGE__->register_method(
4758 method => "mark_users_contact_invalid",
4759 api_name => "open-ils.actor.invalidate.other_phone",
4761 desc => "Given a patron, clear the other_phone field and put the old other_phone into a note and/or create a standing penalty, depending on OU settings",
4763 {desc => "Authentication token", type => "string"},
4764 {desc => "Patron ID", type => "number"},
4765 {desc => "Additional note text (optional)", type => "string"},
4766 {desc => "penalty org unit ID (optional, default to top of org tree)",
4769 return => {desc => "Event describing success or failure", type => "object"}
4773 sub mark_users_contact_invalid {
4774 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4776 # This method invalidates an email address or a phone_number which
4777 # removes the bad email address or phone number, copying its contents
4778 # to a patron note, and institutes a standing penalty for "bad email"
4779 # or "bad phone number" which is cleared when the user is saved or
4780 # optionally only when the user is saved with an email address or
4781 # phone number (or staff manually delete the penalty).
4783 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4785 my $e = new_editor(authtoken => $auth, xact => 1);
4786 return $e->die_event unless $e->checkauth;
4788 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4789 $e, $contact_type, {usr => $patron_id},
4790 $addl_note, $penalty_ou, $e->requestor->id
4794 # Putting the following method in open-ils.actor is a bad fit, except in that
4795 # it serves an interface that lives under 'actor' in the templates directory,
4796 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4798 __PACKAGE__->register_method(
4799 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4800 method => "get_all_at_reactors_in_use",
4805 { name => 'authtoken', type => 'string' }
4808 desc => 'list of reactor names', type => 'array'
4813 sub get_all_at_reactors_in_use {
4814 my ($self, $conn, $auth) = @_;
4816 my $e = new_editor(authtoken => $auth);
4817 $e->checkauth or return $e->die_event;
4818 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4820 my $reactors = $e->json_query({
4822 atevdef => [{column => "reactor", transform => "distinct"}]
4824 from => {atevdef => {}}
4827 return $e->die_event unless ref $reactors eq "ARRAY";
4830 return [ map { $_->{reactor} } @$reactors ];
4833 __PACKAGE__->register_method(
4834 method => "filter_group_entry_crud",
4835 api_name => "open-ils.actor.filter_group_entry.crud",
4838 Provides CRUD access to filter group entry objects. These are not full accessible
4839 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4840 are not accessible via PCRUD (because they have no fields against which to link perms)
4843 {desc => "Authentication token", type => "string"},
4844 {desc => "Entry ID / Entry Object", type => "number"},
4845 {desc => "Additional note text (optional)", type => "string"},
4846 {desc => "penalty org unit ID (optional, default to top of org tree)",
4850 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4856 sub filter_group_entry_crud {
4857 my ($self, $conn, $auth, $arg) = @_;
4859 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4860 my $e = new_editor(authtoken => $auth, xact => 1);
4861 return $e->die_event unless $e->checkauth;
4867 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4868 or return $e->die_event;
4870 return $e->die_event unless $e->allowed(
4871 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4873 my $query = $arg->query;
4874 $query = $e->create_actor_search_query($query) or return $e->die_event;
4875 $arg->query($query->id);
4876 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4877 $entry->query($query);
4882 } elsif ($arg->ischanged) {
4884 my $entry = $e->retrieve_actor_search_filter_group_entry([
4887 flesh_fields => {asfge => ['grp']}
4889 ]) or return $e->die_event;
4891 return $e->die_event unless $e->allowed(
4892 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4894 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4895 $arg->query($arg->query->id);
4896 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4897 $arg->query($query);
4902 } elsif ($arg->isdeleted) {
4904 my $entry = $e->retrieve_actor_search_filter_group_entry([
4907 flesh_fields => {asfge => ['grp', 'query']}
4909 ]) or return $e->die_event;
4911 return $e->die_event unless $e->allowed(
4912 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4914 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4915 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4928 my $entry = $e->retrieve_actor_search_filter_group_entry([
4931 flesh_fields => {asfge => ['grp', 'query']}
4933 ]) or return $e->die_event;
4935 return $e->die_event unless $e->allowed(
4936 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4937 $entry->grp->owner);
4940 $entry->grp($entry->grp->id); # for consistency