1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use OpenILS::Utils::BadContact;
38 use List::Util qw/max reduce/;
40 use UUID::Tiny qw/:std/;
43 OpenILS::Application::Actor::Container->initialize();
44 OpenILS::Application::Actor::UserGroups->initialize();
45 OpenILS::Application::Actor::ClosedDates->initialize();
48 my $apputils = "OpenILS::Application::AppUtils";
51 sub _d { warn "Patron:\n" . Dumper(shift()); }
54 my $set_user_settings;
58 #__PACKAGE__->register_method(
59 # method => "allowed_test",
60 # api_name => "open-ils.actor.allowed_test",
63 # my($self, $conn, $auth, $orgid, $permcode) = @_;
64 # my $e = new_editor(authtoken => $auth);
65 # return $e->die_event unless $e->checkauth;
69 # permcode => $permcode,
70 # result => $e->allowed($permcode, $orgid)
74 __PACKAGE__->register_method(
75 method => "update_user_setting",
76 api_name => "open-ils.actor.patron.settings.update",
78 sub update_user_setting {
79 my($self, $conn, $auth, $user_id, $settings) = @_;
80 my $e = new_editor(xact => 1, authtoken => $auth);
81 return $e->die_event unless $e->checkauth;
83 $user_id = $e->requestor->id unless defined $user_id;
85 unless($e->requestor->id == $user_id) {
86 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
87 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
90 for my $name (keys %$settings) {
91 my $val = $$settings{$name};
92 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
95 $val = OpenSRF::Utils::JSON->perl2JSON($val);
98 $e->update_actor_user_setting($set) or return $e->die_event;
100 $set = Fieldmapper::actor::user_setting->new;
104 $e->create_actor_user_setting($set) or return $e->die_event;
107 $e->delete_actor_user_setting($set) or return $e->die_event;
116 __PACKAGE__->register_method(
117 method => "set_ou_settings",
118 api_name => "open-ils.actor.org_unit.settings.update",
120 desc => "Updates the value for a given org unit setting. The permission to update " .
121 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
122 "permission specified in the update_perm column of the config.org_unit_setting_type " .
123 "table's row corresponding to the setting being changed." ,
125 {desc => 'Authentication token', type => 'string'},
126 {desc => 'Org unit ID', type => 'number'},
127 {desc => 'Hash of setting name-value pairs', type => 'object'}
129 return => {desc => '1 on success, Event on error'}
133 sub set_ou_settings {
134 my( $self, $client, $auth, $org_id, $settings ) = @_;
136 my $e = new_editor(authtoken => $auth, xact => 1);
137 return $e->die_event unless $e->checkauth;
139 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
141 for my $name (keys %$settings) {
142 my $val = $$settings{$name};
144 my $type = $e->retrieve_config_org_unit_setting_type([
146 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
147 ]) or return $e->die_event;
148 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
150 # If there is no relevant permission, the default assumption will
151 # be, "no, the caller cannot change that value."
152 return $e->die_event unless ($all_allowed ||
153 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
156 $val = OpenSRF::Utils::JSON->perl2JSON($val);
159 $e->update_actor_org_unit_setting($set) or return $e->die_event;
161 $set = Fieldmapper::actor::org_unit_setting->new;
162 $set->org_unit($org_id);
165 $e->create_actor_org_unit_setting($set) or return $e->die_event;
168 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
176 __PACKAGE__->register_method(
177 method => "user_settings",
179 api_name => "open-ils.actor.patron.settings.retrieve",
182 my( $self, $client, $auth, $user_id, $setting ) = @_;
184 my $e = new_editor(authtoken => $auth);
185 return $e->event unless $e->checkauth;
186 $user_id = $e->requestor->id unless defined $user_id;
188 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
189 if($e->requestor->id != $user_id) {
190 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
194 my($e, $user_id, $setting) = @_;
195 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
196 return undef unless $val; # XXX this should really return undef, but needs testing
197 return OpenSRF::Utils::JSON->JSON2perl($val->value);
201 if(ref $setting eq 'ARRAY') {
203 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
206 return get_setting($e, $user_id, $setting);
209 my $s = $e->search_actor_user_setting({usr => $user_id});
210 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
215 __PACKAGE__->register_method(
216 method => "ranged_ou_settings",
217 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
219 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
220 "is implied for retrieving OU settings by the authenticated users' permissions.",
222 {desc => 'Authentication token', type => 'string'},
223 {desc => 'Org unit ID', type => 'number'},
225 return => {desc => 'A hashref of "ranged" settings, event on error'}
228 sub ranged_ou_settings {
229 my( $self, $client, $auth, $org_id ) = @_;
231 my $e = new_editor(authtoken => $auth);
232 return $e->event unless $e->checkauth;
235 my $org_list = $U->get_org_ancestors($org_id);
236 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
237 $org_list = [ reverse @$org_list ];
239 # start at the context org and capture the setting value
240 # without clobbering settings we've already captured
241 for my $this_org_id (@$org_list) {
243 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
245 for my $set (@sets) {
246 my $type = $e->retrieve_config_org_unit_setting_type([
248 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
251 # If there is no relevant permission, the default assumption will
252 # be, "yes, the caller can have that value."
253 if ($type && $type->view_perm) {
254 next if not $e->allowed($type->view_perm->code, $org_id);
257 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
258 unless defined $ranged_settings{$set->name};
262 return \%ranged_settings;
267 __PACKAGE__->register_method(
268 api_name => 'open-ils.actor.ou_setting.ancestor_default',
269 method => 'ou_ancestor_setting',
271 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
272 'This method will make sure that the given user has permission to view that setting, if there is a ' .
273 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
274 'the user lacks the permisssion, undef will be returned.' ,
276 { desc => 'Org unit ID', type => 'number' },
277 { desc => 'setting name', type => 'string' },
278 { desc => 'authtoken (optional)', type => 'string' }
280 return => {desc => 'A value for the org unit setting, or undef'}
284 # ------------------------------------------------------------------
285 # Attempts to find the org setting value for a given org. if not
286 # found at the requested org, searches up the org tree until it
287 # finds a parent that has the requested setting.
288 # when found, returns { org => $id, value => $value }
289 # otherwise, returns NULL
290 # ------------------------------------------------------------------
291 sub ou_ancestor_setting {
292 my( $self, $client, $orgid, $name, $auth ) = @_;
293 # Make sure $auth is set to something if not given.
295 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
298 __PACKAGE__->register_method(
299 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
300 method => 'ou_ancestor_setting_batch',
302 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
303 'This method will make sure that the given user has permission to view that setting, if there is a ' .
304 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
305 'the user lacks the permisssion, undef will be returned.' ,
307 { desc => 'Org unit ID', type => 'number' },
308 { desc => 'setting name list', type => 'array' },
309 { desc => 'authtoken (optional)', type => 'string' }
311 return => {desc => 'A hash with name => value pairs for the org unit settings'}
314 sub ou_ancestor_setting_batch {
315 my( $self, $client, $orgid, $name_list, $auth ) = @_;
317 # splitting the list of settings to fetch values
318 # so that ones that *don't* require view_perm checks
319 # can be fetched in one fell swoop, which is
320 # significantly faster in cases where a large
321 # number of settings need to be fetched.
322 my %perm_check_required = ();
323 my @perm_check_not_required = ();
325 # Note that ->ou_ancestor_setting also can check
326 # to see if the setting has a view_perm, but testing
327 # suggests that the redundant checks do not significantly
328 # increase the time it takes to fetch the values of
329 # permission-controlled settings.
330 my $e = new_editor();
331 my $res = $e->search_config_org_unit_setting_type({
333 view_perm => { "!=" => undef },
335 %perm_check_required = map { $_->name() => 1 } @$res;
336 foreach my $setting (@$name_list) {
337 push @perm_check_not_required, $setting
338 unless exists($perm_check_required{$setting});
342 if (@perm_check_not_required) {
343 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
345 $values{$_} = $U->ou_ancestor_setting(
348 ) for keys(%perm_check_required);
354 __PACKAGE__->register_method(
355 method => "update_patron",
356 api_name => "open-ils.actor.patron.update",
359 Update an existing user, or create a new one. Related objects,
360 like cards, addresses, survey responses, and stat cats,
361 can be updated by attaching them to the user object in their
362 respective fields. For examples, the billing address object
363 may be inserted into the 'billing_address' field, etc. For each
364 attached object, indicate if the object should be created,
365 updated, or deleted using the built-in 'isnew', 'ischanged',
366 and 'isdeleted' fields on the object.
369 { desc => 'Authentication token', type => 'string' },
370 { desc => 'Patron data object', type => 'object' }
372 return => {desc => 'A fleshed user object, event on error'}
377 my( $self, $client, $auth, $patron ) = @_;
379 my $e = new_editor(xact => 1, authtoken => $auth);
380 return $e->event unless $e->checkauth;
382 $logger->info($patron->isnew ? "Creating new patron..." :
383 "Updating Patron: " . $patron->id);
385 my $evt = check_group_perm($e, $e->requestor, $patron);
388 # $new_patron is the patron in progress. $patron is the original patron
389 # passed in with the method. new_patron will change as the components
390 # of patron are added/updated.
394 # unflesh the real items on the patron
395 $patron->card( $patron->card->id ) if(ref($patron->card));
396 $patron->billing_address( $patron->billing_address->id )
397 if(ref($patron->billing_address));
398 $patron->mailing_address( $patron->mailing_address->id )
399 if(ref($patron->mailing_address));
401 # create/update the patron first so we can use his id
403 # $patron is the obj from the client (new data) and $new_patron is the
404 # patron object properly built for db insertion, so we need a third variable
405 # if we want to represent the old patron.
408 my $barred_hook = '';
410 if($patron->isnew()) {
411 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
413 if($U->is_true($patron->barred)) {
414 return $e->die_event unless
415 $e->allowed('BAR_PATRON', $patron->home_ou);
418 $new_patron = $patron;
420 # Did auth checking above already.
421 $old_patron = $e->retrieve_actor_user($patron->id) or
422 return $e->die_event;
424 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
425 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
426 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
428 $barred_hook = $U->is_true($new_patron->barred) ?
429 'au.barred' : 'au.unbarred';
433 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
436 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
439 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
442 # re-update the patron if anything has happened to him during this process
443 if($new_patron->ischanged()) {
444 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
448 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
451 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
454 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
457 $evt = apply_invalid_addr_penalty($e, $patron);
462 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
464 $tses->request('open-ils.trigger.event.autocreate',
465 'au.create', $new_patron, $new_patron->home_ou);
467 $tses->request('open-ils.trigger.event.autocreate',
468 'au.update', $new_patron, $new_patron->home_ou);
470 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
471 $new_patron, $new_patron->home_ou) if $barred_hook;
474 $e->xact_begin; # $e->rollback is called in new_flesh_user
475 return flesh_user($new_patron->id(), $e);
478 sub apply_invalid_addr_penalty {
482 # grab the invalid address penalty if set
483 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
485 my ($addr_penalty) = grep
486 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
488 # do we enforce invalid address penalty
489 my $enforce = $U->ou_ancestor_setting_value(
490 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
492 my $addrs = $e->search_actor_user_address(
493 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
494 my $addr_count = scalar(@$addrs);
496 if($addr_count == 0 and $addr_penalty) {
498 # regardless of any settings, remove the penalty when the user has no invalid addresses
499 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
502 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
504 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
505 my $depth = $ptype->org_depth;
506 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
507 $ctx_org = $patron->home_ou unless defined $ctx_org;
509 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
510 $penalty->usr($patron->id);
511 $penalty->org_unit($ctx_org);
512 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
514 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
529 "standing_penalties",
537 push @$fields, "home_ou" if $home_ou;
538 return new_flesh_user($id, $fields, $e );
546 # clone and clear stuff that would break the database
550 my $new_patron = $patron->clone;
552 $new_patron->clear_billing_address();
553 $new_patron->clear_mailing_address();
554 $new_patron->clear_addresses();
555 $new_patron->clear_card();
556 $new_patron->clear_cards();
557 $new_patron->clear_id();
558 $new_patron->clear_isnew();
559 $new_patron->clear_ischanged();
560 $new_patron->clear_isdeleted();
561 $new_patron->clear_stat_cat_entries();
562 $new_patron->clear_permissions();
563 $new_patron->clear_standing_penalties();
574 return (undef, $e->die_event) unless
575 $e->allowed('CREATE_USER', $patron->home_ou);
577 my $ex = $e->search_actor_user(
578 {usrname => $patron->usrname}, {idlist => 1});
579 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
581 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
583 $e->create_actor_user($patron) or return $e->die_event;
584 my $id = $patron->id; # added by CStoreEditor
586 $logger->info("Successfully created new user [$id] in DB");
587 return ($e->retrieve_actor_user($id), undef);
591 sub check_group_perm {
592 my( $e, $requestor, $patron ) = @_;
595 # first let's see if the requestor has
596 # priveleges to update this user in any way
597 if( ! $patron->isnew ) {
598 my $p = $e->retrieve_actor_user($patron->id);
600 # If we are the requestor (trying to update our own account)
601 # and we are not trying to change our profile, we're good
602 if( $p->id == $requestor->id and
603 $p->profile == $patron->profile ) {
608 $evt = group_perm_failed($e, $requestor, $p);
612 # They are allowed to edit this patron.. can they put the
613 # patron into the group requested?
614 $evt = group_perm_failed($e, $requestor, $patron);
620 sub group_perm_failed {
621 my( $e, $requestor, $patron ) = @_;
625 my $grpid = $patron->profile;
629 $logger->debug("user update looking for group perm for group $grpid");
630 $grp = $e->retrieve_permission_grp_tree($grpid);
632 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
634 $logger->info("user update checking perm $perm on user ".
635 $requestor->id." for update/create on user username=".$patron->usrname);
637 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
643 my( $e, $patron, $noperm) = @_;
645 $logger->info("Updating patron ".$patron->id." in DB");
650 return (undef, $e->die_event)
651 unless $e->allowed('UPDATE_USER', $patron->home_ou);
654 # update the password by itself to avoid the password protection magic
655 if( $patron->passwd ) {
656 modify_migrated_user_password($e, $patron->id, $patron->passwd);
657 $patron->clear_passwd;
660 if(!$patron->ident_type) {
661 $patron->clear_ident_type;
662 $patron->clear_ident_value;
665 $evt = verify_last_xact($e, $patron);
666 return (undef, $evt) if $evt;
668 $e->update_actor_user($patron) or return (undef, $e->die_event);
670 # re-fetch the user to pick up the latest last_xact_id value
671 # to avoid collisions.
672 $patron = $e->retrieve_actor_user($patron->id);
677 sub verify_last_xact {
678 my( $e, $patron ) = @_;
679 return undef unless $patron->id and $patron->id > 0;
680 my $p = $e->retrieve_actor_user($patron->id);
681 my $xact = $p->last_xact_id;
682 return undef unless $xact;
683 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
684 return OpenILS::Event->new('XACT_COLLISION')
685 if $xact ne $patron->last_xact_id;
690 sub _check_dup_ident {
691 my( $session, $patron ) = @_;
693 return undef unless $patron->ident_value;
696 ident_type => $patron->ident_type,
697 ident_value => $patron->ident_value,
700 $logger->debug("patron update searching for dup ident values: " .
701 $patron->ident_type . ':' . $patron->ident_value);
703 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
705 my $dups = $session->request(
706 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
709 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
716 sub _add_update_addresses {
720 my $new_patron = shift;
724 my $current_id; # id of the address before creation
726 my $addresses = $patron->addresses();
728 for my $address (@$addresses) {
730 next unless ref $address;
731 $current_id = $address->id();
733 if( $patron->billing_address() and
734 $patron->billing_address() == $current_id ) {
735 $logger->info("setting billing addr to $current_id");
736 $new_patron->billing_address($address->id());
737 $new_patron->ischanged(1);
740 if( $patron->mailing_address() and
741 $patron->mailing_address() == $current_id ) {
742 $new_patron->mailing_address($address->id());
743 $logger->info("setting mailing addr to $current_id");
744 $new_patron->ischanged(1);
748 if($address->isnew()) {
750 $address->usr($new_patron->id());
752 ($address, $evt) = _add_address($e,$address);
753 return (undef, $evt) if $evt;
755 # we need to get the new id
756 if( $patron->billing_address() and
757 $patron->billing_address() == $current_id ) {
758 $new_patron->billing_address($address->id());
759 $logger->info("setting billing addr to $current_id");
760 $new_patron->ischanged(1);
763 if( $patron->mailing_address() and
764 $patron->mailing_address() == $current_id ) {
765 $new_patron->mailing_address($address->id());
766 $logger->info("setting mailing addr to $current_id");
767 $new_patron->ischanged(1);
770 } elsif($address->ischanged() ) {
772 ($address, $evt) = _update_address($e, $address);
773 return (undef, $evt) if $evt;
775 } elsif($address->isdeleted() ) {
777 if( $address->id() == $new_patron->mailing_address() ) {
778 $new_patron->clear_mailing_address();
779 ($new_patron, $evt) = _update_patron($e, $new_patron);
780 return (undef, $evt) if $evt;
783 if( $address->id() == $new_patron->billing_address() ) {
784 $new_patron->clear_billing_address();
785 ($new_patron, $evt) = _update_patron($e, $new_patron);
786 return (undef, $evt) if $evt;
789 $evt = _delete_address($e, $address);
790 return (undef, $evt) if $evt;
794 return ( $new_patron, undef );
798 # adds an address to the db and returns the address with new id
800 my($e, $address) = @_;
801 $address->clear_id();
803 $logger->info("Creating new address at street ".$address->street1);
805 # put the address into the database
806 $e->create_actor_user_address($address) or return (undef, $e->die_event);
807 return ($address, undef);
811 sub _update_address {
812 my( $e, $address ) = @_;
814 $logger->info("Updating address ".$address->id." in the DB");
816 $e->update_actor_user_address($address) or return (undef, $e->die_event);
818 return ($address, undef);
823 sub _add_update_cards {
827 my $new_patron = shift;
831 my $virtual_id; #id of the card before creation
833 my $cards = $patron->cards();
834 for my $card (@$cards) {
836 $card->usr($new_patron->id());
838 if(ref($card) and $card->isnew()) {
840 $virtual_id = $card->id();
841 ( $card, $evt ) = _add_card($e, $card);
842 return (undef, $evt) if $evt;
844 #if(ref($patron->card)) { $patron->card($patron->card->id); }
845 if($patron->card() == $virtual_id) {
846 $new_patron->card($card->id());
847 $new_patron->ischanged(1);
850 } elsif( ref($card) and $card->ischanged() ) {
851 $evt = _update_card($e, $card);
852 return (undef, $evt) if $evt;
856 return ( $new_patron, undef );
860 # adds an card to the db and returns the card with new id
862 my( $e, $card ) = @_;
865 $logger->info("Adding new patron card ".$card->barcode);
867 $e->create_actor_card($card) or return (undef, $e->die_event);
869 return ( $card, undef );
873 # returns event on error. returns undef otherwise
875 my( $e, $card ) = @_;
876 $logger->info("Updating patron card ".$card->id);
878 $e->update_actor_card($card) or return $e->die_event;
885 # returns event on error. returns undef otherwise
886 sub _delete_address {
887 my( $e, $address ) = @_;
889 $logger->info("Deleting address ".$address->id." from DB");
891 $e->delete_actor_user_address($address) or return $e->die_event;
897 sub _add_survey_responses {
898 my ($e, $patron, $new_patron) = @_;
900 $logger->info( "Updating survey responses for patron ".$new_patron->id );
902 my $responses = $patron->survey_responses;
906 $_->usr($new_patron->id) for (@$responses);
908 my $evt = $U->simplereq( "open-ils.circ",
909 "open-ils.circ.survey.submit.user_id", $responses );
911 return (undef, $evt) if defined($U->event_code($evt));
915 return ( $new_patron, undef );
918 sub _clear_badcontact_penalties {
919 my ($e, $old_patron, $new_patron) = @_;
921 return ($new_patron, undef) unless $old_patron;
923 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
925 # This ignores whether the caller of update_patron has any permission
926 # to remove penalties, but these penalties no longer make sense
927 # if an email address field (for example) is changed (and the caller must
928 # have perms to do *that*) so there's no reason not to clear the penalties.
930 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
932 "+csp" => {"name" => [values(%$PNM)]},
933 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
935 "join" => {"csp" => {}},
937 "flesh_fields" => {"ausp" => ["standing_penalty"]}
939 ]) or return (undef, $e->die_event);
941 return ($new_patron, undef) unless @$bad_contact_penalties;
943 my @penalties_to_clear;
944 my ($field, $penalty_name);
946 # For each field that might have an associated bad contact penalty,
947 # check for such penalties and add them to the to-clear list if that
949 while (($field, $penalty_name) = each(%$PNM)) {
950 if ($old_patron->$field ne $new_patron->$field) {
951 push @penalties_to_clear, grep {
952 $_->standing_penalty->name eq $penalty_name
953 } @$bad_contact_penalties;
957 foreach (@penalties_to_clear) {
958 # Note that this "archives" penalties, in the terminology of the staff
959 # client, instead of just deleting them. This may assist reporting,
960 # or preserving old contact information when it is still potentially
962 $_->standing_penalty($_->standing_penalty->id); # deflesh
963 $_->stop_date('now');
964 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
967 return ($new_patron, undef);
971 sub _create_stat_maps {
973 my($e, $patron, $new_patron) = @_;
975 my $maps = $patron->stat_cat_entries();
977 for my $map (@$maps) {
979 my $method = "update_actor_stat_cat_entry_user_map";
981 if ($map->isdeleted()) {
982 $method = "delete_actor_stat_cat_entry_user_map";
984 } elsif ($map->isnew()) {
985 $method = "create_actor_stat_cat_entry_user_map";
990 $map->target_usr($new_patron->id);
992 $logger->info("Updating stat entry with method $method and map $map");
994 $e->$method($map) or return (undef, $e->die_event);
997 return ($new_patron, undef);
1000 sub _create_perm_maps {
1002 my($e, $patron, $new_patron) = @_;
1004 my $maps = $patron->permissions;
1006 for my $map (@$maps) {
1008 my $method = "update_permission_usr_perm_map";
1009 if ($map->isdeleted()) {
1010 $method = "delete_permission_usr_perm_map";
1011 } elsif ($map->isnew()) {
1012 $method = "create_permission_usr_perm_map";
1016 $map->usr($new_patron->id);
1018 $logger->info( "Updating permissions with method $method and map $map" );
1020 $e->$method($map) or return (undef, $e->die_event);
1023 return ($new_patron, undef);
1027 __PACKAGE__->register_method(
1028 method => "set_user_work_ous",
1029 api_name => "open-ils.actor.user.work_ous.update",
1032 sub set_user_work_ous {
1038 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1039 return $evt if $evt;
1041 my $session = $apputils->start_db_session();
1042 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1044 for my $map (@$maps) {
1046 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1047 if ($map->isdeleted()) {
1048 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1049 } elsif ($map->isnew()) {
1050 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1054 #warn( "Updating permissions with method $method and session $ses and map $map" );
1055 $logger->info( "Updating work_ou map with method $method and map $map" );
1057 my $stat = $session->request($method, $map)->gather(1);
1058 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1062 $apputils->commit_db_session($session);
1064 return scalar(@$maps);
1068 __PACKAGE__->register_method(
1069 method => "set_user_perms",
1070 api_name => "open-ils.actor.user.permissions.update",
1073 sub set_user_perms {
1079 my $session = $apputils->start_db_session();
1081 my( $user_obj, $evt ) = $U->checkses($ses);
1082 return $evt if $evt;
1083 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1085 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1088 $all = 1 if ($U->is_true($user_obj->super_user()));
1089 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1091 for my $map (@$maps) {
1093 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1094 if ($map->isdeleted()) {
1095 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1096 } elsif ($map->isnew()) {
1097 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1101 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1102 #warn( "Updating permissions with method $method and session $ses and map $map" );
1103 $logger->info( "Updating permissions with method $method and map $map" );
1105 my $stat = $session->request($method, $map)->gather(1);
1106 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1110 $apputils->commit_db_session($session);
1112 return scalar(@$maps);
1116 __PACKAGE__->register_method(
1117 method => "user_retrieve_by_barcode",
1119 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1121 sub user_retrieve_by_barcode {
1122 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1124 my $e = new_editor(authtoken => $auth);
1125 return $e->event unless $e->checkauth;
1127 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1128 or return $e->event;
1130 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1131 return $e->event unless $e->allowed(
1132 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1139 __PACKAGE__->register_method(
1140 method => "get_user_by_id",
1142 api_name => "open-ils.actor.user.retrieve",
1145 sub get_user_by_id {
1146 my ($self, $client, $auth, $id) = @_;
1147 my $e = new_editor(authtoken=>$auth);
1148 return $e->event unless $e->checkauth;
1149 my $user = $e->retrieve_actor_user($id) or return $e->event;
1150 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1155 __PACKAGE__->register_method(
1156 method => "get_org_types",
1157 api_name => "open-ils.actor.org_types.retrieve",
1160 return $U->get_org_types();
1164 __PACKAGE__->register_method(
1165 method => "get_user_ident_types",
1166 api_name => "open-ils.actor.user.ident_types.retrieve",
1169 sub get_user_ident_types {
1170 return $ident_types if $ident_types;
1171 return $ident_types =
1172 new_editor()->retrieve_all_config_identification_type();
1176 __PACKAGE__->register_method(
1177 method => "get_org_unit",
1178 api_name => "open-ils.actor.org_unit.retrieve",
1182 my( $self, $client, $user_session, $org_id ) = @_;
1183 my $e = new_editor(authtoken => $user_session);
1185 return $e->event unless $e->checkauth;
1186 $org_id = $e->requestor->ws_ou;
1188 my $o = $e->retrieve_actor_org_unit($org_id)
1189 or return $e->event;
1193 __PACKAGE__->register_method(
1194 method => "search_org_unit",
1195 api_name => "open-ils.actor.org_unit_list.search",
1198 sub search_org_unit {
1200 my( $self, $client, $field, $value ) = @_;
1202 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1204 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1205 { $field => $value } );
1211 # build the org tree
1213 __PACKAGE__->register_method(
1214 method => "get_org_tree",
1215 api_name => "open-ils.actor.org_tree.retrieve",
1217 note => "Returns the entire org tree structure",
1223 return $U->get_org_tree($client->session->session_locale);
1227 __PACKAGE__->register_method(
1228 method => "get_org_descendants",
1229 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1232 # depth is optional. org_unit is the id
1233 sub get_org_descendants {
1234 my( $self, $client, $org_unit, $depth ) = @_;
1236 if(ref $org_unit eq 'ARRAY') {
1239 for my $i (0..scalar(@$org_unit)-1) {
1240 my $list = $U->simple_scalar_request(
1242 "open-ils.storage.actor.org_unit.descendants.atomic",
1243 $org_unit->[$i], $depth->[$i] );
1244 push(@trees, $U->build_org_tree($list));
1249 my $orglist = $apputils->simple_scalar_request(
1251 "open-ils.storage.actor.org_unit.descendants.atomic",
1252 $org_unit, $depth );
1253 return $U->build_org_tree($orglist);
1258 __PACKAGE__->register_method(
1259 method => "get_org_ancestors",
1260 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1263 # depth is optional. org_unit is the id
1264 sub get_org_ancestors {
1265 my( $self, $client, $org_unit, $depth ) = @_;
1266 my $orglist = $apputils->simple_scalar_request(
1268 "open-ils.storage.actor.org_unit.ancestors.atomic",
1269 $org_unit, $depth );
1270 return $U->build_org_tree($orglist);
1274 __PACKAGE__->register_method(
1275 method => "get_standings",
1276 api_name => "open-ils.actor.standings.retrieve"
1281 return $user_standings if $user_standings;
1282 return $user_standings =
1283 $apputils->simple_scalar_request(
1285 "open-ils.cstore.direct.config.standing.search.atomic",
1286 { id => { "!=" => undef } }
1291 __PACKAGE__->register_method(
1292 method => "get_my_org_path",
1293 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1296 sub get_my_org_path {
1297 my( $self, $client, $auth, $org_id ) = @_;
1298 my $e = new_editor(authtoken=>$auth);
1299 return $e->event unless $e->checkauth;
1300 $org_id = $e->requestor->ws_ou unless defined $org_id;
1302 return $apputils->simple_scalar_request(
1304 "open-ils.storage.actor.org_unit.full_path.atomic",
1309 __PACKAGE__->register_method(
1310 method => "patron_adv_search",
1311 api_name => "open-ils.actor.patron.search.advanced"
1314 __PACKAGE__->register_method(
1315 method => "patron_adv_search",
1316 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1318 # TODO: change when opensrf 'bundling' is merged.
1319 # set a relatively small bundle size so the caller can start
1320 # seeing results fairly quickly
1321 max_chunk_size => 4096, # bundling
1324 # pending opensrf work -- also, not sure if needed since we're not
1325 # actaully creating an alternate vesrion, only offering to return a
1329 desc => q/Returns a stream of fleshed user objects instead of
1330 a pile of identifiers/
1334 sub patron_adv_search {
1335 my( $self, $client, $auth, $search_hash, $search_limit,
1336 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1338 # API params sanity checks.
1339 # Exit early with empty result if no filter exists.
1340 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1341 my $fleshed = ($self->api_name =~ /fleshed/);
1342 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1344 for my $key (keys %$search_hash) {
1345 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1349 return ($fleshed ? undef : []) unless $search_ok;
1351 my $e = new_editor(authtoken=>$auth);
1352 return $e->event unless $e->checkauth;
1353 return $e->event unless $e->allowed('VIEW_USER');
1355 # depth boundary outside of which patrons must opt-in, default to 0
1356 my $opt_boundary = 0;
1357 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1359 if (not defined $search_ou) {
1360 my $depth = $U->ou_ancestor_setting_value(
1361 $e->requestor->ws_ou,
1362 'circ.patron_edit.duplicate_patron_check_depth'
1365 if (defined $depth) {
1366 $search_ou = $U->org_unit_ancestor_at_depth(
1367 $e->requestor->ws_ou, $depth
1372 my $ids = $U->storagereq(
1373 "open-ils.storage.actor.user.crazy_search", $search_hash,
1374 $search_limit, $search_sort, $include_inactive,
1375 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1377 return $ids unless $self->api_name =~ /fleshed/;
1379 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1385 # A migrated (main) password has the form:
1386 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1387 sub modify_migrated_user_password {
1388 my ($e, $user_id, $passwd) = @_;
1390 # new password gets a new salt
1391 my $new_salt = $e->json_query({
1392 from => ['actor.create_salt', 'main']})->[0];
1393 $new_salt = $new_salt->{'actor.create_salt'};
1400 md5_hex($new_salt . md5_hex($passwd)),
1408 __PACKAGE__->register_method(
1409 method => "update_passwd",
1410 api_name => "open-ils.actor.user.password.update",
1412 desc => "Update the operator's password",
1414 { desc => 'Authentication token', type => 'string' },
1415 { desc => 'New password', type => 'string' },
1416 { desc => 'Current password', type => 'string' }
1418 return => {desc => '1 on success, Event on error or incorrect current password'}
1422 __PACKAGE__->register_method(
1423 method => "update_passwd",
1424 api_name => "open-ils.actor.user.username.update",
1426 desc => "Update the operator's username",
1428 { desc => 'Authentication token', type => 'string' },
1429 { desc => 'New username', type => 'string' },
1430 { desc => 'Current password', type => 'string' }
1432 return => {desc => '1 on success, Event on error or incorrect current password'}
1436 __PACKAGE__->register_method(
1437 method => "update_passwd",
1438 api_name => "open-ils.actor.user.email.update",
1440 desc => "Update the operator's email address",
1442 { desc => 'Authentication token', type => 'string' },
1443 { desc => 'New email address', type => 'string' },
1444 { desc => 'Current password', type => 'string' }
1446 return => {desc => '1 on success, Event on error or incorrect current password'}
1451 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1452 my $e = new_editor(xact=>1, authtoken=>$auth);
1453 return $e->die_event unless $e->checkauth;
1455 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1456 or return $e->die_event;
1457 my $api = $self->api_name;
1459 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1461 return new OpenILS::Event('INCORRECT_PASSWORD');
1464 if( $api =~ /password/o ) {
1465 # NOTE: with access to the plain text password we could crypt
1466 # the password without the extra MD5 pre-hashing. Other changes
1467 # would be required. Noting here for future reference.
1468 modify_migrated_user_password($e, $db_user->id, $new_val);
1469 $db_user->passwd('');
1473 # if we don't clear the password, the user will be updated with
1474 # a hashed version of the hashed version of their password
1475 $db_user->clear_passwd;
1477 if( $api =~ /username/o ) {
1479 # make sure no one else has this username
1480 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1483 return new OpenILS::Event('USERNAME_EXISTS');
1485 $db_user->usrname($new_val);
1487 } elsif( $api =~ /email/o ) {
1488 $db_user->email($new_val);
1492 $e->update_actor_user($db_user) or return $e->die_event;
1495 # update the cached user to pick up these changes
1496 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1502 __PACKAGE__->register_method(
1503 method => "check_user_perms",
1504 api_name => "open-ils.actor.user.perm.check",
1505 notes => <<" NOTES");
1506 Takes a login session, user id, an org id, and an array of perm type strings. For each
1507 perm type, if the user does *not* have the given permission it is added
1508 to a list which is returned from the method. If all permissions
1509 are allowed, an empty list is returned
1510 if the logged in user does not match 'user_id', then the logged in user must
1511 have VIEW_PERMISSION priveleges.
1514 sub check_user_perms {
1515 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1517 my( $staff, $evt ) = $apputils->checkses($login_session);
1518 return $evt if $evt;
1520 if($staff->id ne $user_id) {
1521 if( $evt = $apputils->check_perms(
1522 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1528 for my $perm (@$perm_types) {
1529 if($apputils->check_perms($user_id, $org_id, $perm)) {
1530 push @not_allowed, $perm;
1534 return \@not_allowed
1537 __PACKAGE__->register_method(
1538 method => "check_user_perms2",
1539 api_name => "open-ils.actor.user.perm.check.multi_org",
1541 Checks the permissions on a list of perms and orgs for a user
1542 @param authtoken The login session key
1543 @param user_id The id of the user to check
1544 @param orgs The array of org ids
1545 @param perms The array of permission names
1546 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1547 if the logged in user does not match 'user_id', then the logged in user must
1548 have VIEW_PERMISSION priveleges.
1551 sub check_user_perms2 {
1552 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1554 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1555 $authtoken, $user_id, 'VIEW_PERMISSION' );
1556 return $evt if $evt;
1559 for my $org (@$orgs) {
1560 for my $perm (@$perms) {
1561 if($apputils->check_perms($user_id, $org, $perm)) {
1562 push @not_allowed, [ $org, $perm ];
1567 return \@not_allowed
1571 __PACKAGE__->register_method(
1572 method => 'check_user_perms3',
1573 api_name => 'open-ils.actor.user.perm.highest_org',
1575 Returns the highest org unit id at which a user has a given permission
1576 If the requestor does not match the target user, the requestor must have
1577 'VIEW_PERMISSION' rights at the home org unit of the target user
1578 @param authtoken The login session key
1579 @param userid The id of the user in question
1580 @param perm The permission to check
1581 @return The org unit highest in the org tree within which the user has
1582 the requested permission
1585 sub check_user_perms3 {
1586 my($self, $client, $authtoken, $user_id, $perm) = @_;
1587 my $e = new_editor(authtoken=>$authtoken);
1588 return $e->event unless $e->checkauth;
1590 my $tree = $U->get_org_tree();
1592 unless($e->requestor->id == $user_id) {
1593 my $user = $e->retrieve_actor_user($user_id)
1594 or return $e->event;
1595 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1596 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1599 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1602 __PACKAGE__->register_method(
1603 method => 'user_has_work_perm_at',
1604 api_name => 'open-ils.actor.user.has_work_perm_at',
1608 Returns a set of org unit IDs which represent the highest orgs in
1609 the org tree where the user has the requested permission. The
1610 purpose of this method is to return the smallest set of org units
1611 which represent the full expanse of the user's ability to perform
1612 the requested action. The user whose perms this method should
1613 check is implied by the authtoken. /,
1615 {desc => 'authtoken', type => 'string'},
1616 {desc => 'permission name', type => 'string'},
1617 {desc => q/user id, optional. If present, check perms for
1618 this user instead of the logged in user/, type => 'number'},
1620 return => {desc => 'An array of org IDs'}
1624 sub user_has_work_perm_at {
1625 my($self, $conn, $auth, $perm, $user_id) = @_;
1626 my $e = new_editor(authtoken=>$auth);
1627 return $e->event unless $e->checkauth;
1628 if(defined $user_id) {
1629 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1630 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1632 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1635 __PACKAGE__->register_method(
1636 method => 'user_has_work_perm_at_batch',
1637 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1641 sub user_has_work_perm_at_batch {
1642 my($self, $conn, $auth, $perms, $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);
1650 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1656 __PACKAGE__->register_method(
1657 method => 'check_user_perms4',
1658 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1660 Returns the highest org unit id at which a user has a given permission
1661 If the requestor does not match the target user, the requestor must have
1662 'VIEW_PERMISSION' rights at the home org unit of the target user
1663 @param authtoken The login session key
1664 @param userid The id of the user in question
1665 @param perms An array of perm names to check
1666 @return An array of orgId's representing the org unit
1667 highest in the org tree within which the user has the requested permission
1668 The arrah of orgId's has matches the order of the perms array
1671 sub check_user_perms4 {
1672 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1674 my( $staff, $target, $org, $evt );
1676 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1677 $authtoken, $userid, 'VIEW_PERMISSION' );
1678 return $evt if $evt;
1681 return [] unless ref($perms);
1682 my $tree = $U->get_org_tree();
1684 for my $p (@$perms) {
1685 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1691 __PACKAGE__->register_method(
1692 method => "user_fines_summary",
1693 api_name => "open-ils.actor.user.fines.summary",
1696 desc => 'Returns a short summary of the users total open fines, ' .
1697 'excluding voided fines Params are login_session, user_id' ,
1699 {desc => 'Authentication token', type => 'string'},
1700 {desc => 'User ID', type => 'string'} # number?
1703 desc => "a 'mous' object, event on error",
1708 sub user_fines_summary {
1709 my( $self, $client, $auth, $user_id ) = @_;
1711 my $e = new_editor(authtoken=>$auth);
1712 return $e->event unless $e->checkauth;
1714 if( $user_id ne $e->requestor->id ) {
1715 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1716 return $e->event unless
1717 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1720 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1724 __PACKAGE__->register_method(
1725 method => "user_opac_vitals",
1726 api_name => "open-ils.actor.user.opac.vital_stats",
1730 desc => 'Returns a short summary of the users vital stats, including ' .
1731 'identification information, accumulated balance, number of holds, ' .
1732 'and current open circulation stats' ,
1734 {desc => 'Authentication token', type => 'string'},
1735 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1738 desc => "An object with four properties: user, fines, checkouts and holds."
1743 sub user_opac_vitals {
1744 my( $self, $client, $auth, $user_id ) = @_;
1746 my $e = new_editor(authtoken=>$auth);
1747 return $e->event unless $e->checkauth;
1749 $user_id ||= $e->requestor->id;
1751 my $user = $e->retrieve_actor_user( $user_id );
1754 ->method_lookup('open-ils.actor.user.fines.summary')
1755 ->run($auth => $user_id);
1756 return $fines if (defined($U->event_code($fines)));
1759 $fines = new Fieldmapper::money::open_user_summary ();
1760 $fines->balance_owed(0.00);
1761 $fines->total_owed(0.00);
1762 $fines->total_paid(0.00);
1763 $fines->usr($user_id);
1767 ->method_lookup('open-ils.actor.user.hold_requests.count')
1768 ->run($auth => $user_id);
1769 return $holds if (defined($U->event_code($holds)));
1772 ->method_lookup('open-ils.actor.user.checked_out.count')
1773 ->run($auth => $user_id);
1774 return $out if (defined($U->event_code($out)));
1776 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1778 my $unread_msgs = $e->search_actor_usr_message([
1779 {usr => $user_id, read_date => undef, deleted => 'f'},
1785 first_given_name => $user->first_given_name,
1786 second_given_name => $user->second_given_name,
1787 family_name => $user->family_name,
1788 alias => $user->alias,
1789 usrname => $user->usrname
1791 fines => $fines->to_bare_hash,
1794 messages => { unread => scalar(@$unread_msgs) }
1799 ##### a small consolidation of related method registrations
1800 my $common_params = [
1801 { desc => 'Authentication token', type => 'string' },
1802 { desc => 'User ID', type => 'string' },
1803 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1804 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1807 'open-ils.actor.user.transactions' => '',
1808 'open-ils.actor.user.transactions.fleshed' => '',
1809 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1810 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1811 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1812 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1815 foreach (keys %methods) {
1817 method => "user_transactions",
1820 desc => 'For a given user, retrieve a list of '
1821 . (/\.fleshed/ ? 'fleshed ' : '')
1822 . 'transactions' . $methods{$_}
1823 . ' optionally limited to transactions of a given type.',
1824 params => $common_params,
1826 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1827 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1831 $args{authoritative} = 1;
1832 __PACKAGE__->register_method(%args);
1835 # Now for the counts
1837 'open-ils.actor.user.transactions.count' => '',
1838 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1839 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1842 foreach (keys %methods) {
1844 method => "user_transactions",
1847 desc => 'For a given user, retrieve a count of open '
1848 . 'transactions' . $methods{$_}
1849 . ' optionally limited to transactions of a given type.',
1850 params => $common_params,
1851 return => { desc => "Integer count of transactions, or event on error" }
1854 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1855 __PACKAGE__->register_method(%args);
1858 __PACKAGE__->register_method(
1859 method => "user_transactions",
1860 api_name => "open-ils.actor.user.transactions.have_balance.total",
1863 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1864 . ' optionally limited to transactions of a given type.',
1865 params => $common_params,
1866 return => { desc => "Decimal balance value, or event on error" }
1871 sub user_transactions {
1872 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1875 my $e = new_editor(authtoken => $auth);
1876 return $e->event unless $e->checkauth;
1878 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1880 return $e->event unless
1881 $e->requestor->id == $user_id or
1882 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1884 my $api = $self->api_name();
1886 my $filter = ($api =~ /have_balance/o) ?
1887 { 'balance_owed' => { '<>' => 0 } }:
1888 { 'total_owed' => { '>' => 0 } };
1890 my $method = 'open-ils.actor.user.transactions.history.still_open';
1891 $method = "$method.authoritative" if $api =~ /authoritative/;
1892 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1894 if($api =~ /total/o) {
1896 $total += $_->balance_owed for @$trans;
1900 ($api =~ /count/o ) and return scalar @$trans;
1901 ($api !~ /fleshed/o) and return $trans;
1904 for my $t (@$trans) {
1906 if( $t->xact_type ne 'circulation' ) {
1907 push @resp, {transaction => $t};
1911 my $circ_data = flesh_circ($e, $t->id);
1912 push @resp, {transaction => $t, %$circ_data};
1919 __PACKAGE__->register_method(
1920 method => "user_transaction_retrieve",
1921 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1924 notes => "Returns a fleshed transaction record"
1927 __PACKAGE__->register_method(
1928 method => "user_transaction_retrieve",
1929 api_name => "open-ils.actor.user.transaction.retrieve",
1932 notes => "Returns a transaction record"
1935 sub user_transaction_retrieve {
1936 my($self, $client, $auth, $bill_id) = @_;
1938 my $e = new_editor(authtoken => $auth);
1939 return $e->event unless $e->checkauth;
1941 my $trans = $e->retrieve_money_billable_transaction_summary(
1942 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1944 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1946 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1948 return $trans unless $self->api_name =~ /flesh/;
1949 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1951 my $circ_data = flesh_circ($e, $trans->id, 1);
1953 return {transaction => $trans, %$circ_data};
1958 my $circ_id = shift;
1959 my $flesh_copy = shift;
1961 my $circ = $e->retrieve_action_circulation([
1965 circ => ['target_copy'],
1966 acp => ['call_number'],
1973 my $copy = $circ->target_copy;
1975 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1976 $mods = new Fieldmapper::metabib::virtual_record;
1977 $mods->doc_id(OILS_PRECAT_RECORD);
1978 $mods->title($copy->dummy_title);
1979 $mods->author($copy->dummy_author);
1982 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1986 $circ->target_copy($circ->target_copy->id);
1987 $copy->call_number($copy->call_number->id);
1989 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1993 __PACKAGE__->register_method(
1994 method => "hold_request_count",
1995 api_name => "open-ils.actor.user.hold_requests.count",
1999 Returns hold ready vs. total counts.
2000 If a context org unit is provided, a third value
2001 is returned with key 'behind_desk', which reports
2002 how many holds are ready at the pickup library
2003 with the behind_desk flag set to true.
2007 sub hold_request_count {
2008 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2009 my $e = new_editor(authtoken => $authtoken);
2010 return $e->event unless $e->checkauth;
2012 $user_id = $e->requestor->id unless defined $user_id;
2014 if($e->requestor->id ne $user_id) {
2015 my $user = $e->retrieve_actor_user($user_id);
2016 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2019 my $holds = $e->json_query({
2020 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2024 fulfillment_time => {"=" => undef },
2025 cancel_time => undef,
2030 $_->{current_shelf_lib} and # avoid undef warnings
2031 $_->{pickup_lib} eq $_->{current_shelf_lib}
2035 total => scalar(@$holds),
2036 ready => scalar(@ready)
2040 # count of holds ready at pickup lib with behind_desk true.
2041 $resp->{behind_desk} = scalar(
2043 $_->{pickup_lib} == $ctx_org and
2044 $U->is_true($_->{behind_desk})
2052 __PACKAGE__->register_method(
2053 method => "checked_out",
2054 api_name => "open-ils.actor.user.checked_out",
2058 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2059 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2060 . "(i.e., outstanding balance or some other pending action on the circ). "
2061 . "The .count method also includes a 'total' field which sums all open circs.",
2063 { desc => 'Authentication Token', type => 'string'},
2064 { desc => 'User ID', type => 'string'},
2067 desc => 'Returns event on error, or an object with ID lists, like: '
2068 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2073 __PACKAGE__->register_method(
2074 method => "checked_out",
2075 api_name => "open-ils.actor.user.checked_out.count",
2078 signature => q/@see open-ils.actor.user.checked_out/
2082 my( $self, $conn, $auth, $userid ) = @_;
2084 my $e = new_editor(authtoken=>$auth);
2085 return $e->event unless $e->checkauth;
2087 if( $userid ne $e->requestor->id ) {
2088 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2089 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2091 # see if there is a friend link allowing circ.view perms
2092 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2093 $e, $userid, $e->requestor->id, 'circ.view');
2094 return $e->event unless $allowed;
2098 my $count = $self->api_name =~ /count/;
2099 return _checked_out( $count, $e, $userid );
2103 my( $iscount, $e, $userid ) = @_;
2109 claims_returned => [],
2112 my $meth = 'retrieve_action_open_circ_';
2120 claims_returned => 0,
2127 my $data = $e->$meth($userid);
2131 $result{$_} += $data->$_() for (keys %result);
2132 $result{total} += $data->$_() for (keys %result);
2134 for my $k (keys %result) {
2135 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2145 __PACKAGE__->register_method(
2146 method => "checked_in_with_fines",
2147 api_name => "open-ils.actor.user.checked_in_with_fines",
2150 signature => q/@see open-ils.actor.user.checked_out/
2153 sub checked_in_with_fines {
2154 my( $self, $conn, $auth, $userid ) = @_;
2156 my $e = new_editor(authtoken=>$auth);
2157 return $e->event unless $e->checkauth;
2159 if( $userid ne $e->requestor->id ) {
2160 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2163 # money is owed on these items and they are checked in
2164 my $open = $e->search_action_circulation(
2167 xact_finish => undef,
2168 checkin_time => { "!=" => undef },
2173 my( @lost, @cr, @lo );
2174 for my $c (@$open) {
2175 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2176 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2177 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2182 claims_returned => \@cr,
2183 long_overdue => \@lo
2189 my ($api, $desc, $auth) = @_;
2190 $desc = $desc ? (" " . $desc) : '';
2191 my $ids = ($api =~ /ids$/) ? 1 : 0;
2194 method => "user_transaction_history",
2195 api_name => "open-ils.actor.user.transactions.$api",
2197 desc => "For a given User ID, returns a list of billable transaction" .
2198 ($ids ? " id" : '') .
2199 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2200 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2202 {desc => 'Authentication token', type => 'string'},
2203 {desc => 'User ID', type => 'number'},
2204 {desc => 'Transaction type (optional)', type => 'number'},
2205 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2208 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2212 $auth and push @sig, (authoritative => 1);
2216 my %auth_hist_methods = (
2218 'history.have_charge' => 'that have an initial charge',
2219 'history.still_open' => 'that are not finished',
2220 'history.have_balance' => 'that have a balance',
2221 'history.have_bill' => 'that have billings',
2222 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2223 'history.have_payment' => 'that have at least 1 payment',
2226 foreach (keys %auth_hist_methods) {
2227 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2228 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2229 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2232 sub user_transaction_history {
2233 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2237 my $e = new_editor(authtoken=>$auth);
2238 return $e->die_event unless $e->checkauth;
2240 if ($e->requestor->id ne $userid) {
2241 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2244 my $api = $self->api_name;
2245 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2247 if(defined($type)) {
2248 $filter->{'xact_type'} = $type;
2251 if($api =~ /have_bill_or_payment/o) {
2253 # transactions that have a non-zero sum across all billings or at least 1 payment
2254 $filter->{'-or'} = {
2255 'balance_owed' => { '<>' => 0 },
2256 'last_payment_ts' => { '<>' => undef }
2259 } elsif($api =~ /have_payment/) {
2261 $filter->{last_payment_ts} ||= {'<>' => undef};
2263 } elsif( $api =~ /have_balance/o) {
2265 # transactions that have a non-zero overall balance
2266 $filter->{'balance_owed'} = { '<>' => 0 };
2268 } elsif( $api =~ /have_charge/o) {
2270 # transactions that have at least 1 billing, regardless of whether it was voided
2271 $filter->{'last_billing_ts'} = { '<>' => undef };
2273 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2275 # transactions that have non-zero sum across all billings. This will exclude
2276 # xacts where all billings have been voided
2277 $filter->{'total_owed'} = { '<>' => 0 };
2280 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2281 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2282 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2284 my $mbts = $e->search_money_billable_transaction_summary(
2285 [ { usr => $userid, @xact_finish, %$filter },
2290 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2291 return $mbts unless $api =~ /fleshed/;
2294 for my $t (@$mbts) {
2296 if( $t->xact_type ne 'circulation' ) {
2297 push @resp, {transaction => $t};
2301 my $circ_data = flesh_circ($e, $t->id);
2302 push @resp, {transaction => $t, %$circ_data};
2310 __PACKAGE__->register_method(
2311 method => "user_perms",
2312 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2314 notes => "Returns a list of permissions"
2318 my( $self, $client, $authtoken, $user ) = @_;
2320 my( $staff, $evt ) = $apputils->checkses($authtoken);
2321 return $evt if $evt;
2323 $user ||= $staff->id;
2325 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2329 return $apputils->simple_scalar_request(
2331 "open-ils.storage.permission.user_perms.atomic",
2335 __PACKAGE__->register_method(
2336 method => "retrieve_perms",
2337 api_name => "open-ils.actor.permissions.retrieve",
2338 notes => "Returns a list of permissions"
2340 sub retrieve_perms {
2341 my( $self, $client ) = @_;
2342 return $apputils->simple_scalar_request(
2344 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2345 { id => { '!=' => undef } }
2349 __PACKAGE__->register_method(
2350 method => "retrieve_groups",
2351 api_name => "open-ils.actor.groups.retrieve",
2352 notes => "Returns a list of user groups"
2354 sub retrieve_groups {
2355 my( $self, $client ) = @_;
2356 return new_editor()->retrieve_all_permission_grp_tree();
2359 __PACKAGE__->register_method(
2360 method => "retrieve_org_address",
2361 api_name => "open-ils.actor.org_unit.address.retrieve",
2362 notes => <<' NOTES');
2363 Returns an org_unit address by ID
2364 @param An org_address ID
2366 sub retrieve_org_address {
2367 my( $self, $client, $id ) = @_;
2368 return $apputils->simple_scalar_request(
2370 "open-ils.cstore.direct.actor.org_address.retrieve",
2375 __PACKAGE__->register_method(
2376 method => "retrieve_groups_tree",
2377 api_name => "open-ils.actor.groups.tree.retrieve",
2378 notes => "Returns a list of user groups"
2381 sub retrieve_groups_tree {
2382 my( $self, $client ) = @_;
2383 return new_editor()->search_permission_grp_tree(
2388 flesh_fields => { pgt => ["children"] },
2389 order_by => { pgt => 'name'}
2396 __PACKAGE__->register_method(
2397 method => "add_user_to_groups",
2398 api_name => "open-ils.actor.user.set_groups",
2399 notes => "Adds a user to one or more permission groups"
2402 sub add_user_to_groups {
2403 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2405 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2406 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2407 return $evt if $evt;
2409 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2410 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2411 return $evt if $evt;
2413 $apputils->simplereq(
2415 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2417 for my $group (@$groups) {
2418 my $link = Fieldmapper::permission::usr_grp_map->new;
2420 $link->usr($userid);
2422 my $id = $apputils->simplereq(
2424 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2430 __PACKAGE__->register_method(
2431 method => "get_user_perm_groups",
2432 api_name => "open-ils.actor.user.get_groups",
2433 notes => "Retrieve a user's permission groups."
2437 sub get_user_perm_groups {
2438 my( $self, $client, $authtoken, $userid ) = @_;
2440 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2441 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2442 return $evt if $evt;
2444 return $apputils->simplereq(
2446 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2450 __PACKAGE__->register_method(
2451 method => "get_user_work_ous",
2452 api_name => "open-ils.actor.user.get_work_ous",
2453 notes => "Retrieve a user's work org units."
2456 __PACKAGE__->register_method(
2457 method => "get_user_work_ous",
2458 api_name => "open-ils.actor.user.get_work_ous.ids",
2459 notes => "Retrieve a user's work org units."
2462 sub get_user_work_ous {
2463 my( $self, $client, $auth, $userid ) = @_;
2464 my $e = new_editor(authtoken=>$auth);
2465 return $e->event unless $e->checkauth;
2466 $userid ||= $e->requestor->id;
2468 if($e->requestor->id != $userid) {
2469 my $user = $e->retrieve_actor_user($userid)
2470 or return $e->event;
2471 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2474 return $e->search_permission_usr_work_ou_map({usr => $userid})
2475 unless $self->api_name =~ /.ids$/;
2477 # client just wants a list of org IDs
2478 return $U->get_user_work_ou_ids($e, $userid);
2483 __PACKAGE__->register_method(
2484 method => 'register_workstation',
2485 api_name => 'open-ils.actor.workstation.register.override',
2486 signature => q/@see open-ils.actor.workstation.register/
2489 __PACKAGE__->register_method(
2490 method => 'register_workstation',
2491 api_name => 'open-ils.actor.workstation.register',
2493 Registers a new workstion in the system
2494 @param authtoken The login session key
2495 @param name The name of the workstation id
2496 @param owner The org unit that owns this workstation
2497 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2498 if the name is already in use.
2502 sub register_workstation {
2503 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2505 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2506 return $e->die_event unless $e->checkauth;
2507 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2508 my $existing = $e->search_actor_workstation({name => $name})->[0];
2509 $oargs = { all => 1 } unless defined $oargs;
2513 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2514 # workstation with the given name exists.
2516 if($owner ne $existing->owning_lib) {
2517 # if necessary, update the owning_lib of the workstation
2519 $logger->info("changing owning lib of workstation ".$existing->id.
2520 " from ".$existing->owning_lib." to $owner");
2521 return $e->die_event unless
2522 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2524 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2526 $existing->owning_lib($owner);
2527 return $e->die_event unless $e->update_actor_workstation($existing);
2533 "attempt to register an existing workstation. returning existing ID");
2536 return $existing->id;
2539 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2543 my $ws = Fieldmapper::actor::workstation->new;
2544 $ws->owning_lib($owner);
2546 $e->create_actor_workstation($ws) or return $e->die_event;
2548 return $ws->id; # note: editor sets the id on the new object for us
2551 __PACKAGE__->register_method(
2552 method => 'workstation_list',
2553 api_name => 'open-ils.actor.workstation.list',
2555 Returns a list of workstations registered at the given location
2556 @param authtoken The login session key
2557 @param ids A list of org_unit.id's for the workstation owners
2561 sub workstation_list {
2562 my( $self, $conn, $authtoken, @orgs ) = @_;
2564 my $e = new_editor(authtoken=>$authtoken);
2565 return $e->event unless $e->checkauth;
2570 unless $e->allowed('REGISTER_WORKSTATION', $o);
2571 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2577 __PACKAGE__->register_method(
2578 method => 'fetch_patron_note',
2579 api_name => 'open-ils.actor.note.retrieve.all',
2582 Returns a list of notes for a given user
2583 Requestor must have VIEW_USER permission if pub==false and
2584 @param authtoken The login session key
2585 @param args Hash of params including
2586 patronid : the patron's id
2587 pub : true if retrieving only public notes
2591 sub fetch_patron_note {
2592 my( $self, $conn, $authtoken, $args ) = @_;
2593 my $patronid = $$args{patronid};
2595 my($reqr, $evt) = $U->checkses($authtoken);
2596 return $evt if $evt;
2599 ($patron, $evt) = $U->fetch_user($patronid);
2600 return $evt if $evt;
2603 if( $patronid ne $reqr->id ) {
2604 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2605 return $evt if $evt;
2607 return $U->cstorereq(
2608 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2609 { usr => $patronid, pub => 't' } );
2612 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2613 return $evt if $evt;
2615 return $U->cstorereq(
2616 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2619 __PACKAGE__->register_method(
2620 method => 'create_user_note',
2621 api_name => 'open-ils.actor.note.create',
2623 Creates a new note for the given user
2624 @param authtoken The login session key
2625 @param note The note object
2628 sub create_user_note {
2629 my( $self, $conn, $authtoken, $note ) = @_;
2630 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2631 return $e->die_event unless $e->checkauth;
2633 my $user = $e->retrieve_actor_user($note->usr)
2634 or return $e->die_event;
2636 return $e->die_event unless
2637 $e->allowed('UPDATE_USER',$user->home_ou);
2639 $note->creator($e->requestor->id);
2640 $e->create_actor_usr_note($note) or return $e->die_event;
2646 __PACKAGE__->register_method(
2647 method => 'delete_user_note',
2648 api_name => 'open-ils.actor.note.delete',
2650 Deletes a note for the given user
2651 @param authtoken The login session key
2652 @param noteid The note id
2655 sub delete_user_note {
2656 my( $self, $conn, $authtoken, $noteid ) = @_;
2658 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2659 return $e->die_event unless $e->checkauth;
2660 my $note = $e->retrieve_actor_usr_note($noteid)
2661 or return $e->die_event;
2662 my $user = $e->retrieve_actor_user($note->usr)
2663 or return $e->die_event;
2664 return $e->die_event unless
2665 $e->allowed('UPDATE_USER', $user->home_ou);
2667 $e->delete_actor_usr_note($note) or return $e->die_event;
2673 __PACKAGE__->register_method(
2674 method => 'update_user_note',
2675 api_name => 'open-ils.actor.note.update',
2677 @param authtoken The login session key
2678 @param note The note
2682 sub update_user_note {
2683 my( $self, $conn, $auth, $note ) = @_;
2684 my $e = new_editor(authtoken=>$auth, xact=>1);
2685 return $e->die_event unless $e->checkauth;
2686 my $patron = $e->retrieve_actor_user($note->usr)
2687 or return $e->die_event;
2688 return $e->die_event unless
2689 $e->allowed('UPDATE_USER', $patron->home_ou);
2690 $e->update_actor_user_note($note)
2691 or return $e->die_event;
2696 __PACKAGE__->register_method(
2697 method => 'fetch_patron_messages',
2698 api_name => 'open-ils.actor.message.retrieve',
2701 Returns a list of notes for a given user, not
2702 including ones marked deleted
2703 @param authtoken The login session key
2704 @param patronid patron ID
2705 @param options hash containing optional limit and offset
2709 sub fetch_patron_messages {
2710 my( $self, $conn, $auth, $patronid, $options ) = @_;
2714 my $e = new_editor(authtoken => $auth);
2715 return $e->die_event unless $e->checkauth;
2717 if ($e->requestor->id ne $patronid) {
2718 return $e->die_event unless $e->allowed('VIEW_USER');
2721 my $select_clause = { usr => $patronid };
2722 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2723 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2724 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2726 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2731 __PACKAGE__->register_method(
2732 method => 'usrname_exists',
2733 api_name => 'open-ils.actor.username.exists',
2735 desc => 'Check if a username is already taken (by an undeleted patron)',
2737 {desc => 'Authentication token', type => 'string'},
2738 {desc => 'Username', type => 'string'}
2741 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2746 sub usrname_exists {
2747 my( $self, $conn, $auth, $usrname ) = @_;
2748 my $e = new_editor(authtoken=>$auth);
2749 return $e->event unless $e->checkauth;
2750 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2751 return $$a[0] if $a and @$a;
2755 __PACKAGE__->register_method(
2756 method => 'barcode_exists',
2757 api_name => 'open-ils.actor.barcode.exists',
2759 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2762 sub barcode_exists {
2763 my( $self, $conn, $auth, $barcode ) = @_;
2764 my $e = new_editor(authtoken=>$auth);
2765 return $e->event unless $e->checkauth;
2766 my $card = $e->search_actor_card({barcode => $barcode});
2772 #return undef unless @$card;
2773 #return $card->[0]->usr;
2777 __PACKAGE__->register_method(
2778 method => 'retrieve_net_levels',
2779 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2782 sub retrieve_net_levels {
2783 my( $self, $conn, $auth ) = @_;
2784 my $e = new_editor(authtoken=>$auth);
2785 return $e->event unless $e->checkauth;
2786 return $e->retrieve_all_config_net_access_level();
2789 # Retain the old typo API name just in case
2790 __PACKAGE__->register_method(
2791 method => 'fetch_org_by_shortname',
2792 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2794 __PACKAGE__->register_method(
2795 method => 'fetch_org_by_shortname',
2796 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2798 sub fetch_org_by_shortname {
2799 my( $self, $conn, $sname ) = @_;
2800 my $e = new_editor();
2801 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2802 return $e->event unless $org;
2807 __PACKAGE__->register_method(
2808 method => 'session_home_lib',
2809 api_name => 'open-ils.actor.session.home_lib',
2812 sub session_home_lib {
2813 my( $self, $conn, $auth ) = @_;
2814 my $e = new_editor(authtoken=>$auth);
2815 return undef unless $e->checkauth;
2816 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2817 return $org->shortname;
2820 __PACKAGE__->register_method(
2821 method => 'session_safe_token',
2822 api_name => 'open-ils.actor.session.safe_token',
2824 Returns a hashed session ID that is safe for export to the world.
2825 This safe token will expire after 1 hour of non-use.
2826 @param auth Active authentication token
2830 sub session_safe_token {
2831 my( $self, $conn, $auth ) = @_;
2832 my $e = new_editor(authtoken=>$auth);
2833 return undef unless $e->checkauth;
2835 my $safe_token = md5_hex($auth);
2837 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2839 # add more user fields as needed
2841 "safe-token-user-$safe_token", {
2842 id => $e->requestor->id,
2843 home_ou_shortname => $e->retrieve_actor_org_unit(
2844 $e->requestor->home_ou)->shortname,
2853 __PACKAGE__->register_method(
2854 method => 'safe_token_home_lib',
2855 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2857 Returns the home library shortname from the session
2858 asscociated with a safe token from generated by
2859 open-ils.actor.session.safe_token.
2860 @param safe_token Active safe token
2861 @param who Optional user activity "ewho" value
2865 sub safe_token_home_lib {
2866 my( $self, $conn, $safe_token, $who ) = @_;
2867 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2869 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2870 return unless $blob;
2872 $U->log_user_activity($blob->{id}, $who, 'verify');
2873 return $blob->{home_ou_shortname};
2877 __PACKAGE__->register_method(
2878 method => "update_penalties",
2879 api_name => "open-ils.actor.user.penalties.update"
2882 sub update_penalties {
2883 my($self, $conn, $auth, $user_id) = @_;
2884 my $e = new_editor(authtoken=>$auth, xact => 1);
2885 return $e->die_event unless $e->checkauth;
2886 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2887 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2888 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2889 return $evt if $evt;
2895 __PACKAGE__->register_method(
2896 method => "apply_penalty",
2897 api_name => "open-ils.actor.user.penalty.apply"
2901 my($self, $conn, $auth, $penalty) = @_;
2903 my $e = new_editor(authtoken=>$auth, xact => 1);
2904 return $e->die_event unless $e->checkauth;
2906 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2907 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2909 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2912 (defined $ptype->org_depth) ?
2913 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2916 $penalty->org_unit($ctx_org);
2917 $penalty->staff($e->requestor->id);
2918 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2921 return $penalty->id;
2924 __PACKAGE__->register_method(
2925 method => "remove_penalty",
2926 api_name => "open-ils.actor.user.penalty.remove"
2929 sub remove_penalty {
2930 my($self, $conn, $auth, $penalty) = @_;
2931 my $e = new_editor(authtoken=>$auth, xact => 1);
2932 return $e->die_event unless $e->checkauth;
2933 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2934 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2936 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2941 __PACKAGE__->register_method(
2942 method => "update_penalty_note",
2943 api_name => "open-ils.actor.user.penalty.note.update"
2946 sub update_penalty_note {
2947 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2948 my $e = new_editor(authtoken=>$auth, xact => 1);
2949 return $e->die_event unless $e->checkauth;
2950 for my $penalty_id (@$penalty_ids) {
2951 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2952 if (! $penalty ) { return $e->die_event; }
2953 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2954 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2956 $penalty->note( $note ); $penalty->ischanged( 1 );
2958 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2964 __PACKAGE__->register_method(
2965 method => "ranged_penalty_thresholds",
2966 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2970 sub ranged_penalty_thresholds {
2971 my($self, $conn, $auth, $context_org) = @_;
2972 my $e = new_editor(authtoken=>$auth);
2973 return $e->event unless $e->checkauth;
2974 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2975 my $list = $e->search_permission_grp_penalty_threshold([
2976 {org_unit => $U->get_org_ancestors($context_org)},
2977 {order_by => {pgpt => 'id'}}
2979 $conn->respond($_) for @$list;
2985 __PACKAGE__->register_method(
2986 method => "user_retrieve_fleshed_by_id",
2988 api_name => "open-ils.actor.user.fleshed.retrieve",
2991 sub user_retrieve_fleshed_by_id {
2992 my( $self, $client, $auth, $user_id, $fields ) = @_;
2993 my $e = new_editor(authtoken => $auth);
2994 return $e->event unless $e->checkauth;
2996 if( $e->requestor->id != $user_id ) {
2997 return $e->event unless $e->allowed('VIEW_USER');
3004 "standing_penalties",
3010 return new_flesh_user($user_id, $fields, $e);
3014 sub new_flesh_user {
3017 my $fields = shift || [];
3020 my $fetch_penalties = 0;
3021 if(grep {$_ eq 'standing_penalties'} @$fields) {
3022 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3023 $fetch_penalties = 1;
3026 my $fetch_usr_act = 0;
3027 if(grep {$_ eq 'usr_activity'} @$fields) {
3028 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3032 my $user = $e->retrieve_actor_user(
3037 "flesh_fields" => { "au" => $fields }
3040 ) or return $e->die_event;
3043 if( grep { $_ eq 'addresses' } @$fields ) {
3045 $user->addresses([]) unless @{$user->addresses};
3046 # don't expose "replaced" addresses by default
3047 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3049 if( ref $user->billing_address ) {
3050 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3051 push( @{$user->addresses}, $user->billing_address );
3055 if( ref $user->mailing_address ) {
3056 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3057 push( @{$user->addresses}, $user->mailing_address );
3062 if($fetch_penalties) {
3063 # grab the user penalties ranged for this location
3064 $user->standing_penalties(
3065 $e->search_actor_user_standing_penalty([
3068 {stop_date => undef},
3069 {stop_date => {'>' => 'now'}}
3071 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3074 flesh_fields => {ausp => ['standing_penalty']}
3080 # retrieve the most recent usr_activity entry
3081 if ($fetch_usr_act) {
3083 # max number to return for simple patron fleshing
3084 my $limit = $U->ou_ancestor_setting_value(
3085 $e->requestor->ws_ou,
3086 'circ.patron.usr_activity_retrieve.max');
3090 flesh_fields => {auact => ['etype']},
3091 order_by => {auact => 'event_time DESC'},
3094 # 0 == none, <0 == return all
3095 $limit = 1 unless defined $limit;
3096 $opts->{limit} = $limit if $limit > 0;
3098 $user->usr_activity(
3100 [] : # skip the DB call
3101 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3106 $user->clear_passwd();
3113 __PACKAGE__->register_method(
3114 method => "user_retrieve_parts",
3115 api_name => "open-ils.actor.user.retrieve.parts",
3118 sub user_retrieve_parts {
3119 my( $self, $client, $auth, $user_id, $fields ) = @_;
3120 my $e = new_editor(authtoken => $auth);
3121 return $e->event unless $e->checkauth;
3122 $user_id ||= $e->requestor->id;
3123 if( $e->requestor->id != $user_id ) {
3124 return $e->event unless $e->allowed('VIEW_USER');
3127 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3128 push(@resp, $user->$_()) for(@$fields);
3134 __PACKAGE__->register_method(
3135 method => 'user_opt_in_enabled',
3136 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3137 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3140 sub user_opt_in_enabled {
3141 my($self, $conn) = @_;
3142 my $sc = OpenSRF::Utils::SettingsClient->new;
3143 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3148 __PACKAGE__->register_method(
3149 method => 'user_opt_in_at_org',
3150 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3152 @param $auth The auth token
3153 @param user_id The ID of the user to test
3154 @return 1 if the user has opted in at the specified org,
3155 2 if opt-in is disallowed for the user's home org,
3156 event on error, and 0 otherwise. /
3158 sub user_opt_in_at_org {
3159 my($self, $conn, $auth, $user_id) = @_;
3161 # see if we even need to enforce the opt-in value
3162 return 1 unless user_opt_in_enabled($self);
3164 my $e = new_editor(authtoken => $auth);
3165 return $e->event unless $e->checkauth;
3167 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3168 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3170 my $ws_org = $e->requestor->ws_ou;
3171 # user is automatically opted-in if they are from the local org
3172 return 1 if $user->home_ou eq $ws_org;
3174 # get the boundary setting
3175 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3177 # auto opt in if user falls within the opt boundary
3178 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3180 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3182 # check whether opt-in is restricted at the user's home library
3183 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3184 if ($opt_restrict_depth) {
3185 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3186 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3188 # opt-in is disallowed unless the workstation org is within the home
3189 # library's opt-in scope
3190 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3193 my $vals = $e->search_actor_usr_org_unit_opt_in(
3194 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3200 __PACKAGE__->register_method(
3201 method => 'create_user_opt_in_at_org',
3202 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3204 @param $auth The auth token
3205 @param user_id The ID of the user to test
3206 @return The ID of the newly created object, event on error./
3209 sub create_user_opt_in_at_org {
3210 my($self, $conn, $auth, $user_id, $org_id) = @_;
3212 my $e = new_editor(authtoken => $auth, xact=>1);
3213 return $e->die_event unless $e->checkauth;
3215 # if a specific org unit wasn't passed in, get one based on the defaults;
3217 my $wsou = $e->requestor->ws_ou;
3218 # get the default opt depth
3219 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3220 # get the org unit at that depth
3221 my $org = $e->json_query({
3222 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3223 $org_id = $org->{id};
3226 # fall back to the workstation OU, the pre-opt-in-boundary way
3227 $org_id = $e->requestor->ws_ou;
3230 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3231 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3233 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3235 $opt_in->org_unit($org_id);
3236 $opt_in->usr($user_id);
3237 $opt_in->staff($e->requestor->id);
3238 $opt_in->opt_in_ts('now');
3239 $opt_in->opt_in_ws($e->requestor->wsid);
3241 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3242 or return $e->die_event;
3250 __PACKAGE__->register_method (
3251 method => 'retrieve_org_hours',
3252 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3254 Returns the hours of operation for a specified org unit
3255 @param authtoken The login session key
3256 @param org_id The org_unit ID
3260 sub retrieve_org_hours {
3261 my($self, $conn, $auth, $org_id) = @_;
3262 my $e = new_editor(authtoken => $auth);
3263 return $e->die_event unless $e->checkauth;
3264 $org_id ||= $e->requestor->ws_ou;
3265 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3269 __PACKAGE__->register_method (
3270 method => 'verify_user_password',
3271 api_name => 'open-ils.actor.verify_user_password',
3273 Given a barcode or username and the MD5 encoded password,
3274 returns 1 if the password is correct. Returns 0 otherwise.
3278 sub verify_user_password {
3279 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3280 my $e = new_editor(authtoken => $auth);
3281 return $e->die_event unless $e->checkauth;
3283 my $user_by_barcode;
3284 my $user_by_username;
3286 my $card = $e->search_actor_card([
3287 {barcode => $barcode},
3288 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3289 $user_by_barcode = $card->usr;
3290 $user = $user_by_barcode;
3293 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3294 $user = $user_by_username;
3296 return 0 if (!$user);
3297 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3298 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3299 return $U->verify_migrated_user_password(
3300 $e, $user_by_username->id, $password, 1);
3303 __PACKAGE__->register_method (
3304 method => 'retrieve_usr_id_via_barcode_or_usrname',
3305 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3307 Given a barcode or username returns the id for the user or
3312 sub retrieve_usr_id_via_barcode_or_usrname {
3313 my($self, $conn, $auth, $barcode, $username) = @_;
3314 my $e = new_editor(authtoken => $auth);
3315 return $e->die_event unless $e->checkauth;
3316 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3318 my $user_by_barcode;
3319 my $user_by_username;
3320 $logger->info("$id_as_barcode is the ID as BARCODE");
3322 my $card = $e->search_actor_card([
3323 {barcode => $barcode},
3324 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3325 if ($id_as_barcode =~ /^t/i) {
3327 $user = $e->retrieve_actor_user($barcode);
3328 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3330 $user_by_barcode = $card->usr;
3331 $user = $user_by_barcode;
3334 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3335 $user_by_barcode = $card->usr;
3336 $user = $user_by_barcode;
3341 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3343 $user = $user_by_username;
3345 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3346 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3347 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3352 __PACKAGE__->register_method (
3353 method => 'merge_users',
3354 api_name => 'open-ils.actor.user.merge',
3357 Given a list of source users and destination user, transfer all data from the source
3358 to the dest user and delete the source user. All user related data is
3359 transferred, including circulations, holds, bookbags, etc.
3365 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3366 my $e = new_editor(xact => 1, authtoken => $auth);
3367 return $e->die_event unless $e->checkauth;
3369 # disallow the merge if any subordinate accounts are in collections
3370 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3371 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3373 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3374 my $del_addrs = ($U->ou_ancestor_setting_value(
3375 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3376 my $del_cards = ($U->ou_ancestor_setting_value(
3377 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3378 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3379 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3381 for my $src_id (@$user_ids) {
3382 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3384 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3385 if($src_user->home_ou ne $master_user->home_ou) {
3386 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3389 return $e->die_event unless
3390 $e->json_query({from => [
3405 __PACKAGE__->register_method (
3406 method => 'approve_user_address',
3407 api_name => 'open-ils.actor.user.pending_address.approve',
3414 sub approve_user_address {
3415 my($self, $conn, $auth, $addr) = @_;
3416 my $e = new_editor(xact => 1, authtoken => $auth);
3417 return $e->die_event unless $e->checkauth;
3419 # if the caller passes an address object, assume they want to
3420 # update it first before approving it
3421 $e->update_actor_user_address($addr) or return $e->die_event;
3423 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3425 my $user = $e->retrieve_actor_user($addr->usr);
3426 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3427 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3428 or return $e->die_event;
3430 return [values %$result]->[0];
3434 __PACKAGE__->register_method (
3435 method => 'retrieve_friends',
3436 api_name => 'open-ils.actor.friends.retrieve',
3439 returns { confirmed: [], pending_out: [], pending_in: []}
3440 pending_out are users I'm requesting friendship with
3441 pending_in are users requesting friendship with me
3446 sub retrieve_friends {
3447 my($self, $conn, $auth, $user_id, $options) = @_;
3448 my $e = new_editor(authtoken => $auth);
3449 return $e->event unless $e->checkauth;
3450 $user_id ||= $e->requestor->id;
3452 if($user_id != $e->requestor->id) {
3453 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3454 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3457 return OpenILS::Application::Actor::Friends->retrieve_friends(
3458 $e, $user_id, $options);
3463 __PACKAGE__->register_method (
3464 method => 'apply_friend_perms',
3465 api_name => 'open-ils.actor.friends.perms.apply',
3471 sub apply_friend_perms {
3472 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3473 my $e = new_editor(authtoken => $auth, xact => 1);
3474 return $e->die_event unless $e->checkauth;
3476 if($user_id != $e->requestor->id) {
3477 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3478 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3481 for my $perm (@perms) {
3483 OpenILS::Application::Actor::Friends->apply_friend_perm(
3484 $e, $user_id, $delegate_id, $perm);
3485 return $evt if $evt;
3493 __PACKAGE__->register_method (
3494 method => 'update_user_pending_address',
3495 api_name => 'open-ils.actor.user.address.pending.cud'
3498 sub update_user_pending_address {
3499 my($self, $conn, $auth, $addr) = @_;
3500 my $e = new_editor(authtoken => $auth, xact => 1);
3501 return $e->die_event unless $e->checkauth;
3503 if($addr->usr != $e->requestor->id) {
3504 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3505 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3509 $e->create_actor_user_address($addr) or return $e->die_event;
3510 } elsif($addr->isdeleted) {
3511 $e->delete_actor_user_address($addr) or return $e->die_event;
3513 $e->update_actor_user_address($addr) or return $e->die_event;
3521 __PACKAGE__->register_method (
3522 method => 'user_events',
3523 api_name => 'open-ils.actor.user.events.circ',
3526 __PACKAGE__->register_method (
3527 method => 'user_events',
3528 api_name => 'open-ils.actor.user.events.ahr',
3533 my($self, $conn, $auth, $user_id, $filters) = @_;
3534 my $e = new_editor(authtoken => $auth);
3535 return $e->event unless $e->checkauth;
3537 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3538 my $user_field = 'usr';
3541 $filters->{target} = {
3542 select => { $obj_type => ['id'] },
3544 where => {usr => $user_id}
3547 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3548 if($e->requestor->id != $user_id) {
3549 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3552 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3553 my $req = $ses->request('open-ils.trigger.events_by_target',
3554 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3556 while(my $resp = $req->recv) {
3557 my $val = $resp->content;
3558 my $tgt = $val->target;
3560 if($obj_type eq 'circ') {
3561 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3563 } elsif($obj_type eq 'ahr') {
3564 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3565 if $tgt->current_copy;
3568 $conn->respond($val) if $val;
3574 __PACKAGE__->register_method (
3575 method => 'copy_events',
3576 api_name => 'open-ils.actor.copy.events.circ',
3579 __PACKAGE__->register_method (
3580 method => 'copy_events',
3581 api_name => 'open-ils.actor.copy.events.ahr',
3586 my($self, $conn, $auth, $copy_id, $filters) = @_;
3587 my $e = new_editor(authtoken => $auth);
3588 return $e->event unless $e->checkauth;
3590 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3592 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3594 my $copy_field = 'target_copy';
3595 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3598 $filters->{target} = {
3599 select => { $obj_type => ['id'] },
3601 where => {$copy_field => $copy_id}
3605 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3606 my $req = $ses->request('open-ils.trigger.events_by_target',
3607 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3609 while(my $resp = $req->recv) {
3610 my $val = $resp->content;
3611 my $tgt = $val->target;
3613 my $user = $e->retrieve_actor_user($tgt->usr);
3614 if($e->requestor->id != $user->id) {
3615 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3618 $tgt->$copy_field($copy);
3621 $conn->respond($val) if $val;
3630 __PACKAGE__->register_method (
3631 method => 'update_events',
3632 api_name => 'open-ils.actor.user.event.cancel.batch',
3635 __PACKAGE__->register_method (
3636 method => 'update_events',
3637 api_name => 'open-ils.actor.user.event.reset.batch',
3642 my($self, $conn, $auth, $event_ids) = @_;
3643 my $e = new_editor(xact => 1, authtoken => $auth);
3644 return $e->die_event unless $e->checkauth;
3647 for my $id (@$event_ids) {
3649 # do a little dance to determine what user we are ultimately affecting
3650 my $event = $e->retrieve_action_trigger_event([
3653 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3655 ]) or return $e->die_event;
3658 if($event->event_def->hook->core_type eq 'circ') {
3659 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3660 } elsif($event->event_def->hook->core_type eq 'ahr') {
3661 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3666 my $user = $e->retrieve_actor_user($user_id);
3667 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3669 if($self->api_name =~ /cancel/) {
3670 $event->state('invalid');
3671 } elsif($self->api_name =~ /reset/) {
3672 $event->clear_start_time;
3673 $event->clear_update_time;
3674 $event->state('pending');
3677 $e->update_action_trigger_event($event) or return $e->die_event;
3678 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3682 return {complete => 1};
3686 __PACKAGE__->register_method (
3687 method => 'really_delete_user',
3688 api_name => 'open-ils.actor.user.delete.override',
3689 signature => q/@see open-ils.actor.user.delete/
3692 __PACKAGE__->register_method (
3693 method => 'really_delete_user',
3694 api_name => 'open-ils.actor.user.delete',
3696 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3697 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3698 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3699 dest_usr_id is only required when deleting a user that performs staff functions.
3703 sub really_delete_user {
3704 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3705 my $e = new_editor(authtoken => $auth, xact => 1);
3706 return $e->die_event unless $e->checkauth;
3707 $oargs = { all => 1 } unless defined $oargs;
3709 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3710 my $open_bills = $e->json_query({
3711 select => { mbts => ['id'] },
3714 xact_finish => { '=' => undef },
3715 usr => { '=' => $user_id },
3717 }) or return $e->die_event;
3719 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3721 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3723 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3724 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3725 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3727 # No deleting yourself - UI is supposed to stop you first, though.
3728 return $e->die_event unless $e->requestor->id != $user->id;
3729 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3730 # Check if you are allowed to mess with this patron permission group at all
3731 my $evt = group_perm_failed($e, $e->requestor, $user);
3732 return $e->die_event($evt) if $evt;
3733 my $stat = $e->json_query(
3734 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3735 or return $e->die_event;
3741 __PACKAGE__->register_method (
3742 method => 'user_payments',
3743 api_name => 'open-ils.actor.user.payments.retrieve',
3746 Returns all payments for a given user. Default order is newest payments first.
3747 @param auth Authentication token
3748 @param user_id The user ID
3749 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3754 my($self, $conn, $auth, $user_id, $filters) = @_;
3757 my $e = new_editor(authtoken => $auth);
3758 return $e->die_event unless $e->checkauth;
3760 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3761 return $e->event unless
3762 $e->requestor->id == $user_id or
3763 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3765 # Find all payments for all transactions for user $user_id
3767 select => {mp => ['id']},
3772 select => {mbt => ['id']},
3774 where => {usr => $user_id}
3779 { # by default, order newest payments first
3781 field => 'payment_ts',
3784 # secondary sort in ID as a tie-breaker, since payments created
3785 # within the same transaction will have identical payment_ts's
3792 for (qw/order_by limit offset/) {
3793 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3796 if(defined $filters->{where}) {
3797 foreach (keys %{$filters->{where}}) {
3798 # don't allow the caller to expand the result set to other users
3799 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3803 my $payment_ids = $e->json_query($query);
3804 for my $pid (@$payment_ids) {
3805 my $pay = $e->retrieve_money_payment([
3810 mbt => ['summary', 'circulation', 'grocery'],
3811 circ => ['target_copy'],
3812 acp => ['call_number'],
3820 xact_type => $pay->xact->summary->xact_type,
3821 last_billing_type => $pay->xact->summary->last_billing_type,
3824 if($pay->xact->summary->xact_type eq 'circulation') {
3825 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3826 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3829 $pay->xact($pay->xact->id); # de-flesh
3830 $conn->respond($resp);
3838 __PACKAGE__->register_method (
3839 method => 'negative_balance_users',
3840 api_name => 'open-ils.actor.users.negative_balance',
3843 Returns all users that have an overall negative balance
3844 @param auth Authentication token
3845 @param org_id The context org unit as an ID or list of IDs. This will be the home
3846 library of the user. If no org_unit is specified, no org unit filter is applied
3850 sub negative_balance_users {
3851 my($self, $conn, $auth, $org_id) = @_;
3853 my $e = new_editor(authtoken => $auth);
3854 return $e->die_event unless $e->checkauth;
3855 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3859 mous => ['usr', 'balance_owed'],
3862 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3863 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3880 where => {'+mous' => {balance_owed => {'<' => 0}}}
3883 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3885 my $list = $e->json_query($query, {timeout => 600});
3887 for my $data (@$list) {
3889 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3890 balance_owed => $data->{balance_owed},
3891 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3898 __PACKAGE__->register_method(
3899 method => "request_password_reset",
3900 api_name => "open-ils.actor.patron.password_reset.request",
3902 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3903 "method for changing a user's password. The UUID token is distributed via A/T " .
3904 "templates (i.e. email to the user).",
3906 { desc => 'user_id_type', type => 'string' },
3907 { desc => 'user_id', type => 'string' },
3908 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3910 return => {desc => '1 on success, Event on error'}
3913 sub request_password_reset {
3914 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3916 # Check to see if password reset requests are already being throttled:
3917 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3919 my $e = new_editor(xact => 1);
3922 # Get the user, if any, depending on the input value
3923 if ($user_id_type eq 'username') {
3924 $user = $e->search_actor_user({usrname => $user_id})->[0];
3927 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3929 } elsif ($user_id_type eq 'barcode') {
3930 my $card = $e->search_actor_card([
3931 {barcode => $user_id},
3932 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3935 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3940 # If the user doesn't have an email address, we can't help them
3941 if (!$user->email) {
3943 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3946 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3947 if ($email_must_match) {
3948 if (lc($user->email) ne lc($email)) {
3949 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3953 _reset_password_request($conn, $e, $user);
3956 # Once we have the user, we can issue the password reset request
3957 # XXX Add a wrapper method that accepts barcode + email input
3958 sub _reset_password_request {
3959 my ($conn, $e, $user) = @_;
3961 # 1. Get throttle threshold and time-to-live from OU_settings
3962 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3963 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3965 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3967 # 2. Get time of last request and number of active requests (num_active)
3968 my $active_requests = $e->json_query({
3974 transform => 'COUNT'
3977 column => 'request_time',
3983 has_been_reset => { '=' => 'f' },
3984 request_time => { '>' => $threshold_time }
3988 # Guard against no active requests
3989 if ($active_requests->[0]->{'request_time'}) {
3990 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3991 my $now = DateTime::Format::ISO8601->new();
3993 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3994 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3995 ($last_request->add_duration('1 minute') > $now)) {
3996 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3998 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4002 # TODO Check to see if the user is in a password-reset-restricted group
4004 # Otherwise, go ahead and try to get the user.
4006 # Check the number of active requests for this user
4007 $active_requests = $e->json_query({
4013 transform => 'COUNT'
4018 usr => { '=' => $user->id },
4019 has_been_reset => { '=' => 'f' },
4020 request_time => { '>' => $threshold_time }
4024 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4026 # if less than or equal to per-user threshold, proceed; otherwise, return event
4027 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4028 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4030 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4033 # Create the aupr object and insert into the database
4034 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4035 my $uuid = create_uuid_as_string(UUID_V4);
4036 $reset_request->uuid($uuid);
4037 $reset_request->usr($user->id);
4039 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4042 # Create an event to notify user of the URL to reset their password
4044 # Can we stuff this in the user_data param for trigger autocreate?
4045 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4047 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4048 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4051 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4056 __PACKAGE__->register_method(
4057 method => "commit_password_reset",
4058 api_name => "open-ils.actor.patron.password_reset.commit",
4060 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4061 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4062 "with the supplied password.",
4064 { desc => 'uuid', type => 'string' },
4065 { desc => 'password', type => 'string' },
4067 return => {desc => '1 on success, Event on error'}
4070 sub commit_password_reset {
4071 my($self, $conn, $uuid, $password) = @_;
4073 # Check to see if password reset requests are already being throttled:
4074 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4075 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4076 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4078 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4081 my $e = new_editor(xact => 1);
4083 my $aupr = $e->search_actor_usr_password_reset({
4090 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4092 my $user_id = $aupr->[0]->usr;
4093 my $user = $e->retrieve_actor_user($user_id);
4095 # Ensure we're still within the TTL for the request
4096 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4097 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4098 if ($threshold < DateTime->now(time_zone => 'local')) {
4100 $logger->info("Password reset request needed to be submitted before $threshold");
4101 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4104 # Check complexity of password against OU-defined regex
4105 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4109 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4110 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4111 $is_strong = check_password_strength_custom($password, $pw_regex);
4113 $is_strong = check_password_strength_default($password);
4118 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4121 # All is well; update the password
4122 modify_migrated_user_password($e, $user->id, $password);
4124 # And flag that this password reset request has been honoured
4125 $aupr->[0]->has_been_reset('t');
4126 $e->update_actor_usr_password_reset($aupr->[0]);
4132 sub check_password_strength_default {
4133 my $password = shift;
4134 # Use the default set of checks
4135 if ( (length($password) < 7) or
4136 ($password !~ m/.*\d+.*/) or
4137 ($password !~ m/.*[A-Za-z]+.*/)
4144 sub check_password_strength_custom {
4145 my ($password, $pw_regex) = @_;
4147 $pw_regex = qr/$pw_regex/;
4148 if ($password !~ /$pw_regex/) {
4156 __PACKAGE__->register_method(
4157 method => "event_def_opt_in_settings",
4158 api_name => "open-ils.actor.event_def.opt_in.settings",
4161 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4163 { desc => 'Authentication token', type => 'string'},
4165 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4170 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4177 sub event_def_opt_in_settings {
4178 my($self, $conn, $auth, $org_id) = @_;
4179 my $e = new_editor(authtoken => $auth);
4180 return $e->event unless $e->checkauth;
4182 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4183 return $e->event unless
4184 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4186 $org_id = $e->requestor->home_ou;
4189 # find all config.user_setting_type's related to event_defs for the requested org unit
4190 my $types = $e->json_query({
4191 select => {cust => ['name']},
4192 from => {atevdef => 'cust'},
4195 owner => $U->get_org_ancestors($org_id), # context org plus parents
4202 $conn->respond($_) for
4203 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4210 __PACKAGE__->register_method(
4211 method => "user_circ_history",
4212 api_name => "open-ils.actor.history.circ",
4216 desc => 'Returns user circ history objects for the calling user',
4218 { desc => 'Authentication token', type => 'string'},
4219 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4222 desc => q/Stream of 'auch' circ history objects/,
4228 __PACKAGE__->register_method(
4229 method => "user_circ_history",
4230 api_name => "open-ils.actor.history.circ.clear",
4233 desc => 'Delete all user circ history entries for the calling user',
4235 { desc => 'Authentication token', type => 'string'},
4236 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4239 desc => q/1 on success, event on error/,
4245 __PACKAGE__->register_method(
4246 method => "user_circ_history",
4247 api_name => "open-ils.actor.history.circ.print",
4250 desc => q/Returns printable output for the caller's circ history objects/,
4252 { desc => 'Authentication token', type => 'string'},
4253 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4256 desc => q/An action_trigger.event object or error event./,
4262 __PACKAGE__->register_method(
4263 method => "user_circ_history",
4264 api_name => "open-ils.actor.history.circ.email",
4267 desc => q/Emails the caller's circ history/,
4269 { desc => 'Authentication token', type => 'string'},
4270 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4271 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4274 desc => q/undef, or event on error/
4279 sub user_circ_history {
4280 my ($self, $conn, $auth, $options) = @_;
4283 my $for_print = ($self->api_name =~ /print/);
4284 my $for_email = ($self->api_name =~ /email/);
4285 my $for_clear = ($self->api_name =~ /clear/);
4287 # No perm check is performed. Caller may only access his/her own
4288 # circ history entries.
4289 my $e = new_editor(authtoken => $auth);
4290 return $e->event unless $e->checkauth;
4293 if (!$for_clear) { # clear deletes all
4294 $limits{offset} = $options->{offset} if defined $options->{offset};
4295 $limits{limit} = $options->{limit} if defined $options->{limit};
4298 my %circ_id_filter = $options->{circ_ids} ?
4299 (id => $options->{circ_ids}) : ();
4301 my $circs = $e->search_action_user_circ_history([
4302 { usr => $e->requestor->id,
4305 { # order newest to oldest by default
4306 order_by => {auch => 'xact_start DESC'},
4309 {substream => 1} # could be a large list
4313 return $U->fire_object_event(undef,
4314 'circ.format.history.print', $circs, $e->requestor->home_ou);
4317 $e->xact_begin if $for_clear;
4318 $conn->respond_complete(1) if $for_email; # no sense in waiting
4320 for my $circ (@$circs) {
4323 # events will be fired from action_trigger_runner
4324 $U->create_events_for_hook('circ.format.history.email',
4325 $circ, $e->editor->home_ou, undef, undef, 1);
4327 } elsif ($for_clear) {
4329 $e->delete_action_user_circ_history($circ)
4330 or return $e->die_event;
4333 $conn->respond($circ);
4346 __PACKAGE__->register_method(
4347 method => "user_visible_holds",
4348 api_name => "open-ils.actor.history.hold.visible",
4351 desc => 'Returns the set of opt-in visible holds',
4353 { desc => 'Authentication token', type => 'string'},
4354 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4355 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4358 desc => q/An object with 1 field: "hold"/,
4364 __PACKAGE__->register_method(
4365 method => "user_visible_holds",
4366 api_name => "open-ils.actor.history.hold.visible.print",
4369 desc => 'Returns printable output for the set of opt-in visible holds',
4371 { desc => 'Authentication token', type => 'string'},
4372 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4373 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4376 desc => q/An action_trigger.event object or error event./,
4382 __PACKAGE__->register_method(
4383 method => "user_visible_holds",
4384 api_name => "open-ils.actor.history.hold.visible.email",
4387 desc => 'Emails the set of opt-in visible holds to the requestor',
4389 { desc => 'Authentication token', type => 'string'},
4390 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4391 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4394 desc => q/undef, or event on error/
4399 sub user_visible_holds {
4400 my($self, $conn, $auth, $user_id, $options) = @_;
4403 my $for_print = ($self->api_name =~ /print/);
4404 my $for_email = ($self->api_name =~ /email/);
4405 my $e = new_editor(authtoken => $auth);
4406 return $e->event unless $e->checkauth;
4408 $user_id ||= $e->requestor->id;
4410 $options->{limit} ||= 50;
4411 $options->{offset} ||= 0;
4413 if($user_id != $e->requestor->id) {
4414 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4415 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4416 return $e->event unless $e->allowed($perm, $user->home_ou);
4419 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4421 my $data = $e->json_query({
4422 from => [$db_func, $user_id],
4423 limit => $$options{limit},
4424 offset => $$options{offset}
4426 # TODO: I only want IDs. code below didn't get me there
4427 # {"select":{"au":[{"column":"id", "result_field":"id",
4428 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4433 return undef unless @$data;
4437 # collect the batch of objects
4441 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4442 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4446 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4447 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4450 } elsif ($for_email) {
4452 $conn->respond_complete(1) if $for_email; # no sense in waiting
4460 my $hold = $e->retrieve_action_hold_request($id);
4461 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4462 # events will be fired from action_trigger_runner
4466 my $circ = $e->retrieve_action_circulation($id);
4467 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4468 # events will be fired from action_trigger_runner
4472 } else { # just give me the data please
4480 my $hold = $e->retrieve_action_hold_request($id);
4481 $conn->respond({hold => $hold});
4485 my $circ = $e->retrieve_action_circulation($id);
4488 summary => $U->create_circ_chain_summary($e, $id)
4497 __PACKAGE__->register_method(
4498 method => "user_saved_search_cud",
4499 api_name => "open-ils.actor.user.saved_search.cud",
4502 desc => 'Create/Update/Delete Access to user saved searches',
4504 { desc => 'Authentication token', type => 'string' },
4505 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4508 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4514 __PACKAGE__->register_method(
4515 method => "user_saved_search_cud",
4516 api_name => "open-ils.actor.user.saved_search.retrieve",
4519 desc => 'Retrieve a saved search object',
4521 { desc => 'Authentication token', type => 'string' },
4522 { desc => 'Saved Search ID', type => 'number' }
4525 desc => q/The saved search object, Event on error/,
4531 sub user_saved_search_cud {
4532 my( $self, $client, $auth, $search ) = @_;
4533 my $e = new_editor( authtoken=>$auth );
4534 return $e->die_event unless $e->checkauth;
4536 my $o_search; # prior version of the object, if any
4537 my $res; # to be returned
4539 # branch on the operation type
4541 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4543 # Get the old version, to check ownership
4544 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4545 or return $e->die_event;
4547 # You can't read somebody else's search
4548 return OpenILS::Event->new('BAD_PARAMS')
4549 unless $o_search->owner == $e->requestor->id;
4555 $e->xact_begin; # start an editor transaction
4557 if( $search->isnew ) { # Create
4559 # You can't create a search for somebody else
4560 return OpenILS::Event->new('BAD_PARAMS')
4561 unless $search->owner == $e->requestor->id;
4563 $e->create_actor_usr_saved_search( $search )
4564 or return $e->die_event;
4568 } elsif( $search->ischanged ) { # Update
4570 # You can't change ownership of a search
4571 return OpenILS::Event->new('BAD_PARAMS')
4572 unless $search->owner == $e->requestor->id;
4574 # Get the old version, to check ownership
4575 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4576 or return $e->die_event;
4578 # You can't update somebody else's search
4579 return OpenILS::Event->new('BAD_PARAMS')
4580 unless $o_search->owner == $e->requestor->id;
4583 $e->update_actor_usr_saved_search( $search )
4584 or return $e->die_event;
4588 } elsif( $search->isdeleted ) { # Delete
4590 # Get the old version, to check ownership
4591 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4592 or return $e->die_event;
4594 # You can't delete somebody else's search
4595 return OpenILS::Event->new('BAD_PARAMS')
4596 unless $o_search->owner == $e->requestor->id;
4599 $e->delete_actor_usr_saved_search( $o_search )
4600 or return $e->die_event;
4611 __PACKAGE__->register_method(
4612 method => "get_barcodes",
4613 api_name => "open-ils.actor.get_barcodes"
4617 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4618 my $e = new_editor(authtoken => $auth);
4619 return $e->event unless $e->checkauth;
4620 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4622 my $db_result = $e->json_query(
4624 'evergreen.get_barcodes',
4625 $org_id, $context, $barcode,
4629 if($context =~ /actor/) {
4630 my $filter_result = ();
4632 foreach my $result (@$db_result) {
4633 if($result->{type} eq 'actor') {
4634 if($e->requestor->id != $result->{id}) {
4635 $patron = $e->retrieve_actor_user($result->{id});
4637 push(@$filter_result, $e->event);
4640 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4641 push(@$filter_result, $result);
4644 push(@$filter_result, $e->event);
4648 push(@$filter_result, $result);
4652 push(@$filter_result, $result);
4655 return $filter_result;
4661 __PACKAGE__->register_method(
4662 method => 'address_alert_test',
4663 api_name => 'open-ils.actor.address_alert.test',
4665 desc => "Tests a set of address fields to determine if they match with an address_alert",
4667 {desc => 'Authentication token', type => 'string'},
4668 {desc => 'Org Unit', type => 'number'},
4669 {desc => 'Fields', type => 'hash'},
4671 return => {desc => 'List of matching address_alerts'}
4675 sub address_alert_test {
4676 my ($self, $client, $auth, $org_unit, $fields) = @_;
4677 return [] unless $fields and grep {$_} values %$fields;
4679 my $e = new_editor(authtoken => $auth);
4680 return $e->event unless $e->checkauth;
4681 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4682 $org_unit ||= $e->requestor->ws_ou;
4684 my $alerts = $e->json_query({
4686 'actor.address_alert_matches',
4694 $$fields{post_code},
4695 $$fields{mailing_address},
4696 $$fields{billing_address}
4700 # map the json_query hashes to real objects
4702 map {$e->retrieve_actor_address_alert($_)}
4703 (map {$_->{id}} @$alerts)
4707 __PACKAGE__->register_method(
4708 method => "mark_users_contact_invalid",
4709 api_name => "open-ils.actor.invalidate.email",
4711 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",
4713 {desc => "Authentication token", type => "string"},
4714 {desc => "Patron ID", type => "number"},
4715 {desc => "Additional note text (optional)", type => "string"},
4716 {desc => "penalty org unit ID (optional)", type => "number"}
4718 return => {desc => "Event describing success or failure", type => "object"}
4722 __PACKAGE__->register_method(
4723 method => "mark_users_contact_invalid",
4724 api_name => "open-ils.actor.invalidate.day_phone",
4726 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",
4728 {desc => "Authentication token", type => "string"},
4729 {desc => "Patron ID", type => "number"},
4730 {desc => "Additional note text (optional)", type => "string"},
4731 {desc => "penalty org unit ID (optional)", type => "number"}
4733 return => {desc => "Event describing success or failure", type => "object"}
4737 __PACKAGE__->register_method(
4738 method => "mark_users_contact_invalid",
4739 api_name => "open-ils.actor.invalidate.evening_phone",
4741 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",
4743 {desc => "Authentication token", type => "string"},
4744 {desc => "Patron ID", type => "number"},
4745 {desc => "Additional note text (optional)", type => "string"},
4746 {desc => "penalty org unit ID (optional)", type => "number"}
4748 return => {desc => "Event describing success or failure", type => "object"}
4752 __PACKAGE__->register_method(
4753 method => "mark_users_contact_invalid",
4754 api_name => "open-ils.actor.invalidate.other_phone",
4756 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",
4758 {desc => "Authentication token", type => "string"},
4759 {desc => "Patron ID", type => "number"},
4760 {desc => "Additional note text (optional)", type => "string"},
4761 {desc => "penalty org unit ID (optional, default to top of org tree)",
4764 return => {desc => "Event describing success or failure", type => "object"}
4768 sub mark_users_contact_invalid {
4769 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4771 # This method invalidates an email address or a phone_number which
4772 # removes the bad email address or phone number, copying its contents
4773 # to a patron note, and institutes a standing penalty for "bad email"
4774 # or "bad phone number" which is cleared when the user is saved or
4775 # optionally only when the user is saved with an email address or
4776 # phone number (or staff manually delete the penalty).
4778 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4780 my $e = new_editor(authtoken => $auth, xact => 1);
4781 return $e->die_event unless $e->checkauth;
4783 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4784 $e, $contact_type, {usr => $patron_id},
4785 $addl_note, $penalty_ou, $e->requestor->id
4789 # Putting the following method in open-ils.actor is a bad fit, except in that
4790 # it serves an interface that lives under 'actor' in the templates directory,
4791 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4793 __PACKAGE__->register_method(
4794 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4795 method => "get_all_at_reactors_in_use",
4800 { name => 'authtoken', type => 'string' }
4803 desc => 'list of reactor names', type => 'array'
4808 sub get_all_at_reactors_in_use {
4809 my ($self, $conn, $auth) = @_;
4811 my $e = new_editor(authtoken => $auth);
4812 $e->checkauth or return $e->die_event;
4813 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4815 my $reactors = $e->json_query({
4817 atevdef => [{column => "reactor", transform => "distinct"}]
4819 from => {atevdef => {}}
4822 return $e->die_event unless ref $reactors eq "ARRAY";
4825 return [ map { $_->{reactor} } @$reactors ];
4828 __PACKAGE__->register_method(
4829 method => "filter_group_entry_crud",
4830 api_name => "open-ils.actor.filter_group_entry.crud",
4833 Provides CRUD access to filter group entry objects. These are not full accessible
4834 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4835 are not accessible via PCRUD (because they have no fields against which to link perms)
4838 {desc => "Authentication token", type => "string"},
4839 {desc => "Entry ID / Entry Object", type => "number"},
4840 {desc => "Additional note text (optional)", type => "string"},
4841 {desc => "penalty org unit ID (optional, default to top of org tree)",
4845 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4851 sub filter_group_entry_crud {
4852 my ($self, $conn, $auth, $arg) = @_;
4854 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4855 my $e = new_editor(authtoken => $auth, xact => 1);
4856 return $e->die_event unless $e->checkauth;
4862 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4863 or return $e->die_event;
4865 return $e->die_event unless $e->allowed(
4866 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4868 my $query = $arg->query;
4869 $query = $e->create_actor_search_query($query) or return $e->die_event;
4870 $arg->query($query->id);
4871 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4872 $entry->query($query);
4877 } elsif ($arg->ischanged) {
4879 my $entry = $e->retrieve_actor_search_filter_group_entry([
4882 flesh_fields => {asfge => ['grp']}
4884 ]) or return $e->die_event;
4886 return $e->die_event unless $e->allowed(
4887 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4889 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4890 $arg->query($arg->query->id);
4891 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4892 $arg->query($query);
4897 } elsif ($arg->isdeleted) {
4899 my $entry = $e->retrieve_actor_search_filter_group_entry([
4902 flesh_fields => {asfge => ['grp', 'query']}
4904 ]) or return $e->die_event;
4906 return $e->die_event unless $e->allowed(
4907 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4909 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4910 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4923 my $entry = $e->retrieve_actor_search_filter_group_entry([
4926 flesh_fields => {asfge => ['grp', 'query']}
4928 ]) or return $e->die_event;
4930 return $e->die_event unless $e->allowed(
4931 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4932 $entry->grp->owner);
4935 $entry->grp($entry->grp->id); # for consistency