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 || $U->is_true($user->deleted));
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($e, $user->id, $password, 1);
3302 __PACKAGE__->register_method (
3303 method => 'retrieve_usr_id_via_barcode_or_usrname',
3304 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3306 Given a barcode or username returns the id for the user or
3311 sub retrieve_usr_id_via_barcode_or_usrname {
3312 my($self, $conn, $auth, $barcode, $username) = @_;
3313 my $e = new_editor(authtoken => $auth);
3314 return $e->die_event unless $e->checkauth;
3315 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3317 my $user_by_barcode;
3318 my $user_by_username;
3319 $logger->info("$id_as_barcode is the ID as BARCODE");
3321 my $card = $e->search_actor_card([
3322 {barcode => $barcode},
3323 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3324 if ($id_as_barcode =~ /^t/i) {
3326 $user = $e->retrieve_actor_user($barcode);
3327 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3329 $user_by_barcode = $card->usr;
3330 $user = $user_by_barcode;
3333 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3334 $user_by_barcode = $card->usr;
3335 $user = $user_by_barcode;
3340 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3342 $user = $user_by_username;
3344 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3345 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3346 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3351 __PACKAGE__->register_method (
3352 method => 'merge_users',
3353 api_name => 'open-ils.actor.user.merge',
3356 Given a list of source users and destination user, transfer all data from the source
3357 to the dest user and delete the source user. All user related data is
3358 transferred, including circulations, holds, bookbags, etc.
3364 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3365 my $e = new_editor(xact => 1, authtoken => $auth);
3366 return $e->die_event unless $e->checkauth;
3368 # disallow the merge if any subordinate accounts are in collections
3369 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3370 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3372 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3373 my $del_addrs = ($U->ou_ancestor_setting_value(
3374 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3375 my $del_cards = ($U->ou_ancestor_setting_value(
3376 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3377 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3378 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3380 for my $src_id (@$user_ids) {
3381 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3383 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3384 if($src_user->home_ou ne $master_user->home_ou) {
3385 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3388 return $e->die_event unless
3389 $e->json_query({from => [
3404 __PACKAGE__->register_method (
3405 method => 'approve_user_address',
3406 api_name => 'open-ils.actor.user.pending_address.approve',
3413 sub approve_user_address {
3414 my($self, $conn, $auth, $addr) = @_;
3415 my $e = new_editor(xact => 1, authtoken => $auth);
3416 return $e->die_event unless $e->checkauth;
3418 # if the caller passes an address object, assume they want to
3419 # update it first before approving it
3420 $e->update_actor_user_address($addr) or return $e->die_event;
3422 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3424 my $user = $e->retrieve_actor_user($addr->usr);
3425 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3426 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3427 or return $e->die_event;
3429 return [values %$result]->[0];
3433 __PACKAGE__->register_method (
3434 method => 'retrieve_friends',
3435 api_name => 'open-ils.actor.friends.retrieve',
3438 returns { confirmed: [], pending_out: [], pending_in: []}
3439 pending_out are users I'm requesting friendship with
3440 pending_in are users requesting friendship with me
3445 sub retrieve_friends {
3446 my($self, $conn, $auth, $user_id, $options) = @_;
3447 my $e = new_editor(authtoken => $auth);
3448 return $e->event unless $e->checkauth;
3449 $user_id ||= $e->requestor->id;
3451 if($user_id != $e->requestor->id) {
3452 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3453 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3456 return OpenILS::Application::Actor::Friends->retrieve_friends(
3457 $e, $user_id, $options);
3462 __PACKAGE__->register_method (
3463 method => 'apply_friend_perms',
3464 api_name => 'open-ils.actor.friends.perms.apply',
3470 sub apply_friend_perms {
3471 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3472 my $e = new_editor(authtoken => $auth, xact => 1);
3473 return $e->die_event unless $e->checkauth;
3475 if($user_id != $e->requestor->id) {
3476 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3477 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3480 for my $perm (@perms) {
3482 OpenILS::Application::Actor::Friends->apply_friend_perm(
3483 $e, $user_id, $delegate_id, $perm);
3484 return $evt if $evt;
3492 __PACKAGE__->register_method (
3493 method => 'update_user_pending_address',
3494 api_name => 'open-ils.actor.user.address.pending.cud'
3497 sub update_user_pending_address {
3498 my($self, $conn, $auth, $addr) = @_;
3499 my $e = new_editor(authtoken => $auth, xact => 1);
3500 return $e->die_event unless $e->checkauth;
3502 if($addr->usr != $e->requestor->id) {
3503 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3504 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3508 $e->create_actor_user_address($addr) or return $e->die_event;
3509 } elsif($addr->isdeleted) {
3510 $e->delete_actor_user_address($addr) or return $e->die_event;
3512 $e->update_actor_user_address($addr) or return $e->die_event;
3520 __PACKAGE__->register_method (
3521 method => 'user_events',
3522 api_name => 'open-ils.actor.user.events.circ',
3525 __PACKAGE__->register_method (
3526 method => 'user_events',
3527 api_name => 'open-ils.actor.user.events.ahr',
3532 my($self, $conn, $auth, $user_id, $filters) = @_;
3533 my $e = new_editor(authtoken => $auth);
3534 return $e->event unless $e->checkauth;
3536 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3537 my $user_field = 'usr';
3540 $filters->{target} = {
3541 select => { $obj_type => ['id'] },
3543 where => {usr => $user_id}
3546 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3547 if($e->requestor->id != $user_id) {
3548 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3551 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3552 my $req = $ses->request('open-ils.trigger.events_by_target',
3553 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3555 while(my $resp = $req->recv) {
3556 my $val = $resp->content;
3557 my $tgt = $val->target;
3559 if($obj_type eq 'circ') {
3560 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3562 } elsif($obj_type eq 'ahr') {
3563 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3564 if $tgt->current_copy;
3567 $conn->respond($val) if $val;
3573 __PACKAGE__->register_method (
3574 method => 'copy_events',
3575 api_name => 'open-ils.actor.copy.events.circ',
3578 __PACKAGE__->register_method (
3579 method => 'copy_events',
3580 api_name => 'open-ils.actor.copy.events.ahr',
3585 my($self, $conn, $auth, $copy_id, $filters) = @_;
3586 my $e = new_editor(authtoken => $auth);
3587 return $e->event unless $e->checkauth;
3589 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3591 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3593 my $copy_field = 'target_copy';
3594 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3597 $filters->{target} = {
3598 select => { $obj_type => ['id'] },
3600 where => {$copy_field => $copy_id}
3604 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3605 my $req = $ses->request('open-ils.trigger.events_by_target',
3606 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3608 while(my $resp = $req->recv) {
3609 my $val = $resp->content;
3610 my $tgt = $val->target;
3612 my $user = $e->retrieve_actor_user($tgt->usr);
3613 if($e->requestor->id != $user->id) {
3614 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3617 $tgt->$copy_field($copy);
3620 $conn->respond($val) if $val;
3629 __PACKAGE__->register_method (
3630 method => 'update_events',
3631 api_name => 'open-ils.actor.user.event.cancel.batch',
3634 __PACKAGE__->register_method (
3635 method => 'update_events',
3636 api_name => 'open-ils.actor.user.event.reset.batch',
3641 my($self, $conn, $auth, $event_ids) = @_;
3642 my $e = new_editor(xact => 1, authtoken => $auth);
3643 return $e->die_event unless $e->checkauth;
3646 for my $id (@$event_ids) {
3648 # do a little dance to determine what user we are ultimately affecting
3649 my $event = $e->retrieve_action_trigger_event([
3652 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3654 ]) or return $e->die_event;
3657 if($event->event_def->hook->core_type eq 'circ') {
3658 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3659 } elsif($event->event_def->hook->core_type eq 'ahr') {
3660 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3665 my $user = $e->retrieve_actor_user($user_id);
3666 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3668 if($self->api_name =~ /cancel/) {
3669 $event->state('invalid');
3670 } elsif($self->api_name =~ /reset/) {
3671 $event->clear_start_time;
3672 $event->clear_update_time;
3673 $event->state('pending');
3676 $e->update_action_trigger_event($event) or return $e->die_event;
3677 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3681 return {complete => 1};
3685 __PACKAGE__->register_method (
3686 method => 'really_delete_user',
3687 api_name => 'open-ils.actor.user.delete.override',
3688 signature => q/@see open-ils.actor.user.delete/
3691 __PACKAGE__->register_method (
3692 method => 'really_delete_user',
3693 api_name => 'open-ils.actor.user.delete',
3695 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3696 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3697 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3698 dest_usr_id is only required when deleting a user that performs staff functions.
3702 sub really_delete_user {
3703 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3704 my $e = new_editor(authtoken => $auth, xact => 1);
3705 return $e->die_event unless $e->checkauth;
3706 $oargs = { all => 1 } unless defined $oargs;
3708 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3709 my $open_bills = $e->json_query({
3710 select => { mbts => ['id'] },
3713 xact_finish => { '=' => undef },
3714 usr => { '=' => $user_id },
3716 }) or return $e->die_event;
3718 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3720 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3722 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3723 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3724 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3726 # No deleting yourself - UI is supposed to stop you first, though.
3727 return $e->die_event unless $e->requestor->id != $user->id;
3728 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3729 # Check if you are allowed to mess with this patron permission group at all
3730 my $evt = group_perm_failed($e, $e->requestor, $user);
3731 return $e->die_event($evt) if $evt;
3732 my $stat = $e->json_query(
3733 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3734 or return $e->die_event;
3740 __PACKAGE__->register_method (
3741 method => 'user_payments',
3742 api_name => 'open-ils.actor.user.payments.retrieve',
3745 Returns all payments for a given user. Default order is newest payments first.
3746 @param auth Authentication token
3747 @param user_id The user ID
3748 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3753 my($self, $conn, $auth, $user_id, $filters) = @_;
3756 my $e = new_editor(authtoken => $auth);
3757 return $e->die_event unless $e->checkauth;
3759 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3760 return $e->event unless
3761 $e->requestor->id == $user_id or
3762 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3764 # Find all payments for all transactions for user $user_id
3766 select => {mp => ['id']},
3771 select => {mbt => ['id']},
3773 where => {usr => $user_id}
3778 { # by default, order newest payments first
3780 field => 'payment_ts',
3783 # secondary sort in ID as a tie-breaker, since payments created
3784 # within the same transaction will have identical payment_ts's
3791 for (qw/order_by limit offset/) {
3792 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3795 if(defined $filters->{where}) {
3796 foreach (keys %{$filters->{where}}) {
3797 # don't allow the caller to expand the result set to other users
3798 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3802 my $payment_ids = $e->json_query($query);
3803 for my $pid (@$payment_ids) {
3804 my $pay = $e->retrieve_money_payment([
3809 mbt => ['summary', 'circulation', 'grocery'],
3810 circ => ['target_copy'],
3811 acp => ['call_number'],
3819 xact_type => $pay->xact->summary->xact_type,
3820 last_billing_type => $pay->xact->summary->last_billing_type,
3823 if($pay->xact->summary->xact_type eq 'circulation') {
3824 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3825 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3828 $pay->xact($pay->xact->id); # de-flesh
3829 $conn->respond($resp);
3837 __PACKAGE__->register_method (
3838 method => 'negative_balance_users',
3839 api_name => 'open-ils.actor.users.negative_balance',
3842 Returns all users that have an overall negative balance
3843 @param auth Authentication token
3844 @param org_id The context org unit as an ID or list of IDs. This will be the home
3845 library of the user. If no org_unit is specified, no org unit filter is applied
3849 sub negative_balance_users {
3850 my($self, $conn, $auth, $org_id) = @_;
3852 my $e = new_editor(authtoken => $auth);
3853 return $e->die_event unless $e->checkauth;
3854 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3858 mous => ['usr', 'balance_owed'],
3861 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3862 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3879 where => {'+mous' => {balance_owed => {'<' => 0}}}
3882 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3884 my $list = $e->json_query($query, {timeout => 600});
3886 for my $data (@$list) {
3888 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3889 balance_owed => $data->{balance_owed},
3890 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3897 __PACKAGE__->register_method(
3898 method => "request_password_reset",
3899 api_name => "open-ils.actor.patron.password_reset.request",
3901 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3902 "method for changing a user's password. The UUID token is distributed via A/T " .
3903 "templates (i.e. email to the user).",
3905 { desc => 'user_id_type', type => 'string' },
3906 { desc => 'user_id', type => 'string' },
3907 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3909 return => {desc => '1 on success, Event on error'}
3912 sub request_password_reset {
3913 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3915 # Check to see if password reset requests are already being throttled:
3916 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3918 my $e = new_editor(xact => 1);
3921 # Get the user, if any, depending on the input value
3922 if ($user_id_type eq 'username') {
3923 $user = $e->search_actor_user({usrname => $user_id})->[0];
3926 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3928 } elsif ($user_id_type eq 'barcode') {
3929 my $card = $e->search_actor_card([
3930 {barcode => $user_id},
3931 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3934 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3939 # If the user doesn't have an email address, we can't help them
3940 if (!$user->email) {
3942 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3945 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3946 if ($email_must_match) {
3947 if (lc($user->email) ne lc($email)) {
3948 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3952 _reset_password_request($conn, $e, $user);
3955 # Once we have the user, we can issue the password reset request
3956 # XXX Add a wrapper method that accepts barcode + email input
3957 sub _reset_password_request {
3958 my ($conn, $e, $user) = @_;
3960 # 1. Get throttle threshold and time-to-live from OU_settings
3961 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3962 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3964 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3966 # 2. Get time of last request and number of active requests (num_active)
3967 my $active_requests = $e->json_query({
3973 transform => 'COUNT'
3976 column => 'request_time',
3982 has_been_reset => { '=' => 'f' },
3983 request_time => { '>' => $threshold_time }
3987 # Guard against no active requests
3988 if ($active_requests->[0]->{'request_time'}) {
3989 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3990 my $now = DateTime::Format::ISO8601->new();
3992 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3993 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3994 ($last_request->add_duration('1 minute') > $now)) {
3995 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3997 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4001 # TODO Check to see if the user is in a password-reset-restricted group
4003 # Otherwise, go ahead and try to get the user.
4005 # Check the number of active requests for this user
4006 $active_requests = $e->json_query({
4012 transform => 'COUNT'
4017 usr => { '=' => $user->id },
4018 has_been_reset => { '=' => 'f' },
4019 request_time => { '>' => $threshold_time }
4023 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4025 # if less than or equal to per-user threshold, proceed; otherwise, return event
4026 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4027 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4029 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4032 # Create the aupr object and insert into the database
4033 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4034 my $uuid = create_uuid_as_string(UUID_V4);
4035 $reset_request->uuid($uuid);
4036 $reset_request->usr($user->id);
4038 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4041 # Create an event to notify user of the URL to reset their password
4043 # Can we stuff this in the user_data param for trigger autocreate?
4044 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4046 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4047 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4050 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4055 __PACKAGE__->register_method(
4056 method => "commit_password_reset",
4057 api_name => "open-ils.actor.patron.password_reset.commit",
4059 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4060 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4061 "with the supplied password.",
4063 { desc => 'uuid', type => 'string' },
4064 { desc => 'password', type => 'string' },
4066 return => {desc => '1 on success, Event on error'}
4069 sub commit_password_reset {
4070 my($self, $conn, $uuid, $password) = @_;
4072 # Check to see if password reset requests are already being throttled:
4073 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4074 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4075 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4077 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4080 my $e = new_editor(xact => 1);
4082 my $aupr = $e->search_actor_usr_password_reset({
4089 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4091 my $user_id = $aupr->[0]->usr;
4092 my $user = $e->retrieve_actor_user($user_id);
4094 # Ensure we're still within the TTL for the request
4095 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4096 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4097 if ($threshold < DateTime->now(time_zone => 'local')) {
4099 $logger->info("Password reset request needed to be submitted before $threshold");
4100 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4103 # Check complexity of password against OU-defined regex
4104 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4108 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4109 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4110 $is_strong = check_password_strength_custom($password, $pw_regex);
4112 $is_strong = check_password_strength_default($password);
4117 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4120 # All is well; update the password
4121 modify_migrated_user_password($e, $user->id, $password);
4123 # And flag that this password reset request has been honoured
4124 $aupr->[0]->has_been_reset('t');
4125 $e->update_actor_usr_password_reset($aupr->[0]);
4131 sub check_password_strength_default {
4132 my $password = shift;
4133 # Use the default set of checks
4134 if ( (length($password) < 7) or
4135 ($password !~ m/.*\d+.*/) or
4136 ($password !~ m/.*[A-Za-z]+.*/)
4143 sub check_password_strength_custom {
4144 my ($password, $pw_regex) = @_;
4146 $pw_regex = qr/$pw_regex/;
4147 if ($password !~ /$pw_regex/) {
4155 __PACKAGE__->register_method(
4156 method => "event_def_opt_in_settings",
4157 api_name => "open-ils.actor.event_def.opt_in.settings",
4160 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4162 { desc => 'Authentication token', type => 'string'},
4164 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4169 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4176 sub event_def_opt_in_settings {
4177 my($self, $conn, $auth, $org_id) = @_;
4178 my $e = new_editor(authtoken => $auth);
4179 return $e->event unless $e->checkauth;
4181 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4182 return $e->event unless
4183 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4185 $org_id = $e->requestor->home_ou;
4188 # find all config.user_setting_type's related to event_defs for the requested org unit
4189 my $types = $e->json_query({
4190 select => {cust => ['name']},
4191 from => {atevdef => 'cust'},
4194 owner => $U->get_org_ancestors($org_id), # context org plus parents
4201 $conn->respond($_) for
4202 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4209 __PACKAGE__->register_method(
4210 method => "user_circ_history",
4211 api_name => "open-ils.actor.history.circ",
4215 desc => 'Returns user circ history objects for the calling user',
4217 { desc => 'Authentication token', type => 'string'},
4218 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4221 desc => q/Stream of 'auch' circ history objects/,
4227 __PACKAGE__->register_method(
4228 method => "user_circ_history",
4229 api_name => "open-ils.actor.history.circ.clear",
4232 desc => 'Delete all user circ history entries for the calling user',
4234 { desc => 'Authentication token', type => 'string'},
4235 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4238 desc => q/1 on success, event on error/,
4244 __PACKAGE__->register_method(
4245 method => "user_circ_history",
4246 api_name => "open-ils.actor.history.circ.print",
4249 desc => q/Returns printable output for the caller's circ history objects/,
4251 { desc => 'Authentication token', type => 'string'},
4252 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4255 desc => q/An action_trigger.event object or error event./,
4261 __PACKAGE__->register_method(
4262 method => "user_circ_history",
4263 api_name => "open-ils.actor.history.circ.email",
4266 desc => q/Emails the caller's circ history/,
4268 { desc => 'Authentication token', type => 'string'},
4269 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4270 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4273 desc => q/undef, or event on error/
4278 sub user_circ_history {
4279 my ($self, $conn, $auth, $options) = @_;
4282 my $for_print = ($self->api_name =~ /print/);
4283 my $for_email = ($self->api_name =~ /email/);
4284 my $for_clear = ($self->api_name =~ /clear/);
4286 # No perm check is performed. Caller may only access his/her own
4287 # circ history entries.
4288 my $e = new_editor(authtoken => $auth);
4289 return $e->event unless $e->checkauth;
4292 if (!$for_clear) { # clear deletes all
4293 $limits{offset} = $options->{offset} if defined $options->{offset};
4294 $limits{limit} = $options->{limit} if defined $options->{limit};
4297 my %circ_id_filter = $options->{circ_ids} ?
4298 (id => $options->{circ_ids}) : ();
4300 my $circs = $e->search_action_user_circ_history([
4301 { usr => $e->requestor->id,
4304 { # order newest to oldest by default
4305 order_by => {auch => 'xact_start DESC'},
4308 {substream => 1} # could be a large list
4312 return $U->fire_object_event(undef,
4313 'circ.format.history.print', $circs, $e->requestor->home_ou);
4316 $e->xact_begin if $for_clear;
4317 $conn->respond_complete(1) if $for_email; # no sense in waiting
4319 for my $circ (@$circs) {
4322 # events will be fired from action_trigger_runner
4323 $U->create_events_for_hook('circ.format.history.email',
4324 $circ, $e->editor->home_ou, undef, undef, 1);
4326 } elsif ($for_clear) {
4328 $e->delete_action_user_circ_history($circ)
4329 or return $e->die_event;
4332 $conn->respond($circ);
4345 __PACKAGE__->register_method(
4346 method => "user_visible_holds",
4347 api_name => "open-ils.actor.history.hold.visible",
4350 desc => 'Returns the set of opt-in visible holds',
4352 { desc => 'Authentication token', type => 'string'},
4353 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4354 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4357 desc => q/An object with 1 field: "hold"/,
4363 __PACKAGE__->register_method(
4364 method => "user_visible_holds",
4365 api_name => "open-ils.actor.history.hold.visible.print",
4368 desc => 'Returns printable output for the set of opt-in visible holds',
4370 { desc => 'Authentication token', type => 'string'},
4371 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4372 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4375 desc => q/An action_trigger.event object or error event./,
4381 __PACKAGE__->register_method(
4382 method => "user_visible_holds",
4383 api_name => "open-ils.actor.history.hold.visible.email",
4386 desc => 'Emails the set of opt-in visible holds to the requestor',
4388 { desc => 'Authentication token', type => 'string'},
4389 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4390 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4393 desc => q/undef, or event on error/
4398 sub user_visible_holds {
4399 my($self, $conn, $auth, $user_id, $options) = @_;
4402 my $for_print = ($self->api_name =~ /print/);
4403 my $for_email = ($self->api_name =~ /email/);
4404 my $e = new_editor(authtoken => $auth);
4405 return $e->event unless $e->checkauth;
4407 $user_id ||= $e->requestor->id;
4409 $options->{limit} ||= 50;
4410 $options->{offset} ||= 0;
4412 if($user_id != $e->requestor->id) {
4413 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4414 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4415 return $e->event unless $e->allowed($perm, $user->home_ou);
4418 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4420 my $data = $e->json_query({
4421 from => [$db_func, $user_id],
4422 limit => $$options{limit},
4423 offset => $$options{offset}
4425 # TODO: I only want IDs. code below didn't get me there
4426 # {"select":{"au":[{"column":"id", "result_field":"id",
4427 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4432 return undef unless @$data;
4436 # collect the batch of objects
4440 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4441 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4445 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4446 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4449 } elsif ($for_email) {
4451 $conn->respond_complete(1) if $for_email; # no sense in waiting
4459 my $hold = $e->retrieve_action_hold_request($id);
4460 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4461 # events will be fired from action_trigger_runner
4465 my $circ = $e->retrieve_action_circulation($id);
4466 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4467 # events will be fired from action_trigger_runner
4471 } else { # just give me the data please
4479 my $hold = $e->retrieve_action_hold_request($id);
4480 $conn->respond({hold => $hold});
4484 my $circ = $e->retrieve_action_circulation($id);
4487 summary => $U->create_circ_chain_summary($e, $id)
4496 __PACKAGE__->register_method(
4497 method => "user_saved_search_cud",
4498 api_name => "open-ils.actor.user.saved_search.cud",
4501 desc => 'Create/Update/Delete Access to user saved searches',
4503 { desc => 'Authentication token', type => 'string' },
4504 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4507 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4513 __PACKAGE__->register_method(
4514 method => "user_saved_search_cud",
4515 api_name => "open-ils.actor.user.saved_search.retrieve",
4518 desc => 'Retrieve a saved search object',
4520 { desc => 'Authentication token', type => 'string' },
4521 { desc => 'Saved Search ID', type => 'number' }
4524 desc => q/The saved search object, Event on error/,
4530 sub user_saved_search_cud {
4531 my( $self, $client, $auth, $search ) = @_;
4532 my $e = new_editor( authtoken=>$auth );
4533 return $e->die_event unless $e->checkauth;
4535 my $o_search; # prior version of the object, if any
4536 my $res; # to be returned
4538 # branch on the operation type
4540 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4542 # Get the old version, to check ownership
4543 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4544 or return $e->die_event;
4546 # You can't read somebody else's search
4547 return OpenILS::Event->new('BAD_PARAMS')
4548 unless $o_search->owner == $e->requestor->id;
4554 $e->xact_begin; # start an editor transaction
4556 if( $search->isnew ) { # Create
4558 # You can't create a search for somebody else
4559 return OpenILS::Event->new('BAD_PARAMS')
4560 unless $search->owner == $e->requestor->id;
4562 $e->create_actor_usr_saved_search( $search )
4563 or return $e->die_event;
4567 } elsif( $search->ischanged ) { # Update
4569 # You can't change ownership of a search
4570 return OpenILS::Event->new('BAD_PARAMS')
4571 unless $search->owner == $e->requestor->id;
4573 # Get the old version, to check ownership
4574 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4575 or return $e->die_event;
4577 # You can't update somebody else's search
4578 return OpenILS::Event->new('BAD_PARAMS')
4579 unless $o_search->owner == $e->requestor->id;
4582 $e->update_actor_usr_saved_search( $search )
4583 or return $e->die_event;
4587 } elsif( $search->isdeleted ) { # Delete
4589 # Get the old version, to check ownership
4590 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4591 or return $e->die_event;
4593 # You can't delete somebody else's search
4594 return OpenILS::Event->new('BAD_PARAMS')
4595 unless $o_search->owner == $e->requestor->id;
4598 $e->delete_actor_usr_saved_search( $o_search )
4599 or return $e->die_event;
4610 __PACKAGE__->register_method(
4611 method => "get_barcodes",
4612 api_name => "open-ils.actor.get_barcodes"
4616 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4617 my $e = new_editor(authtoken => $auth);
4618 return $e->event unless $e->checkauth;
4619 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4621 my $db_result = $e->json_query(
4623 'evergreen.get_barcodes',
4624 $org_id, $context, $barcode,
4628 if($context =~ /actor/) {
4629 my $filter_result = ();
4631 foreach my $result (@$db_result) {
4632 if($result->{type} eq 'actor') {
4633 if($e->requestor->id != $result->{id}) {
4634 $patron = $e->retrieve_actor_user($result->{id});
4636 push(@$filter_result, $e->event);
4639 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4640 push(@$filter_result, $result);
4643 push(@$filter_result, $e->event);
4647 push(@$filter_result, $result);
4651 push(@$filter_result, $result);
4654 return $filter_result;
4660 __PACKAGE__->register_method(
4661 method => 'address_alert_test',
4662 api_name => 'open-ils.actor.address_alert.test',
4664 desc => "Tests a set of address fields to determine if they match with an address_alert",
4666 {desc => 'Authentication token', type => 'string'},
4667 {desc => 'Org Unit', type => 'number'},
4668 {desc => 'Fields', type => 'hash'},
4670 return => {desc => 'List of matching address_alerts'}
4674 sub address_alert_test {
4675 my ($self, $client, $auth, $org_unit, $fields) = @_;
4676 return [] unless $fields and grep {$_} values %$fields;
4678 my $e = new_editor(authtoken => $auth);
4679 return $e->event unless $e->checkauth;
4680 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4681 $org_unit ||= $e->requestor->ws_ou;
4683 my $alerts = $e->json_query({
4685 'actor.address_alert_matches',
4693 $$fields{post_code},
4694 $$fields{mailing_address},
4695 $$fields{billing_address}
4699 # map the json_query hashes to real objects
4701 map {$e->retrieve_actor_address_alert($_)}
4702 (map {$_->{id}} @$alerts)
4706 __PACKAGE__->register_method(
4707 method => "mark_users_contact_invalid",
4708 api_name => "open-ils.actor.invalidate.email",
4710 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",
4712 {desc => "Authentication token", type => "string"},
4713 {desc => "Patron ID", type => "number"},
4714 {desc => "Additional note text (optional)", type => "string"},
4715 {desc => "penalty org unit ID (optional)", type => "number"}
4717 return => {desc => "Event describing success or failure", type => "object"}
4721 __PACKAGE__->register_method(
4722 method => "mark_users_contact_invalid",
4723 api_name => "open-ils.actor.invalidate.day_phone",
4725 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",
4727 {desc => "Authentication token", type => "string"},
4728 {desc => "Patron ID", type => "number"},
4729 {desc => "Additional note text (optional)", type => "string"},
4730 {desc => "penalty org unit ID (optional)", type => "number"}
4732 return => {desc => "Event describing success or failure", type => "object"}
4736 __PACKAGE__->register_method(
4737 method => "mark_users_contact_invalid",
4738 api_name => "open-ils.actor.invalidate.evening_phone",
4740 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",
4742 {desc => "Authentication token", type => "string"},
4743 {desc => "Patron ID", type => "number"},
4744 {desc => "Additional note text (optional)", type => "string"},
4745 {desc => "penalty org unit ID (optional)", type => "number"}
4747 return => {desc => "Event describing success or failure", type => "object"}
4751 __PACKAGE__->register_method(
4752 method => "mark_users_contact_invalid",
4753 api_name => "open-ils.actor.invalidate.other_phone",
4755 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",
4757 {desc => "Authentication token", type => "string"},
4758 {desc => "Patron ID", type => "number"},
4759 {desc => "Additional note text (optional)", type => "string"},
4760 {desc => "penalty org unit ID (optional, default to top of org tree)",
4763 return => {desc => "Event describing success or failure", type => "object"}
4767 sub mark_users_contact_invalid {
4768 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4770 # This method invalidates an email address or a phone_number which
4771 # removes the bad email address or phone number, copying its contents
4772 # to a patron note, and institutes a standing penalty for "bad email"
4773 # or "bad phone number" which is cleared when the user is saved or
4774 # optionally only when the user is saved with an email address or
4775 # phone number (or staff manually delete the penalty).
4777 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4779 my $e = new_editor(authtoken => $auth, xact => 1);
4780 return $e->die_event unless $e->checkauth;
4782 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4783 $e, $contact_type, {usr => $patron_id},
4784 $addl_note, $penalty_ou, $e->requestor->id
4788 # Putting the following method in open-ils.actor is a bad fit, except in that
4789 # it serves an interface that lives under 'actor' in the templates directory,
4790 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4792 __PACKAGE__->register_method(
4793 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4794 method => "get_all_at_reactors_in_use",
4799 { name => 'authtoken', type => 'string' }
4802 desc => 'list of reactor names', type => 'array'
4807 sub get_all_at_reactors_in_use {
4808 my ($self, $conn, $auth) = @_;
4810 my $e = new_editor(authtoken => $auth);
4811 $e->checkauth or return $e->die_event;
4812 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4814 my $reactors = $e->json_query({
4816 atevdef => [{column => "reactor", transform => "distinct"}]
4818 from => {atevdef => {}}
4821 return $e->die_event unless ref $reactors eq "ARRAY";
4824 return [ map { $_->{reactor} } @$reactors ];
4827 __PACKAGE__->register_method(
4828 method => "filter_group_entry_crud",
4829 api_name => "open-ils.actor.filter_group_entry.crud",
4832 Provides CRUD access to filter group entry objects. These are not full accessible
4833 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4834 are not accessible via PCRUD (because they have no fields against which to link perms)
4837 {desc => "Authentication token", type => "string"},
4838 {desc => "Entry ID / Entry Object", type => "number"},
4839 {desc => "Additional note text (optional)", type => "string"},
4840 {desc => "penalty org unit ID (optional, default to top of org tree)",
4844 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4850 sub filter_group_entry_crud {
4851 my ($self, $conn, $auth, $arg) = @_;
4853 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4854 my $e = new_editor(authtoken => $auth, xact => 1);
4855 return $e->die_event unless $e->checkauth;
4861 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4862 or return $e->die_event;
4864 return $e->die_event unless $e->allowed(
4865 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4867 my $query = $arg->query;
4868 $query = $e->create_actor_search_query($query) or return $e->die_event;
4869 $arg->query($query->id);
4870 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4871 $entry->query($query);
4876 } elsif ($arg->ischanged) {
4878 my $entry = $e->retrieve_actor_search_filter_group_entry([
4881 flesh_fields => {asfge => ['grp']}
4883 ]) or return $e->die_event;
4885 return $e->die_event unless $e->allowed(
4886 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4888 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4889 $arg->query($arg->query->id);
4890 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4891 $arg->query($query);
4896 } elsif ($arg->isdeleted) {
4898 my $entry = $e->retrieve_actor_search_filter_group_entry([
4901 flesh_fields => {asfge => ['grp', 'query']}
4903 ]) or return $e->die_event;
4905 return $e->die_event unless $e->allowed(
4906 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4908 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4909 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4922 my $entry = $e->retrieve_actor_search_filter_group_entry([
4925 flesh_fields => {asfge => ['grp', 'query']}
4927 ]) or return $e->die_event;
4929 return $e->die_event unless $e->allowed(
4930 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4931 $entry->grp->owner);
4934 $entry->grp($entry->grp->id); # for consistency