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 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
273 'user has permission to view that setting, if there is a permission associated with the setting.' ,
275 { desc => 'Org unit ID', type => 'number' },
276 { desc => 'setting name', type => 'string' },
277 { desc => 'authtoken (optional)', type => 'string' }
279 return => {desc => 'A value for the org unit setting, or undef'}
283 # ------------------------------------------------------------------
284 # Attempts to find the org setting value for a given org. if not
285 # found at the requested org, searches up the org tree until it
286 # finds a parent that has the requested setting.
287 # when found, returns { org => $id, value => $value }
288 # otherwise, returns NULL
289 # ------------------------------------------------------------------
290 sub ou_ancestor_setting {
291 my( $self, $client, $orgid, $name, $auth ) = @_;
292 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
295 __PACKAGE__->register_method(
296 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
297 method => 'ou_ancestor_setting_batch',
299 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
300 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
301 'user has permission to view that setting, if there is a permission associated with the setting.' ,
303 { desc => 'Org unit ID', type => 'number' },
304 { desc => 'setting name list', type => 'array' },
305 { desc => 'authtoken (optional)', type => 'string' }
307 return => {desc => 'A hash with name => value pairs for the org unit settings'}
310 sub ou_ancestor_setting_batch {
311 my( $self, $client, $orgid, $name_list, $auth ) = @_;
313 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
319 __PACKAGE__->register_method(
320 method => "update_patron",
321 api_name => "open-ils.actor.patron.update",
324 Update an existing user, or create a new one. Related objects,
325 like cards, addresses, survey responses, and stat cats,
326 can be updated by attaching them to the user object in their
327 respective fields. For examples, the billing address object
328 may be inserted into the 'billing_address' field, etc. For each
329 attached object, indicate if the object should be created,
330 updated, or deleted using the built-in 'isnew', 'ischanged',
331 and 'isdeleted' fields on the object.
334 { desc => 'Authentication token', type => 'string' },
335 { desc => 'Patron data object', type => 'object' }
337 return => {desc => 'A fleshed user object, event on error'}
342 my( $self, $client, $user_session, $patron ) = @_;
344 my $session = $apputils->start_db_session();
346 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
348 my( $user_obj, $evt ) = $U->checkses($user_session);
351 $evt = check_group_perm($session, $user_obj, $patron);
354 $apputils->set_audit_info($session, $user_session, $user_obj->id, $user_obj->wsid);
356 # $new_patron is the patron in progress. $patron is the original patron
357 # passed in with the method. new_patron will change as the components
358 # of patron are added/updated.
362 # unflesh the real items on the patron
363 $patron->card( $patron->card->id ) if(ref($patron->card));
364 $patron->billing_address( $patron->billing_address->id )
365 if(ref($patron->billing_address));
366 $patron->mailing_address( $patron->mailing_address->id )
367 if(ref($patron->mailing_address));
369 # create/update the patron first so we can use his id
371 # $patron is the obj from the client (new data) and $new_patron is the
372 # patron object properly built for db insertion, so we need a third variable
373 # if we want to represent the old patron.
376 my $barred_hook = '';
378 if($patron->isnew()) {
379 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
381 if($U->is_true($patron->barred)) {
382 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
386 $new_patron = $patron;
388 # Did auth checking above already.
390 $old_patron = $e->retrieve_actor_user($patron->id) or
391 return $e->die_event;
393 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
394 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
397 $barred_hook = $U->is_true($new_patron->barred) ?
398 'au.barred' : 'au.unbarred';
402 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
405 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
408 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
411 # re-update the patron if anything has happened to him during this process
412 if($new_patron->ischanged()) {
413 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
417 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
420 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
423 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
426 $apputils->commit_db_session($session);
428 $evt = apply_invalid_addr_penalty($patron);
431 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
433 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
435 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
437 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
438 $new_patron, $new_patron->home_ou) if $barred_hook;
441 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
444 sub apply_invalid_addr_penalty {
446 my $e = new_editor(xact => 1);
448 # grab the invalid address penalty if set
449 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
451 my ($addr_penalty) = grep
452 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
454 # do we enforce invalid address penalty
455 my $enforce = $U->ou_ancestor_setting_value(
456 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
458 my $addrs = $e->search_actor_user_address(
459 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
460 my $addr_count = scalar(@$addrs);
462 if($addr_count == 0 and $addr_penalty) {
464 # regardless of any settings, remove the penalty when the user has no invalid addresses
465 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
468 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
470 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
471 my $depth = $ptype->org_depth;
472 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
473 $ctx_org = $patron->home_ou unless defined $ctx_org;
475 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
476 $penalty->usr($patron->id);
477 $penalty->org_unit($ctx_org);
478 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
480 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
499 "standing_penalties",
507 push @$fields, "home_ou" if $home_ou;
508 return new_flesh_user($id, $fields, $e );
516 # clone and clear stuff that would break the database
520 my $new_patron = $patron->clone;
522 $new_patron->clear_billing_address();
523 $new_patron->clear_mailing_address();
524 $new_patron->clear_addresses();
525 $new_patron->clear_card();
526 $new_patron->clear_cards();
527 $new_patron->clear_id();
528 $new_patron->clear_isnew();
529 $new_patron->clear_ischanged();
530 $new_patron->clear_isdeleted();
531 $new_patron->clear_stat_cat_entries();
532 $new_patron->clear_permissions();
533 $new_patron->clear_standing_penalties();
543 my $user_obj = shift;
545 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
546 return (undef, $evt) if $evt;
548 my $ex = $session->request(
549 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
551 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
554 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
556 my $id = $session->request(
557 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
558 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
560 $logger->info("Successfully created new user [$id] in DB");
562 return ( $session->request(
563 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
567 sub check_group_perm {
568 my( $session, $requestor, $patron ) = @_;
571 # first let's see if the requestor has
572 # priveleges to update this user in any way
573 if( ! $patron->isnew ) {
574 my $p = $session->request(
575 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
577 # If we are the requestor (trying to update our own account)
578 # and we are not trying to change our profile, we're good
579 if( $p->id == $requestor->id and
580 $p->profile == $patron->profile ) {
585 $evt = group_perm_failed($session, $requestor, $p);
589 # They are allowed to edit this patron.. can they put the
590 # patron into the group requested?
591 $evt = group_perm_failed($session, $requestor, $patron);
597 sub group_perm_failed {
598 my( $session, $requestor, $patron ) = @_;
602 my $grpid = $patron->profile;
606 $logger->debug("user update looking for group perm for group $grpid");
607 $grp = $session->request(
608 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
609 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
611 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
613 $logger->info("user update checking perm $perm on user ".
614 $requestor->id." for update/create on user username=".$patron->usrname);
616 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
624 my( $session, $patron, $user_obj, $noperm) = @_;
626 $logger->info("Updating patron ".$patron->id." in DB");
631 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
632 return (undef, $evt) if $evt;
635 # update the password by itself to avoid the password protection magic
636 if( $patron->passwd ) {
637 my $s = $session->request(
638 'open-ils.storage.direct.actor.user.remote_update',
639 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
640 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
641 $patron->clear_passwd;
644 if(!$patron->ident_type) {
645 $patron->clear_ident_type;
646 $patron->clear_ident_value;
649 $evt = verify_last_xact($session, $patron);
650 return (undef, $evt) if $evt;
652 my $stat = $session->request(
653 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
654 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
659 sub verify_last_xact {
660 my( $session, $patron ) = @_;
661 return undef unless $patron->id and $patron->id > 0;
662 my $p = $session->request(
663 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
664 my $xact = $p->last_xact_id;
665 return undef unless $xact;
666 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
667 return OpenILS::Event->new('XACT_COLLISION')
668 if $xact ne $patron->last_xact_id;
673 sub _check_dup_ident {
674 my( $session, $patron ) = @_;
676 return undef unless $patron->ident_value;
679 ident_type => $patron->ident_type,
680 ident_value => $patron->ident_value,
683 $logger->debug("patron update searching for dup ident values: " .
684 $patron->ident_type . ':' . $patron->ident_value);
686 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
688 my $dups = $session->request(
689 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
692 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
699 sub _add_update_addresses {
703 my $new_patron = shift;
707 my $current_id; # id of the address before creation
709 my $addresses = $patron->addresses();
711 for my $address (@$addresses) {
713 next unless ref $address;
714 $current_id = $address->id();
716 if( $patron->billing_address() and
717 $patron->billing_address() == $current_id ) {
718 $logger->info("setting billing addr to $current_id");
719 $new_patron->billing_address($address->id());
720 $new_patron->ischanged(1);
723 if( $patron->mailing_address() and
724 $patron->mailing_address() == $current_id ) {
725 $new_patron->mailing_address($address->id());
726 $logger->info("setting mailing addr to $current_id");
727 $new_patron->ischanged(1);
731 if($address->isnew()) {
733 $address->usr($new_patron->id());
735 ($address, $evt) = _add_address($session,$address);
736 return (undef, $evt) if $evt;
738 # we need to get the new id
739 if( $patron->billing_address() and
740 $patron->billing_address() == $current_id ) {
741 $new_patron->billing_address($address->id());
742 $logger->info("setting billing addr to $current_id");
743 $new_patron->ischanged(1);
746 if( $patron->mailing_address() and
747 $patron->mailing_address() == $current_id ) {
748 $new_patron->mailing_address($address->id());
749 $logger->info("setting mailing addr to $current_id");
750 $new_patron->ischanged(1);
753 } elsif($address->ischanged() ) {
755 ($address, $evt) = _update_address($session, $address);
756 return (undef, $evt) if $evt;
758 } elsif($address->isdeleted() ) {
760 if( $address->id() == $new_patron->mailing_address() ) {
761 $new_patron->clear_mailing_address();
762 ($new_patron, $evt) = _update_patron($session, $new_patron);
763 return (undef, $evt) if $evt;
766 if( $address->id() == $new_patron->billing_address() ) {
767 $new_patron->clear_billing_address();
768 ($new_patron, $evt) = _update_patron($session, $new_patron);
769 return (undef, $evt) if $evt;
772 $evt = _delete_address($session, $address);
773 return (undef, $evt) if $evt;
777 return ( $new_patron, undef );
781 # adds an address to the db and returns the address with new id
783 my($session, $address) = @_;
784 $address->clear_id();
786 $logger->info("Creating new address at street ".$address->street1);
788 # put the address into the database
789 my $id = $session->request(
790 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
791 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
794 return ($address, undef);
798 sub _update_address {
799 my( $session, $address ) = @_;
801 $logger->info("Updating address ".$address->id." in the DB");
803 my $stat = $session->request(
804 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
806 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
807 return ($address, undef);
812 sub _add_update_cards {
816 my $new_patron = shift;
820 my $virtual_id; #id of the card before creation
822 my $cards = $patron->cards();
823 for my $card (@$cards) {
825 $card->usr($new_patron->id());
827 if(ref($card) and $card->isnew()) {
829 $virtual_id = $card->id();
830 ( $card, $evt ) = _add_card($session,$card);
831 return (undef, $evt) if $evt;
833 #if(ref($patron->card)) { $patron->card($patron->card->id); }
834 if($patron->card() == $virtual_id) {
835 $new_patron->card($card->id());
836 $new_patron->ischanged(1);
839 } elsif( ref($card) and $card->ischanged() ) {
840 $evt = _update_card($session, $card);
841 return (undef, $evt) if $evt;
845 return ( $new_patron, undef );
849 # adds an card to the db and returns the card with new id
851 my( $session, $card ) = @_;
854 $logger->info("Adding new patron card ".$card->barcode);
856 my $id = $session->request(
857 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
858 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
859 $logger->info("Successfully created patron card $id");
862 return ( $card, undef );
866 # returns event on error. returns undef otherwise
868 my( $session, $card ) = @_;
869 $logger->info("Updating patron card ".$card->id);
871 my $stat = $session->request(
872 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
873 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
880 # returns event on error. returns undef otherwise
881 sub _delete_address {
882 my( $session, $address ) = @_;
884 $logger->info("Deleting address ".$address->id." from DB");
886 my $stat = $session->request(
887 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
889 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
895 sub _add_survey_responses {
896 my ($session, $patron, $new_patron) = @_;
898 $logger->info( "Updating survey responses for patron ".$new_patron->id );
900 my $responses = $patron->survey_responses;
904 $_->usr($new_patron->id) for (@$responses);
906 my $evt = $U->simplereq( "open-ils.circ",
907 "open-ils.circ.survey.submit.user_id", $responses );
909 return (undef, $evt) if defined($U->event_code($evt));
913 return ( $new_patron, undef );
916 sub _clear_badcontact_penalties {
917 my ($session, $old_patron, $new_patron, $user_obj) = @_;
919 return ($new_patron, undef) unless $old_patron;
921 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
922 my $e = new_editor(xact => 1);
924 # This ignores whether the caller of update_patron has any permission
925 # to remove penalties, but these penalties no longer make sense
926 # if an email address field (for example) is changed (and the caller must
927 # have perms to do *that*) so there's no reason not to clear the penalties.
929 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
931 "+csp" => {"name" => [values(%$PNM)]},
932 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
934 "join" => {"csp" => {}},
936 "flesh_fields" => {"ausp" => ["standing_penalty"]}
938 ]) or return (undef, $e->die_event);
940 return ($new_patron, undef) unless @$bad_contact_penalties;
942 my @penalties_to_clear;
943 my ($field, $penalty_name);
945 # For each field that might have an associated bad contact penalty,
946 # check for such penalties and add them to the to-clear list if that
948 while (($field, $penalty_name) = each(%$PNM)) {
949 if ($old_patron->$field ne $new_patron->$field) {
950 push @penalties_to_clear, grep {
951 $_->standing_penalty->name eq $penalty_name
952 } @$bad_contact_penalties;
956 foreach (@penalties_to_clear) {
957 # Note that this "archives" penalties, in the terminology of the staff
958 # client, instead of just deleting them. This may assist reporting,
959 # or preserving old contact information when it is still potentially
961 $_->standing_penalty($_->standing_penalty->id); # deflesh
962 $_->stop_date('now');
963 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
967 return ($new_patron, undef);
971 sub _create_stat_maps {
973 my($session, $user_session, $patron, $new_patron) = @_;
975 my $maps = $patron->stat_cat_entries();
977 for my $map (@$maps) {
979 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
981 if ($map->isdeleted()) {
982 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
984 } elsif ($map->isnew()) {
985 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
990 $map->target_usr($new_patron->id);
993 $logger->info("Updating stat entry with method $method and map $map");
995 my $stat = $session->request($method, $map)->gather(1);
996 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1000 return ($new_patron, undef);
1003 sub _create_perm_maps {
1005 my($session, $user_session, $patron, $new_patron) = @_;
1007 my $maps = $patron->permissions;
1009 for my $map (@$maps) {
1011 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1012 if ($map->isdeleted()) {
1013 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1014 } elsif ($map->isnew()) {
1015 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1020 $map->usr($new_patron->id);
1022 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1023 $logger->info( "Updating permissions with method $method and map $map" );
1025 my $stat = $session->request($method, $map)->gather(1);
1026 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1030 return ($new_patron, undef);
1034 __PACKAGE__->register_method(
1035 method => "set_user_work_ous",
1036 api_name => "open-ils.actor.user.work_ous.update",
1039 sub set_user_work_ous {
1045 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1046 return $evt if $evt;
1048 my $session = $apputils->start_db_session();
1049 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1051 for my $map (@$maps) {
1053 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1054 if ($map->isdeleted()) {
1055 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1056 } elsif ($map->isnew()) {
1057 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1061 #warn( "Updating permissions with method $method and session $ses and map $map" );
1062 $logger->info( "Updating work_ou map with method $method and map $map" );
1064 my $stat = $session->request($method, $map)->gather(1);
1065 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1069 $apputils->commit_db_session($session);
1071 return scalar(@$maps);
1075 __PACKAGE__->register_method(
1076 method => "set_user_perms",
1077 api_name => "open-ils.actor.user.permissions.update",
1080 sub set_user_perms {
1086 my $session = $apputils->start_db_session();
1088 my( $user_obj, $evt ) = $U->checkses($ses);
1089 return $evt if $evt;
1090 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1092 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1095 $all = 1 if ($U->is_true($user_obj->super_user()));
1096 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1098 for my $map (@$maps) {
1100 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1101 if ($map->isdeleted()) {
1102 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1103 } elsif ($map->isnew()) {
1104 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1108 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1109 #warn( "Updating permissions with method $method and session $ses and map $map" );
1110 $logger->info( "Updating permissions with method $method and map $map" );
1112 my $stat = $session->request($method, $map)->gather(1);
1113 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1117 $apputils->commit_db_session($session);
1119 return scalar(@$maps);
1123 __PACKAGE__->register_method(
1124 method => "user_retrieve_by_barcode",
1126 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1128 sub user_retrieve_by_barcode {
1129 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1131 my $e = new_editor(authtoken => $auth);
1132 return $e->event unless $e->checkauth;
1134 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1135 or return $e->event;
1137 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1138 return $e->event unless $e->allowed(
1139 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1146 __PACKAGE__->register_method(
1147 method => "get_user_by_id",
1149 api_name => "open-ils.actor.user.retrieve",
1152 sub get_user_by_id {
1153 my ($self, $client, $auth, $id) = @_;
1154 my $e = new_editor(authtoken=>$auth);
1155 return $e->event unless $e->checkauth;
1156 my $user = $e->retrieve_actor_user($id) or return $e->event;
1157 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1162 __PACKAGE__->register_method(
1163 method => "get_org_types",
1164 api_name => "open-ils.actor.org_types.retrieve",
1167 return $U->get_org_types();
1171 __PACKAGE__->register_method(
1172 method => "get_user_ident_types",
1173 api_name => "open-ils.actor.user.ident_types.retrieve",
1176 sub get_user_ident_types {
1177 return $ident_types if $ident_types;
1178 return $ident_types =
1179 new_editor()->retrieve_all_config_identification_type();
1183 __PACKAGE__->register_method(
1184 method => "get_org_unit",
1185 api_name => "open-ils.actor.org_unit.retrieve",
1189 my( $self, $client, $user_session, $org_id ) = @_;
1190 my $e = new_editor(authtoken => $user_session);
1192 return $e->event unless $e->checkauth;
1193 $org_id = $e->requestor->ws_ou;
1195 my $o = $e->retrieve_actor_org_unit($org_id)
1196 or return $e->event;
1200 __PACKAGE__->register_method(
1201 method => "search_org_unit",
1202 api_name => "open-ils.actor.org_unit_list.search",
1205 sub search_org_unit {
1207 my( $self, $client, $field, $value ) = @_;
1209 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1211 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1212 { $field => $value } );
1218 # build the org tree
1220 __PACKAGE__->register_method(
1221 method => "get_org_tree",
1222 api_name => "open-ils.actor.org_tree.retrieve",
1224 note => "Returns the entire org tree structure",
1230 return $U->get_org_tree($client->session->session_locale);
1234 __PACKAGE__->register_method(
1235 method => "get_org_descendants",
1236 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1239 # depth is optional. org_unit is the id
1240 sub get_org_descendants {
1241 my( $self, $client, $org_unit, $depth ) = @_;
1243 if(ref $org_unit eq 'ARRAY') {
1246 for my $i (0..scalar(@$org_unit)-1) {
1247 my $list = $U->simple_scalar_request(
1249 "open-ils.storage.actor.org_unit.descendants.atomic",
1250 $org_unit->[$i], $depth->[$i] );
1251 push(@trees, $U->build_org_tree($list));
1256 my $orglist = $apputils->simple_scalar_request(
1258 "open-ils.storage.actor.org_unit.descendants.atomic",
1259 $org_unit, $depth );
1260 return $U->build_org_tree($orglist);
1265 __PACKAGE__->register_method(
1266 method => "get_org_ancestors",
1267 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1270 # depth is optional. org_unit is the id
1271 sub get_org_ancestors {
1272 my( $self, $client, $org_unit, $depth ) = @_;
1273 my $orglist = $apputils->simple_scalar_request(
1275 "open-ils.storage.actor.org_unit.ancestors.atomic",
1276 $org_unit, $depth );
1277 return $U->build_org_tree($orglist);
1281 __PACKAGE__->register_method(
1282 method => "get_standings",
1283 api_name => "open-ils.actor.standings.retrieve"
1288 return $user_standings if $user_standings;
1289 return $user_standings =
1290 $apputils->simple_scalar_request(
1292 "open-ils.cstore.direct.config.standing.search.atomic",
1293 { id => { "!=" => undef } }
1298 __PACKAGE__->register_method(
1299 method => "get_my_org_path",
1300 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1303 sub get_my_org_path {
1304 my( $self, $client, $auth, $org_id ) = @_;
1305 my $e = new_editor(authtoken=>$auth);
1306 return $e->event unless $e->checkauth;
1307 $org_id = $e->requestor->ws_ou unless defined $org_id;
1309 return $apputils->simple_scalar_request(
1311 "open-ils.storage.actor.org_unit.full_path.atomic",
1316 __PACKAGE__->register_method(
1317 method => "patron_adv_search",
1318 api_name => "open-ils.actor.patron.search.advanced"
1321 __PACKAGE__->register_method(
1322 method => "patron_adv_search",
1323 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1325 # TODO: change when opensrf 'bundling' is merged.
1326 # set a relatively small bundle size so the caller can start
1327 # seeing results fairly quickly
1328 max_chunk_size => 4096, # bundling
1331 # pending opensrf work -- also, not sure if needed since we're not
1332 # actaully creating an alternate vesrion, only offering to return a
1336 desc => q/Returns a stream of fleshed user objects instead of
1337 a pile of identifiers/
1341 sub patron_adv_search {
1342 my( $self, $client, $auth, $search_hash, $search_limit,
1343 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1345 my $e = new_editor(authtoken=>$auth);
1346 return $e->event unless $e->checkauth;
1347 return $e->event unless $e->allowed('VIEW_USER');
1349 # depth boundary outside of which patrons must opt-in, default to 0
1350 my $opt_boundary = 0;
1351 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1353 if (not defined $search_ou) {
1354 my $depth = $U->ou_ancestor_setting_value(
1355 $e->requestor->ws_ou,
1356 'circ.patron_edit.duplicate_patron_check_depth'
1359 if (defined $depth) {
1360 $search_ou = $U->org_unit_ancestor_at_depth(
1361 $e->requestor->ws_ou, $depth
1366 my $ids = $U->storagereq(
1367 "open-ils.storage.actor.user.crazy_search", $search_hash,
1368 $search_limit, $search_sort, $include_inactive,
1369 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1371 return $ids unless $self->api_name =~ /fleshed/;
1373 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1379 __PACKAGE__->register_method(
1380 method => "update_passwd",
1381 api_name => "open-ils.actor.user.password.update",
1383 desc => "Update the operator's password",
1385 { desc => 'Authentication token', type => 'string' },
1386 { desc => 'New password', type => 'string' },
1387 { desc => 'Current password', type => 'string' }
1389 return => {desc => '1 on success, Event on error or incorrect current password'}
1393 __PACKAGE__->register_method(
1394 method => "update_passwd",
1395 api_name => "open-ils.actor.user.username.update",
1397 desc => "Update the operator's username",
1399 { desc => 'Authentication token', type => 'string' },
1400 { desc => 'New username', type => 'string' },
1401 { desc => 'Current password', type => 'string' }
1403 return => {desc => '1 on success, Event on error or incorrect current password'}
1407 __PACKAGE__->register_method(
1408 method => "update_passwd",
1409 api_name => "open-ils.actor.user.email.update",
1411 desc => "Update the operator's email address",
1413 { desc => 'Authentication token', type => 'string' },
1414 { desc => 'New email address', type => 'string' },
1415 { desc => 'Current password', type => 'string' }
1417 return => {desc => '1 on success, Event on error or incorrect current password'}
1422 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1423 my $e = new_editor(xact=>1, authtoken=>$auth);
1424 return $e->die_event unless $e->checkauth;
1426 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1427 or return $e->die_event;
1428 my $api = $self->api_name;
1430 # make sure the original password matches the in-database password
1431 if (md5_hex($orig_pw) ne $db_user->passwd) {
1433 return new OpenILS::Event('INCORRECT_PASSWORD');
1436 if( $api =~ /password/o ) {
1438 $db_user->passwd($new_val);
1442 # if we don't clear the password, the user will be updated with
1443 # a hashed version of the hashed version of their password
1444 $db_user->clear_passwd;
1446 if( $api =~ /username/o ) {
1448 # make sure no one else has this username
1449 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1452 return new OpenILS::Event('USERNAME_EXISTS');
1454 $db_user->usrname($new_val);
1456 } elsif( $api =~ /email/o ) {
1457 $db_user->email($new_val);
1461 $e->update_actor_user($db_user) or return $e->die_event;
1464 # update the cached user to pick up these changes
1465 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1471 __PACKAGE__->register_method(
1472 method => "check_user_perms",
1473 api_name => "open-ils.actor.user.perm.check",
1474 notes => <<" NOTES");
1475 Takes a login session, user id, an org id, and an array of perm type strings. For each
1476 perm type, if the user does *not* have the given permission it is added
1477 to a list which is returned from the method. If all permissions
1478 are allowed, an empty list is returned
1479 if the logged in user does not match 'user_id', then the logged in user must
1480 have VIEW_PERMISSION priveleges.
1483 sub check_user_perms {
1484 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1486 my( $staff, $evt ) = $apputils->checkses($login_session);
1487 return $evt if $evt;
1489 if($staff->id ne $user_id) {
1490 if( $evt = $apputils->check_perms(
1491 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1497 for my $perm (@$perm_types) {
1498 if($apputils->check_perms($user_id, $org_id, $perm)) {
1499 push @not_allowed, $perm;
1503 return \@not_allowed
1506 __PACKAGE__->register_method(
1507 method => "check_user_perms2",
1508 api_name => "open-ils.actor.user.perm.check.multi_org",
1510 Checks the permissions on a list of perms and orgs for a user
1511 @param authtoken The login session key
1512 @param user_id The id of the user to check
1513 @param orgs The array of org ids
1514 @param perms The array of permission names
1515 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1516 if the logged in user does not match 'user_id', then the logged in user must
1517 have VIEW_PERMISSION priveleges.
1520 sub check_user_perms2 {
1521 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1523 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1524 $authtoken, $user_id, 'VIEW_PERMISSION' );
1525 return $evt if $evt;
1528 for my $org (@$orgs) {
1529 for my $perm (@$perms) {
1530 if($apputils->check_perms($user_id, $org, $perm)) {
1531 push @not_allowed, [ $org, $perm ];
1536 return \@not_allowed
1540 __PACKAGE__->register_method(
1541 method => 'check_user_perms3',
1542 api_name => 'open-ils.actor.user.perm.highest_org',
1544 Returns the highest org unit id at which a user has a given permission
1545 If the requestor does not match the target user, the requestor must have
1546 'VIEW_PERMISSION' rights at the home org unit of the target user
1547 @param authtoken The login session key
1548 @param userid The id of the user in question
1549 @param perm The permission to check
1550 @return The org unit highest in the org tree within which the user has
1551 the requested permission
1554 sub check_user_perms3 {
1555 my($self, $client, $authtoken, $user_id, $perm) = @_;
1556 my $e = new_editor(authtoken=>$authtoken);
1557 return $e->event unless $e->checkauth;
1559 my $tree = $U->get_org_tree();
1561 unless($e->requestor->id == $user_id) {
1562 my $user = $e->retrieve_actor_user($user_id)
1563 or return $e->event;
1564 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1565 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1568 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1571 __PACKAGE__->register_method(
1572 method => 'user_has_work_perm_at',
1573 api_name => 'open-ils.actor.user.has_work_perm_at',
1577 Returns a set of org unit IDs which represent the highest orgs in
1578 the org tree where the user has the requested permission. The
1579 purpose of this method is to return the smallest set of org units
1580 which represent the full expanse of the user's ability to perform
1581 the requested action. The user whose perms this method should
1582 check is implied by the authtoken. /,
1584 {desc => 'authtoken', type => 'string'},
1585 {desc => 'permission name', type => 'string'},
1586 {desc => q/user id, optional. If present, check perms for
1587 this user instead of the logged in user/, type => 'number'},
1589 return => {desc => 'An array of org IDs'}
1593 sub user_has_work_perm_at {
1594 my($self, $conn, $auth, $perm, $user_id) = @_;
1595 my $e = new_editor(authtoken=>$auth);
1596 return $e->event unless $e->checkauth;
1597 if(defined $user_id) {
1598 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1599 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1601 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1604 __PACKAGE__->register_method(
1605 method => 'user_has_work_perm_at_batch',
1606 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1610 sub user_has_work_perm_at_batch {
1611 my($self, $conn, $auth, $perms, $user_id) = @_;
1612 my $e = new_editor(authtoken=>$auth);
1613 return $e->event unless $e->checkauth;
1614 if(defined $user_id) {
1615 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1616 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1619 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1625 __PACKAGE__->register_method(
1626 method => 'check_user_perms4',
1627 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1629 Returns the highest org unit id at which a user has a given permission
1630 If the requestor does not match the target user, the requestor must have
1631 'VIEW_PERMISSION' rights at the home org unit of the target user
1632 @param authtoken The login session key
1633 @param userid The id of the user in question
1634 @param perms An array of perm names to check
1635 @return An array of orgId's representing the org unit
1636 highest in the org tree within which the user has the requested permission
1637 The arrah of orgId's has matches the order of the perms array
1640 sub check_user_perms4 {
1641 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1643 my( $staff, $target, $org, $evt );
1645 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1646 $authtoken, $userid, 'VIEW_PERMISSION' );
1647 return $evt if $evt;
1650 return [] unless ref($perms);
1651 my $tree = $U->get_org_tree();
1653 for my $p (@$perms) {
1654 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1660 __PACKAGE__->register_method(
1661 method => "user_fines_summary",
1662 api_name => "open-ils.actor.user.fines.summary",
1665 desc => 'Returns a short summary of the users total open fines, ' .
1666 'excluding voided fines Params are login_session, user_id' ,
1668 {desc => 'Authentication token', type => 'string'},
1669 {desc => 'User ID', type => 'string'} # number?
1672 desc => "a 'mous' object, event on error",
1677 sub user_fines_summary {
1678 my( $self, $client, $auth, $user_id ) = @_;
1680 my $e = new_editor(authtoken=>$auth);
1681 return $e->event unless $e->checkauth;
1683 if( $user_id ne $e->requestor->id ) {
1684 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1685 return $e->event unless
1686 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1689 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1693 __PACKAGE__->register_method(
1694 method => "user_opac_vitals",
1695 api_name => "open-ils.actor.user.opac.vital_stats",
1699 desc => 'Returns a short summary of the users vital stats, including ' .
1700 'identification information, accumulated balance, number of holds, ' .
1701 'and current open circulation stats' ,
1703 {desc => 'Authentication token', type => 'string'},
1704 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1707 desc => "An object with four properties: user, fines, checkouts and holds."
1712 sub user_opac_vitals {
1713 my( $self, $client, $auth, $user_id ) = @_;
1715 my $e = new_editor(authtoken=>$auth);
1716 return $e->event unless $e->checkauth;
1718 $user_id ||= $e->requestor->id;
1720 my $user = $e->retrieve_actor_user( $user_id );
1723 ->method_lookup('open-ils.actor.user.fines.summary')
1724 ->run($auth => $user_id);
1725 return $fines if (defined($U->event_code($fines)));
1728 $fines = new Fieldmapper::money::open_user_summary ();
1729 $fines->balance_owed(0.00);
1730 $fines->total_owed(0.00);
1731 $fines->total_paid(0.00);
1732 $fines->usr($user_id);
1736 ->method_lookup('open-ils.actor.user.hold_requests.count')
1737 ->run($auth => $user_id);
1738 return $holds if (defined($U->event_code($holds)));
1741 ->method_lookup('open-ils.actor.user.checked_out.count')
1742 ->run($auth => $user_id);
1743 return $out if (defined($U->event_code($out)));
1745 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1749 first_given_name => $user->first_given_name,
1750 second_given_name => $user->second_given_name,
1751 family_name => $user->family_name,
1752 alias => $user->alias,
1753 usrname => $user->usrname
1755 fines => $fines->to_bare_hash,
1762 ##### a small consolidation of related method registrations
1763 my $common_params = [
1764 { desc => 'Authentication token', type => 'string' },
1765 { desc => 'User ID', type => 'string' },
1766 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1767 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1770 'open-ils.actor.user.transactions' => '',
1771 'open-ils.actor.user.transactions.fleshed' => '',
1772 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1773 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1774 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1775 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1778 foreach (keys %methods) {
1780 method => "user_transactions",
1783 desc => 'For a given user, retrieve a list of '
1784 . (/\.fleshed/ ? 'fleshed ' : '')
1785 . 'transactions' . $methods{$_}
1786 . ' optionally limited to transactions of a given type.',
1787 params => $common_params,
1789 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1790 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1794 $args{authoritative} = 1;
1795 __PACKAGE__->register_method(%args);
1798 # Now for the counts
1800 'open-ils.actor.user.transactions.count' => '',
1801 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1802 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1805 foreach (keys %methods) {
1807 method => "user_transactions",
1810 desc => 'For a given user, retrieve a count of open '
1811 . 'transactions' . $methods{$_}
1812 . ' optionally limited to transactions of a given type.',
1813 params => $common_params,
1814 return => { desc => "Integer count of transactions, or event on error" }
1817 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1818 __PACKAGE__->register_method(%args);
1821 __PACKAGE__->register_method(
1822 method => "user_transactions",
1823 api_name => "open-ils.actor.user.transactions.have_balance.total",
1826 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1827 . ' optionally limited to transactions of a given type.',
1828 params => $common_params,
1829 return => { desc => "Decimal balance value, or event on error" }
1834 sub user_transactions {
1835 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1838 my $e = new_editor(authtoken => $auth);
1839 return $e->event unless $e->checkauth;
1841 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1843 return $e->event unless
1844 $e->requestor->id == $user_id or
1845 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1847 my $api = $self->api_name();
1849 my $filter = ($api =~ /have_balance/o) ?
1850 { 'balance_owed' => { '<>' => 0 } }:
1851 { 'total_owed' => { '>' => 0 } };
1853 my $method = 'open-ils.actor.user.transactions.history.still_open';
1854 $method = "$method.authoritative" if $api =~ /authoritative/;
1855 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1857 if($api =~ /total/o) {
1859 $total += $_->balance_owed for @$trans;
1863 ($api =~ /count/o ) and return scalar @$trans;
1864 ($api !~ /fleshed/o) and return $trans;
1867 for my $t (@$trans) {
1869 if( $t->xact_type ne 'circulation' ) {
1870 push @resp, {transaction => $t};
1874 my $circ_data = flesh_circ($e, $t->id);
1875 push @resp, {transaction => $t, %$circ_data};
1882 __PACKAGE__->register_method(
1883 method => "user_transaction_retrieve",
1884 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1887 notes => "Returns a fleshed transaction record"
1890 __PACKAGE__->register_method(
1891 method => "user_transaction_retrieve",
1892 api_name => "open-ils.actor.user.transaction.retrieve",
1895 notes => "Returns a transaction record"
1898 sub user_transaction_retrieve {
1899 my($self, $client, $auth, $bill_id) = @_;
1901 my $e = new_editor(authtoken => $auth);
1902 return $e->event unless $e->checkauth;
1904 my $trans = $e->retrieve_money_billable_transaction_summary(
1905 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1907 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1909 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1911 return $trans unless $self->api_name =~ /flesh/;
1912 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1914 my $circ_data = flesh_circ($e, $trans->id, 1);
1916 return {transaction => $trans, %$circ_data};
1921 my $circ_id = shift;
1922 my $flesh_copy = shift;
1924 my $circ = $e->retrieve_action_circulation([
1928 circ => ['target_copy'],
1929 acp => ['call_number'],
1936 my $copy = $circ->target_copy;
1938 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1939 $mods = new Fieldmapper::metabib::virtual_record;
1940 $mods->doc_id(OILS_PRECAT_RECORD);
1941 $mods->title($copy->dummy_title);
1942 $mods->author($copy->dummy_author);
1945 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1949 $circ->target_copy($circ->target_copy->id);
1950 $copy->call_number($copy->call_number->id);
1952 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1956 __PACKAGE__->register_method(
1957 method => "hold_request_count",
1958 api_name => "open-ils.actor.user.hold_requests.count",
1962 Returns hold ready vs. total counts.
1963 If a context org unit is provided, a third value
1964 is returned with key 'behind_desk', which reports
1965 how many holds are ready at the pickup library
1966 with the behind_desk flag set to true.
1970 sub hold_request_count {
1971 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
1972 my $e = new_editor(authtoken => $authtoken);
1973 return $e->event unless $e->checkauth;
1975 $user_id = $e->requestor->id unless defined $user_id;
1977 if($e->requestor->id ne $user_id) {
1978 my $user = $e->retrieve_actor_user($user_id);
1979 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1982 my $holds = $e->json_query({
1983 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
1987 fulfillment_time => {"=" => undef },
1988 cancel_time => undef,
1993 $_->{current_shelf_lib} and # avoid undef warnings
1994 $_->{pickup_lib} eq $_->{current_shelf_lib}
1998 total => scalar(@$holds),
1999 ready => scalar(@ready)
2003 # count of holds ready at pickup lib with behind_desk true.
2004 $resp->{behind_desk} = scalar(
2006 $_->{pickup_lib} == $ctx_org and
2007 $U->is_true($_->{behind_desk})
2015 __PACKAGE__->register_method(
2016 method => "checked_out",
2017 api_name => "open-ils.actor.user.checked_out",
2021 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2022 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2023 . "(i.e., outstanding balance or some other pending action on the circ). "
2024 . "The .count method also includes a 'total' field which sums all open circs.",
2026 { desc => 'Authentication Token', type => 'string'},
2027 { desc => 'User ID', type => 'string'},
2030 desc => 'Returns event on error, or an object with ID lists, like: '
2031 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2036 __PACKAGE__->register_method(
2037 method => "checked_out",
2038 api_name => "open-ils.actor.user.checked_out.count",
2041 signature => q/@see open-ils.actor.user.checked_out/
2045 my( $self, $conn, $auth, $userid ) = @_;
2047 my $e = new_editor(authtoken=>$auth);
2048 return $e->event unless $e->checkauth;
2050 if( $userid ne $e->requestor->id ) {
2051 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2052 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2054 # see if there is a friend link allowing circ.view perms
2055 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2056 $e, $userid, $e->requestor->id, 'circ.view');
2057 return $e->event unless $allowed;
2061 my $count = $self->api_name =~ /count/;
2062 return _checked_out( $count, $e, $userid );
2066 my( $iscount, $e, $userid ) = @_;
2072 claims_returned => [],
2075 my $meth = 'retrieve_action_open_circ_';
2083 claims_returned => 0,
2090 my $data = $e->$meth($userid);
2094 $result{$_} += $data->$_() for (keys %result);
2095 $result{total} += $data->$_() for (keys %result);
2097 for my $k (keys %result) {
2098 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2108 __PACKAGE__->register_method(
2109 method => "checked_in_with_fines",
2110 api_name => "open-ils.actor.user.checked_in_with_fines",
2113 signature => q/@see open-ils.actor.user.checked_out/
2116 sub checked_in_with_fines {
2117 my( $self, $conn, $auth, $userid ) = @_;
2119 my $e = new_editor(authtoken=>$auth);
2120 return $e->event unless $e->checkauth;
2122 if( $userid ne $e->requestor->id ) {
2123 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2126 # money is owed on these items and they are checked in
2127 my $open = $e->search_action_circulation(
2130 xact_finish => undef,
2131 checkin_time => { "!=" => undef },
2136 my( @lost, @cr, @lo );
2137 for my $c (@$open) {
2138 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2139 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2140 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2145 claims_returned => \@cr,
2146 long_overdue => \@lo
2152 my ($api, $desc, $auth) = @_;
2153 $desc = $desc ? (" " . $desc) : '';
2154 my $ids = ($api =~ /ids$/) ? 1 : 0;
2157 method => "user_transaction_history",
2158 api_name => "open-ils.actor.user.transactions.$api",
2160 desc => "For a given User ID, returns a list of billable transaction" .
2161 ($ids ? " id" : '') .
2162 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2163 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2165 {desc => 'Authentication token', type => 'string'},
2166 {desc => 'User ID', type => 'number'},
2167 {desc => 'Transaction type (optional)', type => 'number'},
2168 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2171 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2175 $auth and push @sig, (authoritative => 1);
2179 my %auth_hist_methods = (
2181 'history.have_charge' => 'that have an initial charge',
2182 'history.still_open' => 'that are not finished',
2183 'history.have_balance' => 'that have a balance',
2184 'history.have_bill' => 'that have billings',
2185 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2186 'history.have_payment' => 'that have at least 1 payment',
2189 foreach (keys %auth_hist_methods) {
2190 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2191 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2192 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2195 sub user_transaction_history {
2196 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2200 my $e = new_editor(authtoken=>$auth);
2201 return $e->die_event unless $e->checkauth;
2203 if ($e->requestor->id ne $userid) {
2204 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2207 my $api = $self->api_name;
2208 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2210 if(defined($type)) {
2211 $filter->{'xact_type'} = $type;
2214 if($api =~ /have_bill_or_payment/o) {
2216 # transactions that have a non-zero sum across all billings or at least 1 payment
2217 $filter->{'-or'} = {
2218 'balance_owed' => { '<>' => 0 },
2219 'last_payment_ts' => { '<>' => undef }
2222 } elsif($api =~ /have_payment/) {
2224 $filter->{last_payment_ts} ||= {'<>' => undef};
2226 } elsif( $api =~ /have_balance/o) {
2228 # transactions that have a non-zero overall balance
2229 $filter->{'balance_owed'} = { '<>' => 0 };
2231 } elsif( $api =~ /have_charge/o) {
2233 # transactions that have at least 1 billing, regardless of whether it was voided
2234 $filter->{'last_billing_ts'} = { '<>' => undef };
2236 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2238 # transactions that have non-zero sum across all billings. This will exclude
2239 # xacts where all billings have been voided
2240 $filter->{'total_owed'} = { '<>' => 0 };
2243 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2244 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2245 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2247 my $mbts = $e->search_money_billable_transaction_summary(
2248 [ { usr => $userid, @xact_finish, %$filter },
2253 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2254 return $mbts unless $api =~ /fleshed/;
2257 for my $t (@$mbts) {
2259 if( $t->xact_type ne 'circulation' ) {
2260 push @resp, {transaction => $t};
2264 my $circ_data = flesh_circ($e, $t->id);
2265 push @resp, {transaction => $t, %$circ_data};
2273 __PACKAGE__->register_method(
2274 method => "user_perms",
2275 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2277 notes => "Returns a list of permissions"
2281 my( $self, $client, $authtoken, $user ) = @_;
2283 my( $staff, $evt ) = $apputils->checkses($authtoken);
2284 return $evt if $evt;
2286 $user ||= $staff->id;
2288 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2292 return $apputils->simple_scalar_request(
2294 "open-ils.storage.permission.user_perms.atomic",
2298 __PACKAGE__->register_method(
2299 method => "retrieve_perms",
2300 api_name => "open-ils.actor.permissions.retrieve",
2301 notes => "Returns a list of permissions"
2303 sub retrieve_perms {
2304 my( $self, $client ) = @_;
2305 return $apputils->simple_scalar_request(
2307 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2308 { id => { '!=' => undef } }
2312 __PACKAGE__->register_method(
2313 method => "retrieve_groups",
2314 api_name => "open-ils.actor.groups.retrieve",
2315 notes => "Returns a list of user groups"
2317 sub retrieve_groups {
2318 my( $self, $client ) = @_;
2319 return new_editor()->retrieve_all_permission_grp_tree();
2322 __PACKAGE__->register_method(
2323 method => "retrieve_org_address",
2324 api_name => "open-ils.actor.org_unit.address.retrieve",
2325 notes => <<' NOTES');
2326 Returns an org_unit address by ID
2327 @param An org_address ID
2329 sub retrieve_org_address {
2330 my( $self, $client, $id ) = @_;
2331 return $apputils->simple_scalar_request(
2333 "open-ils.cstore.direct.actor.org_address.retrieve",
2338 __PACKAGE__->register_method(
2339 method => "retrieve_groups_tree",
2340 api_name => "open-ils.actor.groups.tree.retrieve",
2341 notes => "Returns a list of user groups"
2344 sub retrieve_groups_tree {
2345 my( $self, $client ) = @_;
2346 return new_editor()->search_permission_grp_tree(
2351 flesh_fields => { pgt => ["children"] },
2352 order_by => { pgt => 'name'}
2359 __PACKAGE__->register_method(
2360 method => "add_user_to_groups",
2361 api_name => "open-ils.actor.user.set_groups",
2362 notes => "Adds a user to one or more permission groups"
2365 sub add_user_to_groups {
2366 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2368 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2369 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2370 return $evt if $evt;
2372 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2373 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2374 return $evt if $evt;
2376 $apputils->simplereq(
2378 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2380 for my $group (@$groups) {
2381 my $link = Fieldmapper::permission::usr_grp_map->new;
2383 $link->usr($userid);
2385 my $id = $apputils->simplereq(
2387 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2393 __PACKAGE__->register_method(
2394 method => "get_user_perm_groups",
2395 api_name => "open-ils.actor.user.get_groups",
2396 notes => "Retrieve a user's permission groups."
2400 sub get_user_perm_groups {
2401 my( $self, $client, $authtoken, $userid ) = @_;
2403 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2404 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2405 return $evt if $evt;
2407 return $apputils->simplereq(
2409 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2413 __PACKAGE__->register_method(
2414 method => "get_user_work_ous",
2415 api_name => "open-ils.actor.user.get_work_ous",
2416 notes => "Retrieve a user's work org units."
2419 __PACKAGE__->register_method(
2420 method => "get_user_work_ous",
2421 api_name => "open-ils.actor.user.get_work_ous.ids",
2422 notes => "Retrieve a user's work org units."
2425 sub get_user_work_ous {
2426 my( $self, $client, $auth, $userid ) = @_;
2427 my $e = new_editor(authtoken=>$auth);
2428 return $e->event unless $e->checkauth;
2429 $userid ||= $e->requestor->id;
2431 if($e->requestor->id != $userid) {
2432 my $user = $e->retrieve_actor_user($userid)
2433 or return $e->event;
2434 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2437 return $e->search_permission_usr_work_ou_map({usr => $userid})
2438 unless $self->api_name =~ /.ids$/;
2440 # client just wants a list of org IDs
2441 return $U->get_user_work_ou_ids($e, $userid);
2446 __PACKAGE__->register_method(
2447 method => 'register_workstation',
2448 api_name => 'open-ils.actor.workstation.register.override',
2449 signature => q/@see open-ils.actor.workstation.register/
2452 __PACKAGE__->register_method(
2453 method => 'register_workstation',
2454 api_name => 'open-ils.actor.workstation.register',
2456 Registers a new workstion in the system
2457 @param authtoken The login session key
2458 @param name The name of the workstation id
2459 @param owner The org unit that owns this workstation
2460 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2461 if the name is already in use.
2465 sub register_workstation {
2466 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2468 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2469 return $e->die_event unless $e->checkauth;
2470 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2471 my $existing = $e->search_actor_workstation({name => $name})->[0];
2472 $oargs = { all => 1 } unless defined $oargs;
2476 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2477 # workstation with the given name exists.
2479 if($owner ne $existing->owning_lib) {
2480 # if necessary, update the owning_lib of the workstation
2482 $logger->info("changing owning lib of workstation ".$existing->id.
2483 " from ".$existing->owning_lib." to $owner");
2484 return $e->die_event unless
2485 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2487 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2489 $existing->owning_lib($owner);
2490 return $e->die_event unless $e->update_actor_workstation($existing);
2496 "attempt to register an existing workstation. returning existing ID");
2499 return $existing->id;
2502 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2506 my $ws = Fieldmapper::actor::workstation->new;
2507 $ws->owning_lib($owner);
2509 $e->create_actor_workstation($ws) or return $e->die_event;
2511 return $ws->id; # note: editor sets the id on the new object for us
2514 __PACKAGE__->register_method(
2515 method => 'workstation_list',
2516 api_name => 'open-ils.actor.workstation.list',
2518 Returns a list of workstations registered at the given location
2519 @param authtoken The login session key
2520 @param ids A list of org_unit.id's for the workstation owners
2524 sub workstation_list {
2525 my( $self, $conn, $authtoken, @orgs ) = @_;
2527 my $e = new_editor(authtoken=>$authtoken);
2528 return $e->event unless $e->checkauth;
2533 unless $e->allowed('REGISTER_WORKSTATION', $o);
2534 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2540 __PACKAGE__->register_method(
2541 method => 'fetch_patron_note',
2542 api_name => 'open-ils.actor.note.retrieve.all',
2545 Returns a list of notes for a given user
2546 Requestor must have VIEW_USER permission if pub==false and
2547 @param authtoken The login session key
2548 @param args Hash of params including
2549 patronid : the patron's id
2550 pub : true if retrieving only public notes
2554 sub fetch_patron_note {
2555 my( $self, $conn, $authtoken, $args ) = @_;
2556 my $patronid = $$args{patronid};
2558 my($reqr, $evt) = $U->checkses($authtoken);
2559 return $evt if $evt;
2562 ($patron, $evt) = $U->fetch_user($patronid);
2563 return $evt if $evt;
2566 if( $patronid ne $reqr->id ) {
2567 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2568 return $evt if $evt;
2570 return $U->cstorereq(
2571 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2572 { usr => $patronid, pub => 't' } );
2575 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2576 return $evt if $evt;
2578 return $U->cstorereq(
2579 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2582 __PACKAGE__->register_method(
2583 method => 'create_user_note',
2584 api_name => 'open-ils.actor.note.create',
2586 Creates a new note for the given user
2587 @param authtoken The login session key
2588 @param note The note object
2591 sub create_user_note {
2592 my( $self, $conn, $authtoken, $note ) = @_;
2593 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2594 return $e->die_event unless $e->checkauth;
2596 my $user = $e->retrieve_actor_user($note->usr)
2597 or return $e->die_event;
2599 return $e->die_event unless
2600 $e->allowed('UPDATE_USER',$user->home_ou);
2602 $note->creator($e->requestor->id);
2603 $e->create_actor_usr_note($note) or return $e->die_event;
2609 __PACKAGE__->register_method(
2610 method => 'delete_user_note',
2611 api_name => 'open-ils.actor.note.delete',
2613 Deletes a note for the given user
2614 @param authtoken The login session key
2615 @param noteid The note id
2618 sub delete_user_note {
2619 my( $self, $conn, $authtoken, $noteid ) = @_;
2621 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2622 return $e->die_event unless $e->checkauth;
2623 my $note = $e->retrieve_actor_usr_note($noteid)
2624 or return $e->die_event;
2625 my $user = $e->retrieve_actor_user($note->usr)
2626 or return $e->die_event;
2627 return $e->die_event unless
2628 $e->allowed('UPDATE_USER', $user->home_ou);
2630 $e->delete_actor_usr_note($note) or return $e->die_event;
2636 __PACKAGE__->register_method(
2637 method => 'update_user_note',
2638 api_name => 'open-ils.actor.note.update',
2640 @param authtoken The login session key
2641 @param note The note
2645 sub update_user_note {
2646 my( $self, $conn, $auth, $note ) = @_;
2647 my $e = new_editor(authtoken=>$auth, xact=>1);
2648 return $e->die_event unless $e->checkauth;
2649 my $patron = $e->retrieve_actor_user($note->usr)
2650 or return $e->die_event;
2651 return $e->die_event unless
2652 $e->allowed('UPDATE_USER', $patron->home_ou);
2653 $e->update_actor_user_note($note)
2654 or return $e->die_event;
2659 __PACKAGE__->register_method(
2660 method => 'fetch_patron_messages',
2661 api_name => 'open-ils.actor.message.retrieve',
2664 Returns a list of notes for a given user, not
2665 including ones marked deleted
2666 @param authtoken The login session key
2667 @param patronid patron ID
2668 @param options hash containing optional limit and offset
2672 sub fetch_patron_messages {
2673 my( $self, $conn, $auth, $patronid, $options ) = @_;
2677 my $e = new_editor(authtoken => $auth);
2678 return $e->die_event unless $e->checkauth;
2680 if ($e->requestor->id ne $patronid) {
2681 return $e->die_event unless $e->allowed('VIEW_USER');
2684 my $select_clause = { usr => $patronid };
2685 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2686 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2687 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2689 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2694 __PACKAGE__->register_method(
2695 method => 'create_closed_date',
2696 api_name => 'open-ils.actor.org_unit.closed_date.create',
2698 Creates a new closing entry for the given org_unit
2699 @param authtoken The login session key
2700 @param note The closed_date object
2703 sub create_closed_date {
2704 my( $self, $conn, $authtoken, $cd ) = @_;
2706 my( $user, $evt ) = $U->checkses($authtoken);
2707 return $evt if $evt;
2709 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2710 return $evt if $evt;
2712 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2714 my $id = $U->storagereq(
2715 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2716 return $U->DB_UPDATE_FAILED($cd) unless $id;
2721 __PACKAGE__->register_method(
2722 method => 'delete_closed_date',
2723 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2725 Deletes a closing entry for the given org_unit
2726 @param authtoken The login session key
2727 @param noteid The close_date id
2730 sub delete_closed_date {
2731 my( $self, $conn, $authtoken, $cd ) = @_;
2733 my( $user, $evt ) = $U->checkses($authtoken);
2734 return $evt if $evt;
2737 ($cd_obj, $evt) = fetch_closed_date($cd);
2738 return $evt if $evt;
2740 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2741 return $evt if $evt;
2743 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2745 my $stat = $U->storagereq(
2746 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2747 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2752 __PACKAGE__->register_method(
2753 method => 'usrname_exists',
2754 api_name => 'open-ils.actor.username.exists',
2756 desc => 'Check if a username is already taken (by an undeleted patron)',
2758 {desc => 'Authentication token', type => 'string'},
2759 {desc => 'Username', type => 'string'}
2762 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2767 sub usrname_exists {
2768 my( $self, $conn, $auth, $usrname ) = @_;
2769 my $e = new_editor(authtoken=>$auth);
2770 return $e->event unless $e->checkauth;
2771 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2772 return $$a[0] if $a and @$a;
2776 __PACKAGE__->register_method(
2777 method => 'barcode_exists',
2778 api_name => 'open-ils.actor.barcode.exists',
2780 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2783 sub barcode_exists {
2784 my( $self, $conn, $auth, $barcode ) = @_;
2785 my $e = new_editor(authtoken=>$auth);
2786 return $e->event unless $e->checkauth;
2787 my $card = $e->search_actor_card({barcode => $barcode});
2793 #return undef unless @$card;
2794 #return $card->[0]->usr;
2798 __PACKAGE__->register_method(
2799 method => 'retrieve_net_levels',
2800 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2803 sub retrieve_net_levels {
2804 my( $self, $conn, $auth ) = @_;
2805 my $e = new_editor(authtoken=>$auth);
2806 return $e->event unless $e->checkauth;
2807 return $e->retrieve_all_config_net_access_level();
2810 # Retain the old typo API name just in case
2811 __PACKAGE__->register_method(
2812 method => 'fetch_org_by_shortname',
2813 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2815 __PACKAGE__->register_method(
2816 method => 'fetch_org_by_shortname',
2817 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2819 sub fetch_org_by_shortname {
2820 my( $self, $conn, $sname ) = @_;
2821 my $e = new_editor();
2822 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2823 return $e->event unless $org;
2828 __PACKAGE__->register_method(
2829 method => 'session_home_lib',
2830 api_name => 'open-ils.actor.session.home_lib',
2833 sub session_home_lib {
2834 my( $self, $conn, $auth ) = @_;
2835 my $e = new_editor(authtoken=>$auth);
2836 return undef unless $e->checkauth;
2837 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2838 return $org->shortname;
2841 __PACKAGE__->register_method(
2842 method => 'session_safe_token',
2843 api_name => 'open-ils.actor.session.safe_token',
2845 Returns a hashed session ID that is safe for export to the world.
2846 This safe token will expire after 1 hour of non-use.
2847 @param auth Active authentication token
2851 sub session_safe_token {
2852 my( $self, $conn, $auth ) = @_;
2853 my $e = new_editor(authtoken=>$auth);
2854 return undef unless $e->checkauth;
2856 my $safe_token = md5_hex($auth);
2858 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2860 # Add more like the following if needed...
2862 "safe-token-home_lib-shortname-$safe_token",
2863 $e->retrieve_actor_org_unit(
2864 $e->requestor->home_ou
2873 __PACKAGE__->register_method(
2874 method => 'safe_token_home_lib',
2875 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2877 Returns the home library shortname from the session
2878 asscociated with a safe token from generated by
2879 open-ils.actor.session.safe_token.
2880 @param safe_token Active safe token
2884 sub safe_token_home_lib {
2885 my( $self, $conn, $safe_token ) = @_;
2887 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2888 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2892 __PACKAGE__->register_method(
2893 method => "update_penalties",
2894 api_name => "open-ils.actor.user.penalties.update"
2897 sub update_penalties {
2898 my($self, $conn, $auth, $user_id) = @_;
2899 my $e = new_editor(authtoken=>$auth, xact => 1);
2900 return $e->die_event unless $e->checkauth;
2901 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2902 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2903 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2904 return $evt if $evt;
2910 __PACKAGE__->register_method(
2911 method => "apply_penalty",
2912 api_name => "open-ils.actor.user.penalty.apply"
2916 my($self, $conn, $auth, $penalty) = @_;
2918 my $e = new_editor(authtoken=>$auth, xact => 1);
2919 return $e->die_event unless $e->checkauth;
2921 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2922 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2924 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2927 (defined $ptype->org_depth) ?
2928 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2931 $penalty->org_unit($ctx_org);
2932 $penalty->staff($e->requestor->id);
2933 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2936 return $penalty->id;
2939 __PACKAGE__->register_method(
2940 method => "remove_penalty",
2941 api_name => "open-ils.actor.user.penalty.remove"
2944 sub remove_penalty {
2945 my($self, $conn, $auth, $penalty) = @_;
2946 my $e = new_editor(authtoken=>$auth, xact => 1);
2947 return $e->die_event unless $e->checkauth;
2948 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2949 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2951 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2956 __PACKAGE__->register_method(
2957 method => "update_penalty_note",
2958 api_name => "open-ils.actor.user.penalty.note.update"
2961 sub update_penalty_note {
2962 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2963 my $e = new_editor(authtoken=>$auth, xact => 1);
2964 return $e->die_event unless $e->checkauth;
2965 for my $penalty_id (@$penalty_ids) {
2966 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2967 if (! $penalty ) { return $e->die_event; }
2968 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2969 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2971 $penalty->note( $note ); $penalty->ischanged( 1 );
2973 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2979 __PACKAGE__->register_method(
2980 method => "ranged_penalty_thresholds",
2981 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2985 sub ranged_penalty_thresholds {
2986 my($self, $conn, $auth, $context_org) = @_;
2987 my $e = new_editor(authtoken=>$auth);
2988 return $e->event unless $e->checkauth;
2989 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2990 my $list = $e->search_permission_grp_penalty_threshold([
2991 {org_unit => $U->get_org_ancestors($context_org)},
2992 {order_by => {pgpt => 'id'}}
2994 $conn->respond($_) for @$list;
3000 __PACKAGE__->register_method(
3001 method => "user_retrieve_fleshed_by_id",
3003 api_name => "open-ils.actor.user.fleshed.retrieve",
3006 sub user_retrieve_fleshed_by_id {
3007 my( $self, $client, $auth, $user_id, $fields ) = @_;
3008 my $e = new_editor(authtoken => $auth);
3009 return $e->event unless $e->checkauth;
3011 if( $e->requestor->id != $user_id ) {
3012 return $e->event unless $e->allowed('VIEW_USER');
3019 "standing_penalties",
3025 return new_flesh_user($user_id, $fields, $e);
3029 sub new_flesh_user {
3032 my $fields = shift || [];
3035 my $fetch_penalties = 0;
3036 if(grep {$_ eq 'standing_penalties'} @$fields) {
3037 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3038 $fetch_penalties = 1;
3041 my $fetch_usr_act = 0;
3042 if(grep {$_ eq 'usr_activity'} @$fields) {
3043 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3047 my $user = $e->retrieve_actor_user(
3052 "flesh_fields" => { "au" => $fields }
3055 ) or return $e->die_event;
3058 if( grep { $_ eq 'addresses' } @$fields ) {
3060 $user->addresses([]) unless @{$user->addresses};
3061 # don't expose "replaced" addresses by default
3062 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3064 if( ref $user->billing_address ) {
3065 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3066 push( @{$user->addresses}, $user->billing_address );
3070 if( ref $user->mailing_address ) {
3071 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3072 push( @{$user->addresses}, $user->mailing_address );
3077 if($fetch_penalties) {
3078 # grab the user penalties ranged for this location
3079 $user->standing_penalties(
3080 $e->search_actor_user_standing_penalty([
3083 {stop_date => undef},
3084 {stop_date => {'>' => 'now'}}
3086 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3089 flesh_fields => {ausp => ['standing_penalty']}
3095 # retrieve the most recent usr_activity entry
3096 if ($fetch_usr_act) {
3098 # max number to return for simple patron fleshing
3099 my $limit = $U->ou_ancestor_setting_value(
3100 $e->requestor->ws_ou,
3101 'circ.patron.usr_activity_retrieve.max');
3105 flesh_fields => {auact => ['etype']},
3106 order_by => {auact => 'event_time DESC'},
3109 # 0 == none, <0 == return all
3110 $limit = 1 unless defined $limit;
3111 $opts->{limit} = $limit if $limit > 0;
3113 $user->usr_activity(
3115 [] : # skip the DB call
3116 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3121 $user->clear_passwd();
3128 __PACKAGE__->register_method(
3129 method => "user_retrieve_parts",
3130 api_name => "open-ils.actor.user.retrieve.parts",
3133 sub user_retrieve_parts {
3134 my( $self, $client, $auth, $user_id, $fields ) = @_;
3135 my $e = new_editor(authtoken => $auth);
3136 return $e->event unless $e->checkauth;
3137 $user_id ||= $e->requestor->id;
3138 if( $e->requestor->id != $user_id ) {
3139 return $e->event unless $e->allowed('VIEW_USER');
3142 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3143 push(@resp, $user->$_()) for(@$fields);
3149 __PACKAGE__->register_method(
3150 method => 'user_opt_in_enabled',
3151 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3152 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3155 sub user_opt_in_enabled {
3156 my($self, $conn) = @_;
3157 my $sc = OpenSRF::Utils::SettingsClient->new;
3158 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3163 __PACKAGE__->register_method(
3164 method => 'user_opt_in_at_org',
3165 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3167 @param $auth The auth token
3168 @param user_id The ID of the user to test
3169 @return 1 if the user has opted in at the specified org,
3170 event on error, and 0 otherwise. /
3172 sub user_opt_in_at_org {
3173 my($self, $conn, $auth, $user_id) = @_;
3175 # see if we even need to enforce the opt-in value
3176 return 1 unless user_opt_in_enabled($self);
3178 my $e = new_editor(authtoken => $auth);
3179 return $e->event unless $e->checkauth;
3181 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3182 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3184 my $ws_org = $e->requestor->ws_ou;
3185 # user is automatically opted-in if they are from the local org
3186 return 1 if $user->home_ou eq $ws_org;
3188 # get the boundary setting
3189 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3191 # auto opt in if user falls within the opt boundary
3192 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3194 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3196 my $vals = $e->search_actor_usr_org_unit_opt_in(
3197 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3203 __PACKAGE__->register_method(
3204 method => 'create_user_opt_in_at_org',
3205 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3207 @param $auth The auth token
3208 @param user_id The ID of the user to test
3209 @return The ID of the newly created object, event on error./
3212 sub create_user_opt_in_at_org {
3213 my($self, $conn, $auth, $user_id, $org_id) = @_;
3215 my $e = new_editor(authtoken => $auth, xact=>1);
3216 return $e->die_event unless $e->checkauth;
3218 # if a specific org unit wasn't passed in, get one based on the defaults;
3220 my $wsou = $e->requestor->ws_ou;
3221 # get the default opt depth
3222 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3223 # get the org unit at that depth
3224 my $org = $e->json_query({
3225 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3226 $org_id = $org->{id};
3229 # fall back to the workstation OU, the pre-opt-in-boundary way
3230 $org_id = $e->requestor->ws_ou;
3233 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3234 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3236 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3238 $opt_in->org_unit($org_id);
3239 $opt_in->usr($user_id);
3240 $opt_in->staff($e->requestor->id);
3241 $opt_in->opt_in_ts('now');
3242 $opt_in->opt_in_ws($e->requestor->wsid);
3244 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3245 or return $e->die_event;
3253 __PACKAGE__->register_method (
3254 method => 'retrieve_org_hours',
3255 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3257 Returns the hours of operation for a specified org unit
3258 @param authtoken The login session key
3259 @param org_id The org_unit ID
3263 sub retrieve_org_hours {
3264 my($self, $conn, $auth, $org_id) = @_;
3265 my $e = new_editor(authtoken => $auth);
3266 return $e->die_event unless $e->checkauth;
3267 $org_id ||= $e->requestor->ws_ou;
3268 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3272 __PACKAGE__->register_method (
3273 method => 'verify_user_password',
3274 api_name => 'open-ils.actor.verify_user_password',
3276 Given a barcode or username and the MD5 encoded password,
3277 returns 1 if the password is correct. Returns 0 otherwise.
3281 sub verify_user_password {
3282 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3283 my $e = new_editor(authtoken => $auth);
3284 return $e->die_event unless $e->checkauth;
3286 my $user_by_barcode;
3287 my $user_by_username;
3289 my $card = $e->search_actor_card([
3290 {barcode => $barcode},
3291 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3292 $user_by_barcode = $card->usr;
3293 $user = $user_by_barcode;
3296 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3297 $user = $user_by_username;
3299 return 0 if (!$user);
3300 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3301 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3302 return 1 if $user->passwd eq $password;
3306 __PACKAGE__->register_method (
3307 method => 'retrieve_usr_id_via_barcode_or_usrname',
3308 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3310 Given a barcode or username returns the id for the user or
3315 sub retrieve_usr_id_via_barcode_or_usrname {
3316 my($self, $conn, $auth, $barcode, $username) = @_;
3317 my $e = new_editor(authtoken => $auth);
3318 return $e->die_event unless $e->checkauth;
3319 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3321 my $user_by_barcode;
3322 my $user_by_username;
3323 $logger->info("$id_as_barcode is the ID as BARCODE");
3325 my $card = $e->search_actor_card([
3326 {barcode => $barcode},
3327 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3328 if ($id_as_barcode =~ /^t/i) {
3330 $user = $e->retrieve_actor_user($barcode);
3331 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3333 $user_by_barcode = $card->usr;
3334 $user = $user_by_barcode;
3337 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3338 $user_by_barcode = $card->usr;
3339 $user = $user_by_barcode;
3344 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3346 $user = $user_by_username;
3348 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3349 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3350 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3355 __PACKAGE__->register_method (
3356 method => 'merge_users',
3357 api_name => 'open-ils.actor.user.merge',
3360 Given a list of source users and destination user, transfer all data from the source
3361 to the dest user and delete the source user. All user related data is
3362 transferred, including circulations, holds, bookbags, etc.
3368 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3369 my $e = new_editor(xact => 1, authtoken => $auth);
3370 return $e->die_event unless $e->checkauth;
3372 # disallow the merge if any subordinate accounts are in collections
3373 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3374 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3376 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3377 my $del_addrs = ($U->ou_ancestor_setting_value(
3378 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3379 my $del_cards = ($U->ou_ancestor_setting_value(
3380 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3381 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3382 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3384 for my $src_id (@$user_ids) {
3385 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3387 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3388 if($src_user->home_ou ne $master_user->home_ou) {
3389 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3392 return $e->die_event unless
3393 $e->json_query({from => [
3408 __PACKAGE__->register_method (
3409 method => 'approve_user_address',
3410 api_name => 'open-ils.actor.user.pending_address.approve',
3417 sub approve_user_address {
3418 my($self, $conn, $auth, $addr) = @_;
3419 my $e = new_editor(xact => 1, authtoken => $auth);
3420 return $e->die_event unless $e->checkauth;
3422 # if the caller passes an address object, assume they want to
3423 # update it first before approving it
3424 $e->update_actor_user_address($addr) or return $e->die_event;
3426 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3428 my $user = $e->retrieve_actor_user($addr->usr);
3429 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3430 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3431 or return $e->die_event;
3433 return [values %$result]->[0];
3437 __PACKAGE__->register_method (
3438 method => 'retrieve_friends',
3439 api_name => 'open-ils.actor.friends.retrieve',
3442 returns { confirmed: [], pending_out: [], pending_in: []}
3443 pending_out are users I'm requesting friendship with
3444 pending_in are users requesting friendship with me
3449 sub retrieve_friends {
3450 my($self, $conn, $auth, $user_id, $options) = @_;
3451 my $e = new_editor(authtoken => $auth);
3452 return $e->event unless $e->checkauth;
3453 $user_id ||= $e->requestor->id;
3455 if($user_id != $e->requestor->id) {
3456 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3457 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3460 return OpenILS::Application::Actor::Friends->retrieve_friends(
3461 $e, $user_id, $options);
3466 __PACKAGE__->register_method (
3467 method => 'apply_friend_perms',
3468 api_name => 'open-ils.actor.friends.perms.apply',
3474 sub apply_friend_perms {
3475 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3476 my $e = new_editor(authtoken => $auth, xact => 1);
3477 return $e->die_event unless $e->checkauth;
3479 if($user_id != $e->requestor->id) {
3480 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3481 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3484 for my $perm (@perms) {
3486 OpenILS::Application::Actor::Friends->apply_friend_perm(
3487 $e, $user_id, $delegate_id, $perm);
3488 return $evt if $evt;
3496 __PACKAGE__->register_method (
3497 method => 'update_user_pending_address',
3498 api_name => 'open-ils.actor.user.address.pending.cud'
3501 sub update_user_pending_address {
3502 my($self, $conn, $auth, $addr) = @_;
3503 my $e = new_editor(authtoken => $auth, xact => 1);
3504 return $e->die_event unless $e->checkauth;
3506 if($addr->usr != $e->requestor->id) {
3507 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3508 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3512 $e->create_actor_user_address($addr) or return $e->die_event;
3513 } elsif($addr->isdeleted) {
3514 $e->delete_actor_user_address($addr) or return $e->die_event;
3516 $e->update_actor_user_address($addr) or return $e->die_event;
3524 __PACKAGE__->register_method (
3525 method => 'user_events',
3526 api_name => 'open-ils.actor.user.events.circ',
3529 __PACKAGE__->register_method (
3530 method => 'user_events',
3531 api_name => 'open-ils.actor.user.events.ahr',
3536 my($self, $conn, $auth, $user_id, $filters) = @_;
3537 my $e = new_editor(authtoken => $auth);
3538 return $e->event unless $e->checkauth;
3540 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3541 my $user_field = 'usr';
3544 $filters->{target} = {
3545 select => { $obj_type => ['id'] },
3547 where => {usr => $user_id}
3550 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3551 if($e->requestor->id != $user_id) {
3552 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3555 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3556 my $req = $ses->request('open-ils.trigger.events_by_target',
3557 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3559 while(my $resp = $req->recv) {
3560 my $val = $resp->content;
3561 my $tgt = $val->target;
3563 if($obj_type eq 'circ') {
3564 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3566 } elsif($obj_type eq 'ahr') {
3567 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3568 if $tgt->current_copy;
3571 $conn->respond($val) if $val;
3577 __PACKAGE__->register_method (
3578 method => 'copy_events',
3579 api_name => 'open-ils.actor.copy.events.circ',
3582 __PACKAGE__->register_method (
3583 method => 'copy_events',
3584 api_name => 'open-ils.actor.copy.events.ahr',
3589 my($self, $conn, $auth, $copy_id, $filters) = @_;
3590 my $e = new_editor(authtoken => $auth);
3591 return $e->event unless $e->checkauth;
3593 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3595 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3597 my $copy_field = 'target_copy';
3598 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3601 $filters->{target} = {
3602 select => { $obj_type => ['id'] },
3604 where => {$copy_field => $copy_id}
3608 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3609 my $req = $ses->request('open-ils.trigger.events_by_target',
3610 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3612 while(my $resp = $req->recv) {
3613 my $val = $resp->content;
3614 my $tgt = $val->target;
3616 my $user = $e->retrieve_actor_user($tgt->usr);
3617 if($e->requestor->id != $user->id) {
3618 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3621 $tgt->$copy_field($copy);
3624 $conn->respond($val) if $val;
3633 __PACKAGE__->register_method (
3634 method => 'update_events',
3635 api_name => 'open-ils.actor.user.event.cancel.batch',
3638 __PACKAGE__->register_method (
3639 method => 'update_events',
3640 api_name => 'open-ils.actor.user.event.reset.batch',
3645 my($self, $conn, $auth, $event_ids) = @_;
3646 my $e = new_editor(xact => 1, authtoken => $auth);
3647 return $e->die_event unless $e->checkauth;
3650 for my $id (@$event_ids) {
3652 # do a little dance to determine what user we are ultimately affecting
3653 my $event = $e->retrieve_action_trigger_event([
3656 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3658 ]) or return $e->die_event;
3661 if($event->event_def->hook->core_type eq 'circ') {
3662 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3663 } elsif($event->event_def->hook->core_type eq 'ahr') {
3664 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3669 my $user = $e->retrieve_actor_user($user_id);
3670 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3672 if($self->api_name =~ /cancel/) {
3673 $event->state('invalid');
3674 } elsif($self->api_name =~ /reset/) {
3675 $event->clear_start_time;
3676 $event->clear_update_time;
3677 $event->state('pending');
3680 $e->update_action_trigger_event($event) or return $e->die_event;
3681 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3685 return {complete => 1};
3689 __PACKAGE__->register_method (
3690 method => 'really_delete_user',
3691 api_name => 'open-ils.actor.user.delete.override',
3692 signature => q/@see open-ils.actor.user.delete/
3695 __PACKAGE__->register_method (
3696 method => 'really_delete_user',
3697 api_name => 'open-ils.actor.user.delete',
3699 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3700 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3701 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3702 dest_usr_id is only required when deleting a user that performs staff functions.
3706 sub really_delete_user {
3707 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3708 my $e = new_editor(authtoken => $auth, xact => 1);
3709 return $e->die_event unless $e->checkauth;
3710 $oargs = { all => 1 } unless defined $oargs;
3712 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3713 my $open_bills = $e->json_query({
3714 select => { mbts => ['id'] },
3717 xact_finish => { '=' => undef },
3718 usr => { '=' => $user_id },
3720 }) or return $e->die_event;
3722 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3724 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3726 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3727 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3728 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3730 # No deleting yourself - UI is supposed to stop you first, though.
3731 return $e->die_event unless $e->requestor->id != $user->id;
3732 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3733 # Check if you are allowed to mess with this patron permission group at all
3734 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3735 my $evt = group_perm_failed($session, $e->requestor, $user);
3736 return $e->die_event($evt) if $evt;
3737 my $stat = $e->json_query(
3738 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3739 or return $e->die_event;
3745 __PACKAGE__->register_method (
3746 method => 'user_payments',
3747 api_name => 'open-ils.actor.user.payments.retrieve',
3750 Returns all payments for a given user. Default order is newest payments first.
3751 @param auth Authentication token
3752 @param user_id The user ID
3753 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3758 my($self, $conn, $auth, $user_id, $filters) = @_;
3761 my $e = new_editor(authtoken => $auth);
3762 return $e->die_event unless $e->checkauth;
3764 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3765 return $e->event unless
3766 $e->requestor->id == $user_id or
3767 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3769 # Find all payments for all transactions for user $user_id
3771 select => {mp => ['id']},
3776 select => {mbt => ['id']},
3778 where => {usr => $user_id}
3783 { # by default, order newest payments first
3785 field => 'payment_ts',
3788 # secondary sort in ID as a tie-breaker, since payments created
3789 # within the same transaction will have identical payment_ts's
3796 for (qw/order_by limit offset/) {
3797 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3800 if(defined $filters->{where}) {
3801 foreach (keys %{$filters->{where}}) {
3802 # don't allow the caller to expand the result set to other users
3803 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3807 my $payment_ids = $e->json_query($query);
3808 for my $pid (@$payment_ids) {
3809 my $pay = $e->retrieve_money_payment([
3814 mbt => ['summary', 'circulation', 'grocery'],
3815 circ => ['target_copy'],
3816 acp => ['call_number'],
3824 xact_type => $pay->xact->summary->xact_type,
3825 last_billing_type => $pay->xact->summary->last_billing_type,
3828 if($pay->xact->summary->xact_type eq 'circulation') {
3829 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3830 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3833 $pay->xact($pay->xact->id); # de-flesh
3834 $conn->respond($resp);
3842 __PACKAGE__->register_method (
3843 method => 'negative_balance_users',
3844 api_name => 'open-ils.actor.users.negative_balance',
3847 Returns all users that have an overall negative balance
3848 @param auth Authentication token
3849 @param org_id The context org unit as an ID or list of IDs. This will be the home
3850 library of the user. If no org_unit is specified, no org unit filter is applied
3854 sub negative_balance_users {
3855 my($self, $conn, $auth, $org_id) = @_;
3857 my $e = new_editor(authtoken => $auth);
3858 return $e->die_event unless $e->checkauth;
3859 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3863 mous => ['usr', 'balance_owed'],
3866 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3867 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3884 where => {'+mous' => {balance_owed => {'<' => 0}}}
3887 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3889 my $list = $e->json_query($query, {timeout => 600});
3891 for my $data (@$list) {
3893 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3894 balance_owed => $data->{balance_owed},
3895 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3902 __PACKAGE__->register_method(
3903 method => "request_password_reset",
3904 api_name => "open-ils.actor.patron.password_reset.request",
3906 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3907 "method for changing a user's password. The UUID token is distributed via A/T " .
3908 "templates (i.e. email to the user).",
3910 { desc => 'user_id_type', type => 'string' },
3911 { desc => 'user_id', type => 'string' },
3912 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3914 return => {desc => '1 on success, Event on error'}
3917 sub request_password_reset {
3918 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3920 # Check to see if password reset requests are already being throttled:
3921 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3923 my $e = new_editor(xact => 1);
3926 # Get the user, if any, depending on the input value
3927 if ($user_id_type eq 'username') {
3928 $user = $e->search_actor_user({usrname => $user_id})->[0];
3931 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3933 } elsif ($user_id_type eq 'barcode') {
3934 my $card = $e->search_actor_card([
3935 {barcode => $user_id},
3936 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3939 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3944 # If the user doesn't have an email address, we can't help them
3945 if (!$user->email) {
3947 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3950 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3951 if ($email_must_match) {
3952 if ($user->email ne $email) {
3953 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3957 _reset_password_request($conn, $e, $user);
3960 # Once we have the user, we can issue the password reset request
3961 # XXX Add a wrapper method that accepts barcode + email input
3962 sub _reset_password_request {
3963 my ($conn, $e, $user) = @_;
3965 # 1. Get throttle threshold and time-to-live from OU_settings
3966 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3967 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3969 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3971 # 2. Get time of last request and number of active requests (num_active)
3972 my $active_requests = $e->json_query({
3978 transform => 'COUNT'
3981 column => 'request_time',
3987 has_been_reset => { '=' => 'f' },
3988 request_time => { '>' => $threshold_time }
3992 # Guard against no active requests
3993 if ($active_requests->[0]->{'request_time'}) {
3994 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3995 my $now = DateTime::Format::ISO8601->new();
3997 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3998 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3999 ($last_request->add_duration('1 minute') > $now)) {
4000 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4002 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4006 # TODO Check to see if the user is in a password-reset-restricted group
4008 # Otherwise, go ahead and try to get the user.
4010 # Check the number of active requests for this user
4011 $active_requests = $e->json_query({
4017 transform => 'COUNT'
4022 usr => { '=' => $user->id },
4023 has_been_reset => { '=' => 'f' },
4024 request_time => { '>' => $threshold_time }
4028 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4030 # if less than or equal to per-user threshold, proceed; otherwise, return event
4031 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4032 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4034 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4037 # Create the aupr object and insert into the database
4038 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4039 my $uuid = create_uuid_as_string(UUID_V4);
4040 $reset_request->uuid($uuid);
4041 $reset_request->usr($user->id);
4043 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4046 # Create an event to notify user of the URL to reset their password
4048 # Can we stuff this in the user_data param for trigger autocreate?
4049 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4051 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4052 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4055 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4060 __PACKAGE__->register_method(
4061 method => "commit_password_reset",
4062 api_name => "open-ils.actor.patron.password_reset.commit",
4064 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4065 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4066 "with the supplied password.",
4068 { desc => 'uuid', type => 'string' },
4069 { desc => 'password', type => 'string' },
4071 return => {desc => '1 on success, Event on error'}
4074 sub commit_password_reset {
4075 my($self, $conn, $uuid, $password) = @_;
4077 # Check to see if password reset requests are already being throttled:
4078 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4079 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4080 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4082 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4085 my $e = new_editor(xact => 1);
4087 my $aupr = $e->search_actor_usr_password_reset({
4094 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4096 my $user_id = $aupr->[0]->usr;
4097 my $user = $e->retrieve_actor_user($user_id);
4099 # Ensure we're still within the TTL for the request
4100 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4101 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4102 if ($threshold < DateTime->now(time_zone => 'local')) {
4104 $logger->info("Password reset request needed to be submitted before $threshold");
4105 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4108 # Check complexity of password against OU-defined regex
4109 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4113 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4114 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4115 $is_strong = check_password_strength_custom($password, $pw_regex);
4117 $is_strong = check_password_strength_default($password);
4122 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4125 # All is well; update the password
4126 $user->passwd($password);
4127 $e->update_actor_user($user);
4129 # And flag that this password reset request has been honoured
4130 $aupr->[0]->has_been_reset('t');
4131 $e->update_actor_usr_password_reset($aupr->[0]);
4137 sub check_password_strength_default {
4138 my $password = shift;
4139 # Use the default set of checks
4140 if ( (length($password) < 7) or
4141 ($password !~ m/.*\d+.*/) or
4142 ($password !~ m/.*[A-Za-z]+.*/)
4149 sub check_password_strength_custom {
4150 my ($password, $pw_regex) = @_;
4152 $pw_regex = qr/$pw_regex/;
4153 if ($password !~ /$pw_regex/) {
4161 __PACKAGE__->register_method(
4162 method => "event_def_opt_in_settings",
4163 api_name => "open-ils.actor.event_def.opt_in.settings",
4166 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4168 { desc => 'Authentication token', type => 'string'},
4170 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4175 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4182 sub event_def_opt_in_settings {
4183 my($self, $conn, $auth, $org_id) = @_;
4184 my $e = new_editor(authtoken => $auth);
4185 return $e->event unless $e->checkauth;
4187 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4188 return $e->event unless
4189 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4191 $org_id = $e->requestor->home_ou;
4194 # find all config.user_setting_type's related to event_defs for the requested org unit
4195 my $types = $e->json_query({
4196 select => {cust => ['name']},
4197 from => {atevdef => 'cust'},
4200 owner => $U->get_org_ancestors($org_id), # context org plus parents
4207 $conn->respond($_) for
4208 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4215 __PACKAGE__->register_method(
4216 method => "user_visible_circs",
4217 api_name => "open-ils.actor.history.circ.visible",
4220 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4222 { desc => 'Authentication token', type => 'string'},
4223 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4224 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4227 desc => q/An object with 2 fields: circulation and summary.
4228 circulation is the "circ" object. summary is the related "accs" object/,
4234 __PACKAGE__->register_method(
4235 method => "user_visible_circs",
4236 api_name => "open-ils.actor.history.circ.visible.print",
4239 desc => 'Returns printable output for the set of opt-in visible circulations',
4241 { desc => 'Authentication token', type => 'string'},
4242 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4243 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4246 desc => q/An action_trigger.event object or error event./,
4252 __PACKAGE__->register_method(
4253 method => "user_visible_circs",
4254 api_name => "open-ils.actor.history.circ.visible.email",
4257 desc => 'Emails the set of opt-in visible circulations to the requestor',
4259 { desc => 'Authentication token', type => 'string'},
4260 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4261 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4264 desc => q/undef, or event on error/
4269 __PACKAGE__->register_method(
4270 method => "user_visible_circs",
4271 api_name => "open-ils.actor.history.hold.visible",
4274 desc => 'Returns the set of opt-in visible holds',
4276 { desc => 'Authentication token', type => 'string'},
4277 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4278 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4281 desc => q/An object with 1 field: "hold"/,
4287 __PACKAGE__->register_method(
4288 method => "user_visible_circs",
4289 api_name => "open-ils.actor.history.hold.visible.print",
4292 desc => 'Returns printable output for the set of opt-in visible holds',
4294 { desc => 'Authentication token', type => 'string'},
4295 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4296 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4299 desc => q/An action_trigger.event object or error event./,
4305 __PACKAGE__->register_method(
4306 method => "user_visible_circs",
4307 api_name => "open-ils.actor.history.hold.visible.email",
4310 desc => 'Emails the set of opt-in visible holds to the requestor',
4312 { desc => 'Authentication token', type => 'string'},
4313 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4314 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4317 desc => q/undef, or event on error/
4322 sub user_visible_circs {
4323 my($self, $conn, $auth, $user_id, $options) = @_;
4325 my $is_hold = ($self->api_name =~ /hold/);
4326 my $for_print = ($self->api_name =~ /print/);
4327 my $for_email = ($self->api_name =~ /email/);
4328 my $e = new_editor(authtoken => $auth);
4329 return $e->event unless $e->checkauth;
4331 $user_id ||= $e->requestor->id;
4333 $options->{limit} ||= 50;
4334 $options->{offset} ||= 0;
4336 if($user_id != $e->requestor->id) {
4337 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4338 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4339 return $e->event unless $e->allowed($perm, $user->home_ou);
4342 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4344 my $data = $e->json_query({
4345 from => [$db_func, $user_id],
4346 limit => $$options{limit},
4347 offset => $$options{offset}
4349 # TODO: I only want IDs. code below didn't get me there
4350 # {"select":{"au":[{"column":"id", "result_field":"id",
4351 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4356 return undef unless @$data;
4360 # collect the batch of objects
4364 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4365 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4369 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4370 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4373 } elsif ($for_email) {
4375 $conn->respond_complete(1) if $for_email; # no sense in waiting
4383 my $hold = $e->retrieve_action_hold_request($id);
4384 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4385 # events will be fired from action_trigger_runner
4389 my $circ = $e->retrieve_action_circulation($id);
4390 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4391 # events will be fired from action_trigger_runner
4395 } else { # just give me the data please
4403 my $hold = $e->retrieve_action_hold_request($id);
4404 $conn->respond({hold => $hold});
4408 my $circ = $e->retrieve_action_circulation($id);
4411 summary => $U->create_circ_chain_summary($e, $id)
4420 __PACKAGE__->register_method(
4421 method => "user_saved_search_cud",
4422 api_name => "open-ils.actor.user.saved_search.cud",
4425 desc => 'Create/Update/Delete Access to user saved searches',
4427 { desc => 'Authentication token', type => 'string' },
4428 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4431 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4437 __PACKAGE__->register_method(
4438 method => "user_saved_search_cud",
4439 api_name => "open-ils.actor.user.saved_search.retrieve",
4442 desc => 'Retrieve a saved search object',
4444 { desc => 'Authentication token', type => 'string' },
4445 { desc => 'Saved Search ID', type => 'number' }
4448 desc => q/The saved search object, Event on error/,
4454 sub user_saved_search_cud {
4455 my( $self, $client, $auth, $search ) = @_;
4456 my $e = new_editor( authtoken=>$auth );
4457 return $e->die_event unless $e->checkauth;
4459 my $o_search; # prior version of the object, if any
4460 my $res; # to be returned
4462 # branch on the operation type
4464 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4466 # Get the old version, to check ownership
4467 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4468 or return $e->die_event;
4470 # You can't read somebody else's search
4471 return OpenILS::Event->new('BAD_PARAMS')
4472 unless $o_search->owner == $e->requestor->id;
4478 $e->xact_begin; # start an editor transaction
4480 if( $search->isnew ) { # Create
4482 # You can't create a search for somebody else
4483 return OpenILS::Event->new('BAD_PARAMS')
4484 unless $search->owner == $e->requestor->id;
4486 $e->create_actor_usr_saved_search( $search )
4487 or return $e->die_event;
4491 } elsif( $search->ischanged ) { # Update
4493 # You can't change ownership of a search
4494 return OpenILS::Event->new('BAD_PARAMS')
4495 unless $search->owner == $e->requestor->id;
4497 # Get the old version, to check ownership
4498 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4499 or return $e->die_event;
4501 # You can't update somebody else's search
4502 return OpenILS::Event->new('BAD_PARAMS')
4503 unless $o_search->owner == $e->requestor->id;
4506 $e->update_actor_usr_saved_search( $search )
4507 or return $e->die_event;
4511 } elsif( $search->isdeleted ) { # Delete
4513 # Get the old version, to check ownership
4514 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4515 or return $e->die_event;
4517 # You can't delete somebody else's search
4518 return OpenILS::Event->new('BAD_PARAMS')
4519 unless $o_search->owner == $e->requestor->id;
4522 $e->delete_actor_usr_saved_search( $o_search )
4523 or return $e->die_event;
4534 __PACKAGE__->register_method(
4535 method => "get_barcodes",
4536 api_name => "open-ils.actor.get_barcodes"
4540 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4541 my $e = new_editor(authtoken => $auth);
4542 return $e->event unless $e->checkauth;
4543 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4545 my $db_result = $e->json_query(
4547 'evergreen.get_barcodes',
4548 $org_id, $context, $barcode,
4552 if($context =~ /actor/) {
4553 my $filter_result = ();
4555 foreach my $result (@$db_result) {
4556 if($result->{type} eq 'actor') {
4557 if($e->requestor->id != $result->{id}) {
4558 $patron = $e->retrieve_actor_user($result->{id});
4560 push(@$filter_result, $e->event);
4563 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4564 push(@$filter_result, $result);
4567 push(@$filter_result, $e->event);
4571 push(@$filter_result, $result);
4575 push(@$filter_result, $result);
4578 return $filter_result;
4584 __PACKAGE__->register_method(
4585 method => 'address_alert_test',
4586 api_name => 'open-ils.actor.address_alert.test',
4588 desc => "Tests a set of address fields to determine if they match with an address_alert",
4590 {desc => 'Authentication token', type => 'string'},
4591 {desc => 'Org Unit', type => 'number'},
4592 {desc => 'Fields', type => 'hash'},
4594 return => {desc => 'List of matching address_alerts'}
4598 sub address_alert_test {
4599 my ($self, $client, $auth, $org_unit, $fields) = @_;
4600 return [] unless $fields and grep {$_} values %$fields;
4602 my $e = new_editor(authtoken => $auth);
4603 return $e->event unless $e->checkauth;
4604 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4605 $org_unit ||= $e->requestor->ws_ou;
4607 my $alerts = $e->json_query({
4609 'actor.address_alert_matches',
4617 $$fields{post_code},
4618 $$fields{mailing_address},
4619 $$fields{billing_address}
4623 # map the json_query hashes to real objects
4625 map {$e->retrieve_actor_address_alert($_)}
4626 (map {$_->{id}} @$alerts)
4630 __PACKAGE__->register_method(
4631 method => "mark_users_contact_invalid",
4632 api_name => "open-ils.actor.invalidate.email",
4634 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",
4636 {desc => "Authentication token", type => "string"},
4637 {desc => "Patron ID", type => "number"},
4638 {desc => "Additional note text (optional)", type => "string"},
4639 {desc => "penalty org unit ID (optional)", type => "number"}
4641 return => {desc => "Event describing success or failure", type => "object"}
4645 __PACKAGE__->register_method(
4646 method => "mark_users_contact_invalid",
4647 api_name => "open-ils.actor.invalidate.day_phone",
4649 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",
4651 {desc => "Authentication token", type => "string"},
4652 {desc => "Patron ID", type => "number"},
4653 {desc => "Additional note text (optional)", type => "string"},
4654 {desc => "penalty org unit ID (optional)", type => "number"}
4656 return => {desc => "Event describing success or failure", type => "object"}
4660 __PACKAGE__->register_method(
4661 method => "mark_users_contact_invalid",
4662 api_name => "open-ils.actor.invalidate.evening_phone",
4664 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",
4666 {desc => "Authentication token", type => "string"},
4667 {desc => "Patron ID", type => "number"},
4668 {desc => "Additional note text (optional)", type => "string"},
4669 {desc => "penalty org unit ID (optional)", type => "number"}
4671 return => {desc => "Event describing success or failure", type => "object"}
4675 __PACKAGE__->register_method(
4676 method => "mark_users_contact_invalid",
4677 api_name => "open-ils.actor.invalidate.other_phone",
4679 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",
4681 {desc => "Authentication token", type => "string"},
4682 {desc => "Patron ID", type => "number"},
4683 {desc => "Additional note text (optional)", type => "string"},
4684 {desc => "penalty org unit ID (optional, default to top of org tree)",
4687 return => {desc => "Event describing success or failure", type => "object"}
4691 sub mark_users_contact_invalid {
4692 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4694 # This method invalidates an email address or a phone_number which
4695 # removes the bad email address or phone number, copying its contents
4696 # to a patron note, and institutes a standing penalty for "bad email"
4697 # or "bad phone number" which is cleared when the user is saved or
4698 # optionally only when the user is saved with an email address or
4699 # phone number (or staff manually delete the penalty).
4701 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4703 my $e = new_editor(authtoken => $auth, xact => 1);
4704 return $e->die_event unless $e->checkauth;
4706 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4707 $e, $contact_type, {usr => $patron_id},
4708 $addl_note, $penalty_ou, $e->requestor->id
4712 # Putting the following method in open-ils.actor is a bad fit, except in that
4713 # it serves an interface that lives under 'actor' in the templates directory,
4714 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4716 __PACKAGE__->register_method(
4717 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4718 method => "get_all_at_reactors_in_use",
4723 { name => 'authtoken', type => 'string' }
4726 desc => 'list of reactor names', type => 'array'
4731 sub get_all_at_reactors_in_use {
4732 my ($self, $conn, $auth) = @_;
4734 my $e = new_editor(authtoken => $auth);
4735 $e->checkauth or return $e->die_event;
4736 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4738 my $reactors = $e->json_query({
4740 atevdef => [{column => "reactor", transform => "distinct"}]
4742 from => {atevdef => {}}
4745 return $e->die_event unless ref $reactors eq "ARRAY";
4748 return [ map { $_->{reactor} } @$reactors ];
4751 __PACKAGE__->register_method(
4752 method => "filter_group_entry_crud",
4753 api_name => "open-ils.actor.filter_group_entry.crud",
4756 Provides CRUD access to filter group entry objects. These are not full accessible
4757 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4758 are not accessible via PCRUD (because they have no fields against which to link perms)
4761 {desc => "Authentication token", type => "string"},
4762 {desc => "Entry ID / Entry Object", type => "number"},
4763 {desc => "Additional note text (optional)", type => "string"},
4764 {desc => "penalty org unit ID (optional, default to top of org tree)",
4768 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4774 sub filter_group_entry_crud {
4775 my ($self, $conn, $auth, $arg) = @_;
4777 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4778 my $e = new_editor(authtoken => $auth, xact => 1);
4779 return $e->die_event unless $e->checkauth;
4785 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4786 or return $e->die_event;
4788 return $e->die_event unless $e->allowed(
4789 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4791 my $query = $arg->query;
4792 $query = $e->create_actor_search_query($query) or return $e->die_event;
4793 $arg->query($query->id);
4794 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4795 $entry->query($query);
4800 } elsif ($arg->ischanged) {
4802 my $entry = $e->retrieve_actor_search_filter_group_entry([
4805 flesh_fields => {asfge => ['grp']}
4807 ]) or return $e->die_event;
4809 return $e->die_event unless $e->allowed(
4810 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4812 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4813 $arg->query($arg->query->id);
4814 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4815 $arg->query($query);
4820 } elsif ($arg->isdeleted) {
4822 my $entry = $e->retrieve_actor_search_filter_group_entry([
4825 flesh_fields => {asfge => ['grp', 'query']}
4827 ]) or return $e->die_event;
4829 return $e->die_event unless $e->allowed(
4830 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4832 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4833 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4846 my $entry = $e->retrieve_actor_search_filter_group_entry([
4849 flesh_fields => {asfge => ['grp', 'query']}
4851 ]) or return $e->die_event;
4853 return $e->die_event unless $e->allowed(
4854 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4855 $entry->grp->owner);
4858 $entry->grp($entry->grp->id); # for consistency