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 my %must_check_perm = ();
318 unless (defined $auth) {
319 # figure out which OU settings *require* view permission
321 my $e = new_editor();
322 my $res = $e->search_config_org_unit_setting_type({
324 view_perm => { "!=" => undef },
326 $must_check_perm{ $_->name() } = -1 for @$res;
329 $values{$_} = $U->ou_ancestor_setting(
331 ($auth ? $auth : $must_check_perm{$_})
338 __PACKAGE__->register_method(
339 method => "update_patron",
340 api_name => "open-ils.actor.patron.update",
343 Update an existing user, or create a new one. Related objects,
344 like cards, addresses, survey responses, and stat cats,
345 can be updated by attaching them to the user object in their
346 respective fields. For examples, the billing address object
347 may be inserted into the 'billing_address' field, etc. For each
348 attached object, indicate if the object should be created,
349 updated, or deleted using the built-in 'isnew', 'ischanged',
350 and 'isdeleted' fields on the object.
353 { desc => 'Authentication token', type => 'string' },
354 { desc => 'Patron data object', type => 'object' }
356 return => {desc => 'A fleshed user object, event on error'}
361 my( $self, $client, $user_session, $patron ) = @_;
363 my $session = $apputils->start_db_session();
365 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
367 my( $user_obj, $evt ) = $U->checkses($user_session);
370 $evt = check_group_perm($session, $user_obj, $patron);
373 $apputils->set_audit_info($session, $user_session, $user_obj->id, $user_obj->wsid);
375 # $new_patron is the patron in progress. $patron is the original patron
376 # passed in with the method. new_patron will change as the components
377 # of patron are added/updated.
381 # unflesh the real items on the patron
382 $patron->card( $patron->card->id ) if(ref($patron->card));
383 $patron->billing_address( $patron->billing_address->id )
384 if(ref($patron->billing_address));
385 $patron->mailing_address( $patron->mailing_address->id )
386 if(ref($patron->mailing_address));
388 # create/update the patron first so we can use his id
390 # $patron is the obj from the client (new data) and $new_patron is the
391 # patron object properly built for db insertion, so we need a third variable
392 # if we want to represent the old patron.
395 my $barred_hook = '';
397 if($patron->isnew()) {
398 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
400 if($U->is_true($patron->barred)) {
401 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
405 $new_patron = $patron;
407 # Did auth checking above already.
409 $old_patron = $e->retrieve_actor_user($patron->id) or
410 return $e->die_event;
412 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
413 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
416 $barred_hook = $U->is_true($new_patron->barred) ?
417 'au.barred' : 'au.unbarred';
421 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
424 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
427 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
430 # re-update the patron if anything has happened to him during this process
431 if($new_patron->ischanged()) {
432 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
436 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
439 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
442 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
445 $apputils->commit_db_session($session);
447 $evt = apply_invalid_addr_penalty($patron);
450 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
452 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
454 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
456 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
457 $new_patron, $new_patron->home_ou) if $barred_hook;
460 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
463 sub apply_invalid_addr_penalty {
465 my $e = new_editor(xact => 1);
467 # grab the invalid address penalty if set
468 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
470 my ($addr_penalty) = grep
471 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
473 # do we enforce invalid address penalty
474 my $enforce = $U->ou_ancestor_setting_value(
475 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
477 my $addrs = $e->search_actor_user_address(
478 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
479 my $addr_count = scalar(@$addrs);
481 if($addr_count == 0 and $addr_penalty) {
483 # regardless of any settings, remove the penalty when the user has no invalid addresses
484 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
487 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
489 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
490 my $depth = $ptype->org_depth;
491 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
492 $ctx_org = $patron->home_ou unless defined $ctx_org;
494 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
495 $penalty->usr($patron->id);
496 $penalty->org_unit($ctx_org);
497 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
499 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
518 "standing_penalties",
526 push @$fields, "home_ou" if $home_ou;
527 return new_flesh_user($id, $fields, $e );
535 # clone and clear stuff that would break the database
539 my $new_patron = $patron->clone;
541 $new_patron->clear_billing_address();
542 $new_patron->clear_mailing_address();
543 $new_patron->clear_addresses();
544 $new_patron->clear_card();
545 $new_patron->clear_cards();
546 $new_patron->clear_id();
547 $new_patron->clear_isnew();
548 $new_patron->clear_ischanged();
549 $new_patron->clear_isdeleted();
550 $new_patron->clear_stat_cat_entries();
551 $new_patron->clear_permissions();
552 $new_patron->clear_standing_penalties();
562 my $user_obj = shift;
564 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
565 return (undef, $evt) if $evt;
567 my $ex = $session->request(
568 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
570 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
573 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
575 my $id = $session->request(
576 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
577 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
579 $logger->info("Successfully created new user [$id] in DB");
581 return ( $session->request(
582 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
586 sub check_group_perm {
587 my( $session, $requestor, $patron ) = @_;
590 # first let's see if the requestor has
591 # priveleges to update this user in any way
592 if( ! $patron->isnew ) {
593 my $p = $session->request(
594 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
596 # If we are the requestor (trying to update our own account)
597 # and we are not trying to change our profile, we're good
598 if( $p->id == $requestor->id and
599 $p->profile == $patron->profile ) {
604 $evt = group_perm_failed($session, $requestor, $p);
608 # They are allowed to edit this patron.. can they put the
609 # patron into the group requested?
610 $evt = group_perm_failed($session, $requestor, $patron);
616 sub group_perm_failed {
617 my( $session, $requestor, $patron ) = @_;
621 my $grpid = $patron->profile;
625 $logger->debug("user update looking for group perm for group $grpid");
626 $grp = $session->request(
627 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
628 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
630 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
632 $logger->info("user update checking perm $perm on user ".
633 $requestor->id." for update/create on user username=".$patron->usrname);
635 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
643 my( $session, $patron, $user_obj, $noperm) = @_;
645 $logger->info("Updating patron ".$patron->id." in DB");
650 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
651 return (undef, $evt) if $evt;
654 # update the password by itself to avoid the password protection magic
655 if( $patron->passwd ) {
656 my $s = $session->request(
657 'open-ils.storage.direct.actor.user.remote_update',
658 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
659 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
660 $patron->clear_passwd;
663 if(!$patron->ident_type) {
664 $patron->clear_ident_type;
665 $patron->clear_ident_value;
668 $evt = verify_last_xact($session, $patron);
669 return (undef, $evt) if $evt;
671 my $stat = $session->request(
672 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
673 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
678 sub verify_last_xact {
679 my( $session, $patron ) = @_;
680 return undef unless $patron->id and $patron->id > 0;
681 my $p = $session->request(
682 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
683 my $xact = $p->last_xact_id;
684 return undef unless $xact;
685 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
686 return OpenILS::Event->new('XACT_COLLISION')
687 if $xact ne $patron->last_xact_id;
692 sub _check_dup_ident {
693 my( $session, $patron ) = @_;
695 return undef unless $patron->ident_value;
698 ident_type => $patron->ident_type,
699 ident_value => $patron->ident_value,
702 $logger->debug("patron update searching for dup ident values: " .
703 $patron->ident_type . ':' . $patron->ident_value);
705 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
707 my $dups = $session->request(
708 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
711 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
718 sub _add_update_addresses {
722 my $new_patron = shift;
726 my $current_id; # id of the address before creation
728 my $addresses = $patron->addresses();
730 for my $address (@$addresses) {
732 next unless ref $address;
733 $current_id = $address->id();
735 if( $patron->billing_address() and
736 $patron->billing_address() == $current_id ) {
737 $logger->info("setting billing addr to $current_id");
738 $new_patron->billing_address($address->id());
739 $new_patron->ischanged(1);
742 if( $patron->mailing_address() and
743 $patron->mailing_address() == $current_id ) {
744 $new_patron->mailing_address($address->id());
745 $logger->info("setting mailing addr to $current_id");
746 $new_patron->ischanged(1);
750 if($address->isnew()) {
752 $address->usr($new_patron->id());
754 ($address, $evt) = _add_address($session,$address);
755 return (undef, $evt) if $evt;
757 # we need to get the new id
758 if( $patron->billing_address() and
759 $patron->billing_address() == $current_id ) {
760 $new_patron->billing_address($address->id());
761 $logger->info("setting billing addr to $current_id");
762 $new_patron->ischanged(1);
765 if( $patron->mailing_address() and
766 $patron->mailing_address() == $current_id ) {
767 $new_patron->mailing_address($address->id());
768 $logger->info("setting mailing addr to $current_id");
769 $new_patron->ischanged(1);
772 } elsif($address->ischanged() ) {
774 ($address, $evt) = _update_address($session, $address);
775 return (undef, $evt) if $evt;
777 } elsif($address->isdeleted() ) {
779 if( $address->id() == $new_patron->mailing_address() ) {
780 $new_patron->clear_mailing_address();
781 ($new_patron, $evt) = _update_patron($session, $new_patron);
782 return (undef, $evt) if $evt;
785 if( $address->id() == $new_patron->billing_address() ) {
786 $new_patron->clear_billing_address();
787 ($new_patron, $evt) = _update_patron($session, $new_patron);
788 return (undef, $evt) if $evt;
791 $evt = _delete_address($session, $address);
792 return (undef, $evt) if $evt;
796 return ( $new_patron, undef );
800 # adds an address to the db and returns the address with new id
802 my($session, $address) = @_;
803 $address->clear_id();
805 $logger->info("Creating new address at street ".$address->street1);
807 # put the address into the database
808 my $id = $session->request(
809 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
810 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
813 return ($address, undef);
817 sub _update_address {
818 my( $session, $address ) = @_;
820 $logger->info("Updating address ".$address->id." in the DB");
822 my $stat = $session->request(
823 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
825 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
826 return ($address, undef);
831 sub _add_update_cards {
835 my $new_patron = shift;
839 my $virtual_id; #id of the card before creation
841 my $cards = $patron->cards();
842 for my $card (@$cards) {
844 $card->usr($new_patron->id());
846 if(ref($card) and $card->isnew()) {
848 $virtual_id = $card->id();
849 ( $card, $evt ) = _add_card($session,$card);
850 return (undef, $evt) if $evt;
852 #if(ref($patron->card)) { $patron->card($patron->card->id); }
853 if($patron->card() == $virtual_id) {
854 $new_patron->card($card->id());
855 $new_patron->ischanged(1);
858 } elsif( ref($card) and $card->ischanged() ) {
859 $evt = _update_card($session, $card);
860 return (undef, $evt) if $evt;
864 return ( $new_patron, undef );
868 # adds an card to the db and returns the card with new id
870 my( $session, $card ) = @_;
873 $logger->info("Adding new patron card ".$card->barcode);
875 my $id = $session->request(
876 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
877 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
878 $logger->info("Successfully created patron card $id");
881 return ( $card, undef );
885 # returns event on error. returns undef otherwise
887 my( $session, $card ) = @_;
888 $logger->info("Updating patron card ".$card->id);
890 my $stat = $session->request(
891 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
892 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
899 # returns event on error. returns undef otherwise
900 sub _delete_address {
901 my( $session, $address ) = @_;
903 $logger->info("Deleting address ".$address->id." from DB");
905 my $stat = $session->request(
906 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
908 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
914 sub _add_survey_responses {
915 my ($session, $patron, $new_patron) = @_;
917 $logger->info( "Updating survey responses for patron ".$new_patron->id );
919 my $responses = $patron->survey_responses;
923 $_->usr($new_patron->id) for (@$responses);
925 my $evt = $U->simplereq( "open-ils.circ",
926 "open-ils.circ.survey.submit.user_id", $responses );
928 return (undef, $evt) if defined($U->event_code($evt));
932 return ( $new_patron, undef );
935 sub _clear_badcontact_penalties {
936 my ($session, $old_patron, $new_patron, $user_obj) = @_;
938 return ($new_patron, undef) unless $old_patron;
940 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
941 my $e = new_editor(xact => 1);
943 # This ignores whether the caller of update_patron has any permission
944 # to remove penalties, but these penalties no longer make sense
945 # if an email address field (for example) is changed (and the caller must
946 # have perms to do *that*) so there's no reason not to clear the penalties.
948 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
950 "+csp" => {"name" => [values(%$PNM)]},
951 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
953 "join" => {"csp" => {}},
955 "flesh_fields" => {"ausp" => ["standing_penalty"]}
957 ]) or return (undef, $e->die_event);
959 return ($new_patron, undef) unless @$bad_contact_penalties;
961 my @penalties_to_clear;
962 my ($field, $penalty_name);
964 # For each field that might have an associated bad contact penalty,
965 # check for such penalties and add them to the to-clear list if that
967 while (($field, $penalty_name) = each(%$PNM)) {
968 if ($old_patron->$field ne $new_patron->$field) {
969 push @penalties_to_clear, grep {
970 $_->standing_penalty->name eq $penalty_name
971 } @$bad_contact_penalties;
975 foreach (@penalties_to_clear) {
976 # Note that this "archives" penalties, in the terminology of the staff
977 # client, instead of just deleting them. This may assist reporting,
978 # or preserving old contact information when it is still potentially
980 $_->standing_penalty($_->standing_penalty->id); # deflesh
981 $_->stop_date('now');
982 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
986 return ($new_patron, undef);
990 sub _create_stat_maps {
992 my($session, $user_session, $patron, $new_patron) = @_;
994 my $maps = $patron->stat_cat_entries();
996 for my $map (@$maps) {
998 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
1000 if ($map->isdeleted()) {
1001 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
1003 } elsif ($map->isnew()) {
1004 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
1009 $map->target_usr($new_patron->id);
1012 $logger->info("Updating stat entry with method $method and map $map");
1014 my $stat = $session->request($method, $map)->gather(1);
1015 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1019 return ($new_patron, undef);
1022 sub _create_perm_maps {
1024 my($session, $user_session, $patron, $new_patron) = @_;
1026 my $maps = $patron->permissions;
1028 for my $map (@$maps) {
1030 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1031 if ($map->isdeleted()) {
1032 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1033 } elsif ($map->isnew()) {
1034 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1039 $map->usr($new_patron->id);
1041 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1042 $logger->info( "Updating permissions with method $method and map $map" );
1044 my $stat = $session->request($method, $map)->gather(1);
1045 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1049 return ($new_patron, undef);
1053 __PACKAGE__->register_method(
1054 method => "set_user_work_ous",
1055 api_name => "open-ils.actor.user.work_ous.update",
1058 sub set_user_work_ous {
1064 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1065 return $evt if $evt;
1067 my $session = $apputils->start_db_session();
1068 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1070 for my $map (@$maps) {
1072 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1073 if ($map->isdeleted()) {
1074 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1075 } elsif ($map->isnew()) {
1076 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1080 #warn( "Updating permissions with method $method and session $ses and map $map" );
1081 $logger->info( "Updating work_ou map with method $method and map $map" );
1083 my $stat = $session->request($method, $map)->gather(1);
1084 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1088 $apputils->commit_db_session($session);
1090 return scalar(@$maps);
1094 __PACKAGE__->register_method(
1095 method => "set_user_perms",
1096 api_name => "open-ils.actor.user.permissions.update",
1099 sub set_user_perms {
1105 my $session = $apputils->start_db_session();
1107 my( $user_obj, $evt ) = $U->checkses($ses);
1108 return $evt if $evt;
1109 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1111 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1114 $all = 1 if ($U->is_true($user_obj->super_user()));
1115 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1117 for my $map (@$maps) {
1119 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1120 if ($map->isdeleted()) {
1121 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1122 } elsif ($map->isnew()) {
1123 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1127 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1128 #warn( "Updating permissions with method $method and session $ses and map $map" );
1129 $logger->info( "Updating permissions with method $method and map $map" );
1131 my $stat = $session->request($method, $map)->gather(1);
1132 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1136 $apputils->commit_db_session($session);
1138 return scalar(@$maps);
1142 __PACKAGE__->register_method(
1143 method => "user_retrieve_by_barcode",
1145 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1147 sub user_retrieve_by_barcode {
1148 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1150 my $e = new_editor(authtoken => $auth);
1151 return $e->event unless $e->checkauth;
1153 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1154 or return $e->event;
1156 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1157 return $e->event unless $e->allowed(
1158 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1165 __PACKAGE__->register_method(
1166 method => "get_user_by_id",
1168 api_name => "open-ils.actor.user.retrieve",
1171 sub get_user_by_id {
1172 my ($self, $client, $auth, $id) = @_;
1173 my $e = new_editor(authtoken=>$auth);
1174 return $e->event unless $e->checkauth;
1175 my $user = $e->retrieve_actor_user($id) or return $e->event;
1176 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1181 __PACKAGE__->register_method(
1182 method => "get_org_types",
1183 api_name => "open-ils.actor.org_types.retrieve",
1186 return $U->get_org_types();
1190 __PACKAGE__->register_method(
1191 method => "get_user_ident_types",
1192 api_name => "open-ils.actor.user.ident_types.retrieve",
1195 sub get_user_ident_types {
1196 return $ident_types if $ident_types;
1197 return $ident_types =
1198 new_editor()->retrieve_all_config_identification_type();
1202 __PACKAGE__->register_method(
1203 method => "get_org_unit",
1204 api_name => "open-ils.actor.org_unit.retrieve",
1208 my( $self, $client, $user_session, $org_id ) = @_;
1209 my $e = new_editor(authtoken => $user_session);
1211 return $e->event unless $e->checkauth;
1212 $org_id = $e->requestor->ws_ou;
1214 my $o = $e->retrieve_actor_org_unit($org_id)
1215 or return $e->event;
1219 __PACKAGE__->register_method(
1220 method => "search_org_unit",
1221 api_name => "open-ils.actor.org_unit_list.search",
1224 sub search_org_unit {
1226 my( $self, $client, $field, $value ) = @_;
1228 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1230 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1231 { $field => $value } );
1237 # build the org tree
1239 __PACKAGE__->register_method(
1240 method => "get_org_tree",
1241 api_name => "open-ils.actor.org_tree.retrieve",
1243 note => "Returns the entire org tree structure",
1249 return $U->get_org_tree($client->session->session_locale);
1253 __PACKAGE__->register_method(
1254 method => "get_org_descendants",
1255 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1258 # depth is optional. org_unit is the id
1259 sub get_org_descendants {
1260 my( $self, $client, $org_unit, $depth ) = @_;
1262 if(ref $org_unit eq 'ARRAY') {
1265 for my $i (0..scalar(@$org_unit)-1) {
1266 my $list = $U->simple_scalar_request(
1268 "open-ils.storage.actor.org_unit.descendants.atomic",
1269 $org_unit->[$i], $depth->[$i] );
1270 push(@trees, $U->build_org_tree($list));
1275 my $orglist = $apputils->simple_scalar_request(
1277 "open-ils.storage.actor.org_unit.descendants.atomic",
1278 $org_unit, $depth );
1279 return $U->build_org_tree($orglist);
1284 __PACKAGE__->register_method(
1285 method => "get_org_ancestors",
1286 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1289 # depth is optional. org_unit is the id
1290 sub get_org_ancestors {
1291 my( $self, $client, $org_unit, $depth ) = @_;
1292 my $orglist = $apputils->simple_scalar_request(
1294 "open-ils.storage.actor.org_unit.ancestors.atomic",
1295 $org_unit, $depth );
1296 return $U->build_org_tree($orglist);
1300 __PACKAGE__->register_method(
1301 method => "get_standings",
1302 api_name => "open-ils.actor.standings.retrieve"
1307 return $user_standings if $user_standings;
1308 return $user_standings =
1309 $apputils->simple_scalar_request(
1311 "open-ils.cstore.direct.config.standing.search.atomic",
1312 { id => { "!=" => undef } }
1317 __PACKAGE__->register_method(
1318 method => "get_my_org_path",
1319 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1322 sub get_my_org_path {
1323 my( $self, $client, $auth, $org_id ) = @_;
1324 my $e = new_editor(authtoken=>$auth);
1325 return $e->event unless $e->checkauth;
1326 $org_id = $e->requestor->ws_ou unless defined $org_id;
1328 return $apputils->simple_scalar_request(
1330 "open-ils.storage.actor.org_unit.full_path.atomic",
1335 __PACKAGE__->register_method(
1336 method => "patron_adv_search",
1337 api_name => "open-ils.actor.patron.search.advanced"
1340 __PACKAGE__->register_method(
1341 method => "patron_adv_search",
1342 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1344 # TODO: change when opensrf 'bundling' is merged.
1345 # set a relatively small bundle size so the caller can start
1346 # seeing results fairly quickly
1347 max_chunk_size => 4096, # bundling
1350 # pending opensrf work -- also, not sure if needed since we're not
1351 # actaully creating an alternate vesrion, only offering to return a
1355 desc => q/Returns a stream of fleshed user objects instead of
1356 a pile of identifiers/
1360 sub patron_adv_search {
1361 my( $self, $client, $auth, $search_hash, $search_limit,
1362 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1364 # API params sanity checks.
1365 # Exit early with empty result if no filter exists.
1366 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1367 my $fleshed = ($self->api_name =~ /fleshed/);
1368 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1370 for my $key (keys %$search_hash) {
1371 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1375 return ($fleshed ? undef : []) unless $search_ok;
1377 my $e = new_editor(authtoken=>$auth);
1378 return $e->event unless $e->checkauth;
1379 return $e->event unless $e->allowed('VIEW_USER');
1381 # depth boundary outside of which patrons must opt-in, default to 0
1382 my $opt_boundary = 0;
1383 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1385 if (not defined $search_ou) {
1386 my $depth = $U->ou_ancestor_setting_value(
1387 $e->requestor->ws_ou,
1388 'circ.patron_edit.duplicate_patron_check_depth'
1391 if (defined $depth) {
1392 $search_ou = $U->org_unit_ancestor_at_depth(
1393 $e->requestor->ws_ou, $depth
1398 my $ids = $U->storagereq(
1399 "open-ils.storage.actor.user.crazy_search", $search_hash,
1400 $search_limit, $search_sort, $include_inactive,
1401 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1403 return $ids unless $self->api_name =~ /fleshed/;
1405 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1411 __PACKAGE__->register_method(
1412 method => "update_passwd",
1413 api_name => "open-ils.actor.user.password.update",
1415 desc => "Update the operator's password",
1417 { desc => 'Authentication token', type => 'string' },
1418 { desc => 'New password', type => 'string' },
1419 { desc => 'Current password', type => 'string' }
1421 return => {desc => '1 on success, Event on error or incorrect current password'}
1425 __PACKAGE__->register_method(
1426 method => "update_passwd",
1427 api_name => "open-ils.actor.user.username.update",
1429 desc => "Update the operator's username",
1431 { desc => 'Authentication token', type => 'string' },
1432 { desc => 'New username', type => 'string' },
1433 { desc => 'Current password', type => 'string' }
1435 return => {desc => '1 on success, Event on error or incorrect current password'}
1439 __PACKAGE__->register_method(
1440 method => "update_passwd",
1441 api_name => "open-ils.actor.user.email.update",
1443 desc => "Update the operator's email address",
1445 { desc => 'Authentication token', type => 'string' },
1446 { desc => 'New email address', type => 'string' },
1447 { desc => 'Current password', type => 'string' }
1449 return => {desc => '1 on success, Event on error or incorrect current password'}
1454 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1455 my $e = new_editor(xact=>1, authtoken=>$auth);
1456 return $e->die_event unless $e->checkauth;
1458 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1459 or return $e->die_event;
1460 my $api = $self->api_name;
1462 # make sure the original password matches the in-database password
1463 if (md5_hex($orig_pw) ne $db_user->passwd) {
1465 return new OpenILS::Event('INCORRECT_PASSWORD');
1468 if( $api =~ /password/o ) {
1470 $db_user->passwd($new_val);
1474 # if we don't clear the password, the user will be updated with
1475 # a hashed version of the hashed version of their password
1476 $db_user->clear_passwd;
1478 if( $api =~ /username/o ) {
1480 # make sure no one else has this username
1481 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1484 return new OpenILS::Event('USERNAME_EXISTS');
1486 $db_user->usrname($new_val);
1488 } elsif( $api =~ /email/o ) {
1489 $db_user->email($new_val);
1493 $e->update_actor_user($db_user) or return $e->die_event;
1496 # update the cached user to pick up these changes
1497 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1503 __PACKAGE__->register_method(
1504 method => "check_user_perms",
1505 api_name => "open-ils.actor.user.perm.check",
1506 notes => <<" NOTES");
1507 Takes a login session, user id, an org id, and an array of perm type strings. For each
1508 perm type, if the user does *not* have the given permission it is added
1509 to a list which is returned from the method. If all permissions
1510 are allowed, an empty list is returned
1511 if the logged in user does not match 'user_id', then the logged in user must
1512 have VIEW_PERMISSION priveleges.
1515 sub check_user_perms {
1516 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1518 my( $staff, $evt ) = $apputils->checkses($login_session);
1519 return $evt if $evt;
1521 if($staff->id ne $user_id) {
1522 if( $evt = $apputils->check_perms(
1523 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1529 for my $perm (@$perm_types) {
1530 if($apputils->check_perms($user_id, $org_id, $perm)) {
1531 push @not_allowed, $perm;
1535 return \@not_allowed
1538 __PACKAGE__->register_method(
1539 method => "check_user_perms2",
1540 api_name => "open-ils.actor.user.perm.check.multi_org",
1542 Checks the permissions on a list of perms and orgs for a user
1543 @param authtoken The login session key
1544 @param user_id The id of the user to check
1545 @param orgs The array of org ids
1546 @param perms The array of permission names
1547 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1548 if the logged in user does not match 'user_id', then the logged in user must
1549 have VIEW_PERMISSION priveleges.
1552 sub check_user_perms2 {
1553 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1555 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1556 $authtoken, $user_id, 'VIEW_PERMISSION' );
1557 return $evt if $evt;
1560 for my $org (@$orgs) {
1561 for my $perm (@$perms) {
1562 if($apputils->check_perms($user_id, $org, $perm)) {
1563 push @not_allowed, [ $org, $perm ];
1568 return \@not_allowed
1572 __PACKAGE__->register_method(
1573 method => 'check_user_perms3',
1574 api_name => 'open-ils.actor.user.perm.highest_org',
1576 Returns the highest org unit id at which a user has a given permission
1577 If the requestor does not match the target user, the requestor must have
1578 'VIEW_PERMISSION' rights at the home org unit of the target user
1579 @param authtoken The login session key
1580 @param userid The id of the user in question
1581 @param perm The permission to check
1582 @return The org unit highest in the org tree within which the user has
1583 the requested permission
1586 sub check_user_perms3 {
1587 my($self, $client, $authtoken, $user_id, $perm) = @_;
1588 my $e = new_editor(authtoken=>$authtoken);
1589 return $e->event unless $e->checkauth;
1591 my $tree = $U->get_org_tree();
1593 unless($e->requestor->id == $user_id) {
1594 my $user = $e->retrieve_actor_user($user_id)
1595 or return $e->event;
1596 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1597 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1600 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1603 __PACKAGE__->register_method(
1604 method => 'user_has_work_perm_at',
1605 api_name => 'open-ils.actor.user.has_work_perm_at',
1609 Returns a set of org unit IDs which represent the highest orgs in
1610 the org tree where the user has the requested permission. The
1611 purpose of this method is to return the smallest set of org units
1612 which represent the full expanse of the user's ability to perform
1613 the requested action. The user whose perms this method should
1614 check is implied by the authtoken. /,
1616 {desc => 'authtoken', type => 'string'},
1617 {desc => 'permission name', type => 'string'},
1618 {desc => q/user id, optional. If present, check perms for
1619 this user instead of the logged in user/, type => 'number'},
1621 return => {desc => 'An array of org IDs'}
1625 sub user_has_work_perm_at {
1626 my($self, $conn, $auth, $perm, $user_id) = @_;
1627 my $e = new_editor(authtoken=>$auth);
1628 return $e->event unless $e->checkauth;
1629 if(defined $user_id) {
1630 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1631 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1633 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1636 __PACKAGE__->register_method(
1637 method => 'user_has_work_perm_at_batch',
1638 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1642 sub user_has_work_perm_at_batch {
1643 my($self, $conn, $auth, $perms, $user_id) = @_;
1644 my $e = new_editor(authtoken=>$auth);
1645 return $e->event unless $e->checkauth;
1646 if(defined $user_id) {
1647 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1648 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1651 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1657 __PACKAGE__->register_method(
1658 method => 'check_user_perms4',
1659 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1661 Returns the highest org unit id at which a user has a given permission
1662 If the requestor does not match the target user, the requestor must have
1663 'VIEW_PERMISSION' rights at the home org unit of the target user
1664 @param authtoken The login session key
1665 @param userid The id of the user in question
1666 @param perms An array of perm names to check
1667 @return An array of orgId's representing the org unit
1668 highest in the org tree within which the user has the requested permission
1669 The arrah of orgId's has matches the order of the perms array
1672 sub check_user_perms4 {
1673 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1675 my( $staff, $target, $org, $evt );
1677 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1678 $authtoken, $userid, 'VIEW_PERMISSION' );
1679 return $evt if $evt;
1682 return [] unless ref($perms);
1683 my $tree = $U->get_org_tree();
1685 for my $p (@$perms) {
1686 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1692 __PACKAGE__->register_method(
1693 method => "user_fines_summary",
1694 api_name => "open-ils.actor.user.fines.summary",
1697 desc => 'Returns a short summary of the users total open fines, ' .
1698 'excluding voided fines Params are login_session, user_id' ,
1700 {desc => 'Authentication token', type => 'string'},
1701 {desc => 'User ID', type => 'string'} # number?
1704 desc => "a 'mous' object, event on error",
1709 sub user_fines_summary {
1710 my( $self, $client, $auth, $user_id ) = @_;
1712 my $e = new_editor(authtoken=>$auth);
1713 return $e->event unless $e->checkauth;
1715 if( $user_id ne $e->requestor->id ) {
1716 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1717 return $e->event unless
1718 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1721 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1725 __PACKAGE__->register_method(
1726 method => "user_opac_vitals",
1727 api_name => "open-ils.actor.user.opac.vital_stats",
1731 desc => 'Returns a short summary of the users vital stats, including ' .
1732 'identification information, accumulated balance, number of holds, ' .
1733 'and current open circulation stats' ,
1735 {desc => 'Authentication token', type => 'string'},
1736 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1739 desc => "An object with four properties: user, fines, checkouts and holds."
1744 sub user_opac_vitals {
1745 my( $self, $client, $auth, $user_id ) = @_;
1747 my $e = new_editor(authtoken=>$auth);
1748 return $e->event unless $e->checkauth;
1750 $user_id ||= $e->requestor->id;
1752 my $user = $e->retrieve_actor_user( $user_id );
1755 ->method_lookup('open-ils.actor.user.fines.summary')
1756 ->run($auth => $user_id);
1757 return $fines if (defined($U->event_code($fines)));
1760 $fines = new Fieldmapper::money::open_user_summary ();
1761 $fines->balance_owed(0.00);
1762 $fines->total_owed(0.00);
1763 $fines->total_paid(0.00);
1764 $fines->usr($user_id);
1768 ->method_lookup('open-ils.actor.user.hold_requests.count')
1769 ->run($auth => $user_id);
1770 return $holds if (defined($U->event_code($holds)));
1773 ->method_lookup('open-ils.actor.user.checked_out.count')
1774 ->run($auth => $user_id);
1775 return $out if (defined($U->event_code($out)));
1777 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1779 my $unread_msgs = $e->search_actor_usr_message([
1780 {usr => $user_id, read_date => undef, deleted => 'f'},
1786 first_given_name => $user->first_given_name,
1787 second_given_name => $user->second_given_name,
1788 family_name => $user->family_name,
1789 alias => $user->alias,
1790 usrname => $user->usrname
1792 fines => $fines->to_bare_hash,
1795 messages => { unread => scalar(@$unread_msgs) }
1800 ##### a small consolidation of related method registrations
1801 my $common_params = [
1802 { desc => 'Authentication token', type => 'string' },
1803 { desc => 'User ID', type => 'string' },
1804 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1805 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1808 'open-ils.actor.user.transactions' => '',
1809 'open-ils.actor.user.transactions.fleshed' => '',
1810 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1811 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1812 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1813 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1816 foreach (keys %methods) {
1818 method => "user_transactions",
1821 desc => 'For a given user, retrieve a list of '
1822 . (/\.fleshed/ ? 'fleshed ' : '')
1823 . 'transactions' . $methods{$_}
1824 . ' optionally limited to transactions of a given type.',
1825 params => $common_params,
1827 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1828 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1832 $args{authoritative} = 1;
1833 __PACKAGE__->register_method(%args);
1836 # Now for the counts
1838 'open-ils.actor.user.transactions.count' => '',
1839 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1840 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1843 foreach (keys %methods) {
1845 method => "user_transactions",
1848 desc => 'For a given user, retrieve a count of open '
1849 . 'transactions' . $methods{$_}
1850 . ' optionally limited to transactions of a given type.',
1851 params => $common_params,
1852 return => { desc => "Integer count of transactions, or event on error" }
1855 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1856 __PACKAGE__->register_method(%args);
1859 __PACKAGE__->register_method(
1860 method => "user_transactions",
1861 api_name => "open-ils.actor.user.transactions.have_balance.total",
1864 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1865 . ' optionally limited to transactions of a given type.',
1866 params => $common_params,
1867 return => { desc => "Decimal balance value, or event on error" }
1872 sub user_transactions {
1873 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1876 my $e = new_editor(authtoken => $auth);
1877 return $e->event unless $e->checkauth;
1879 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1881 return $e->event unless
1882 $e->requestor->id == $user_id or
1883 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1885 my $api = $self->api_name();
1887 my $filter = ($api =~ /have_balance/o) ?
1888 { 'balance_owed' => { '<>' => 0 } }:
1889 { 'total_owed' => { '>' => 0 } };
1891 my $method = 'open-ils.actor.user.transactions.history.still_open';
1892 $method = "$method.authoritative" if $api =~ /authoritative/;
1893 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1895 if($api =~ /total/o) {
1897 $total += $_->balance_owed for @$trans;
1901 ($api =~ /count/o ) and return scalar @$trans;
1902 ($api !~ /fleshed/o) and return $trans;
1905 for my $t (@$trans) {
1907 if( $t->xact_type ne 'circulation' ) {
1908 push @resp, {transaction => $t};
1912 my $circ_data = flesh_circ($e, $t->id);
1913 push @resp, {transaction => $t, %$circ_data};
1920 __PACKAGE__->register_method(
1921 method => "user_transaction_retrieve",
1922 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1925 notes => "Returns a fleshed transaction record"
1928 __PACKAGE__->register_method(
1929 method => "user_transaction_retrieve",
1930 api_name => "open-ils.actor.user.transaction.retrieve",
1933 notes => "Returns a transaction record"
1936 sub user_transaction_retrieve {
1937 my($self, $client, $auth, $bill_id) = @_;
1939 my $e = new_editor(authtoken => $auth);
1940 return $e->event unless $e->checkauth;
1942 my $trans = $e->retrieve_money_billable_transaction_summary(
1943 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1945 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1947 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1949 return $trans unless $self->api_name =~ /flesh/;
1950 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1952 my $circ_data = flesh_circ($e, $trans->id, 1);
1954 return {transaction => $trans, %$circ_data};
1959 my $circ_id = shift;
1960 my $flesh_copy = shift;
1962 my $circ = $e->retrieve_action_circulation([
1966 circ => ['target_copy'],
1967 acp => ['call_number'],
1974 my $copy = $circ->target_copy;
1976 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1977 $mods = new Fieldmapper::metabib::virtual_record;
1978 $mods->doc_id(OILS_PRECAT_RECORD);
1979 $mods->title($copy->dummy_title);
1980 $mods->author($copy->dummy_author);
1983 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1987 $circ->target_copy($circ->target_copy->id);
1988 $copy->call_number($copy->call_number->id);
1990 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1994 __PACKAGE__->register_method(
1995 method => "hold_request_count",
1996 api_name => "open-ils.actor.user.hold_requests.count",
2000 Returns hold ready vs. total counts.
2001 If a context org unit is provided, a third value
2002 is returned with key 'behind_desk', which reports
2003 how many holds are ready at the pickup library
2004 with the behind_desk flag set to true.
2008 sub hold_request_count {
2009 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2010 my $e = new_editor(authtoken => $authtoken);
2011 return $e->event unless $e->checkauth;
2013 $user_id = $e->requestor->id unless defined $user_id;
2015 if($e->requestor->id ne $user_id) {
2016 my $user = $e->retrieve_actor_user($user_id);
2017 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2020 my $holds = $e->json_query({
2021 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2025 fulfillment_time => {"=" => undef },
2026 cancel_time => undef,
2031 $_->{current_shelf_lib} and # avoid undef warnings
2032 $_->{pickup_lib} eq $_->{current_shelf_lib}
2036 total => scalar(@$holds),
2037 ready => scalar(@ready)
2041 # count of holds ready at pickup lib with behind_desk true.
2042 $resp->{behind_desk} = scalar(
2044 $_->{pickup_lib} == $ctx_org and
2045 $U->is_true($_->{behind_desk})
2053 __PACKAGE__->register_method(
2054 method => "checked_out",
2055 api_name => "open-ils.actor.user.checked_out",
2059 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2060 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2061 . "(i.e., outstanding balance or some other pending action on the circ). "
2062 . "The .count method also includes a 'total' field which sums all open circs.",
2064 { desc => 'Authentication Token', type => 'string'},
2065 { desc => 'User ID', type => 'string'},
2068 desc => 'Returns event on error, or an object with ID lists, like: '
2069 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2074 __PACKAGE__->register_method(
2075 method => "checked_out",
2076 api_name => "open-ils.actor.user.checked_out.count",
2079 signature => q/@see open-ils.actor.user.checked_out/
2083 my( $self, $conn, $auth, $userid ) = @_;
2085 my $e = new_editor(authtoken=>$auth);
2086 return $e->event unless $e->checkauth;
2088 if( $userid ne $e->requestor->id ) {
2089 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2090 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2092 # see if there is a friend link allowing circ.view perms
2093 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2094 $e, $userid, $e->requestor->id, 'circ.view');
2095 return $e->event unless $allowed;
2099 my $count = $self->api_name =~ /count/;
2100 return _checked_out( $count, $e, $userid );
2104 my( $iscount, $e, $userid ) = @_;
2110 claims_returned => [],
2113 my $meth = 'retrieve_action_open_circ_';
2121 claims_returned => 0,
2128 my $data = $e->$meth($userid);
2132 $result{$_} += $data->$_() for (keys %result);
2133 $result{total} += $data->$_() for (keys %result);
2135 for my $k (keys %result) {
2136 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2146 __PACKAGE__->register_method(
2147 method => "checked_in_with_fines",
2148 api_name => "open-ils.actor.user.checked_in_with_fines",
2151 signature => q/@see open-ils.actor.user.checked_out/
2154 sub checked_in_with_fines {
2155 my( $self, $conn, $auth, $userid ) = @_;
2157 my $e = new_editor(authtoken=>$auth);
2158 return $e->event unless $e->checkauth;
2160 if( $userid ne $e->requestor->id ) {
2161 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2164 # money is owed on these items and they are checked in
2165 my $open = $e->search_action_circulation(
2168 xact_finish => undef,
2169 checkin_time => { "!=" => undef },
2174 my( @lost, @cr, @lo );
2175 for my $c (@$open) {
2176 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2177 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2178 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2183 claims_returned => \@cr,
2184 long_overdue => \@lo
2190 my ($api, $desc, $auth) = @_;
2191 $desc = $desc ? (" " . $desc) : '';
2192 my $ids = ($api =~ /ids$/) ? 1 : 0;
2195 method => "user_transaction_history",
2196 api_name => "open-ils.actor.user.transactions.$api",
2198 desc => "For a given User ID, returns a list of billable transaction" .
2199 ($ids ? " id" : '') .
2200 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2201 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2203 {desc => 'Authentication token', type => 'string'},
2204 {desc => 'User ID', type => 'number'},
2205 {desc => 'Transaction type (optional)', type => 'number'},
2206 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2209 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2213 $auth and push @sig, (authoritative => 1);
2217 my %auth_hist_methods = (
2219 'history.have_charge' => 'that have an initial charge',
2220 'history.still_open' => 'that are not finished',
2221 'history.have_balance' => 'that have a balance',
2222 'history.have_bill' => 'that have billings',
2223 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2224 'history.have_payment' => 'that have at least 1 payment',
2227 foreach (keys %auth_hist_methods) {
2228 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2229 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2230 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2233 sub user_transaction_history {
2234 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2238 my $e = new_editor(authtoken=>$auth);
2239 return $e->die_event unless $e->checkauth;
2241 if ($e->requestor->id ne $userid) {
2242 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2245 my $api = $self->api_name;
2246 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2248 if(defined($type)) {
2249 $filter->{'xact_type'} = $type;
2252 if($api =~ /have_bill_or_payment/o) {
2254 # transactions that have a non-zero sum across all billings or at least 1 payment
2255 $filter->{'-or'} = {
2256 'balance_owed' => { '<>' => 0 },
2257 'last_payment_ts' => { '<>' => undef }
2260 } elsif($api =~ /have_payment/) {
2262 $filter->{last_payment_ts} ||= {'<>' => undef};
2264 } elsif( $api =~ /have_balance/o) {
2266 # transactions that have a non-zero overall balance
2267 $filter->{'balance_owed'} = { '<>' => 0 };
2269 } elsif( $api =~ /have_charge/o) {
2271 # transactions that have at least 1 billing, regardless of whether it was voided
2272 $filter->{'last_billing_ts'} = { '<>' => undef };
2274 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2276 # transactions that have non-zero sum across all billings. This will exclude
2277 # xacts where all billings have been voided
2278 $filter->{'total_owed'} = { '<>' => 0 };
2281 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2282 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2283 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2285 my $mbts = $e->search_money_billable_transaction_summary(
2286 [ { usr => $userid, @xact_finish, %$filter },
2291 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2292 return $mbts unless $api =~ /fleshed/;
2295 for my $t (@$mbts) {
2297 if( $t->xact_type ne 'circulation' ) {
2298 push @resp, {transaction => $t};
2302 my $circ_data = flesh_circ($e, $t->id);
2303 push @resp, {transaction => $t, %$circ_data};
2311 __PACKAGE__->register_method(
2312 method => "user_perms",
2313 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2315 notes => "Returns a list of permissions"
2319 my( $self, $client, $authtoken, $user ) = @_;
2321 my( $staff, $evt ) = $apputils->checkses($authtoken);
2322 return $evt if $evt;
2324 $user ||= $staff->id;
2326 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2330 return $apputils->simple_scalar_request(
2332 "open-ils.storage.permission.user_perms.atomic",
2336 __PACKAGE__->register_method(
2337 method => "retrieve_perms",
2338 api_name => "open-ils.actor.permissions.retrieve",
2339 notes => "Returns a list of permissions"
2341 sub retrieve_perms {
2342 my( $self, $client ) = @_;
2343 return $apputils->simple_scalar_request(
2345 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2346 { id => { '!=' => undef } }
2350 __PACKAGE__->register_method(
2351 method => "retrieve_groups",
2352 api_name => "open-ils.actor.groups.retrieve",
2353 notes => "Returns a list of user groups"
2355 sub retrieve_groups {
2356 my( $self, $client ) = @_;
2357 return new_editor()->retrieve_all_permission_grp_tree();
2360 __PACKAGE__->register_method(
2361 method => "retrieve_org_address",
2362 api_name => "open-ils.actor.org_unit.address.retrieve",
2363 notes => <<' NOTES');
2364 Returns an org_unit address by ID
2365 @param An org_address ID
2367 sub retrieve_org_address {
2368 my( $self, $client, $id ) = @_;
2369 return $apputils->simple_scalar_request(
2371 "open-ils.cstore.direct.actor.org_address.retrieve",
2376 __PACKAGE__->register_method(
2377 method => "retrieve_groups_tree",
2378 api_name => "open-ils.actor.groups.tree.retrieve",
2379 notes => "Returns a list of user groups"
2382 sub retrieve_groups_tree {
2383 my( $self, $client ) = @_;
2384 return new_editor()->search_permission_grp_tree(
2389 flesh_fields => { pgt => ["children"] },
2390 order_by => { pgt => 'name'}
2397 __PACKAGE__->register_method(
2398 method => "add_user_to_groups",
2399 api_name => "open-ils.actor.user.set_groups",
2400 notes => "Adds a user to one or more permission groups"
2403 sub add_user_to_groups {
2404 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2406 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2407 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2408 return $evt if $evt;
2410 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2411 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2412 return $evt if $evt;
2414 $apputils->simplereq(
2416 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2418 for my $group (@$groups) {
2419 my $link = Fieldmapper::permission::usr_grp_map->new;
2421 $link->usr($userid);
2423 my $id = $apputils->simplereq(
2425 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2431 __PACKAGE__->register_method(
2432 method => "get_user_perm_groups",
2433 api_name => "open-ils.actor.user.get_groups",
2434 notes => "Retrieve a user's permission groups."
2438 sub get_user_perm_groups {
2439 my( $self, $client, $authtoken, $userid ) = @_;
2441 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2442 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2443 return $evt if $evt;
2445 return $apputils->simplereq(
2447 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2451 __PACKAGE__->register_method(
2452 method => "get_user_work_ous",
2453 api_name => "open-ils.actor.user.get_work_ous",
2454 notes => "Retrieve a user's work org units."
2457 __PACKAGE__->register_method(
2458 method => "get_user_work_ous",
2459 api_name => "open-ils.actor.user.get_work_ous.ids",
2460 notes => "Retrieve a user's work org units."
2463 sub get_user_work_ous {
2464 my( $self, $client, $auth, $userid ) = @_;
2465 my $e = new_editor(authtoken=>$auth);
2466 return $e->event unless $e->checkauth;
2467 $userid ||= $e->requestor->id;
2469 if($e->requestor->id != $userid) {
2470 my $user = $e->retrieve_actor_user($userid)
2471 or return $e->event;
2472 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2475 return $e->search_permission_usr_work_ou_map({usr => $userid})
2476 unless $self->api_name =~ /.ids$/;
2478 # client just wants a list of org IDs
2479 return $U->get_user_work_ou_ids($e, $userid);
2484 __PACKAGE__->register_method(
2485 method => 'register_workstation',
2486 api_name => 'open-ils.actor.workstation.register.override',
2487 signature => q/@see open-ils.actor.workstation.register/
2490 __PACKAGE__->register_method(
2491 method => 'register_workstation',
2492 api_name => 'open-ils.actor.workstation.register',
2494 Registers a new workstion in the system
2495 @param authtoken The login session key
2496 @param name The name of the workstation id
2497 @param owner The org unit that owns this workstation
2498 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2499 if the name is already in use.
2503 sub register_workstation {
2504 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2506 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2507 return $e->die_event unless $e->checkauth;
2508 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2509 my $existing = $e->search_actor_workstation({name => $name})->[0];
2510 $oargs = { all => 1 } unless defined $oargs;
2514 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2515 # workstation with the given name exists.
2517 if($owner ne $existing->owning_lib) {
2518 # if necessary, update the owning_lib of the workstation
2520 $logger->info("changing owning lib of workstation ".$existing->id.
2521 " from ".$existing->owning_lib." to $owner");
2522 return $e->die_event unless
2523 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2525 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2527 $existing->owning_lib($owner);
2528 return $e->die_event unless $e->update_actor_workstation($existing);
2534 "attempt to register an existing workstation. returning existing ID");
2537 return $existing->id;
2540 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2544 my $ws = Fieldmapper::actor::workstation->new;
2545 $ws->owning_lib($owner);
2547 $e->create_actor_workstation($ws) or return $e->die_event;
2549 return $ws->id; # note: editor sets the id on the new object for us
2552 __PACKAGE__->register_method(
2553 method => 'workstation_list',
2554 api_name => 'open-ils.actor.workstation.list',
2556 Returns a list of workstations registered at the given location
2557 @param authtoken The login session key
2558 @param ids A list of org_unit.id's for the workstation owners
2562 sub workstation_list {
2563 my( $self, $conn, $authtoken, @orgs ) = @_;
2565 my $e = new_editor(authtoken=>$authtoken);
2566 return $e->event unless $e->checkauth;
2571 unless $e->allowed('REGISTER_WORKSTATION', $o);
2572 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2578 __PACKAGE__->register_method(
2579 method => 'fetch_patron_note',
2580 api_name => 'open-ils.actor.note.retrieve.all',
2583 Returns a list of notes for a given user
2584 Requestor must have VIEW_USER permission if pub==false and
2585 @param authtoken The login session key
2586 @param args Hash of params including
2587 patronid : the patron's id
2588 pub : true if retrieving only public notes
2592 sub fetch_patron_note {
2593 my( $self, $conn, $authtoken, $args ) = @_;
2594 my $patronid = $$args{patronid};
2596 my($reqr, $evt) = $U->checkses($authtoken);
2597 return $evt if $evt;
2600 ($patron, $evt) = $U->fetch_user($patronid);
2601 return $evt if $evt;
2604 if( $patronid ne $reqr->id ) {
2605 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2606 return $evt if $evt;
2608 return $U->cstorereq(
2609 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2610 { usr => $patronid, pub => 't' } );
2613 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2614 return $evt if $evt;
2616 return $U->cstorereq(
2617 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2620 __PACKAGE__->register_method(
2621 method => 'create_user_note',
2622 api_name => 'open-ils.actor.note.create',
2624 Creates a new note for the given user
2625 @param authtoken The login session key
2626 @param note The note object
2629 sub create_user_note {
2630 my( $self, $conn, $authtoken, $note ) = @_;
2631 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2632 return $e->die_event unless $e->checkauth;
2634 my $user = $e->retrieve_actor_user($note->usr)
2635 or return $e->die_event;
2637 return $e->die_event unless
2638 $e->allowed('UPDATE_USER',$user->home_ou);
2640 $note->creator($e->requestor->id);
2641 $e->create_actor_usr_note($note) or return $e->die_event;
2647 __PACKAGE__->register_method(
2648 method => 'delete_user_note',
2649 api_name => 'open-ils.actor.note.delete',
2651 Deletes a note for the given user
2652 @param authtoken The login session key
2653 @param noteid The note id
2656 sub delete_user_note {
2657 my( $self, $conn, $authtoken, $noteid ) = @_;
2659 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2660 return $e->die_event unless $e->checkauth;
2661 my $note = $e->retrieve_actor_usr_note($noteid)
2662 or return $e->die_event;
2663 my $user = $e->retrieve_actor_user($note->usr)
2664 or return $e->die_event;
2665 return $e->die_event unless
2666 $e->allowed('UPDATE_USER', $user->home_ou);
2668 $e->delete_actor_usr_note($note) or return $e->die_event;
2674 __PACKAGE__->register_method(
2675 method => 'update_user_note',
2676 api_name => 'open-ils.actor.note.update',
2678 @param authtoken The login session key
2679 @param note The note
2683 sub update_user_note {
2684 my( $self, $conn, $auth, $note ) = @_;
2685 my $e = new_editor(authtoken=>$auth, xact=>1);
2686 return $e->die_event unless $e->checkauth;
2687 my $patron = $e->retrieve_actor_user($note->usr)
2688 or return $e->die_event;
2689 return $e->die_event unless
2690 $e->allowed('UPDATE_USER', $patron->home_ou);
2691 $e->update_actor_user_note($note)
2692 or return $e->die_event;
2697 __PACKAGE__->register_method(
2698 method => 'fetch_patron_messages',
2699 api_name => 'open-ils.actor.message.retrieve',
2702 Returns a list of notes for a given user, not
2703 including ones marked deleted
2704 @param authtoken The login session key
2705 @param patronid patron ID
2706 @param options hash containing optional limit and offset
2710 sub fetch_patron_messages {
2711 my( $self, $conn, $auth, $patronid, $options ) = @_;
2715 my $e = new_editor(authtoken => $auth);
2716 return $e->die_event unless $e->checkauth;
2718 if ($e->requestor->id ne $patronid) {
2719 return $e->die_event unless $e->allowed('VIEW_USER');
2722 my $select_clause = { usr => $patronid };
2723 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2724 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2725 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2727 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2732 __PACKAGE__->register_method(
2733 method => 'create_closed_date',
2734 api_name => 'open-ils.actor.org_unit.closed_date.create',
2736 Creates a new closing entry for the given org_unit
2737 @param authtoken The login session key
2738 @param note The closed_date object
2741 sub create_closed_date {
2742 my( $self, $conn, $authtoken, $cd ) = @_;
2744 my( $user, $evt ) = $U->checkses($authtoken);
2745 return $evt if $evt;
2747 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2748 return $evt if $evt;
2750 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2752 my $id = $U->storagereq(
2753 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2754 return $U->DB_UPDATE_FAILED($cd) unless $id;
2759 __PACKAGE__->register_method(
2760 method => 'delete_closed_date',
2761 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2763 Deletes a closing entry for the given org_unit
2764 @param authtoken The login session key
2765 @param noteid The close_date id
2768 sub delete_closed_date {
2769 my( $self, $conn, $authtoken, $cd ) = @_;
2771 my( $user, $evt ) = $U->checkses($authtoken);
2772 return $evt if $evt;
2775 ($cd_obj, $evt) = fetch_closed_date($cd);
2776 return $evt if $evt;
2778 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2779 return $evt if $evt;
2781 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2783 my $stat = $U->storagereq(
2784 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2785 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2790 __PACKAGE__->register_method(
2791 method => 'usrname_exists',
2792 api_name => 'open-ils.actor.username.exists',
2794 desc => 'Check if a username is already taken (by an undeleted patron)',
2796 {desc => 'Authentication token', type => 'string'},
2797 {desc => 'Username', type => 'string'}
2800 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2805 sub usrname_exists {
2806 my( $self, $conn, $auth, $usrname ) = @_;
2807 my $e = new_editor(authtoken=>$auth);
2808 return $e->event unless $e->checkauth;
2809 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2810 return $$a[0] if $a and @$a;
2814 __PACKAGE__->register_method(
2815 method => 'barcode_exists',
2816 api_name => 'open-ils.actor.barcode.exists',
2818 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2821 sub barcode_exists {
2822 my( $self, $conn, $auth, $barcode ) = @_;
2823 my $e = new_editor(authtoken=>$auth);
2824 return $e->event unless $e->checkauth;
2825 my $card = $e->search_actor_card({barcode => $barcode});
2831 #return undef unless @$card;
2832 #return $card->[0]->usr;
2836 __PACKAGE__->register_method(
2837 method => 'retrieve_net_levels',
2838 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2841 sub retrieve_net_levels {
2842 my( $self, $conn, $auth ) = @_;
2843 my $e = new_editor(authtoken=>$auth);
2844 return $e->event unless $e->checkauth;
2845 return $e->retrieve_all_config_net_access_level();
2848 # Retain the old typo API name just in case
2849 __PACKAGE__->register_method(
2850 method => 'fetch_org_by_shortname',
2851 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2853 __PACKAGE__->register_method(
2854 method => 'fetch_org_by_shortname',
2855 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2857 sub fetch_org_by_shortname {
2858 my( $self, $conn, $sname ) = @_;
2859 my $e = new_editor();
2860 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2861 return $e->event unless $org;
2866 __PACKAGE__->register_method(
2867 method => 'session_home_lib',
2868 api_name => 'open-ils.actor.session.home_lib',
2871 sub session_home_lib {
2872 my( $self, $conn, $auth ) = @_;
2873 my $e = new_editor(authtoken=>$auth);
2874 return undef unless $e->checkauth;
2875 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2876 return $org->shortname;
2879 __PACKAGE__->register_method(
2880 method => 'session_safe_token',
2881 api_name => 'open-ils.actor.session.safe_token',
2883 Returns a hashed session ID that is safe for export to the world.
2884 This safe token will expire after 1 hour of non-use.
2885 @param auth Active authentication token
2889 sub session_safe_token {
2890 my( $self, $conn, $auth ) = @_;
2891 my $e = new_editor(authtoken=>$auth);
2892 return undef unless $e->checkauth;
2894 my $safe_token = md5_hex($auth);
2896 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2898 # add more user fields as needed
2900 "safe-token-user-$safe_token", {
2901 id => $e->requestor->id,
2902 home_ou_shortname => $e->retrieve_actor_org_unit(
2903 $e->requestor->home_ou)->shortname,
2912 __PACKAGE__->register_method(
2913 method => 'safe_token_home_lib',
2914 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2916 Returns the home library shortname from the session
2917 asscociated with a safe token from generated by
2918 open-ils.actor.session.safe_token.
2919 @param safe_token Active safe token
2920 @param who Optional user activity "ewho" value
2924 sub safe_token_home_lib {
2925 my( $self, $conn, $safe_token, $who ) = @_;
2926 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2928 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2929 return unless $blob;
2931 $U->log_user_activity($blob->{id}, $who, 'verify');
2932 return $blob->{home_ou_shortname};
2936 __PACKAGE__->register_method(
2937 method => "update_penalties",
2938 api_name => "open-ils.actor.user.penalties.update"
2941 sub update_penalties {
2942 my($self, $conn, $auth, $user_id) = @_;
2943 my $e = new_editor(authtoken=>$auth, xact => 1);
2944 return $e->die_event unless $e->checkauth;
2945 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2946 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2947 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2948 return $evt if $evt;
2954 __PACKAGE__->register_method(
2955 method => "apply_penalty",
2956 api_name => "open-ils.actor.user.penalty.apply"
2960 my($self, $conn, $auth, $penalty) = @_;
2962 my $e = new_editor(authtoken=>$auth, xact => 1);
2963 return $e->die_event unless $e->checkauth;
2965 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2966 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2968 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2971 (defined $ptype->org_depth) ?
2972 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2975 $penalty->org_unit($ctx_org);
2976 $penalty->staff($e->requestor->id);
2977 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2980 return $penalty->id;
2983 __PACKAGE__->register_method(
2984 method => "remove_penalty",
2985 api_name => "open-ils.actor.user.penalty.remove"
2988 sub remove_penalty {
2989 my($self, $conn, $auth, $penalty) = @_;
2990 my $e = new_editor(authtoken=>$auth, xact => 1);
2991 return $e->die_event unless $e->checkauth;
2992 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2993 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2995 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
3000 __PACKAGE__->register_method(
3001 method => "update_penalty_note",
3002 api_name => "open-ils.actor.user.penalty.note.update"
3005 sub update_penalty_note {
3006 my($self, $conn, $auth, $penalty_ids, $note) = @_;
3007 my $e = new_editor(authtoken=>$auth, xact => 1);
3008 return $e->die_event unless $e->checkauth;
3009 for my $penalty_id (@$penalty_ids) {
3010 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
3011 if (! $penalty ) { return $e->die_event; }
3012 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3013 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3015 $penalty->note( $note ); $penalty->ischanged( 1 );
3017 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3023 __PACKAGE__->register_method(
3024 method => "ranged_penalty_thresholds",
3025 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3029 sub ranged_penalty_thresholds {
3030 my($self, $conn, $auth, $context_org) = @_;
3031 my $e = new_editor(authtoken=>$auth);
3032 return $e->event unless $e->checkauth;
3033 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3034 my $list = $e->search_permission_grp_penalty_threshold([
3035 {org_unit => $U->get_org_ancestors($context_org)},
3036 {order_by => {pgpt => 'id'}}
3038 $conn->respond($_) for @$list;
3044 __PACKAGE__->register_method(
3045 method => "user_retrieve_fleshed_by_id",
3047 api_name => "open-ils.actor.user.fleshed.retrieve",
3050 sub user_retrieve_fleshed_by_id {
3051 my( $self, $client, $auth, $user_id, $fields ) = @_;
3052 my $e = new_editor(authtoken => $auth);
3053 return $e->event unless $e->checkauth;
3055 if( $e->requestor->id != $user_id ) {
3056 return $e->event unless $e->allowed('VIEW_USER');
3063 "standing_penalties",
3069 return new_flesh_user($user_id, $fields, $e);
3073 sub new_flesh_user {
3076 my $fields = shift || [];
3079 my $fetch_penalties = 0;
3080 if(grep {$_ eq 'standing_penalties'} @$fields) {
3081 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3082 $fetch_penalties = 1;
3085 my $fetch_usr_act = 0;
3086 if(grep {$_ eq 'usr_activity'} @$fields) {
3087 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3091 my $user = $e->retrieve_actor_user(
3096 "flesh_fields" => { "au" => $fields }
3099 ) or return $e->die_event;
3102 if( grep { $_ eq 'addresses' } @$fields ) {
3104 $user->addresses([]) unless @{$user->addresses};
3105 # don't expose "replaced" addresses by default
3106 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3108 if( ref $user->billing_address ) {
3109 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3110 push( @{$user->addresses}, $user->billing_address );
3114 if( ref $user->mailing_address ) {
3115 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3116 push( @{$user->addresses}, $user->mailing_address );
3121 if($fetch_penalties) {
3122 # grab the user penalties ranged for this location
3123 $user->standing_penalties(
3124 $e->search_actor_user_standing_penalty([
3127 {stop_date => undef},
3128 {stop_date => {'>' => 'now'}}
3130 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3133 flesh_fields => {ausp => ['standing_penalty']}
3139 # retrieve the most recent usr_activity entry
3140 if ($fetch_usr_act) {
3142 # max number to return for simple patron fleshing
3143 my $limit = $U->ou_ancestor_setting_value(
3144 $e->requestor->ws_ou,
3145 'circ.patron.usr_activity_retrieve.max');
3149 flesh_fields => {auact => ['etype']},
3150 order_by => {auact => 'event_time DESC'},
3153 # 0 == none, <0 == return all
3154 $limit = 1 unless defined $limit;
3155 $opts->{limit} = $limit if $limit > 0;
3157 $user->usr_activity(
3159 [] : # skip the DB call
3160 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3165 $user->clear_passwd();
3172 __PACKAGE__->register_method(
3173 method => "user_retrieve_parts",
3174 api_name => "open-ils.actor.user.retrieve.parts",
3177 sub user_retrieve_parts {
3178 my( $self, $client, $auth, $user_id, $fields ) = @_;
3179 my $e = new_editor(authtoken => $auth);
3180 return $e->event unless $e->checkauth;
3181 $user_id ||= $e->requestor->id;
3182 if( $e->requestor->id != $user_id ) {
3183 return $e->event unless $e->allowed('VIEW_USER');
3186 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3187 push(@resp, $user->$_()) for(@$fields);
3193 __PACKAGE__->register_method(
3194 method => 'user_opt_in_enabled',
3195 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3196 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3199 sub user_opt_in_enabled {
3200 my($self, $conn) = @_;
3201 my $sc = OpenSRF::Utils::SettingsClient->new;
3202 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3207 __PACKAGE__->register_method(
3208 method => 'user_opt_in_at_org',
3209 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3211 @param $auth The auth token
3212 @param user_id The ID of the user to test
3213 @return 1 if the user has opted in at the specified org,
3214 event on error, and 0 otherwise. /
3216 sub user_opt_in_at_org {
3217 my($self, $conn, $auth, $user_id) = @_;
3219 # see if we even need to enforce the opt-in value
3220 return 1 unless user_opt_in_enabled($self);
3222 my $e = new_editor(authtoken => $auth);
3223 return $e->event unless $e->checkauth;
3225 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3226 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3228 my $ws_org = $e->requestor->ws_ou;
3229 # user is automatically opted-in if they are from the local org
3230 return 1 if $user->home_ou eq $ws_org;
3232 # get the boundary setting
3233 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3235 # auto opt in if user falls within the opt boundary
3236 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3238 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3240 my $vals = $e->search_actor_usr_org_unit_opt_in(
3241 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3247 __PACKAGE__->register_method(
3248 method => 'create_user_opt_in_at_org',
3249 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3251 @param $auth The auth token
3252 @param user_id The ID of the user to test
3253 @return The ID of the newly created object, event on error./
3256 sub create_user_opt_in_at_org {
3257 my($self, $conn, $auth, $user_id, $org_id) = @_;
3259 my $e = new_editor(authtoken => $auth, xact=>1);
3260 return $e->die_event unless $e->checkauth;
3262 # if a specific org unit wasn't passed in, get one based on the defaults;
3264 my $wsou = $e->requestor->ws_ou;
3265 # get the default opt depth
3266 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3267 # get the org unit at that depth
3268 my $org = $e->json_query({
3269 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3270 $org_id = $org->{id};
3273 # fall back to the workstation OU, the pre-opt-in-boundary way
3274 $org_id = $e->requestor->ws_ou;
3277 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3278 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3280 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3282 $opt_in->org_unit($org_id);
3283 $opt_in->usr($user_id);
3284 $opt_in->staff($e->requestor->id);
3285 $opt_in->opt_in_ts('now');
3286 $opt_in->opt_in_ws($e->requestor->wsid);
3288 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3289 or return $e->die_event;
3297 __PACKAGE__->register_method (
3298 method => 'retrieve_org_hours',
3299 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3301 Returns the hours of operation for a specified org unit
3302 @param authtoken The login session key
3303 @param org_id The org_unit ID
3307 sub retrieve_org_hours {
3308 my($self, $conn, $auth, $org_id) = @_;
3309 my $e = new_editor(authtoken => $auth);
3310 return $e->die_event unless $e->checkauth;
3311 $org_id ||= $e->requestor->ws_ou;
3312 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3316 __PACKAGE__->register_method (
3317 method => 'verify_user_password',
3318 api_name => 'open-ils.actor.verify_user_password',
3320 Given a barcode or username and the MD5 encoded password,
3321 returns 1 if the password is correct. Returns 0 otherwise.
3325 sub verify_user_password {
3326 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3327 my $e = new_editor(authtoken => $auth);
3328 return $e->die_event unless $e->checkauth;
3330 my $user_by_barcode;
3331 my $user_by_username;
3333 my $card = $e->search_actor_card([
3334 {barcode => $barcode},
3335 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3336 $user_by_barcode = $card->usr;
3337 $user = $user_by_barcode;
3340 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3341 $user = $user_by_username;
3343 return 0 if (!$user);
3344 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3345 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3346 return 1 if $user->passwd eq $password;
3350 __PACKAGE__->register_method (
3351 method => 'retrieve_usr_id_via_barcode_or_usrname',
3352 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3354 Given a barcode or username returns the id for the user or
3359 sub retrieve_usr_id_via_barcode_or_usrname {
3360 my($self, $conn, $auth, $barcode, $username) = @_;
3361 my $e = new_editor(authtoken => $auth);
3362 return $e->die_event unless $e->checkauth;
3363 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3365 my $user_by_barcode;
3366 my $user_by_username;
3367 $logger->info("$id_as_barcode is the ID as BARCODE");
3369 my $card = $e->search_actor_card([
3370 {barcode => $barcode},
3371 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3372 if ($id_as_barcode =~ /^t/i) {
3374 $user = $e->retrieve_actor_user($barcode);
3375 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3377 $user_by_barcode = $card->usr;
3378 $user = $user_by_barcode;
3381 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3382 $user_by_barcode = $card->usr;
3383 $user = $user_by_barcode;
3388 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3390 $user = $user_by_username;
3392 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3393 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3394 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3399 __PACKAGE__->register_method (
3400 method => 'merge_users',
3401 api_name => 'open-ils.actor.user.merge',
3404 Given a list of source users and destination user, transfer all data from the source
3405 to the dest user and delete the source user. All user related data is
3406 transferred, including circulations, holds, bookbags, etc.
3412 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3413 my $e = new_editor(xact => 1, authtoken => $auth);
3414 return $e->die_event unless $e->checkauth;
3416 # disallow the merge if any subordinate accounts are in collections
3417 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3418 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3420 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3421 my $del_addrs = ($U->ou_ancestor_setting_value(
3422 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3423 my $del_cards = ($U->ou_ancestor_setting_value(
3424 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3425 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3426 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3428 for my $src_id (@$user_ids) {
3429 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3431 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3432 if($src_user->home_ou ne $master_user->home_ou) {
3433 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3436 return $e->die_event unless
3437 $e->json_query({from => [
3452 __PACKAGE__->register_method (
3453 method => 'approve_user_address',
3454 api_name => 'open-ils.actor.user.pending_address.approve',
3461 sub approve_user_address {
3462 my($self, $conn, $auth, $addr) = @_;
3463 my $e = new_editor(xact => 1, authtoken => $auth);
3464 return $e->die_event unless $e->checkauth;
3466 # if the caller passes an address object, assume they want to
3467 # update it first before approving it
3468 $e->update_actor_user_address($addr) or return $e->die_event;
3470 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3472 my $user = $e->retrieve_actor_user($addr->usr);
3473 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3474 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3475 or return $e->die_event;
3477 return [values %$result]->[0];
3481 __PACKAGE__->register_method (
3482 method => 'retrieve_friends',
3483 api_name => 'open-ils.actor.friends.retrieve',
3486 returns { confirmed: [], pending_out: [], pending_in: []}
3487 pending_out are users I'm requesting friendship with
3488 pending_in are users requesting friendship with me
3493 sub retrieve_friends {
3494 my($self, $conn, $auth, $user_id, $options) = @_;
3495 my $e = new_editor(authtoken => $auth);
3496 return $e->event unless $e->checkauth;
3497 $user_id ||= $e->requestor->id;
3499 if($user_id != $e->requestor->id) {
3500 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3501 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3504 return OpenILS::Application::Actor::Friends->retrieve_friends(
3505 $e, $user_id, $options);
3510 __PACKAGE__->register_method (
3511 method => 'apply_friend_perms',
3512 api_name => 'open-ils.actor.friends.perms.apply',
3518 sub apply_friend_perms {
3519 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3520 my $e = new_editor(authtoken => $auth, xact => 1);
3521 return $e->die_event unless $e->checkauth;
3523 if($user_id != $e->requestor->id) {
3524 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3525 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3528 for my $perm (@perms) {
3530 OpenILS::Application::Actor::Friends->apply_friend_perm(
3531 $e, $user_id, $delegate_id, $perm);
3532 return $evt if $evt;
3540 __PACKAGE__->register_method (
3541 method => 'update_user_pending_address',
3542 api_name => 'open-ils.actor.user.address.pending.cud'
3545 sub update_user_pending_address {
3546 my($self, $conn, $auth, $addr) = @_;
3547 my $e = new_editor(authtoken => $auth, xact => 1);
3548 return $e->die_event unless $e->checkauth;
3550 if($addr->usr != $e->requestor->id) {
3551 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3552 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3556 $e->create_actor_user_address($addr) or return $e->die_event;
3557 } elsif($addr->isdeleted) {
3558 $e->delete_actor_user_address($addr) or return $e->die_event;
3560 $e->update_actor_user_address($addr) or return $e->die_event;
3568 __PACKAGE__->register_method (
3569 method => 'user_events',
3570 api_name => 'open-ils.actor.user.events.circ',
3573 __PACKAGE__->register_method (
3574 method => 'user_events',
3575 api_name => 'open-ils.actor.user.events.ahr',
3580 my($self, $conn, $auth, $user_id, $filters) = @_;
3581 my $e = new_editor(authtoken => $auth);
3582 return $e->event unless $e->checkauth;
3584 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3585 my $user_field = 'usr';
3588 $filters->{target} = {
3589 select => { $obj_type => ['id'] },
3591 where => {usr => $user_id}
3594 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3595 if($e->requestor->id != $user_id) {
3596 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3599 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3600 my $req = $ses->request('open-ils.trigger.events_by_target',
3601 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3603 while(my $resp = $req->recv) {
3604 my $val = $resp->content;
3605 my $tgt = $val->target;
3607 if($obj_type eq 'circ') {
3608 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3610 } elsif($obj_type eq 'ahr') {
3611 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3612 if $tgt->current_copy;
3615 $conn->respond($val) if $val;
3621 __PACKAGE__->register_method (
3622 method => 'copy_events',
3623 api_name => 'open-ils.actor.copy.events.circ',
3626 __PACKAGE__->register_method (
3627 method => 'copy_events',
3628 api_name => 'open-ils.actor.copy.events.ahr',
3633 my($self, $conn, $auth, $copy_id, $filters) = @_;
3634 my $e = new_editor(authtoken => $auth);
3635 return $e->event unless $e->checkauth;
3637 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3639 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3641 my $copy_field = 'target_copy';
3642 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3645 $filters->{target} = {
3646 select => { $obj_type => ['id'] },
3648 where => {$copy_field => $copy_id}
3652 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3653 my $req = $ses->request('open-ils.trigger.events_by_target',
3654 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3656 while(my $resp = $req->recv) {
3657 my $val = $resp->content;
3658 my $tgt = $val->target;
3660 my $user = $e->retrieve_actor_user($tgt->usr);
3661 if($e->requestor->id != $user->id) {
3662 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3665 $tgt->$copy_field($copy);
3668 $conn->respond($val) if $val;
3677 __PACKAGE__->register_method (
3678 method => 'update_events',
3679 api_name => 'open-ils.actor.user.event.cancel.batch',
3682 __PACKAGE__->register_method (
3683 method => 'update_events',
3684 api_name => 'open-ils.actor.user.event.reset.batch',
3689 my($self, $conn, $auth, $event_ids) = @_;
3690 my $e = new_editor(xact => 1, authtoken => $auth);
3691 return $e->die_event unless $e->checkauth;
3694 for my $id (@$event_ids) {
3696 # do a little dance to determine what user we are ultimately affecting
3697 my $event = $e->retrieve_action_trigger_event([
3700 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3702 ]) or return $e->die_event;
3705 if($event->event_def->hook->core_type eq 'circ') {
3706 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3707 } elsif($event->event_def->hook->core_type eq 'ahr') {
3708 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3713 my $user = $e->retrieve_actor_user($user_id);
3714 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3716 if($self->api_name =~ /cancel/) {
3717 $event->state('invalid');
3718 } elsif($self->api_name =~ /reset/) {
3719 $event->clear_start_time;
3720 $event->clear_update_time;
3721 $event->state('pending');
3724 $e->update_action_trigger_event($event) or return $e->die_event;
3725 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3729 return {complete => 1};
3733 __PACKAGE__->register_method (
3734 method => 'really_delete_user',
3735 api_name => 'open-ils.actor.user.delete.override',
3736 signature => q/@see open-ils.actor.user.delete/
3739 __PACKAGE__->register_method (
3740 method => 'really_delete_user',
3741 api_name => 'open-ils.actor.user.delete',
3743 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3744 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3745 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3746 dest_usr_id is only required when deleting a user that performs staff functions.
3750 sub really_delete_user {
3751 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3752 my $e = new_editor(authtoken => $auth, xact => 1);
3753 return $e->die_event unless $e->checkauth;
3754 $oargs = { all => 1 } unless defined $oargs;
3756 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3757 my $open_bills = $e->json_query({
3758 select => { mbts => ['id'] },
3761 xact_finish => { '=' => undef },
3762 usr => { '=' => $user_id },
3764 }) or return $e->die_event;
3766 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3768 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3770 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3771 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3772 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3774 # No deleting yourself - UI is supposed to stop you first, though.
3775 return $e->die_event unless $e->requestor->id != $user->id;
3776 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3777 # Check if you are allowed to mess with this patron permission group at all
3778 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3779 my $evt = group_perm_failed($session, $e->requestor, $user);
3780 return $e->die_event($evt) if $evt;
3781 my $stat = $e->json_query(
3782 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3783 or return $e->die_event;
3789 __PACKAGE__->register_method (
3790 method => 'user_payments',
3791 api_name => 'open-ils.actor.user.payments.retrieve',
3794 Returns all payments for a given user. Default order is newest payments first.
3795 @param auth Authentication token
3796 @param user_id The user ID
3797 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3802 my($self, $conn, $auth, $user_id, $filters) = @_;
3805 my $e = new_editor(authtoken => $auth);
3806 return $e->die_event unless $e->checkauth;
3808 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3809 return $e->event unless
3810 $e->requestor->id == $user_id or
3811 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3813 # Find all payments for all transactions for user $user_id
3815 select => {mp => ['id']},
3820 select => {mbt => ['id']},
3822 where => {usr => $user_id}
3827 { # by default, order newest payments first
3829 field => 'payment_ts',
3832 # secondary sort in ID as a tie-breaker, since payments created
3833 # within the same transaction will have identical payment_ts's
3840 for (qw/order_by limit offset/) {
3841 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3844 if(defined $filters->{where}) {
3845 foreach (keys %{$filters->{where}}) {
3846 # don't allow the caller to expand the result set to other users
3847 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3851 my $payment_ids = $e->json_query($query);
3852 for my $pid (@$payment_ids) {
3853 my $pay = $e->retrieve_money_payment([
3858 mbt => ['summary', 'circulation', 'grocery'],
3859 circ => ['target_copy'],
3860 acp => ['call_number'],
3868 xact_type => $pay->xact->summary->xact_type,
3869 last_billing_type => $pay->xact->summary->last_billing_type,
3872 if($pay->xact->summary->xact_type eq 'circulation') {
3873 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3874 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3877 $pay->xact($pay->xact->id); # de-flesh
3878 $conn->respond($resp);
3886 __PACKAGE__->register_method (
3887 method => 'negative_balance_users',
3888 api_name => 'open-ils.actor.users.negative_balance',
3891 Returns all users that have an overall negative balance
3892 @param auth Authentication token
3893 @param org_id The context org unit as an ID or list of IDs. This will be the home
3894 library of the user. If no org_unit is specified, no org unit filter is applied
3898 sub negative_balance_users {
3899 my($self, $conn, $auth, $org_id) = @_;
3901 my $e = new_editor(authtoken => $auth);
3902 return $e->die_event unless $e->checkauth;
3903 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3907 mous => ['usr', 'balance_owed'],
3910 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3911 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3928 where => {'+mous' => {balance_owed => {'<' => 0}}}
3931 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3933 my $list = $e->json_query($query, {timeout => 600});
3935 for my $data (@$list) {
3937 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3938 balance_owed => $data->{balance_owed},
3939 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3946 __PACKAGE__->register_method(
3947 method => "request_password_reset",
3948 api_name => "open-ils.actor.patron.password_reset.request",
3950 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3951 "method for changing a user's password. The UUID token is distributed via A/T " .
3952 "templates (i.e. email to the user).",
3954 { desc => 'user_id_type', type => 'string' },
3955 { desc => 'user_id', type => 'string' },
3956 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3958 return => {desc => '1 on success, Event on error'}
3961 sub request_password_reset {
3962 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3964 # Check to see if password reset requests are already being throttled:
3965 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3967 my $e = new_editor(xact => 1);
3970 # Get the user, if any, depending on the input value
3971 if ($user_id_type eq 'username') {
3972 $user = $e->search_actor_user({usrname => $user_id})->[0];
3975 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3977 } elsif ($user_id_type eq 'barcode') {
3978 my $card = $e->search_actor_card([
3979 {barcode => $user_id},
3980 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3983 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3988 # If the user doesn't have an email address, we can't help them
3989 if (!$user->email) {
3991 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3994 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3995 if ($email_must_match) {
3996 if ($user->email ne $email) {
3997 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4001 _reset_password_request($conn, $e, $user);
4004 # Once we have the user, we can issue the password reset request
4005 # XXX Add a wrapper method that accepts barcode + email input
4006 sub _reset_password_request {
4007 my ($conn, $e, $user) = @_;
4009 # 1. Get throttle threshold and time-to-live from OU_settings
4010 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4011 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4013 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4015 # 2. Get time of last request and number of active requests (num_active)
4016 my $active_requests = $e->json_query({
4022 transform => 'COUNT'
4025 column => 'request_time',
4031 has_been_reset => { '=' => 'f' },
4032 request_time => { '>' => $threshold_time }
4036 # Guard against no active requests
4037 if ($active_requests->[0]->{'request_time'}) {
4038 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
4039 my $now = DateTime::Format::ISO8601->new();
4041 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4042 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4043 ($last_request->add_duration('1 minute') > $now)) {
4044 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4046 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4050 # TODO Check to see if the user is in a password-reset-restricted group
4052 # Otherwise, go ahead and try to get the user.
4054 # Check the number of active requests for this user
4055 $active_requests = $e->json_query({
4061 transform => 'COUNT'
4066 usr => { '=' => $user->id },
4067 has_been_reset => { '=' => 'f' },
4068 request_time => { '>' => $threshold_time }
4072 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4074 # if less than or equal to per-user threshold, proceed; otherwise, return event
4075 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4076 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4078 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4081 # Create the aupr object and insert into the database
4082 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4083 my $uuid = create_uuid_as_string(UUID_V4);
4084 $reset_request->uuid($uuid);
4085 $reset_request->usr($user->id);
4087 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4090 # Create an event to notify user of the URL to reset their password
4092 # Can we stuff this in the user_data param for trigger autocreate?
4093 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4095 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4096 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4099 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4104 __PACKAGE__->register_method(
4105 method => "commit_password_reset",
4106 api_name => "open-ils.actor.patron.password_reset.commit",
4108 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4109 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4110 "with the supplied password.",
4112 { desc => 'uuid', type => 'string' },
4113 { desc => 'password', type => 'string' },
4115 return => {desc => '1 on success, Event on error'}
4118 sub commit_password_reset {
4119 my($self, $conn, $uuid, $password) = @_;
4121 # Check to see if password reset requests are already being throttled:
4122 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4123 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4124 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4126 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4129 my $e = new_editor(xact => 1);
4131 my $aupr = $e->search_actor_usr_password_reset({
4138 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4140 my $user_id = $aupr->[0]->usr;
4141 my $user = $e->retrieve_actor_user($user_id);
4143 # Ensure we're still within the TTL for the request
4144 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4145 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4146 if ($threshold < DateTime->now(time_zone => 'local')) {
4148 $logger->info("Password reset request needed to be submitted before $threshold");
4149 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4152 # Check complexity of password against OU-defined regex
4153 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4157 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4158 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4159 $is_strong = check_password_strength_custom($password, $pw_regex);
4161 $is_strong = check_password_strength_default($password);
4166 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4169 # All is well; update the password
4170 $user->passwd($password);
4171 $e->update_actor_user($user);
4173 # And flag that this password reset request has been honoured
4174 $aupr->[0]->has_been_reset('t');
4175 $e->update_actor_usr_password_reset($aupr->[0]);
4181 sub check_password_strength_default {
4182 my $password = shift;
4183 # Use the default set of checks
4184 if ( (length($password) < 7) or
4185 ($password !~ m/.*\d+.*/) or
4186 ($password !~ m/.*[A-Za-z]+.*/)
4193 sub check_password_strength_custom {
4194 my ($password, $pw_regex) = @_;
4196 $pw_regex = qr/$pw_regex/;
4197 if ($password !~ /$pw_regex/) {
4205 __PACKAGE__->register_method(
4206 method => "event_def_opt_in_settings",
4207 api_name => "open-ils.actor.event_def.opt_in.settings",
4210 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4212 { desc => 'Authentication token', type => 'string'},
4214 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4219 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4226 sub event_def_opt_in_settings {
4227 my($self, $conn, $auth, $org_id) = @_;
4228 my $e = new_editor(authtoken => $auth);
4229 return $e->event unless $e->checkauth;
4231 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4232 return $e->event unless
4233 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4235 $org_id = $e->requestor->home_ou;
4238 # find all config.user_setting_type's related to event_defs for the requested org unit
4239 my $types = $e->json_query({
4240 select => {cust => ['name']},
4241 from => {atevdef => 'cust'},
4244 owner => $U->get_org_ancestors($org_id), # context org plus parents
4251 $conn->respond($_) for
4252 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4259 __PACKAGE__->register_method(
4260 method => "user_visible_circs",
4261 api_name => "open-ils.actor.history.circ.visible",
4264 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4266 { desc => 'Authentication token', type => 'string'},
4267 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4268 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4271 desc => q/An object with 2 fields: circulation and summary.
4272 circulation is the "circ" object. summary is the related "accs" object/,
4278 __PACKAGE__->register_method(
4279 method => "user_visible_circs",
4280 api_name => "open-ils.actor.history.circ.visible.print",
4283 desc => 'Returns printable output for the set of opt-in visible circulations',
4285 { desc => 'Authentication token', type => 'string'},
4286 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4287 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4290 desc => q/An action_trigger.event object or error event./,
4296 __PACKAGE__->register_method(
4297 method => "user_visible_circs",
4298 api_name => "open-ils.actor.history.circ.visible.email",
4301 desc => 'Emails the set of opt-in visible circulations to the requestor',
4303 { desc => 'Authentication token', type => 'string'},
4304 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4305 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4308 desc => q/undef, or event on error/
4313 __PACKAGE__->register_method(
4314 method => "user_visible_circs",
4315 api_name => "open-ils.actor.history.hold.visible",
4318 desc => 'Returns the set of opt-in visible holds',
4320 { desc => 'Authentication token', type => 'string'},
4321 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4322 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4325 desc => q/An object with 1 field: "hold"/,
4331 __PACKAGE__->register_method(
4332 method => "user_visible_circs",
4333 api_name => "open-ils.actor.history.hold.visible.print",
4336 desc => 'Returns printable output for the set of opt-in visible holds',
4338 { desc => 'Authentication token', type => 'string'},
4339 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4340 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4343 desc => q/An action_trigger.event object or error event./,
4349 __PACKAGE__->register_method(
4350 method => "user_visible_circs",
4351 api_name => "open-ils.actor.history.hold.visible.email",
4354 desc => 'Emails the set of opt-in visible holds to the requestor',
4356 { desc => 'Authentication token', type => 'string'},
4357 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4358 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4361 desc => q/undef, or event on error/
4366 sub user_visible_circs {
4367 my($self, $conn, $auth, $user_id, $options) = @_;
4369 my $is_hold = ($self->api_name =~ /hold/);
4370 my $for_print = ($self->api_name =~ /print/);
4371 my $for_email = ($self->api_name =~ /email/);
4372 my $e = new_editor(authtoken => $auth);
4373 return $e->event unless $e->checkauth;
4375 $user_id ||= $e->requestor->id;
4377 $options->{limit} ||= 50;
4378 $options->{offset} ||= 0;
4380 if($user_id != $e->requestor->id) {
4381 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4382 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4383 return $e->event unless $e->allowed($perm, $user->home_ou);
4386 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4388 my $data = $e->json_query({
4389 from => [$db_func, $user_id],
4390 limit => $$options{limit},
4391 offset => $$options{offset}
4393 # TODO: I only want IDs. code below didn't get me there
4394 # {"select":{"au":[{"column":"id", "result_field":"id",
4395 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4400 return undef unless @$data;
4404 # collect the batch of objects
4408 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4409 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4413 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4414 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4417 } elsif ($for_email) {
4419 $conn->respond_complete(1) if $for_email; # no sense in waiting
4427 my $hold = $e->retrieve_action_hold_request($id);
4428 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4429 # events will be fired from action_trigger_runner
4433 my $circ = $e->retrieve_action_circulation($id);
4434 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4435 # events will be fired from action_trigger_runner
4439 } else { # just give me the data please
4447 my $hold = $e->retrieve_action_hold_request($id);
4448 $conn->respond({hold => $hold});
4452 my $circ = $e->retrieve_action_circulation($id);
4455 summary => $U->create_circ_chain_summary($e, $id)
4464 __PACKAGE__->register_method(
4465 method => "user_saved_search_cud",
4466 api_name => "open-ils.actor.user.saved_search.cud",
4469 desc => 'Create/Update/Delete Access to user saved searches',
4471 { desc => 'Authentication token', type => 'string' },
4472 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4475 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4481 __PACKAGE__->register_method(
4482 method => "user_saved_search_cud",
4483 api_name => "open-ils.actor.user.saved_search.retrieve",
4486 desc => 'Retrieve a saved search object',
4488 { desc => 'Authentication token', type => 'string' },
4489 { desc => 'Saved Search ID', type => 'number' }
4492 desc => q/The saved search object, Event on error/,
4498 sub user_saved_search_cud {
4499 my( $self, $client, $auth, $search ) = @_;
4500 my $e = new_editor( authtoken=>$auth );
4501 return $e->die_event unless $e->checkauth;
4503 my $o_search; # prior version of the object, if any
4504 my $res; # to be returned
4506 # branch on the operation type
4508 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4510 # Get the old version, to check ownership
4511 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4512 or return $e->die_event;
4514 # You can't read somebody else's search
4515 return OpenILS::Event->new('BAD_PARAMS')
4516 unless $o_search->owner == $e->requestor->id;
4522 $e->xact_begin; # start an editor transaction
4524 if( $search->isnew ) { # Create
4526 # You can't create a search for somebody else
4527 return OpenILS::Event->new('BAD_PARAMS')
4528 unless $search->owner == $e->requestor->id;
4530 $e->create_actor_usr_saved_search( $search )
4531 or return $e->die_event;
4535 } elsif( $search->ischanged ) { # Update
4537 # You can't change ownership of a search
4538 return OpenILS::Event->new('BAD_PARAMS')
4539 unless $search->owner == $e->requestor->id;
4541 # Get the old version, to check ownership
4542 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4543 or return $e->die_event;
4545 # You can't update somebody else's search
4546 return OpenILS::Event->new('BAD_PARAMS')
4547 unless $o_search->owner == $e->requestor->id;
4550 $e->update_actor_usr_saved_search( $search )
4551 or return $e->die_event;
4555 } elsif( $search->isdeleted ) { # Delete
4557 # Get the old version, to check ownership
4558 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4559 or return $e->die_event;
4561 # You can't delete somebody else's search
4562 return OpenILS::Event->new('BAD_PARAMS')
4563 unless $o_search->owner == $e->requestor->id;
4566 $e->delete_actor_usr_saved_search( $o_search )
4567 or return $e->die_event;
4578 __PACKAGE__->register_method(
4579 method => "get_barcodes",
4580 api_name => "open-ils.actor.get_barcodes"
4584 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4585 my $e = new_editor(authtoken => $auth);
4586 return $e->event unless $e->checkauth;
4587 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4589 my $db_result = $e->json_query(
4591 'evergreen.get_barcodes',
4592 $org_id, $context, $barcode,
4596 if($context =~ /actor/) {
4597 my $filter_result = ();
4599 foreach my $result (@$db_result) {
4600 if($result->{type} eq 'actor') {
4601 if($e->requestor->id != $result->{id}) {
4602 $patron = $e->retrieve_actor_user($result->{id});
4604 push(@$filter_result, $e->event);
4607 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4608 push(@$filter_result, $result);
4611 push(@$filter_result, $e->event);
4615 push(@$filter_result, $result);
4619 push(@$filter_result, $result);
4622 return $filter_result;
4628 __PACKAGE__->register_method(
4629 method => 'address_alert_test',
4630 api_name => 'open-ils.actor.address_alert.test',
4632 desc => "Tests a set of address fields to determine if they match with an address_alert",
4634 {desc => 'Authentication token', type => 'string'},
4635 {desc => 'Org Unit', type => 'number'},
4636 {desc => 'Fields', type => 'hash'},
4638 return => {desc => 'List of matching address_alerts'}
4642 sub address_alert_test {
4643 my ($self, $client, $auth, $org_unit, $fields) = @_;
4644 return [] unless $fields and grep {$_} values %$fields;
4646 my $e = new_editor(authtoken => $auth);
4647 return $e->event unless $e->checkauth;
4648 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4649 $org_unit ||= $e->requestor->ws_ou;
4651 my $alerts = $e->json_query({
4653 'actor.address_alert_matches',
4661 $$fields{post_code},
4662 $$fields{mailing_address},
4663 $$fields{billing_address}
4667 # map the json_query hashes to real objects
4669 map {$e->retrieve_actor_address_alert($_)}
4670 (map {$_->{id}} @$alerts)
4674 __PACKAGE__->register_method(
4675 method => "mark_users_contact_invalid",
4676 api_name => "open-ils.actor.invalidate.email",
4678 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",
4680 {desc => "Authentication token", type => "string"},
4681 {desc => "Patron ID", type => "number"},
4682 {desc => "Additional note text (optional)", type => "string"},
4683 {desc => "penalty org unit ID (optional)", type => "number"}
4685 return => {desc => "Event describing success or failure", type => "object"}
4689 __PACKAGE__->register_method(
4690 method => "mark_users_contact_invalid",
4691 api_name => "open-ils.actor.invalidate.day_phone",
4693 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",
4695 {desc => "Authentication token", type => "string"},
4696 {desc => "Patron ID", type => "number"},
4697 {desc => "Additional note text (optional)", type => "string"},
4698 {desc => "penalty org unit ID (optional)", type => "number"}
4700 return => {desc => "Event describing success or failure", type => "object"}
4704 __PACKAGE__->register_method(
4705 method => "mark_users_contact_invalid",
4706 api_name => "open-ils.actor.invalidate.evening_phone",
4708 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",
4710 {desc => "Authentication token", type => "string"},
4711 {desc => "Patron ID", type => "number"},
4712 {desc => "Additional note text (optional)", type => "string"},
4713 {desc => "penalty org unit ID (optional)", type => "number"}
4715 return => {desc => "Event describing success or failure", type => "object"}
4719 __PACKAGE__->register_method(
4720 method => "mark_users_contact_invalid",
4721 api_name => "open-ils.actor.invalidate.other_phone",
4723 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",
4725 {desc => "Authentication token", type => "string"},
4726 {desc => "Patron ID", type => "number"},
4727 {desc => "Additional note text (optional)", type => "string"},
4728 {desc => "penalty org unit ID (optional, default to top of org tree)",
4731 return => {desc => "Event describing success or failure", type => "object"}
4735 sub mark_users_contact_invalid {
4736 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4738 # This method invalidates an email address or a phone_number which
4739 # removes the bad email address or phone number, copying its contents
4740 # to a patron note, and institutes a standing penalty for "bad email"
4741 # or "bad phone number" which is cleared when the user is saved or
4742 # optionally only when the user is saved with an email address or
4743 # phone number (or staff manually delete the penalty).
4745 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4747 my $e = new_editor(authtoken => $auth, xact => 1);
4748 return $e->die_event unless $e->checkauth;
4750 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4751 $e, $contact_type, {usr => $patron_id},
4752 $addl_note, $penalty_ou, $e->requestor->id
4756 # Putting the following method in open-ils.actor is a bad fit, except in that
4757 # it serves an interface that lives under 'actor' in the templates directory,
4758 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4760 __PACKAGE__->register_method(
4761 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4762 method => "get_all_at_reactors_in_use",
4767 { name => 'authtoken', type => 'string' }
4770 desc => 'list of reactor names', type => 'array'
4775 sub get_all_at_reactors_in_use {
4776 my ($self, $conn, $auth) = @_;
4778 my $e = new_editor(authtoken => $auth);
4779 $e->checkauth or return $e->die_event;
4780 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4782 my $reactors = $e->json_query({
4784 atevdef => [{column => "reactor", transform => "distinct"}]
4786 from => {atevdef => {}}
4789 return $e->die_event unless ref $reactors eq "ARRAY";
4792 return [ map { $_->{reactor} } @$reactors ];
4795 __PACKAGE__->register_method(
4796 method => "filter_group_entry_crud",
4797 api_name => "open-ils.actor.filter_group_entry.crud",
4800 Provides CRUD access to filter group entry objects. These are not full accessible
4801 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4802 are not accessible via PCRUD (because they have no fields against which to link perms)
4805 {desc => "Authentication token", type => "string"},
4806 {desc => "Entry ID / Entry Object", type => "number"},
4807 {desc => "Additional note text (optional)", type => "string"},
4808 {desc => "penalty org unit ID (optional, default to top of org tree)",
4812 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4818 sub filter_group_entry_crud {
4819 my ($self, $conn, $auth, $arg) = @_;
4821 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4822 my $e = new_editor(authtoken => $auth, xact => 1);
4823 return $e->die_event unless $e->checkauth;
4829 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4830 or return $e->die_event;
4832 return $e->die_event unless $e->allowed(
4833 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4835 my $query = $arg->query;
4836 $query = $e->create_actor_search_query($query) or return $e->die_event;
4837 $arg->query($query->id);
4838 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4839 $entry->query($query);
4844 } elsif ($arg->ischanged) {
4846 my $entry = $e->retrieve_actor_search_filter_group_entry([
4849 flesh_fields => {asfge => ['grp']}
4851 ]) or return $e->die_event;
4853 return $e->die_event unless $e->allowed(
4854 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4856 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4857 $arg->query($arg->query->id);
4858 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4859 $arg->query($query);
4864 } elsif ($arg->isdeleted) {
4866 my $entry = $e->retrieve_actor_search_filter_group_entry([
4869 flesh_fields => {asfge => ['grp', 'query']}
4871 ]) or return $e->die_event;
4873 return $e->die_event unless $e->allowed(
4874 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4876 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4877 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4890 my $entry = $e->retrieve_actor_search_filter_group_entry([
4893 flesh_fields => {asfge => ['grp', 'query']}
4895 ]) or return $e->die_event;
4897 return $e->die_event unless $e->allowed(
4898 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4899 $entry->grp->owner);
4902 $entry->grp($entry->grp->id); # for consistency