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, $user_session, $patron ) = @_;
379 my $session = $apputils->start_db_session();
381 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
383 my( $user_obj, $evt ) = $U->checkses($user_session);
386 $evt = check_group_perm($session, $user_obj, $patron);
389 $apputils->set_audit_info($session, $user_session, $user_obj->id, $user_obj->wsid);
391 # $new_patron is the patron in progress. $patron is the original patron
392 # passed in with the method. new_patron will change as the components
393 # of patron are added/updated.
397 # unflesh the real items on the patron
398 $patron->card( $patron->card->id ) if(ref($patron->card));
399 $patron->billing_address( $patron->billing_address->id )
400 if(ref($patron->billing_address));
401 $patron->mailing_address( $patron->mailing_address->id )
402 if(ref($patron->mailing_address));
404 # create/update the patron first so we can use his id
406 # $patron is the obj from the client (new data) and $new_patron is the
407 # patron object properly built for db insertion, so we need a third variable
408 # if we want to represent the old patron.
411 my $barred_hook = '';
413 if($patron->isnew()) {
414 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
416 if($U->is_true($patron->barred)) {
417 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
421 $new_patron = $patron;
423 # Did auth checking above already.
425 $old_patron = $e->retrieve_actor_user($patron->id) or
426 return $e->die_event;
428 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
429 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
432 $barred_hook = $U->is_true($new_patron->barred) ?
433 'au.barred' : 'au.unbarred';
437 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
440 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
443 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
446 # re-update the patron if anything has happened to him during this process
447 if($new_patron->ischanged()) {
448 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
452 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
455 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
458 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
461 $apputils->commit_db_session($session);
463 $evt = apply_invalid_addr_penalty($patron);
466 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
468 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
470 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
472 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
473 $new_patron, $new_patron->home_ou) if $barred_hook;
476 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
479 sub apply_invalid_addr_penalty {
481 my $e = new_editor(xact => 1);
483 # grab the invalid address penalty if set
484 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
486 my ($addr_penalty) = grep
487 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
489 # do we enforce invalid address penalty
490 my $enforce = $U->ou_ancestor_setting_value(
491 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
493 my $addrs = $e->search_actor_user_address(
494 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
495 my $addr_count = scalar(@$addrs);
497 if($addr_count == 0 and $addr_penalty) {
499 # regardless of any settings, remove the penalty when the user has no invalid addresses
500 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
503 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
505 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
506 my $depth = $ptype->org_depth;
507 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
508 $ctx_org = $patron->home_ou unless defined $ctx_org;
510 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
511 $penalty->usr($patron->id);
512 $penalty->org_unit($ctx_org);
513 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
515 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
534 "standing_penalties",
542 push @$fields, "home_ou" if $home_ou;
543 return new_flesh_user($id, $fields, $e );
551 # clone and clear stuff that would break the database
555 my $new_patron = $patron->clone;
557 $new_patron->clear_billing_address();
558 $new_patron->clear_mailing_address();
559 $new_patron->clear_addresses();
560 $new_patron->clear_card();
561 $new_patron->clear_cards();
562 $new_patron->clear_id();
563 $new_patron->clear_isnew();
564 $new_patron->clear_ischanged();
565 $new_patron->clear_isdeleted();
566 $new_patron->clear_stat_cat_entries();
567 $new_patron->clear_permissions();
568 $new_patron->clear_standing_penalties();
578 my $user_obj = shift;
580 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
581 return (undef, $evt) if $evt;
583 my $ex = $session->request(
584 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
586 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
589 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
591 my $id = $session->request(
592 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
593 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
595 $logger->info("Successfully created new user [$id] in DB");
597 return ( $session->request(
598 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
602 sub check_group_perm {
603 my( $session, $requestor, $patron ) = @_;
606 # first let's see if the requestor has
607 # priveleges to update this user in any way
608 if( ! $patron->isnew ) {
609 my $p = $session->request(
610 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
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($session, $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($session, $requestor, $patron);
632 sub group_perm_failed {
633 my( $session, $requestor, $patron ) = @_;
637 my $grpid = $patron->profile;
641 $logger->debug("user update looking for group perm for group $grpid");
642 $grp = $session->request(
643 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
644 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
646 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
648 $logger->info("user update checking perm $perm on user ".
649 $requestor->id." for update/create on user username=".$patron->usrname);
651 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
659 my( $session, $patron, $user_obj, $noperm) = @_;
661 $logger->info("Updating patron ".$patron->id." in DB");
666 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
667 return (undef, $evt) if $evt;
670 # update the password by itself to avoid the password protection magic
671 if( $patron->passwd ) {
672 my $s = $session->request(
673 'open-ils.storage.direct.actor.user.remote_update',
674 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
675 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
676 $patron->clear_passwd;
679 if(!$patron->ident_type) {
680 $patron->clear_ident_type;
681 $patron->clear_ident_value;
684 $evt = verify_last_xact($session, $patron);
685 return (undef, $evt) if $evt;
687 my $stat = $session->request(
688 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
689 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
694 sub verify_last_xact {
695 my( $session, $patron ) = @_;
696 return undef unless $patron->id and $patron->id > 0;
697 my $p = $session->request(
698 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
699 my $xact = $p->last_xact_id;
700 return undef unless $xact;
701 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
702 return OpenILS::Event->new('XACT_COLLISION')
703 if $xact ne $patron->last_xact_id;
708 sub _check_dup_ident {
709 my( $session, $patron ) = @_;
711 return undef unless $patron->ident_value;
714 ident_type => $patron->ident_type,
715 ident_value => $patron->ident_value,
718 $logger->debug("patron update searching for dup ident values: " .
719 $patron->ident_type . ':' . $patron->ident_value);
721 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
723 my $dups = $session->request(
724 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
727 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
734 sub _add_update_addresses {
738 my $new_patron = shift;
742 my $current_id; # id of the address before creation
744 my $addresses = $patron->addresses();
746 for my $address (@$addresses) {
748 next unless ref $address;
749 $current_id = $address->id();
751 if( $patron->billing_address() and
752 $patron->billing_address() == $current_id ) {
753 $logger->info("setting billing addr to $current_id");
754 $new_patron->billing_address($address->id());
755 $new_patron->ischanged(1);
758 if( $patron->mailing_address() and
759 $patron->mailing_address() == $current_id ) {
760 $new_patron->mailing_address($address->id());
761 $logger->info("setting mailing addr to $current_id");
762 $new_patron->ischanged(1);
766 if($address->isnew()) {
768 $address->usr($new_patron->id());
770 ($address, $evt) = _add_address($session,$address);
771 return (undef, $evt) if $evt;
773 # we need to get the new id
774 if( $patron->billing_address() and
775 $patron->billing_address() == $current_id ) {
776 $new_patron->billing_address($address->id());
777 $logger->info("setting billing addr to $current_id");
778 $new_patron->ischanged(1);
781 if( $patron->mailing_address() and
782 $patron->mailing_address() == $current_id ) {
783 $new_patron->mailing_address($address->id());
784 $logger->info("setting mailing addr to $current_id");
785 $new_patron->ischanged(1);
788 } elsif($address->ischanged() ) {
790 ($address, $evt) = _update_address($session, $address);
791 return (undef, $evt) if $evt;
793 } elsif($address->isdeleted() ) {
795 if( $address->id() == $new_patron->mailing_address() ) {
796 $new_patron->clear_mailing_address();
797 ($new_patron, $evt) = _update_patron($session, $new_patron);
798 return (undef, $evt) if $evt;
801 if( $address->id() == $new_patron->billing_address() ) {
802 $new_patron->clear_billing_address();
803 ($new_patron, $evt) = _update_patron($session, $new_patron);
804 return (undef, $evt) if $evt;
807 $evt = _delete_address($session, $address);
808 return (undef, $evt) if $evt;
812 return ( $new_patron, undef );
816 # adds an address to the db and returns the address with new id
818 my($session, $address) = @_;
819 $address->clear_id();
821 $logger->info("Creating new address at street ".$address->street1);
823 # put the address into the database
824 my $id = $session->request(
825 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
826 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
829 return ($address, undef);
833 sub _update_address {
834 my( $session, $address ) = @_;
836 $logger->info("Updating address ".$address->id." in the DB");
838 my $stat = $session->request(
839 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
841 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
842 return ($address, undef);
847 sub _add_update_cards {
851 my $new_patron = shift;
855 my $virtual_id; #id of the card before creation
857 my $cards = $patron->cards();
858 for my $card (@$cards) {
860 $card->usr($new_patron->id());
862 if(ref($card) and $card->isnew()) {
864 $virtual_id = $card->id();
865 ( $card, $evt ) = _add_card($session,$card);
866 return (undef, $evt) if $evt;
868 #if(ref($patron->card)) { $patron->card($patron->card->id); }
869 if($patron->card() == $virtual_id) {
870 $new_patron->card($card->id());
871 $new_patron->ischanged(1);
874 } elsif( ref($card) and $card->ischanged() ) {
875 $evt = _update_card($session, $card);
876 return (undef, $evt) if $evt;
880 return ( $new_patron, undef );
884 # adds an card to the db and returns the card with new id
886 my( $session, $card ) = @_;
889 $logger->info("Adding new patron card ".$card->barcode);
891 my $id = $session->request(
892 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
893 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
894 $logger->info("Successfully created patron card $id");
897 return ( $card, undef );
901 # returns event on error. returns undef otherwise
903 my( $session, $card ) = @_;
904 $logger->info("Updating patron card ".$card->id);
906 my $stat = $session->request(
907 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
908 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
915 # returns event on error. returns undef otherwise
916 sub _delete_address {
917 my( $session, $address ) = @_;
919 $logger->info("Deleting address ".$address->id." from DB");
921 my $stat = $session->request(
922 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
924 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
930 sub _add_survey_responses {
931 my ($session, $patron, $new_patron) = @_;
933 $logger->info( "Updating survey responses for patron ".$new_patron->id );
935 my $responses = $patron->survey_responses;
939 $_->usr($new_patron->id) for (@$responses);
941 my $evt = $U->simplereq( "open-ils.circ",
942 "open-ils.circ.survey.submit.user_id", $responses );
944 return (undef, $evt) if defined($U->event_code($evt));
948 return ( $new_patron, undef );
951 sub _clear_badcontact_penalties {
952 my ($session, $old_patron, $new_patron, $user_obj) = @_;
954 return ($new_patron, undef) unless $old_patron;
956 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
957 my $e = new_editor(xact => 1);
959 # This ignores whether the caller of update_patron has any permission
960 # to remove penalties, but these penalties no longer make sense
961 # if an email address field (for example) is changed (and the caller must
962 # have perms to do *that*) so there's no reason not to clear the penalties.
964 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
966 "+csp" => {"name" => [values(%$PNM)]},
967 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
969 "join" => {"csp" => {}},
971 "flesh_fields" => {"ausp" => ["standing_penalty"]}
973 ]) or return (undef, $e->die_event);
975 return ($new_patron, undef) unless @$bad_contact_penalties;
977 my @penalties_to_clear;
978 my ($field, $penalty_name);
980 # For each field that might have an associated bad contact penalty,
981 # check for such penalties and add them to the to-clear list if that
983 while (($field, $penalty_name) = each(%$PNM)) {
984 if ($old_patron->$field ne $new_patron->$field) {
985 push @penalties_to_clear, grep {
986 $_->standing_penalty->name eq $penalty_name
987 } @$bad_contact_penalties;
991 foreach (@penalties_to_clear) {
992 # Note that this "archives" penalties, in the terminology of the staff
993 # client, instead of just deleting them. This may assist reporting,
994 # or preserving old contact information when it is still potentially
996 $_->standing_penalty($_->standing_penalty->id); # deflesh
997 $_->stop_date('now');
998 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
1002 return ($new_patron, undef);
1006 sub _create_stat_maps {
1008 my($session, $user_session, $patron, $new_patron) = @_;
1010 my $maps = $patron->stat_cat_entries();
1012 for my $map (@$maps) {
1014 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
1016 if ($map->isdeleted()) {
1017 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
1019 } elsif ($map->isnew()) {
1020 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
1025 $map->target_usr($new_patron->id);
1028 $logger->info("Updating stat entry with method $method and map $map");
1030 my $stat = $session->request($method, $map)->gather(1);
1031 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1035 return ($new_patron, undef);
1038 sub _create_perm_maps {
1040 my($session, $user_session, $patron, $new_patron) = @_;
1042 my $maps = $patron->permissions;
1044 for my $map (@$maps) {
1046 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1047 if ($map->isdeleted()) {
1048 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1049 } elsif ($map->isnew()) {
1050 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1055 $map->usr($new_patron->id);
1057 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1058 $logger->info( "Updating permissions with method $method and map $map" );
1060 my $stat = $session->request($method, $map)->gather(1);
1061 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1065 return ($new_patron, undef);
1069 __PACKAGE__->register_method(
1070 method => "set_user_work_ous",
1071 api_name => "open-ils.actor.user.work_ous.update",
1074 sub set_user_work_ous {
1080 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1081 return $evt if $evt;
1083 my $session = $apputils->start_db_session();
1084 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1086 for my $map (@$maps) {
1088 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1089 if ($map->isdeleted()) {
1090 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1091 } elsif ($map->isnew()) {
1092 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1096 #warn( "Updating permissions with method $method and session $ses and map $map" );
1097 $logger->info( "Updating work_ou map with method $method and map $map" );
1099 my $stat = $session->request($method, $map)->gather(1);
1100 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1104 $apputils->commit_db_session($session);
1106 return scalar(@$maps);
1110 __PACKAGE__->register_method(
1111 method => "set_user_perms",
1112 api_name => "open-ils.actor.user.permissions.update",
1115 sub set_user_perms {
1121 my $session = $apputils->start_db_session();
1123 my( $user_obj, $evt ) = $U->checkses($ses);
1124 return $evt if $evt;
1125 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1127 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1130 $all = 1 if ($U->is_true($user_obj->super_user()));
1131 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1133 for my $map (@$maps) {
1135 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1136 if ($map->isdeleted()) {
1137 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1138 } elsif ($map->isnew()) {
1139 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1143 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1144 #warn( "Updating permissions with method $method and session $ses and map $map" );
1145 $logger->info( "Updating permissions with method $method and map $map" );
1147 my $stat = $session->request($method, $map)->gather(1);
1148 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1152 $apputils->commit_db_session($session);
1154 return scalar(@$maps);
1158 __PACKAGE__->register_method(
1159 method => "user_retrieve_by_barcode",
1161 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1163 sub user_retrieve_by_barcode {
1164 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1166 my $e = new_editor(authtoken => $auth);
1167 return $e->event unless $e->checkauth;
1169 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1170 or return $e->event;
1172 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1173 return $e->event unless $e->allowed(
1174 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1181 __PACKAGE__->register_method(
1182 method => "get_user_by_id",
1184 api_name => "open-ils.actor.user.retrieve",
1187 sub get_user_by_id {
1188 my ($self, $client, $auth, $id) = @_;
1189 my $e = new_editor(authtoken=>$auth);
1190 return $e->event unless $e->checkauth;
1191 my $user = $e->retrieve_actor_user($id) or return $e->event;
1192 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1197 __PACKAGE__->register_method(
1198 method => "get_org_types",
1199 api_name => "open-ils.actor.org_types.retrieve",
1202 return $U->get_org_types();
1206 __PACKAGE__->register_method(
1207 method => "get_user_ident_types",
1208 api_name => "open-ils.actor.user.ident_types.retrieve",
1211 sub get_user_ident_types {
1212 return $ident_types if $ident_types;
1213 return $ident_types =
1214 new_editor()->retrieve_all_config_identification_type();
1218 __PACKAGE__->register_method(
1219 method => "get_org_unit",
1220 api_name => "open-ils.actor.org_unit.retrieve",
1224 my( $self, $client, $user_session, $org_id ) = @_;
1225 my $e = new_editor(authtoken => $user_session);
1227 return $e->event unless $e->checkauth;
1228 $org_id = $e->requestor->ws_ou;
1230 my $o = $e->retrieve_actor_org_unit($org_id)
1231 or return $e->event;
1235 __PACKAGE__->register_method(
1236 method => "search_org_unit",
1237 api_name => "open-ils.actor.org_unit_list.search",
1240 sub search_org_unit {
1242 my( $self, $client, $field, $value ) = @_;
1244 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1246 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1247 { $field => $value } );
1253 # build the org tree
1255 __PACKAGE__->register_method(
1256 method => "get_org_tree",
1257 api_name => "open-ils.actor.org_tree.retrieve",
1259 note => "Returns the entire org tree structure",
1265 return $U->get_org_tree($client->session->session_locale);
1269 __PACKAGE__->register_method(
1270 method => "get_org_descendants",
1271 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1274 # depth is optional. org_unit is the id
1275 sub get_org_descendants {
1276 my( $self, $client, $org_unit, $depth ) = @_;
1278 if(ref $org_unit eq 'ARRAY') {
1281 for my $i (0..scalar(@$org_unit)-1) {
1282 my $list = $U->simple_scalar_request(
1284 "open-ils.storage.actor.org_unit.descendants.atomic",
1285 $org_unit->[$i], $depth->[$i] );
1286 push(@trees, $U->build_org_tree($list));
1291 my $orglist = $apputils->simple_scalar_request(
1293 "open-ils.storage.actor.org_unit.descendants.atomic",
1294 $org_unit, $depth );
1295 return $U->build_org_tree($orglist);
1300 __PACKAGE__->register_method(
1301 method => "get_org_ancestors",
1302 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1305 # depth is optional. org_unit is the id
1306 sub get_org_ancestors {
1307 my( $self, $client, $org_unit, $depth ) = @_;
1308 my $orglist = $apputils->simple_scalar_request(
1310 "open-ils.storage.actor.org_unit.ancestors.atomic",
1311 $org_unit, $depth );
1312 return $U->build_org_tree($orglist);
1316 __PACKAGE__->register_method(
1317 method => "get_standings",
1318 api_name => "open-ils.actor.standings.retrieve"
1323 return $user_standings if $user_standings;
1324 return $user_standings =
1325 $apputils->simple_scalar_request(
1327 "open-ils.cstore.direct.config.standing.search.atomic",
1328 { id => { "!=" => undef } }
1333 __PACKAGE__->register_method(
1334 method => "get_my_org_path",
1335 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1338 sub get_my_org_path {
1339 my( $self, $client, $auth, $org_id ) = @_;
1340 my $e = new_editor(authtoken=>$auth);
1341 return $e->event unless $e->checkauth;
1342 $org_id = $e->requestor->ws_ou unless defined $org_id;
1344 return $apputils->simple_scalar_request(
1346 "open-ils.storage.actor.org_unit.full_path.atomic",
1351 __PACKAGE__->register_method(
1352 method => "patron_adv_search",
1353 api_name => "open-ils.actor.patron.search.advanced"
1356 __PACKAGE__->register_method(
1357 method => "patron_adv_search",
1358 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1360 # TODO: change when opensrf 'bundling' is merged.
1361 # set a relatively small bundle size so the caller can start
1362 # seeing results fairly quickly
1363 max_chunk_size => 4096, # bundling
1366 # pending opensrf work -- also, not sure if needed since we're not
1367 # actaully creating an alternate vesrion, only offering to return a
1371 desc => q/Returns a stream of fleshed user objects instead of
1372 a pile of identifiers/
1376 sub patron_adv_search {
1377 my( $self, $client, $auth, $search_hash, $search_limit,
1378 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1380 # API params sanity checks.
1381 # Exit early with empty result if no filter exists.
1382 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1383 my $fleshed = ($self->api_name =~ /fleshed/);
1384 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1386 for my $key (keys %$search_hash) {
1387 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1391 return ($fleshed ? undef : []) unless $search_ok;
1393 my $e = new_editor(authtoken=>$auth);
1394 return $e->event unless $e->checkauth;
1395 return $e->event unless $e->allowed('VIEW_USER');
1397 # depth boundary outside of which patrons must opt-in, default to 0
1398 my $opt_boundary = 0;
1399 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1401 if (not defined $search_ou) {
1402 my $depth = $U->ou_ancestor_setting_value(
1403 $e->requestor->ws_ou,
1404 'circ.patron_edit.duplicate_patron_check_depth'
1407 if (defined $depth) {
1408 $search_ou = $U->org_unit_ancestor_at_depth(
1409 $e->requestor->ws_ou, $depth
1414 my $ids = $U->storagereq(
1415 "open-ils.storage.actor.user.crazy_search", $search_hash,
1416 $search_limit, $search_sort, $include_inactive,
1417 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1419 return $ids unless $self->api_name =~ /fleshed/;
1421 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1427 __PACKAGE__->register_method(
1428 method => "update_passwd",
1429 api_name => "open-ils.actor.user.password.update",
1431 desc => "Update the operator's password",
1433 { desc => 'Authentication token', type => 'string' },
1434 { desc => 'New password', type => 'string' },
1435 { desc => 'Current password', type => 'string' }
1437 return => {desc => '1 on success, Event on error or incorrect current password'}
1441 __PACKAGE__->register_method(
1442 method => "update_passwd",
1443 api_name => "open-ils.actor.user.username.update",
1445 desc => "Update the operator's username",
1447 { desc => 'Authentication token', type => 'string' },
1448 { desc => 'New username', type => 'string' },
1449 { desc => 'Current password', type => 'string' }
1451 return => {desc => '1 on success, Event on error or incorrect current password'}
1455 __PACKAGE__->register_method(
1456 method => "update_passwd",
1457 api_name => "open-ils.actor.user.email.update",
1459 desc => "Update the operator's email address",
1461 { desc => 'Authentication token', type => 'string' },
1462 { desc => 'New email address', type => 'string' },
1463 { desc => 'Current password', type => 'string' }
1465 return => {desc => '1 on success, Event on error or incorrect current password'}
1470 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1471 my $e = new_editor(xact=>1, authtoken=>$auth);
1472 return $e->die_event unless $e->checkauth;
1474 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1475 or return $e->die_event;
1476 my $api = $self->api_name;
1478 # make sure the original password matches the in-database password
1479 if (md5_hex($orig_pw) ne $db_user->passwd) {
1481 return new OpenILS::Event('INCORRECT_PASSWORD');
1484 if( $api =~ /password/o ) {
1486 $db_user->passwd($new_val);
1490 # if we don't clear the password, the user will be updated with
1491 # a hashed version of the hashed version of their password
1492 $db_user->clear_passwd;
1494 if( $api =~ /username/o ) {
1496 # make sure no one else has this username
1497 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1500 return new OpenILS::Event('USERNAME_EXISTS');
1502 $db_user->usrname($new_val);
1504 } elsif( $api =~ /email/o ) {
1505 $db_user->email($new_val);
1509 $e->update_actor_user($db_user) or return $e->die_event;
1512 # update the cached user to pick up these changes
1513 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1519 __PACKAGE__->register_method(
1520 method => "check_user_perms",
1521 api_name => "open-ils.actor.user.perm.check",
1522 notes => <<" NOTES");
1523 Takes a login session, user id, an org id, and an array of perm type strings. For each
1524 perm type, if the user does *not* have the given permission it is added
1525 to a list which is returned from the method. If all permissions
1526 are allowed, an empty list is returned
1527 if the logged in user does not match 'user_id', then the logged in user must
1528 have VIEW_PERMISSION priveleges.
1531 sub check_user_perms {
1532 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1534 my( $staff, $evt ) = $apputils->checkses($login_session);
1535 return $evt if $evt;
1537 if($staff->id ne $user_id) {
1538 if( $evt = $apputils->check_perms(
1539 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1545 for my $perm (@$perm_types) {
1546 if($apputils->check_perms($user_id, $org_id, $perm)) {
1547 push @not_allowed, $perm;
1551 return \@not_allowed
1554 __PACKAGE__->register_method(
1555 method => "check_user_perms2",
1556 api_name => "open-ils.actor.user.perm.check.multi_org",
1558 Checks the permissions on a list of perms and orgs for a user
1559 @param authtoken The login session key
1560 @param user_id The id of the user to check
1561 @param orgs The array of org ids
1562 @param perms The array of permission names
1563 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1564 if the logged in user does not match 'user_id', then the logged in user must
1565 have VIEW_PERMISSION priveleges.
1568 sub check_user_perms2 {
1569 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1571 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1572 $authtoken, $user_id, 'VIEW_PERMISSION' );
1573 return $evt if $evt;
1576 for my $org (@$orgs) {
1577 for my $perm (@$perms) {
1578 if($apputils->check_perms($user_id, $org, $perm)) {
1579 push @not_allowed, [ $org, $perm ];
1584 return \@not_allowed
1588 __PACKAGE__->register_method(
1589 method => 'check_user_perms3',
1590 api_name => 'open-ils.actor.user.perm.highest_org',
1592 Returns the highest org unit id at which a user has a given permission
1593 If the requestor does not match the target user, the requestor must have
1594 'VIEW_PERMISSION' rights at the home org unit of the target user
1595 @param authtoken The login session key
1596 @param userid The id of the user in question
1597 @param perm The permission to check
1598 @return The org unit highest in the org tree within which the user has
1599 the requested permission
1602 sub check_user_perms3 {
1603 my($self, $client, $authtoken, $user_id, $perm) = @_;
1604 my $e = new_editor(authtoken=>$authtoken);
1605 return $e->event unless $e->checkauth;
1607 my $tree = $U->get_org_tree();
1609 unless($e->requestor->id == $user_id) {
1610 my $user = $e->retrieve_actor_user($user_id)
1611 or return $e->event;
1612 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1613 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1616 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1619 __PACKAGE__->register_method(
1620 method => 'user_has_work_perm_at',
1621 api_name => 'open-ils.actor.user.has_work_perm_at',
1625 Returns a set of org unit IDs which represent the highest orgs in
1626 the org tree where the user has the requested permission. The
1627 purpose of this method is to return the smallest set of org units
1628 which represent the full expanse of the user's ability to perform
1629 the requested action. The user whose perms this method should
1630 check is implied by the authtoken. /,
1632 {desc => 'authtoken', type => 'string'},
1633 {desc => 'permission name', type => 'string'},
1634 {desc => q/user id, optional. If present, check perms for
1635 this user instead of the logged in user/, type => 'number'},
1637 return => {desc => 'An array of org IDs'}
1641 sub user_has_work_perm_at {
1642 my($self, $conn, $auth, $perm, $user_id) = @_;
1643 my $e = new_editor(authtoken=>$auth);
1644 return $e->event unless $e->checkauth;
1645 if(defined $user_id) {
1646 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1647 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1649 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1652 __PACKAGE__->register_method(
1653 method => 'user_has_work_perm_at_batch',
1654 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1658 sub user_has_work_perm_at_batch {
1659 my($self, $conn, $auth, $perms, $user_id) = @_;
1660 my $e = new_editor(authtoken=>$auth);
1661 return $e->event unless $e->checkauth;
1662 if(defined $user_id) {
1663 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1664 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1667 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1673 __PACKAGE__->register_method(
1674 method => 'check_user_perms4',
1675 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1677 Returns the highest org unit id at which a user has a given permission
1678 If the requestor does not match the target user, the requestor must have
1679 'VIEW_PERMISSION' rights at the home org unit of the target user
1680 @param authtoken The login session key
1681 @param userid The id of the user in question
1682 @param perms An array of perm names to check
1683 @return An array of orgId's representing the org unit
1684 highest in the org tree within which the user has the requested permission
1685 The arrah of orgId's has matches the order of the perms array
1688 sub check_user_perms4 {
1689 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1691 my( $staff, $target, $org, $evt );
1693 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1694 $authtoken, $userid, 'VIEW_PERMISSION' );
1695 return $evt if $evt;
1698 return [] unless ref($perms);
1699 my $tree = $U->get_org_tree();
1701 for my $p (@$perms) {
1702 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1708 __PACKAGE__->register_method(
1709 method => "user_fines_summary",
1710 api_name => "open-ils.actor.user.fines.summary",
1713 desc => 'Returns a short summary of the users total open fines, ' .
1714 'excluding voided fines Params are login_session, user_id' ,
1716 {desc => 'Authentication token', type => 'string'},
1717 {desc => 'User ID', type => 'string'} # number?
1720 desc => "a 'mous' object, event on error",
1725 sub user_fines_summary {
1726 my( $self, $client, $auth, $user_id ) = @_;
1728 my $e = new_editor(authtoken=>$auth);
1729 return $e->event unless $e->checkauth;
1731 if( $user_id ne $e->requestor->id ) {
1732 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1733 return $e->event unless
1734 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1737 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1741 __PACKAGE__->register_method(
1742 method => "user_opac_vitals",
1743 api_name => "open-ils.actor.user.opac.vital_stats",
1747 desc => 'Returns a short summary of the users vital stats, including ' .
1748 'identification information, accumulated balance, number of holds, ' .
1749 'and current open circulation stats' ,
1751 {desc => 'Authentication token', type => 'string'},
1752 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1755 desc => "An object with four properties: user, fines, checkouts and holds."
1760 sub user_opac_vitals {
1761 my( $self, $client, $auth, $user_id ) = @_;
1763 my $e = new_editor(authtoken=>$auth);
1764 return $e->event unless $e->checkauth;
1766 $user_id ||= $e->requestor->id;
1768 my $user = $e->retrieve_actor_user( $user_id );
1771 ->method_lookup('open-ils.actor.user.fines.summary')
1772 ->run($auth => $user_id);
1773 return $fines if (defined($U->event_code($fines)));
1776 $fines = new Fieldmapper::money::open_user_summary ();
1777 $fines->balance_owed(0.00);
1778 $fines->total_owed(0.00);
1779 $fines->total_paid(0.00);
1780 $fines->usr($user_id);
1784 ->method_lookup('open-ils.actor.user.hold_requests.count')
1785 ->run($auth => $user_id);
1786 return $holds if (defined($U->event_code($holds)));
1789 ->method_lookup('open-ils.actor.user.checked_out.count')
1790 ->run($auth => $user_id);
1791 return $out if (defined($U->event_code($out)));
1793 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1795 my $unread_msgs = $e->search_actor_usr_message([
1796 {usr => $user_id, read_date => undef, deleted => 'f'},
1802 first_given_name => $user->first_given_name,
1803 second_given_name => $user->second_given_name,
1804 family_name => $user->family_name,
1805 alias => $user->alias,
1806 usrname => $user->usrname
1808 fines => $fines->to_bare_hash,
1811 messages => { unread => scalar(@$unread_msgs) }
1816 ##### a small consolidation of related method registrations
1817 my $common_params = [
1818 { desc => 'Authentication token', type => 'string' },
1819 { desc => 'User ID', type => 'string' },
1820 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1821 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1824 'open-ils.actor.user.transactions' => '',
1825 'open-ils.actor.user.transactions.fleshed' => '',
1826 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1827 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1828 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1829 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1832 foreach (keys %methods) {
1834 method => "user_transactions",
1837 desc => 'For a given user, retrieve a list of '
1838 . (/\.fleshed/ ? 'fleshed ' : '')
1839 . 'transactions' . $methods{$_}
1840 . ' optionally limited to transactions of a given type.',
1841 params => $common_params,
1843 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1844 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1848 $args{authoritative} = 1;
1849 __PACKAGE__->register_method(%args);
1852 # Now for the counts
1854 'open-ils.actor.user.transactions.count' => '',
1855 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1856 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1859 foreach (keys %methods) {
1861 method => "user_transactions",
1864 desc => 'For a given user, retrieve a count of open '
1865 . 'transactions' . $methods{$_}
1866 . ' optionally limited to transactions of a given type.',
1867 params => $common_params,
1868 return => { desc => "Integer count of transactions, or event on error" }
1871 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1872 __PACKAGE__->register_method(%args);
1875 __PACKAGE__->register_method(
1876 method => "user_transactions",
1877 api_name => "open-ils.actor.user.transactions.have_balance.total",
1880 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1881 . ' optionally limited to transactions of a given type.',
1882 params => $common_params,
1883 return => { desc => "Decimal balance value, or event on error" }
1888 sub user_transactions {
1889 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1892 my $e = new_editor(authtoken => $auth);
1893 return $e->event unless $e->checkauth;
1895 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1897 return $e->event unless
1898 $e->requestor->id == $user_id or
1899 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1901 my $api = $self->api_name();
1903 my $filter = ($api =~ /have_balance/o) ?
1904 { 'balance_owed' => { '<>' => 0 } }:
1905 { 'total_owed' => { '>' => 0 } };
1907 my $method = 'open-ils.actor.user.transactions.history.still_open';
1908 $method = "$method.authoritative" if $api =~ /authoritative/;
1909 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1911 if($api =~ /total/o) {
1913 $total += $_->balance_owed for @$trans;
1917 ($api =~ /count/o ) and return scalar @$trans;
1918 ($api !~ /fleshed/o) and return $trans;
1921 for my $t (@$trans) {
1923 if( $t->xact_type ne 'circulation' ) {
1924 push @resp, {transaction => $t};
1928 my $circ_data = flesh_circ($e, $t->id);
1929 push @resp, {transaction => $t, %$circ_data};
1936 __PACKAGE__->register_method(
1937 method => "user_transaction_retrieve",
1938 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1941 notes => "Returns a fleshed transaction record"
1944 __PACKAGE__->register_method(
1945 method => "user_transaction_retrieve",
1946 api_name => "open-ils.actor.user.transaction.retrieve",
1949 notes => "Returns a transaction record"
1952 sub user_transaction_retrieve {
1953 my($self, $client, $auth, $bill_id) = @_;
1955 my $e = new_editor(authtoken => $auth);
1956 return $e->event unless $e->checkauth;
1958 my $trans = $e->retrieve_money_billable_transaction_summary(
1959 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1961 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1963 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1965 return $trans unless $self->api_name =~ /flesh/;
1966 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1968 my $circ_data = flesh_circ($e, $trans->id, 1);
1970 return {transaction => $trans, %$circ_data};
1975 my $circ_id = shift;
1976 my $flesh_copy = shift;
1978 my $circ = $e->retrieve_action_circulation([
1982 circ => ['target_copy'],
1983 acp => ['call_number'],
1990 my $copy = $circ->target_copy;
1992 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1993 $mods = new Fieldmapper::metabib::virtual_record;
1994 $mods->doc_id(OILS_PRECAT_RECORD);
1995 $mods->title($copy->dummy_title);
1996 $mods->author($copy->dummy_author);
1999 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
2003 $circ->target_copy($circ->target_copy->id);
2004 $copy->call_number($copy->call_number->id);
2006 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2010 __PACKAGE__->register_method(
2011 method => "hold_request_count",
2012 api_name => "open-ils.actor.user.hold_requests.count",
2016 Returns hold ready vs. total counts.
2017 If a context org unit is provided, a third value
2018 is returned with key 'behind_desk', which reports
2019 how many holds are ready at the pickup library
2020 with the behind_desk flag set to true.
2024 sub hold_request_count {
2025 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2026 my $e = new_editor(authtoken => $authtoken);
2027 return $e->event unless $e->checkauth;
2029 $user_id = $e->requestor->id unless defined $user_id;
2031 if($e->requestor->id ne $user_id) {
2032 my $user = $e->retrieve_actor_user($user_id);
2033 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2036 my $holds = $e->json_query({
2037 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2041 fulfillment_time => {"=" => undef },
2042 cancel_time => undef,
2047 $_->{current_shelf_lib} and # avoid undef warnings
2048 $_->{pickup_lib} eq $_->{current_shelf_lib}
2052 total => scalar(@$holds),
2053 ready => scalar(@ready)
2057 # count of holds ready at pickup lib with behind_desk true.
2058 $resp->{behind_desk} = scalar(
2060 $_->{pickup_lib} == $ctx_org and
2061 $U->is_true($_->{behind_desk})
2069 __PACKAGE__->register_method(
2070 method => "checked_out",
2071 api_name => "open-ils.actor.user.checked_out",
2075 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2076 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2077 . "(i.e., outstanding balance or some other pending action on the circ). "
2078 . "The .count method also includes a 'total' field which sums all open circs.",
2080 { desc => 'Authentication Token', type => 'string'},
2081 { desc => 'User ID', type => 'string'},
2084 desc => 'Returns event on error, or an object with ID lists, like: '
2085 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2090 __PACKAGE__->register_method(
2091 method => "checked_out",
2092 api_name => "open-ils.actor.user.checked_out.count",
2095 signature => q/@see open-ils.actor.user.checked_out/
2099 my( $self, $conn, $auth, $userid ) = @_;
2101 my $e = new_editor(authtoken=>$auth);
2102 return $e->event unless $e->checkauth;
2104 if( $userid ne $e->requestor->id ) {
2105 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2106 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2108 # see if there is a friend link allowing circ.view perms
2109 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2110 $e, $userid, $e->requestor->id, 'circ.view');
2111 return $e->event unless $allowed;
2115 my $count = $self->api_name =~ /count/;
2116 return _checked_out( $count, $e, $userid );
2120 my( $iscount, $e, $userid ) = @_;
2126 claims_returned => [],
2129 my $meth = 'retrieve_action_open_circ_';
2137 claims_returned => 0,
2144 my $data = $e->$meth($userid);
2148 $result{$_} += $data->$_() for (keys %result);
2149 $result{total} += $data->$_() for (keys %result);
2151 for my $k (keys %result) {
2152 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2162 __PACKAGE__->register_method(
2163 method => "checked_in_with_fines",
2164 api_name => "open-ils.actor.user.checked_in_with_fines",
2167 signature => q/@see open-ils.actor.user.checked_out/
2170 sub checked_in_with_fines {
2171 my( $self, $conn, $auth, $userid ) = @_;
2173 my $e = new_editor(authtoken=>$auth);
2174 return $e->event unless $e->checkauth;
2176 if( $userid ne $e->requestor->id ) {
2177 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2180 # money is owed on these items and they are checked in
2181 my $open = $e->search_action_circulation(
2184 xact_finish => undef,
2185 checkin_time => { "!=" => undef },
2190 my( @lost, @cr, @lo );
2191 for my $c (@$open) {
2192 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2193 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2194 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2199 claims_returned => \@cr,
2200 long_overdue => \@lo
2206 my ($api, $desc, $auth) = @_;
2207 $desc = $desc ? (" " . $desc) : '';
2208 my $ids = ($api =~ /ids$/) ? 1 : 0;
2211 method => "user_transaction_history",
2212 api_name => "open-ils.actor.user.transactions.$api",
2214 desc => "For a given User ID, returns a list of billable transaction" .
2215 ($ids ? " id" : '') .
2216 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2217 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2219 {desc => 'Authentication token', type => 'string'},
2220 {desc => 'User ID', type => 'number'},
2221 {desc => 'Transaction type (optional)', type => 'number'},
2222 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2225 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2229 $auth and push @sig, (authoritative => 1);
2233 my %auth_hist_methods = (
2235 'history.have_charge' => 'that have an initial charge',
2236 'history.still_open' => 'that are not finished',
2237 'history.have_balance' => 'that have a balance',
2238 'history.have_bill' => 'that have billings',
2239 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2240 'history.have_payment' => 'that have at least 1 payment',
2243 foreach (keys %auth_hist_methods) {
2244 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2245 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2246 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2249 sub user_transaction_history {
2250 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2254 my $e = new_editor(authtoken=>$auth);
2255 return $e->die_event unless $e->checkauth;
2257 if ($e->requestor->id ne $userid) {
2258 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2261 my $api = $self->api_name;
2262 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2264 if(defined($type)) {
2265 $filter->{'xact_type'} = $type;
2268 if($api =~ /have_bill_or_payment/o) {
2270 # transactions that have a non-zero sum across all billings or at least 1 payment
2271 $filter->{'-or'} = {
2272 'balance_owed' => { '<>' => 0 },
2273 'last_payment_ts' => { '<>' => undef }
2276 } elsif($api =~ /have_payment/) {
2278 $filter->{last_payment_ts} ||= {'<>' => undef};
2280 } elsif( $api =~ /have_balance/o) {
2282 # transactions that have a non-zero overall balance
2283 $filter->{'balance_owed'} = { '<>' => 0 };
2285 } elsif( $api =~ /have_charge/o) {
2287 # transactions that have at least 1 billing, regardless of whether it was voided
2288 $filter->{'last_billing_ts'} = { '<>' => undef };
2290 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2292 # transactions that have non-zero sum across all billings. This will exclude
2293 # xacts where all billings have been voided
2294 $filter->{'total_owed'} = { '<>' => 0 };
2297 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2298 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2299 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2301 my $mbts = $e->search_money_billable_transaction_summary(
2302 [ { usr => $userid, @xact_finish, %$filter },
2307 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2308 return $mbts unless $api =~ /fleshed/;
2311 for my $t (@$mbts) {
2313 if( $t->xact_type ne 'circulation' ) {
2314 push @resp, {transaction => $t};
2318 my $circ_data = flesh_circ($e, $t->id);
2319 push @resp, {transaction => $t, %$circ_data};
2327 __PACKAGE__->register_method(
2328 method => "user_perms",
2329 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2331 notes => "Returns a list of permissions"
2335 my( $self, $client, $authtoken, $user ) = @_;
2337 my( $staff, $evt ) = $apputils->checkses($authtoken);
2338 return $evt if $evt;
2340 $user ||= $staff->id;
2342 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2346 return $apputils->simple_scalar_request(
2348 "open-ils.storage.permission.user_perms.atomic",
2352 __PACKAGE__->register_method(
2353 method => "retrieve_perms",
2354 api_name => "open-ils.actor.permissions.retrieve",
2355 notes => "Returns a list of permissions"
2357 sub retrieve_perms {
2358 my( $self, $client ) = @_;
2359 return $apputils->simple_scalar_request(
2361 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2362 { id => { '!=' => undef } }
2366 __PACKAGE__->register_method(
2367 method => "retrieve_groups",
2368 api_name => "open-ils.actor.groups.retrieve",
2369 notes => "Returns a list of user groups"
2371 sub retrieve_groups {
2372 my( $self, $client ) = @_;
2373 return new_editor()->retrieve_all_permission_grp_tree();
2376 __PACKAGE__->register_method(
2377 method => "retrieve_org_address",
2378 api_name => "open-ils.actor.org_unit.address.retrieve",
2379 notes => <<' NOTES');
2380 Returns an org_unit address by ID
2381 @param An org_address ID
2383 sub retrieve_org_address {
2384 my( $self, $client, $id ) = @_;
2385 return $apputils->simple_scalar_request(
2387 "open-ils.cstore.direct.actor.org_address.retrieve",
2392 __PACKAGE__->register_method(
2393 method => "retrieve_groups_tree",
2394 api_name => "open-ils.actor.groups.tree.retrieve",
2395 notes => "Returns a list of user groups"
2398 sub retrieve_groups_tree {
2399 my( $self, $client ) = @_;
2400 return new_editor()->search_permission_grp_tree(
2405 flesh_fields => { pgt => ["children"] },
2406 order_by => { pgt => 'name'}
2413 __PACKAGE__->register_method(
2414 method => "add_user_to_groups",
2415 api_name => "open-ils.actor.user.set_groups",
2416 notes => "Adds a user to one or more permission groups"
2419 sub add_user_to_groups {
2420 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2422 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2423 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2424 return $evt if $evt;
2426 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2427 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2428 return $evt if $evt;
2430 $apputils->simplereq(
2432 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2434 for my $group (@$groups) {
2435 my $link = Fieldmapper::permission::usr_grp_map->new;
2437 $link->usr($userid);
2439 my $id = $apputils->simplereq(
2441 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2447 __PACKAGE__->register_method(
2448 method => "get_user_perm_groups",
2449 api_name => "open-ils.actor.user.get_groups",
2450 notes => "Retrieve a user's permission groups."
2454 sub get_user_perm_groups {
2455 my( $self, $client, $authtoken, $userid ) = @_;
2457 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2458 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2459 return $evt if $evt;
2461 return $apputils->simplereq(
2463 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2467 __PACKAGE__->register_method(
2468 method => "get_user_work_ous",
2469 api_name => "open-ils.actor.user.get_work_ous",
2470 notes => "Retrieve a user's work org units."
2473 __PACKAGE__->register_method(
2474 method => "get_user_work_ous",
2475 api_name => "open-ils.actor.user.get_work_ous.ids",
2476 notes => "Retrieve a user's work org units."
2479 sub get_user_work_ous {
2480 my( $self, $client, $auth, $userid ) = @_;
2481 my $e = new_editor(authtoken=>$auth);
2482 return $e->event unless $e->checkauth;
2483 $userid ||= $e->requestor->id;
2485 if($e->requestor->id != $userid) {
2486 my $user = $e->retrieve_actor_user($userid)
2487 or return $e->event;
2488 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2491 return $e->search_permission_usr_work_ou_map({usr => $userid})
2492 unless $self->api_name =~ /.ids$/;
2494 # client just wants a list of org IDs
2495 return $U->get_user_work_ou_ids($e, $userid);
2500 __PACKAGE__->register_method(
2501 method => 'register_workstation',
2502 api_name => 'open-ils.actor.workstation.register.override',
2503 signature => q/@see open-ils.actor.workstation.register/
2506 __PACKAGE__->register_method(
2507 method => 'register_workstation',
2508 api_name => 'open-ils.actor.workstation.register',
2510 Registers a new workstion in the system
2511 @param authtoken The login session key
2512 @param name The name of the workstation id
2513 @param owner The org unit that owns this workstation
2514 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2515 if the name is already in use.
2519 sub register_workstation {
2520 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2522 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2523 return $e->die_event unless $e->checkauth;
2524 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2525 my $existing = $e->search_actor_workstation({name => $name})->[0];
2526 $oargs = { all => 1 } unless defined $oargs;
2530 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2531 # workstation with the given name exists.
2533 if($owner ne $existing->owning_lib) {
2534 # if necessary, update the owning_lib of the workstation
2536 $logger->info("changing owning lib of workstation ".$existing->id.
2537 " from ".$existing->owning_lib." to $owner");
2538 return $e->die_event unless
2539 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2541 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2543 $existing->owning_lib($owner);
2544 return $e->die_event unless $e->update_actor_workstation($existing);
2550 "attempt to register an existing workstation. returning existing ID");
2553 return $existing->id;
2556 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2560 my $ws = Fieldmapper::actor::workstation->new;
2561 $ws->owning_lib($owner);
2563 $e->create_actor_workstation($ws) or return $e->die_event;
2565 return $ws->id; # note: editor sets the id on the new object for us
2568 __PACKAGE__->register_method(
2569 method => 'workstation_list',
2570 api_name => 'open-ils.actor.workstation.list',
2572 Returns a list of workstations registered at the given location
2573 @param authtoken The login session key
2574 @param ids A list of org_unit.id's for the workstation owners
2578 sub workstation_list {
2579 my( $self, $conn, $authtoken, @orgs ) = @_;
2581 my $e = new_editor(authtoken=>$authtoken);
2582 return $e->event unless $e->checkauth;
2587 unless $e->allowed('REGISTER_WORKSTATION', $o);
2588 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2594 __PACKAGE__->register_method(
2595 method => 'fetch_patron_note',
2596 api_name => 'open-ils.actor.note.retrieve.all',
2599 Returns a list of notes for a given user
2600 Requestor must have VIEW_USER permission if pub==false and
2601 @param authtoken The login session key
2602 @param args Hash of params including
2603 patronid : the patron's id
2604 pub : true if retrieving only public notes
2608 sub fetch_patron_note {
2609 my( $self, $conn, $authtoken, $args ) = @_;
2610 my $patronid = $$args{patronid};
2612 my($reqr, $evt) = $U->checkses($authtoken);
2613 return $evt if $evt;
2616 ($patron, $evt) = $U->fetch_user($patronid);
2617 return $evt if $evt;
2620 if( $patronid ne $reqr->id ) {
2621 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2622 return $evt if $evt;
2624 return $U->cstorereq(
2625 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2626 { usr => $patronid, pub => 't' } );
2629 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2630 return $evt if $evt;
2632 return $U->cstorereq(
2633 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2636 __PACKAGE__->register_method(
2637 method => 'create_user_note',
2638 api_name => 'open-ils.actor.note.create',
2640 Creates a new note for the given user
2641 @param authtoken The login session key
2642 @param note The note object
2645 sub create_user_note {
2646 my( $self, $conn, $authtoken, $note ) = @_;
2647 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2648 return $e->die_event unless $e->checkauth;
2650 my $user = $e->retrieve_actor_user($note->usr)
2651 or return $e->die_event;
2653 return $e->die_event unless
2654 $e->allowed('UPDATE_USER',$user->home_ou);
2656 $note->creator($e->requestor->id);
2657 $e->create_actor_usr_note($note) or return $e->die_event;
2663 __PACKAGE__->register_method(
2664 method => 'delete_user_note',
2665 api_name => 'open-ils.actor.note.delete',
2667 Deletes a note for the given user
2668 @param authtoken The login session key
2669 @param noteid The note id
2672 sub delete_user_note {
2673 my( $self, $conn, $authtoken, $noteid ) = @_;
2675 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2676 return $e->die_event unless $e->checkauth;
2677 my $note = $e->retrieve_actor_usr_note($noteid)
2678 or return $e->die_event;
2679 my $user = $e->retrieve_actor_user($note->usr)
2680 or return $e->die_event;
2681 return $e->die_event unless
2682 $e->allowed('UPDATE_USER', $user->home_ou);
2684 $e->delete_actor_usr_note($note) or return $e->die_event;
2690 __PACKAGE__->register_method(
2691 method => 'update_user_note',
2692 api_name => 'open-ils.actor.note.update',
2694 @param authtoken The login session key
2695 @param note The note
2699 sub update_user_note {
2700 my( $self, $conn, $auth, $note ) = @_;
2701 my $e = new_editor(authtoken=>$auth, xact=>1);
2702 return $e->die_event unless $e->checkauth;
2703 my $patron = $e->retrieve_actor_user($note->usr)
2704 or return $e->die_event;
2705 return $e->die_event unless
2706 $e->allowed('UPDATE_USER', $patron->home_ou);
2707 $e->update_actor_user_note($note)
2708 or return $e->die_event;
2713 __PACKAGE__->register_method(
2714 method => 'fetch_patron_messages',
2715 api_name => 'open-ils.actor.message.retrieve',
2718 Returns a list of notes for a given user, not
2719 including ones marked deleted
2720 @param authtoken The login session key
2721 @param patronid patron ID
2722 @param options hash containing optional limit and offset
2726 sub fetch_patron_messages {
2727 my( $self, $conn, $auth, $patronid, $options ) = @_;
2731 my $e = new_editor(authtoken => $auth);
2732 return $e->die_event unless $e->checkauth;
2734 if ($e->requestor->id ne $patronid) {
2735 return $e->die_event unless $e->allowed('VIEW_USER');
2738 my $select_clause = { usr => $patronid };
2739 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2740 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2741 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2743 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2748 __PACKAGE__->register_method(
2749 method => 'create_closed_date',
2750 api_name => 'open-ils.actor.org_unit.closed_date.create',
2752 Creates a new closing entry for the given org_unit
2753 @param authtoken The login session key
2754 @param note The closed_date object
2757 sub create_closed_date {
2758 my( $self, $conn, $authtoken, $cd ) = @_;
2760 my( $user, $evt ) = $U->checkses($authtoken);
2761 return $evt if $evt;
2763 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2764 return $evt if $evt;
2766 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2768 my $id = $U->storagereq(
2769 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2770 return $U->DB_UPDATE_FAILED($cd) unless $id;
2775 __PACKAGE__->register_method(
2776 method => 'delete_closed_date',
2777 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2779 Deletes a closing entry for the given org_unit
2780 @param authtoken The login session key
2781 @param noteid The close_date id
2784 sub delete_closed_date {
2785 my( $self, $conn, $authtoken, $cd ) = @_;
2787 my( $user, $evt ) = $U->checkses($authtoken);
2788 return $evt if $evt;
2791 ($cd_obj, $evt) = fetch_closed_date($cd);
2792 return $evt if $evt;
2794 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2795 return $evt if $evt;
2797 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2799 my $stat = $U->storagereq(
2800 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2801 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2806 __PACKAGE__->register_method(
2807 method => 'usrname_exists',
2808 api_name => 'open-ils.actor.username.exists',
2810 desc => 'Check if a username is already taken (by an undeleted patron)',
2812 {desc => 'Authentication token', type => 'string'},
2813 {desc => 'Username', type => 'string'}
2816 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2821 sub usrname_exists {
2822 my( $self, $conn, $auth, $usrname ) = @_;
2823 my $e = new_editor(authtoken=>$auth);
2824 return $e->event unless $e->checkauth;
2825 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2826 return $$a[0] if $a and @$a;
2830 __PACKAGE__->register_method(
2831 method => 'barcode_exists',
2832 api_name => 'open-ils.actor.barcode.exists',
2834 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2837 sub barcode_exists {
2838 my( $self, $conn, $auth, $barcode ) = @_;
2839 my $e = new_editor(authtoken=>$auth);
2840 return $e->event unless $e->checkauth;
2841 my $card = $e->search_actor_card({barcode => $barcode});
2847 #return undef unless @$card;
2848 #return $card->[0]->usr;
2852 __PACKAGE__->register_method(
2853 method => 'retrieve_net_levels',
2854 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2857 sub retrieve_net_levels {
2858 my( $self, $conn, $auth ) = @_;
2859 my $e = new_editor(authtoken=>$auth);
2860 return $e->event unless $e->checkauth;
2861 return $e->retrieve_all_config_net_access_level();
2864 # Retain the old typo API name just in case
2865 __PACKAGE__->register_method(
2866 method => 'fetch_org_by_shortname',
2867 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2869 __PACKAGE__->register_method(
2870 method => 'fetch_org_by_shortname',
2871 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2873 sub fetch_org_by_shortname {
2874 my( $self, $conn, $sname ) = @_;
2875 my $e = new_editor();
2876 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2877 return $e->event unless $org;
2882 __PACKAGE__->register_method(
2883 method => 'session_home_lib',
2884 api_name => 'open-ils.actor.session.home_lib',
2887 sub session_home_lib {
2888 my( $self, $conn, $auth ) = @_;
2889 my $e = new_editor(authtoken=>$auth);
2890 return undef unless $e->checkauth;
2891 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2892 return $org->shortname;
2895 __PACKAGE__->register_method(
2896 method => 'session_safe_token',
2897 api_name => 'open-ils.actor.session.safe_token',
2899 Returns a hashed session ID that is safe for export to the world.
2900 This safe token will expire after 1 hour of non-use.
2901 @param auth Active authentication token
2905 sub session_safe_token {
2906 my( $self, $conn, $auth ) = @_;
2907 my $e = new_editor(authtoken=>$auth);
2908 return undef unless $e->checkauth;
2910 my $safe_token = md5_hex($auth);
2912 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2914 # add more user fields as needed
2916 "safe-token-user-$safe_token", {
2917 id => $e->requestor->id,
2918 home_ou_shortname => $e->retrieve_actor_org_unit(
2919 $e->requestor->home_ou)->shortname,
2928 __PACKAGE__->register_method(
2929 method => 'safe_token_home_lib',
2930 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2932 Returns the home library shortname from the session
2933 asscociated with a safe token from generated by
2934 open-ils.actor.session.safe_token.
2935 @param safe_token Active safe token
2936 @param who Optional user activity "ewho" value
2940 sub safe_token_home_lib {
2941 my( $self, $conn, $safe_token, $who ) = @_;
2942 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2944 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2945 return unless $blob;
2947 $U->log_user_activity($blob->{id}, $who, 'verify');
2948 return $blob->{home_ou_shortname};
2952 __PACKAGE__->register_method(
2953 method => "update_penalties",
2954 api_name => "open-ils.actor.user.penalties.update"
2957 sub update_penalties {
2958 my($self, $conn, $auth, $user_id) = @_;
2959 my $e = new_editor(authtoken=>$auth, xact => 1);
2960 return $e->die_event unless $e->checkauth;
2961 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2962 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2963 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2964 return $evt if $evt;
2970 __PACKAGE__->register_method(
2971 method => "apply_penalty",
2972 api_name => "open-ils.actor.user.penalty.apply"
2976 my($self, $conn, $auth, $penalty) = @_;
2978 my $e = new_editor(authtoken=>$auth, xact => 1);
2979 return $e->die_event unless $e->checkauth;
2981 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2982 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2984 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2987 (defined $ptype->org_depth) ?
2988 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2991 $penalty->org_unit($ctx_org);
2992 $penalty->staff($e->requestor->id);
2993 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2996 return $penalty->id;
2999 __PACKAGE__->register_method(
3000 method => "remove_penalty",
3001 api_name => "open-ils.actor.user.penalty.remove"
3004 sub remove_penalty {
3005 my($self, $conn, $auth, $penalty) = @_;
3006 my $e = new_editor(authtoken=>$auth, xact => 1);
3007 return $e->die_event unless $e->checkauth;
3008 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3009 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3011 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3016 __PACKAGE__->register_method(
3017 method => "update_penalty_note",
3018 api_name => "open-ils.actor.user.penalty.note.update"
3021 sub update_penalty_note {
3022 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3023 my $e = new_editor(authtoken=>$auth, xact => 1);
3024 return $e->die_event unless $e->checkauth;
3025 for my $penalty_id (@$penalty_ids) {
3026 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3027 if (! $penalty ) { return $e->die_event; }
3028 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3029 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3031 $penalty->note( $note ); $penalty->ischanged( 1 );
3033 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3039 __PACKAGE__->register_method(
3040 method => "ranged_penalty_thresholds",
3041 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3045 sub ranged_penalty_thresholds {
3046 my($self, $conn, $auth, $context_org) = @_;
3047 my $e = new_editor(authtoken=>$auth);
3048 return $e->event unless $e->checkauth;
3049 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3050 my $list = $e->search_permission_grp_penalty_threshold([
3051 {org_unit => $U->get_org_ancestors($context_org)},
3052 {order_by => {pgpt => 'id'}}
3054 $conn->respond($_) for @$list;
3060 __PACKAGE__->register_method(
3061 method => "user_retrieve_fleshed_by_id",
3063 api_name => "open-ils.actor.user.fleshed.retrieve",
3066 sub user_retrieve_fleshed_by_id {
3067 my( $self, $client, $auth, $user_id, $fields ) = @_;
3068 my $e = new_editor(authtoken => $auth);
3069 return $e->event unless $e->checkauth;
3071 if( $e->requestor->id != $user_id ) {
3072 return $e->event unless $e->allowed('VIEW_USER');
3079 "standing_penalties",
3085 return new_flesh_user($user_id, $fields, $e);
3089 sub new_flesh_user {
3092 my $fields = shift || [];
3095 my $fetch_penalties = 0;
3096 if(grep {$_ eq 'standing_penalties'} @$fields) {
3097 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3098 $fetch_penalties = 1;
3101 my $fetch_usr_act = 0;
3102 if(grep {$_ eq 'usr_activity'} @$fields) {
3103 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3107 my $user = $e->retrieve_actor_user(
3112 "flesh_fields" => { "au" => $fields }
3115 ) or return $e->die_event;
3118 if( grep { $_ eq 'addresses' } @$fields ) {
3120 $user->addresses([]) unless @{$user->addresses};
3121 # don't expose "replaced" addresses by default
3122 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3124 if( ref $user->billing_address ) {
3125 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3126 push( @{$user->addresses}, $user->billing_address );
3130 if( ref $user->mailing_address ) {
3131 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3132 push( @{$user->addresses}, $user->mailing_address );
3137 if($fetch_penalties) {
3138 # grab the user penalties ranged for this location
3139 $user->standing_penalties(
3140 $e->search_actor_user_standing_penalty([
3143 {stop_date => undef},
3144 {stop_date => {'>' => 'now'}}
3146 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3149 flesh_fields => {ausp => ['standing_penalty']}
3155 # retrieve the most recent usr_activity entry
3156 if ($fetch_usr_act) {
3158 # max number to return for simple patron fleshing
3159 my $limit = $U->ou_ancestor_setting_value(
3160 $e->requestor->ws_ou,
3161 'circ.patron.usr_activity_retrieve.max');
3165 flesh_fields => {auact => ['etype']},
3166 order_by => {auact => 'event_time DESC'},
3169 # 0 == none, <0 == return all
3170 $limit = 1 unless defined $limit;
3171 $opts->{limit} = $limit if $limit > 0;
3173 $user->usr_activity(
3175 [] : # skip the DB call
3176 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3181 $user->clear_passwd();
3188 __PACKAGE__->register_method(
3189 method => "user_retrieve_parts",
3190 api_name => "open-ils.actor.user.retrieve.parts",
3193 sub user_retrieve_parts {
3194 my( $self, $client, $auth, $user_id, $fields ) = @_;
3195 my $e = new_editor(authtoken => $auth);
3196 return $e->event unless $e->checkauth;
3197 $user_id ||= $e->requestor->id;
3198 if( $e->requestor->id != $user_id ) {
3199 return $e->event unless $e->allowed('VIEW_USER');
3202 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3203 push(@resp, $user->$_()) for(@$fields);
3209 __PACKAGE__->register_method(
3210 method => 'user_opt_in_enabled',
3211 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3212 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3215 sub user_opt_in_enabled {
3216 my($self, $conn) = @_;
3217 my $sc = OpenSRF::Utils::SettingsClient->new;
3218 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3223 __PACKAGE__->register_method(
3224 method => 'user_opt_in_at_org',
3225 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3227 @param $auth The auth token
3228 @param user_id The ID of the user to test
3229 @return 1 if the user has opted in at the specified org,
3230 event on error, and 0 otherwise. /
3232 sub user_opt_in_at_org {
3233 my($self, $conn, $auth, $user_id) = @_;
3235 # see if we even need to enforce the opt-in value
3236 return 1 unless user_opt_in_enabled($self);
3238 my $e = new_editor(authtoken => $auth);
3239 return $e->event unless $e->checkauth;
3241 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3242 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3244 my $ws_org = $e->requestor->ws_ou;
3245 # user is automatically opted-in if they are from the local org
3246 return 1 if $user->home_ou eq $ws_org;
3248 # get the boundary setting
3249 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3251 # auto opt in if user falls within the opt boundary
3252 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3254 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3256 my $vals = $e->search_actor_usr_org_unit_opt_in(
3257 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3263 __PACKAGE__->register_method(
3264 method => 'create_user_opt_in_at_org',
3265 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3267 @param $auth The auth token
3268 @param user_id The ID of the user to test
3269 @return The ID of the newly created object, event on error./
3272 sub create_user_opt_in_at_org {
3273 my($self, $conn, $auth, $user_id, $org_id) = @_;
3275 my $e = new_editor(authtoken => $auth, xact=>1);
3276 return $e->die_event unless $e->checkauth;
3278 # if a specific org unit wasn't passed in, get one based on the defaults;
3280 my $wsou = $e->requestor->ws_ou;
3281 # get the default opt depth
3282 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3283 # get the org unit at that depth
3284 my $org = $e->json_query({
3285 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3286 $org_id = $org->{id};
3289 # fall back to the workstation OU, the pre-opt-in-boundary way
3290 $org_id = $e->requestor->ws_ou;
3293 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3294 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3296 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3298 $opt_in->org_unit($org_id);
3299 $opt_in->usr($user_id);
3300 $opt_in->staff($e->requestor->id);
3301 $opt_in->opt_in_ts('now');
3302 $opt_in->opt_in_ws($e->requestor->wsid);
3304 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3305 or return $e->die_event;
3313 __PACKAGE__->register_method (
3314 method => 'retrieve_org_hours',
3315 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3317 Returns the hours of operation for a specified org unit
3318 @param authtoken The login session key
3319 @param org_id The org_unit ID
3323 sub retrieve_org_hours {
3324 my($self, $conn, $auth, $org_id) = @_;
3325 my $e = new_editor(authtoken => $auth);
3326 return $e->die_event unless $e->checkauth;
3327 $org_id ||= $e->requestor->ws_ou;
3328 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3332 __PACKAGE__->register_method (
3333 method => 'verify_user_password',
3334 api_name => 'open-ils.actor.verify_user_password',
3336 Given a barcode or username and the MD5 encoded password,
3337 returns 1 if the password is correct. Returns 0 otherwise.
3341 sub verify_user_password {
3342 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3343 my $e = new_editor(authtoken => $auth);
3344 return $e->die_event unless $e->checkauth;
3346 my $user_by_barcode;
3347 my $user_by_username;
3349 my $card = $e->search_actor_card([
3350 {barcode => $barcode},
3351 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3352 $user_by_barcode = $card->usr;
3353 $user = $user_by_barcode;
3356 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3357 $user = $user_by_username;
3359 return 0 if (!$user);
3360 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3361 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3362 return 1 if $user->passwd eq $password;
3366 __PACKAGE__->register_method (
3367 method => 'retrieve_usr_id_via_barcode_or_usrname',
3368 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3370 Given a barcode or username returns the id for the user or
3375 sub retrieve_usr_id_via_barcode_or_usrname {
3376 my($self, $conn, $auth, $barcode, $username) = @_;
3377 my $e = new_editor(authtoken => $auth);
3378 return $e->die_event unless $e->checkauth;
3379 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3381 my $user_by_barcode;
3382 my $user_by_username;
3383 $logger->info("$id_as_barcode is the ID as BARCODE");
3385 my $card = $e->search_actor_card([
3386 {barcode => $barcode},
3387 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3388 if ($id_as_barcode =~ /^t/i) {
3390 $user = $e->retrieve_actor_user($barcode);
3391 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3393 $user_by_barcode = $card->usr;
3394 $user = $user_by_barcode;
3397 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3398 $user_by_barcode = $card->usr;
3399 $user = $user_by_barcode;
3404 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3406 $user = $user_by_username;
3408 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3409 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3410 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3415 __PACKAGE__->register_method (
3416 method => 'merge_users',
3417 api_name => 'open-ils.actor.user.merge',
3420 Given a list of source users and destination user, transfer all data from the source
3421 to the dest user and delete the source user. All user related data is
3422 transferred, including circulations, holds, bookbags, etc.
3428 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3429 my $e = new_editor(xact => 1, authtoken => $auth);
3430 return $e->die_event unless $e->checkauth;
3432 # disallow the merge if any subordinate accounts are in collections
3433 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3434 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3436 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3437 my $del_addrs = ($U->ou_ancestor_setting_value(
3438 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3439 my $del_cards = ($U->ou_ancestor_setting_value(
3440 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3441 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3442 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3444 for my $src_id (@$user_ids) {
3445 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3447 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3448 if($src_user->home_ou ne $master_user->home_ou) {
3449 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3452 return $e->die_event unless
3453 $e->json_query({from => [
3468 __PACKAGE__->register_method (
3469 method => 'approve_user_address',
3470 api_name => 'open-ils.actor.user.pending_address.approve',
3477 sub approve_user_address {
3478 my($self, $conn, $auth, $addr) = @_;
3479 my $e = new_editor(xact => 1, authtoken => $auth);
3480 return $e->die_event unless $e->checkauth;
3482 # if the caller passes an address object, assume they want to
3483 # update it first before approving it
3484 $e->update_actor_user_address($addr) or return $e->die_event;
3486 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3488 my $user = $e->retrieve_actor_user($addr->usr);
3489 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3490 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3491 or return $e->die_event;
3493 return [values %$result]->[0];
3497 __PACKAGE__->register_method (
3498 method => 'retrieve_friends',
3499 api_name => 'open-ils.actor.friends.retrieve',
3502 returns { confirmed: [], pending_out: [], pending_in: []}
3503 pending_out are users I'm requesting friendship with
3504 pending_in are users requesting friendship with me
3509 sub retrieve_friends {
3510 my($self, $conn, $auth, $user_id, $options) = @_;
3511 my $e = new_editor(authtoken => $auth);
3512 return $e->event unless $e->checkauth;
3513 $user_id ||= $e->requestor->id;
3515 if($user_id != $e->requestor->id) {
3516 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3517 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3520 return OpenILS::Application::Actor::Friends->retrieve_friends(
3521 $e, $user_id, $options);
3526 __PACKAGE__->register_method (
3527 method => 'apply_friend_perms',
3528 api_name => 'open-ils.actor.friends.perms.apply',
3534 sub apply_friend_perms {
3535 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3536 my $e = new_editor(authtoken => $auth, xact => 1);
3537 return $e->die_event unless $e->checkauth;
3539 if($user_id != $e->requestor->id) {
3540 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3541 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3544 for my $perm (@perms) {
3546 OpenILS::Application::Actor::Friends->apply_friend_perm(
3547 $e, $user_id, $delegate_id, $perm);
3548 return $evt if $evt;
3556 __PACKAGE__->register_method (
3557 method => 'update_user_pending_address',
3558 api_name => 'open-ils.actor.user.address.pending.cud'
3561 sub update_user_pending_address {
3562 my($self, $conn, $auth, $addr) = @_;
3563 my $e = new_editor(authtoken => $auth, xact => 1);
3564 return $e->die_event unless $e->checkauth;
3566 if($addr->usr != $e->requestor->id) {
3567 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3568 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3572 $e->create_actor_user_address($addr) or return $e->die_event;
3573 } elsif($addr->isdeleted) {
3574 $e->delete_actor_user_address($addr) or return $e->die_event;
3576 $e->update_actor_user_address($addr) or return $e->die_event;
3584 __PACKAGE__->register_method (
3585 method => 'user_events',
3586 api_name => 'open-ils.actor.user.events.circ',
3589 __PACKAGE__->register_method (
3590 method => 'user_events',
3591 api_name => 'open-ils.actor.user.events.ahr',
3596 my($self, $conn, $auth, $user_id, $filters) = @_;
3597 my $e = new_editor(authtoken => $auth);
3598 return $e->event unless $e->checkauth;
3600 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3601 my $user_field = 'usr';
3604 $filters->{target} = {
3605 select => { $obj_type => ['id'] },
3607 where => {usr => $user_id}
3610 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3611 if($e->requestor->id != $user_id) {
3612 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3615 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3616 my $req = $ses->request('open-ils.trigger.events_by_target',
3617 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3619 while(my $resp = $req->recv) {
3620 my $val = $resp->content;
3621 my $tgt = $val->target;
3623 if($obj_type eq 'circ') {
3624 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3626 } elsif($obj_type eq 'ahr') {
3627 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3628 if $tgt->current_copy;
3631 $conn->respond($val) if $val;
3637 __PACKAGE__->register_method (
3638 method => 'copy_events',
3639 api_name => 'open-ils.actor.copy.events.circ',
3642 __PACKAGE__->register_method (
3643 method => 'copy_events',
3644 api_name => 'open-ils.actor.copy.events.ahr',
3649 my($self, $conn, $auth, $copy_id, $filters) = @_;
3650 my $e = new_editor(authtoken => $auth);
3651 return $e->event unless $e->checkauth;
3653 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3655 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3657 my $copy_field = 'target_copy';
3658 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3661 $filters->{target} = {
3662 select => { $obj_type => ['id'] },
3664 where => {$copy_field => $copy_id}
3668 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3669 my $req = $ses->request('open-ils.trigger.events_by_target',
3670 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3672 while(my $resp = $req->recv) {
3673 my $val = $resp->content;
3674 my $tgt = $val->target;
3676 my $user = $e->retrieve_actor_user($tgt->usr);
3677 if($e->requestor->id != $user->id) {
3678 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3681 $tgt->$copy_field($copy);
3684 $conn->respond($val) if $val;
3693 __PACKAGE__->register_method (
3694 method => 'update_events',
3695 api_name => 'open-ils.actor.user.event.cancel.batch',
3698 __PACKAGE__->register_method (
3699 method => 'update_events',
3700 api_name => 'open-ils.actor.user.event.reset.batch',
3705 my($self, $conn, $auth, $event_ids) = @_;
3706 my $e = new_editor(xact => 1, authtoken => $auth);
3707 return $e->die_event unless $e->checkauth;
3710 for my $id (@$event_ids) {
3712 # do a little dance to determine what user we are ultimately affecting
3713 my $event = $e->retrieve_action_trigger_event([
3716 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3718 ]) or return $e->die_event;
3721 if($event->event_def->hook->core_type eq 'circ') {
3722 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3723 } elsif($event->event_def->hook->core_type eq 'ahr') {
3724 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3729 my $user = $e->retrieve_actor_user($user_id);
3730 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3732 if($self->api_name =~ /cancel/) {
3733 $event->state('invalid');
3734 } elsif($self->api_name =~ /reset/) {
3735 $event->clear_start_time;
3736 $event->clear_update_time;
3737 $event->state('pending');
3740 $e->update_action_trigger_event($event) or return $e->die_event;
3741 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3745 return {complete => 1};
3749 __PACKAGE__->register_method (
3750 method => 'really_delete_user',
3751 api_name => 'open-ils.actor.user.delete.override',
3752 signature => q/@see open-ils.actor.user.delete/
3755 __PACKAGE__->register_method (
3756 method => 'really_delete_user',
3757 api_name => 'open-ils.actor.user.delete',
3759 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3760 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3761 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3762 dest_usr_id is only required when deleting a user that performs staff functions.
3766 sub really_delete_user {
3767 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3768 my $e = new_editor(authtoken => $auth, xact => 1);
3769 return $e->die_event unless $e->checkauth;
3770 $oargs = { all => 1 } unless defined $oargs;
3772 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3773 my $open_bills = $e->json_query({
3774 select => { mbts => ['id'] },
3777 xact_finish => { '=' => undef },
3778 usr => { '=' => $user_id },
3780 }) or return $e->die_event;
3782 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3784 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3786 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3787 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3788 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3790 # No deleting yourself - UI is supposed to stop you first, though.
3791 return $e->die_event unless $e->requestor->id != $user->id;
3792 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3793 # Check if you are allowed to mess with this patron permission group at all
3794 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3795 my $evt = group_perm_failed($session, $e->requestor, $user);
3796 return $e->die_event($evt) if $evt;
3797 my $stat = $e->json_query(
3798 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3799 or return $e->die_event;
3805 __PACKAGE__->register_method (
3806 method => 'user_payments',
3807 api_name => 'open-ils.actor.user.payments.retrieve',
3810 Returns all payments for a given user. Default order is newest payments first.
3811 @param auth Authentication token
3812 @param user_id The user ID
3813 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3818 my($self, $conn, $auth, $user_id, $filters) = @_;
3821 my $e = new_editor(authtoken => $auth);
3822 return $e->die_event unless $e->checkauth;
3824 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3825 return $e->event unless
3826 $e->requestor->id == $user_id or
3827 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3829 # Find all payments for all transactions for user $user_id
3831 select => {mp => ['id']},
3836 select => {mbt => ['id']},
3838 where => {usr => $user_id}
3843 { # by default, order newest payments first
3845 field => 'payment_ts',
3848 # secondary sort in ID as a tie-breaker, since payments created
3849 # within the same transaction will have identical payment_ts's
3856 for (qw/order_by limit offset/) {
3857 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3860 if(defined $filters->{where}) {
3861 foreach (keys %{$filters->{where}}) {
3862 # don't allow the caller to expand the result set to other users
3863 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3867 my $payment_ids = $e->json_query($query);
3868 for my $pid (@$payment_ids) {
3869 my $pay = $e->retrieve_money_payment([
3874 mbt => ['summary', 'circulation', 'grocery'],
3875 circ => ['target_copy'],
3876 acp => ['call_number'],
3884 xact_type => $pay->xact->summary->xact_type,
3885 last_billing_type => $pay->xact->summary->last_billing_type,
3888 if($pay->xact->summary->xact_type eq 'circulation') {
3889 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3890 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3893 $pay->xact($pay->xact->id); # de-flesh
3894 $conn->respond($resp);
3902 __PACKAGE__->register_method (
3903 method => 'negative_balance_users',
3904 api_name => 'open-ils.actor.users.negative_balance',
3907 Returns all users that have an overall negative balance
3908 @param auth Authentication token
3909 @param org_id The context org unit as an ID or list of IDs. This will be the home
3910 library of the user. If no org_unit is specified, no org unit filter is applied
3914 sub negative_balance_users {
3915 my($self, $conn, $auth, $org_id) = @_;
3917 my $e = new_editor(authtoken => $auth);
3918 return $e->die_event unless $e->checkauth;
3919 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3923 mous => ['usr', 'balance_owed'],
3926 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3927 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3944 where => {'+mous' => {balance_owed => {'<' => 0}}}
3947 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3949 my $list = $e->json_query($query, {timeout => 600});
3951 for my $data (@$list) {
3953 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3954 balance_owed => $data->{balance_owed},
3955 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3962 __PACKAGE__->register_method(
3963 method => "request_password_reset",
3964 api_name => "open-ils.actor.patron.password_reset.request",
3966 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3967 "method for changing a user's password. The UUID token is distributed via A/T " .
3968 "templates (i.e. email to the user).",
3970 { desc => 'user_id_type', type => 'string' },
3971 { desc => 'user_id', type => 'string' },
3972 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3974 return => {desc => '1 on success, Event on error'}
3977 sub request_password_reset {
3978 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3980 # Check to see if password reset requests are already being throttled:
3981 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3983 my $e = new_editor(xact => 1);
3986 # Get the user, if any, depending on the input value
3987 if ($user_id_type eq 'username') {
3988 $user = $e->search_actor_user({usrname => $user_id})->[0];
3991 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3993 } elsif ($user_id_type eq 'barcode') {
3994 my $card = $e->search_actor_card([
3995 {barcode => $user_id},
3996 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3999 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4004 # If the user doesn't have an email address, we can't help them
4005 if (!$user->email) {
4007 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4010 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4011 if ($email_must_match) {
4012 if ($user->email ne $email) {
4013 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4017 _reset_password_request($conn, $e, $user);
4020 # Once we have the user, we can issue the password reset request
4021 # XXX Add a wrapper method that accepts barcode + email input
4022 sub _reset_password_request {
4023 my ($conn, $e, $user) = @_;
4025 # 1. Get throttle threshold and time-to-live from OU_settings
4026 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4027 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4029 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4031 # 2. Get time of last request and number of active requests (num_active)
4032 my $active_requests = $e->json_query({
4038 transform => 'COUNT'
4041 column => 'request_time',
4047 has_been_reset => { '=' => 'f' },
4048 request_time => { '>' => $threshold_time }
4052 # Guard against no active requests
4053 if ($active_requests->[0]->{'request_time'}) {
4054 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
4055 my $now = DateTime::Format::ISO8601->new();
4057 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4058 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4059 ($last_request->add_duration('1 minute') > $now)) {
4060 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4062 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4066 # TODO Check to see if the user is in a password-reset-restricted group
4068 # Otherwise, go ahead and try to get the user.
4070 # Check the number of active requests for this user
4071 $active_requests = $e->json_query({
4077 transform => 'COUNT'
4082 usr => { '=' => $user->id },
4083 has_been_reset => { '=' => 'f' },
4084 request_time => { '>' => $threshold_time }
4088 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4090 # if less than or equal to per-user threshold, proceed; otherwise, return event
4091 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4092 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4094 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4097 # Create the aupr object and insert into the database
4098 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4099 my $uuid = create_uuid_as_string(UUID_V4);
4100 $reset_request->uuid($uuid);
4101 $reset_request->usr($user->id);
4103 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4106 # Create an event to notify user of the URL to reset their password
4108 # Can we stuff this in the user_data param for trigger autocreate?
4109 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4111 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4112 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4115 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4120 __PACKAGE__->register_method(
4121 method => "commit_password_reset",
4122 api_name => "open-ils.actor.patron.password_reset.commit",
4124 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4125 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4126 "with the supplied password.",
4128 { desc => 'uuid', type => 'string' },
4129 { desc => 'password', type => 'string' },
4131 return => {desc => '1 on success, Event on error'}
4134 sub commit_password_reset {
4135 my($self, $conn, $uuid, $password) = @_;
4137 # Check to see if password reset requests are already being throttled:
4138 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4139 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4140 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4142 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4145 my $e = new_editor(xact => 1);
4147 my $aupr = $e->search_actor_usr_password_reset({
4154 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4156 my $user_id = $aupr->[0]->usr;
4157 my $user = $e->retrieve_actor_user($user_id);
4159 # Ensure we're still within the TTL for the request
4160 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4161 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4162 if ($threshold < DateTime->now(time_zone => 'local')) {
4164 $logger->info("Password reset request needed to be submitted before $threshold");
4165 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4168 # Check complexity of password against OU-defined regex
4169 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4173 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4174 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4175 $is_strong = check_password_strength_custom($password, $pw_regex);
4177 $is_strong = check_password_strength_default($password);
4182 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4185 # All is well; update the password
4186 $user->passwd($password);
4187 $e->update_actor_user($user);
4189 # And flag that this password reset request has been honoured
4190 $aupr->[0]->has_been_reset('t');
4191 $e->update_actor_usr_password_reset($aupr->[0]);
4197 sub check_password_strength_default {
4198 my $password = shift;
4199 # Use the default set of checks
4200 if ( (length($password) < 7) or
4201 ($password !~ m/.*\d+.*/) or
4202 ($password !~ m/.*[A-Za-z]+.*/)
4209 sub check_password_strength_custom {
4210 my ($password, $pw_regex) = @_;
4212 $pw_regex = qr/$pw_regex/;
4213 if ($password !~ /$pw_regex/) {
4221 __PACKAGE__->register_method(
4222 method => "event_def_opt_in_settings",
4223 api_name => "open-ils.actor.event_def.opt_in.settings",
4226 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4228 { desc => 'Authentication token', type => 'string'},
4230 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4235 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4242 sub event_def_opt_in_settings {
4243 my($self, $conn, $auth, $org_id) = @_;
4244 my $e = new_editor(authtoken => $auth);
4245 return $e->event unless $e->checkauth;
4247 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4248 return $e->event unless
4249 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4251 $org_id = $e->requestor->home_ou;
4254 # find all config.user_setting_type's related to event_defs for the requested org unit
4255 my $types = $e->json_query({
4256 select => {cust => ['name']},
4257 from => {atevdef => 'cust'},
4260 owner => $U->get_org_ancestors($org_id), # context org plus parents
4267 $conn->respond($_) for
4268 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4275 __PACKAGE__->register_method(
4276 method => "user_visible_circs",
4277 api_name => "open-ils.actor.history.circ.visible",
4280 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4282 { desc => 'Authentication token', type => 'string'},
4283 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4284 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4287 desc => q/An object with 2 fields: circulation and summary.
4288 circulation is the "circ" object. summary is the related "accs" object/,
4294 __PACKAGE__->register_method(
4295 method => "user_visible_circs",
4296 api_name => "open-ils.actor.history.circ.visible.print",
4299 desc => 'Returns printable output for the set of opt-in visible circulations',
4301 { desc => 'Authentication token', type => 'string'},
4302 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4303 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4306 desc => q/An action_trigger.event object or error event./,
4312 __PACKAGE__->register_method(
4313 method => "user_visible_circs",
4314 api_name => "open-ils.actor.history.circ.visible.email",
4317 desc => 'Emails the set of opt-in visible circulations to the requestor',
4319 { desc => 'Authentication token', type => 'string'},
4320 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4321 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4324 desc => q/undef, or event on error/
4329 __PACKAGE__->register_method(
4330 method => "user_visible_circs",
4331 api_name => "open-ils.actor.history.hold.visible",
4334 desc => 'Returns the set of opt-in visible holds',
4336 { desc => 'Authentication token', type => 'string'},
4337 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4338 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4341 desc => q/An object with 1 field: "hold"/,
4347 __PACKAGE__->register_method(
4348 method => "user_visible_circs",
4349 api_name => "open-ils.actor.history.hold.visible.print",
4352 desc => 'Returns printable output for the set of opt-in visible holds',
4354 { desc => 'Authentication token', type => 'string'},
4355 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4356 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4359 desc => q/An action_trigger.event object or error event./,
4365 __PACKAGE__->register_method(
4366 method => "user_visible_circs",
4367 api_name => "open-ils.actor.history.hold.visible.email",
4370 desc => 'Emails the set of opt-in visible holds to the requestor',
4372 { desc => 'Authentication token', type => 'string'},
4373 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4374 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4377 desc => q/undef, or event on error/
4382 sub user_visible_circs {
4383 my($self, $conn, $auth, $user_id, $options) = @_;
4385 my $is_hold = ($self->api_name =~ /hold/);
4386 my $for_print = ($self->api_name =~ /print/);
4387 my $for_email = ($self->api_name =~ /email/);
4388 my $e = new_editor(authtoken => $auth);
4389 return $e->event unless $e->checkauth;
4391 $user_id ||= $e->requestor->id;
4393 $options->{limit} ||= 50;
4394 $options->{offset} ||= 0;
4396 if($user_id != $e->requestor->id) {
4397 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4398 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4399 return $e->event unless $e->allowed($perm, $user->home_ou);
4402 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4404 my $data = $e->json_query({
4405 from => [$db_func, $user_id],
4406 limit => $$options{limit},
4407 offset => $$options{offset}
4409 # TODO: I only want IDs. code below didn't get me there
4410 # {"select":{"au":[{"column":"id", "result_field":"id",
4411 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4416 return undef unless @$data;
4420 # collect the batch of objects
4424 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4425 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4429 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4430 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4433 } elsif ($for_email) {
4435 $conn->respond_complete(1) if $for_email; # no sense in waiting
4443 my $hold = $e->retrieve_action_hold_request($id);
4444 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4445 # events will be fired from action_trigger_runner
4449 my $circ = $e->retrieve_action_circulation($id);
4450 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4451 # events will be fired from action_trigger_runner
4455 } else { # just give me the data please
4463 my $hold = $e->retrieve_action_hold_request($id);
4464 $conn->respond({hold => $hold});
4468 my $circ = $e->retrieve_action_circulation($id);
4471 summary => $U->create_circ_chain_summary($e, $id)
4480 __PACKAGE__->register_method(
4481 method => "user_saved_search_cud",
4482 api_name => "open-ils.actor.user.saved_search.cud",
4485 desc => 'Create/Update/Delete Access to user saved searches',
4487 { desc => 'Authentication token', type => 'string' },
4488 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4491 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4497 __PACKAGE__->register_method(
4498 method => "user_saved_search_cud",
4499 api_name => "open-ils.actor.user.saved_search.retrieve",
4502 desc => 'Retrieve a saved search object',
4504 { desc => 'Authentication token', type => 'string' },
4505 { desc => 'Saved Search ID', type => 'number' }
4508 desc => q/The saved search object, Event on error/,
4514 sub user_saved_search_cud {
4515 my( $self, $client, $auth, $search ) = @_;
4516 my $e = new_editor( authtoken=>$auth );
4517 return $e->die_event unless $e->checkauth;
4519 my $o_search; # prior version of the object, if any
4520 my $res; # to be returned
4522 # branch on the operation type
4524 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4526 # Get the old version, to check ownership
4527 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4528 or return $e->die_event;
4530 # You can't read somebody else's search
4531 return OpenILS::Event->new('BAD_PARAMS')
4532 unless $o_search->owner == $e->requestor->id;
4538 $e->xact_begin; # start an editor transaction
4540 if( $search->isnew ) { # Create
4542 # You can't create a search for somebody else
4543 return OpenILS::Event->new('BAD_PARAMS')
4544 unless $search->owner == $e->requestor->id;
4546 $e->create_actor_usr_saved_search( $search )
4547 or return $e->die_event;
4551 } elsif( $search->ischanged ) { # Update
4553 # You can't change ownership of a search
4554 return OpenILS::Event->new('BAD_PARAMS')
4555 unless $search->owner == $e->requestor->id;
4557 # Get the old version, to check ownership
4558 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4559 or return $e->die_event;
4561 # You can't update somebody else's search
4562 return OpenILS::Event->new('BAD_PARAMS')
4563 unless $o_search->owner == $e->requestor->id;
4566 $e->update_actor_usr_saved_search( $search )
4567 or return $e->die_event;
4571 } elsif( $search->isdeleted ) { # Delete
4573 # Get the old version, to check ownership
4574 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4575 or return $e->die_event;
4577 # You can't delete somebody else's search
4578 return OpenILS::Event->new('BAD_PARAMS')
4579 unless $o_search->owner == $e->requestor->id;
4582 $e->delete_actor_usr_saved_search( $o_search )
4583 or return $e->die_event;
4594 __PACKAGE__->register_method(
4595 method => "get_barcodes",
4596 api_name => "open-ils.actor.get_barcodes"
4600 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4601 my $e = new_editor(authtoken => $auth);
4602 return $e->event unless $e->checkauth;
4603 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4605 my $db_result = $e->json_query(
4607 'evergreen.get_barcodes',
4608 $org_id, $context, $barcode,
4612 if($context =~ /actor/) {
4613 my $filter_result = ();
4615 foreach my $result (@$db_result) {
4616 if($result->{type} eq 'actor') {
4617 if($e->requestor->id != $result->{id}) {
4618 $patron = $e->retrieve_actor_user($result->{id});
4620 push(@$filter_result, $e->event);
4623 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4624 push(@$filter_result, $result);
4627 push(@$filter_result, $e->event);
4631 push(@$filter_result, $result);
4635 push(@$filter_result, $result);
4638 return $filter_result;
4644 __PACKAGE__->register_method(
4645 method => 'address_alert_test',
4646 api_name => 'open-ils.actor.address_alert.test',
4648 desc => "Tests a set of address fields to determine if they match with an address_alert",
4650 {desc => 'Authentication token', type => 'string'},
4651 {desc => 'Org Unit', type => 'number'},
4652 {desc => 'Fields', type => 'hash'},
4654 return => {desc => 'List of matching address_alerts'}
4658 sub address_alert_test {
4659 my ($self, $client, $auth, $org_unit, $fields) = @_;
4660 return [] unless $fields and grep {$_} values %$fields;
4662 my $e = new_editor(authtoken => $auth);
4663 return $e->event unless $e->checkauth;
4664 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4665 $org_unit ||= $e->requestor->ws_ou;
4667 my $alerts = $e->json_query({
4669 'actor.address_alert_matches',
4677 $$fields{post_code},
4678 $$fields{mailing_address},
4679 $$fields{billing_address}
4683 # map the json_query hashes to real objects
4685 map {$e->retrieve_actor_address_alert($_)}
4686 (map {$_->{id}} @$alerts)
4690 __PACKAGE__->register_method(
4691 method => "mark_users_contact_invalid",
4692 api_name => "open-ils.actor.invalidate.email",
4694 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",
4696 {desc => "Authentication token", type => "string"},
4697 {desc => "Patron ID", type => "number"},
4698 {desc => "Additional note text (optional)", type => "string"},
4699 {desc => "penalty org unit ID (optional)", type => "number"}
4701 return => {desc => "Event describing success or failure", type => "object"}
4705 __PACKAGE__->register_method(
4706 method => "mark_users_contact_invalid",
4707 api_name => "open-ils.actor.invalidate.day_phone",
4709 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",
4711 {desc => "Authentication token", type => "string"},
4712 {desc => "Patron ID", type => "number"},
4713 {desc => "Additional note text (optional)", type => "string"},
4714 {desc => "penalty org unit ID (optional)", type => "number"}
4716 return => {desc => "Event describing success or failure", type => "object"}
4720 __PACKAGE__->register_method(
4721 method => "mark_users_contact_invalid",
4722 api_name => "open-ils.actor.invalidate.evening_phone",
4724 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",
4726 {desc => "Authentication token", type => "string"},
4727 {desc => "Patron ID", type => "number"},
4728 {desc => "Additional note text (optional)", type => "string"},
4729 {desc => "penalty org unit ID (optional)", type => "number"}
4731 return => {desc => "Event describing success or failure", type => "object"}
4735 __PACKAGE__->register_method(
4736 method => "mark_users_contact_invalid",
4737 api_name => "open-ils.actor.invalidate.other_phone",
4739 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",
4741 {desc => "Authentication token", type => "string"},
4742 {desc => "Patron ID", type => "number"},
4743 {desc => "Additional note text (optional)", type => "string"},
4744 {desc => "penalty org unit ID (optional, default to top of org tree)",
4747 return => {desc => "Event describing success or failure", type => "object"}
4751 sub mark_users_contact_invalid {
4752 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4754 # This method invalidates an email address or a phone_number which
4755 # removes the bad email address or phone number, copying its contents
4756 # to a patron note, and institutes a standing penalty for "bad email"
4757 # or "bad phone number" which is cleared when the user is saved or
4758 # optionally only when the user is saved with an email address or
4759 # phone number (or staff manually delete the penalty).
4761 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4763 my $e = new_editor(authtoken => $auth, xact => 1);
4764 return $e->die_event unless $e->checkauth;
4766 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4767 $e, $contact_type, {usr => $patron_id},
4768 $addl_note, $penalty_ou, $e->requestor->id
4772 # Putting the following method in open-ils.actor is a bad fit, except in that
4773 # it serves an interface that lives under 'actor' in the templates directory,
4774 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4776 __PACKAGE__->register_method(
4777 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4778 method => "get_all_at_reactors_in_use",
4783 { name => 'authtoken', type => 'string' }
4786 desc => 'list of reactor names', type => 'array'
4791 sub get_all_at_reactors_in_use {
4792 my ($self, $conn, $auth) = @_;
4794 my $e = new_editor(authtoken => $auth);
4795 $e->checkauth or return $e->die_event;
4796 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4798 my $reactors = $e->json_query({
4800 atevdef => [{column => "reactor", transform => "distinct"}]
4802 from => {atevdef => {}}
4805 return $e->die_event unless ref $reactors eq "ARRAY";
4808 return [ map { $_->{reactor} } @$reactors ];
4811 __PACKAGE__->register_method(
4812 method => "filter_group_entry_crud",
4813 api_name => "open-ils.actor.filter_group_entry.crud",
4816 Provides CRUD access to filter group entry objects. These are not full accessible
4817 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4818 are not accessible via PCRUD (because they have no fields against which to link perms)
4821 {desc => "Authentication token", type => "string"},
4822 {desc => "Entry ID / Entry Object", type => "number"},
4823 {desc => "Additional note text (optional)", type => "string"},
4824 {desc => "penalty org unit ID (optional, default to top of org tree)",
4828 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4834 sub filter_group_entry_crud {
4835 my ($self, $conn, $auth, $arg) = @_;
4837 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4838 my $e = new_editor(authtoken => $auth, xact => 1);
4839 return $e->die_event unless $e->checkauth;
4845 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4846 or return $e->die_event;
4848 return $e->die_event unless $e->allowed(
4849 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4851 my $query = $arg->query;
4852 $query = $e->create_actor_search_query($query) or return $e->die_event;
4853 $arg->query($query->id);
4854 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4855 $entry->query($query);
4860 } elsif ($arg->ischanged) {
4862 my $entry = $e->retrieve_actor_search_filter_group_entry([
4865 flesh_fields => {asfge => ['grp']}
4867 ]) or return $e->die_event;
4869 return $e->die_event unless $e->allowed(
4870 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4872 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4873 $arg->query($arg->query->id);
4874 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4875 $arg->query($query);
4880 } elsif ($arg->isdeleted) {
4882 my $entry = $e->retrieve_actor_search_filter_group_entry([
4885 flesh_fields => {asfge => ['grp', 'query']}
4887 ]) or return $e->die_event;
4889 return $e->die_event unless $e->allowed(
4890 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4892 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4893 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4906 my $entry = $e->retrieve_actor_search_filter_group_entry([
4909 flesh_fields => {asfge => ['grp', 'query']}
4911 ]) or return $e->die_event;
4913 return $e->die_event unless $e->allowed(
4914 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4915 $entry->grp->owner);
4918 $entry->grp($entry->grp->id); # for consistency