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/;
1747 my $unread_msgs = $e->search_actor_usr_message([
1748 {usr => $user_id, read_date => undef, deleted => 'f'},
1754 first_given_name => $user->first_given_name,
1755 second_given_name => $user->second_given_name,
1756 family_name => $user->family_name,
1757 alias => $user->alias,
1758 usrname => $user->usrname
1760 fines => $fines->to_bare_hash,
1763 messages => { unread => scalar(@$unread_msgs) }
1768 ##### a small consolidation of related method registrations
1769 my $common_params = [
1770 { desc => 'Authentication token', type => 'string' },
1771 { desc => 'User ID', type => 'string' },
1772 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1773 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1776 'open-ils.actor.user.transactions' => '',
1777 'open-ils.actor.user.transactions.fleshed' => '',
1778 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1779 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1780 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1781 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1784 foreach (keys %methods) {
1786 method => "user_transactions",
1789 desc => 'For a given user, retrieve a list of '
1790 . (/\.fleshed/ ? 'fleshed ' : '')
1791 . 'transactions' . $methods{$_}
1792 . ' optionally limited to transactions of a given type.',
1793 params => $common_params,
1795 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1796 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1800 $args{authoritative} = 1;
1801 __PACKAGE__->register_method(%args);
1804 # Now for the counts
1806 'open-ils.actor.user.transactions.count' => '',
1807 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1808 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1811 foreach (keys %methods) {
1813 method => "user_transactions",
1816 desc => 'For a given user, retrieve a count of open '
1817 . 'transactions' . $methods{$_}
1818 . ' optionally limited to transactions of a given type.',
1819 params => $common_params,
1820 return => { desc => "Integer count of transactions, or event on error" }
1823 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1824 __PACKAGE__->register_method(%args);
1827 __PACKAGE__->register_method(
1828 method => "user_transactions",
1829 api_name => "open-ils.actor.user.transactions.have_balance.total",
1832 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1833 . ' optionally limited to transactions of a given type.',
1834 params => $common_params,
1835 return => { desc => "Decimal balance value, or event on error" }
1840 sub user_transactions {
1841 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1844 my $e = new_editor(authtoken => $auth);
1845 return $e->event unless $e->checkauth;
1847 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1849 return $e->event unless
1850 $e->requestor->id == $user_id or
1851 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1853 my $api = $self->api_name();
1855 my $filter = ($api =~ /have_balance/o) ?
1856 { 'balance_owed' => { '<>' => 0 } }:
1857 { 'total_owed' => { '>' => 0 } };
1859 my $method = 'open-ils.actor.user.transactions.history.still_open';
1860 $method = "$method.authoritative" if $api =~ /authoritative/;
1861 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1863 if($api =~ /total/o) {
1865 $total += $_->balance_owed for @$trans;
1869 ($api =~ /count/o ) and return scalar @$trans;
1870 ($api !~ /fleshed/o) and return $trans;
1873 for my $t (@$trans) {
1875 if( $t->xact_type ne 'circulation' ) {
1876 push @resp, {transaction => $t};
1880 my $circ_data = flesh_circ($e, $t->id);
1881 push @resp, {transaction => $t, %$circ_data};
1888 __PACKAGE__->register_method(
1889 method => "user_transaction_retrieve",
1890 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1893 notes => "Returns a fleshed transaction record"
1896 __PACKAGE__->register_method(
1897 method => "user_transaction_retrieve",
1898 api_name => "open-ils.actor.user.transaction.retrieve",
1901 notes => "Returns a transaction record"
1904 sub user_transaction_retrieve {
1905 my($self, $client, $auth, $bill_id) = @_;
1907 my $e = new_editor(authtoken => $auth);
1908 return $e->event unless $e->checkauth;
1910 my $trans = $e->retrieve_money_billable_transaction_summary(
1911 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1913 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1915 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1917 return $trans unless $self->api_name =~ /flesh/;
1918 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1920 my $circ_data = flesh_circ($e, $trans->id, 1);
1922 return {transaction => $trans, %$circ_data};
1927 my $circ_id = shift;
1928 my $flesh_copy = shift;
1930 my $circ = $e->retrieve_action_circulation([
1934 circ => ['target_copy'],
1935 acp => ['call_number'],
1942 my $copy = $circ->target_copy;
1944 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1945 $mods = new Fieldmapper::metabib::virtual_record;
1946 $mods->doc_id(OILS_PRECAT_RECORD);
1947 $mods->title($copy->dummy_title);
1948 $mods->author($copy->dummy_author);
1951 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1955 $circ->target_copy($circ->target_copy->id);
1956 $copy->call_number($copy->call_number->id);
1958 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1962 __PACKAGE__->register_method(
1963 method => "hold_request_count",
1964 api_name => "open-ils.actor.user.hold_requests.count",
1968 Returns hold ready vs. total counts.
1969 If a context org unit is provided, a third value
1970 is returned with key 'behind_desk', which reports
1971 how many holds are ready at the pickup library
1972 with the behind_desk flag set to true.
1976 sub hold_request_count {
1977 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
1978 my $e = new_editor(authtoken => $authtoken);
1979 return $e->event unless $e->checkauth;
1981 $user_id = $e->requestor->id unless defined $user_id;
1983 if($e->requestor->id ne $user_id) {
1984 my $user = $e->retrieve_actor_user($user_id);
1985 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1988 my $holds = $e->json_query({
1989 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
1993 fulfillment_time => {"=" => undef },
1994 cancel_time => undef,
1999 $_->{current_shelf_lib} and # avoid undef warnings
2000 $_->{pickup_lib} eq $_->{current_shelf_lib}
2004 total => scalar(@$holds),
2005 ready => scalar(@ready)
2009 # count of holds ready at pickup lib with behind_desk true.
2010 $resp->{behind_desk} = scalar(
2012 $_->{pickup_lib} == $ctx_org and
2013 $U->is_true($_->{behind_desk})
2021 __PACKAGE__->register_method(
2022 method => "checked_out",
2023 api_name => "open-ils.actor.user.checked_out",
2027 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2028 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2029 . "(i.e., outstanding balance or some other pending action on the circ). "
2030 . "The .count method also includes a 'total' field which sums all open circs.",
2032 { desc => 'Authentication Token', type => 'string'},
2033 { desc => 'User ID', type => 'string'},
2036 desc => 'Returns event on error, or an object with ID lists, like: '
2037 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2042 __PACKAGE__->register_method(
2043 method => "checked_out",
2044 api_name => "open-ils.actor.user.checked_out.count",
2047 signature => q/@see open-ils.actor.user.checked_out/
2051 my( $self, $conn, $auth, $userid ) = @_;
2053 my $e = new_editor(authtoken=>$auth);
2054 return $e->event unless $e->checkauth;
2056 if( $userid ne $e->requestor->id ) {
2057 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2058 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2060 # see if there is a friend link allowing circ.view perms
2061 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2062 $e, $userid, $e->requestor->id, 'circ.view');
2063 return $e->event unless $allowed;
2067 my $count = $self->api_name =~ /count/;
2068 return _checked_out( $count, $e, $userid );
2072 my( $iscount, $e, $userid ) = @_;
2078 claims_returned => [],
2081 my $meth = 'retrieve_action_open_circ_';
2089 claims_returned => 0,
2096 my $data = $e->$meth($userid);
2100 $result{$_} += $data->$_() for (keys %result);
2101 $result{total} += $data->$_() for (keys %result);
2103 for my $k (keys %result) {
2104 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2114 __PACKAGE__->register_method(
2115 method => "checked_in_with_fines",
2116 api_name => "open-ils.actor.user.checked_in_with_fines",
2119 signature => q/@see open-ils.actor.user.checked_out/
2122 sub checked_in_with_fines {
2123 my( $self, $conn, $auth, $userid ) = @_;
2125 my $e = new_editor(authtoken=>$auth);
2126 return $e->event unless $e->checkauth;
2128 if( $userid ne $e->requestor->id ) {
2129 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2132 # money is owed on these items and they are checked in
2133 my $open = $e->search_action_circulation(
2136 xact_finish => undef,
2137 checkin_time => { "!=" => undef },
2142 my( @lost, @cr, @lo );
2143 for my $c (@$open) {
2144 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2145 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2146 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2151 claims_returned => \@cr,
2152 long_overdue => \@lo
2158 my ($api, $desc, $auth) = @_;
2159 $desc = $desc ? (" " . $desc) : '';
2160 my $ids = ($api =~ /ids$/) ? 1 : 0;
2163 method => "user_transaction_history",
2164 api_name => "open-ils.actor.user.transactions.$api",
2166 desc => "For a given User ID, returns a list of billable transaction" .
2167 ($ids ? " id" : '') .
2168 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2169 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2171 {desc => 'Authentication token', type => 'string'},
2172 {desc => 'User ID', type => 'number'},
2173 {desc => 'Transaction type (optional)', type => 'number'},
2174 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2177 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2181 $auth and push @sig, (authoritative => 1);
2185 my %auth_hist_methods = (
2187 'history.have_charge' => 'that have an initial charge',
2188 'history.still_open' => 'that are not finished',
2189 'history.have_balance' => 'that have a balance',
2190 'history.have_bill' => 'that have billings',
2191 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2192 'history.have_payment' => 'that have at least 1 payment',
2195 foreach (keys %auth_hist_methods) {
2196 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2197 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2198 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2201 sub user_transaction_history {
2202 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2206 my $e = new_editor(authtoken=>$auth);
2207 return $e->die_event unless $e->checkauth;
2209 if ($e->requestor->id ne $userid) {
2210 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2213 my $api = $self->api_name;
2214 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2216 if(defined($type)) {
2217 $filter->{'xact_type'} = $type;
2220 if($api =~ /have_bill_or_payment/o) {
2222 # transactions that have a non-zero sum across all billings or at least 1 payment
2223 $filter->{'-or'} = {
2224 'balance_owed' => { '<>' => 0 },
2225 'last_payment_ts' => { '<>' => undef }
2228 } elsif($api =~ /have_payment/) {
2230 $filter->{last_payment_ts} ||= {'<>' => undef};
2232 } elsif( $api =~ /have_balance/o) {
2234 # transactions that have a non-zero overall balance
2235 $filter->{'balance_owed'} = { '<>' => 0 };
2237 } elsif( $api =~ /have_charge/o) {
2239 # transactions that have at least 1 billing, regardless of whether it was voided
2240 $filter->{'last_billing_ts'} = { '<>' => undef };
2242 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2244 # transactions that have non-zero sum across all billings. This will exclude
2245 # xacts where all billings have been voided
2246 $filter->{'total_owed'} = { '<>' => 0 };
2249 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2250 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2251 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2253 my $mbts = $e->search_money_billable_transaction_summary(
2254 [ { usr => $userid, @xact_finish, %$filter },
2259 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2260 return $mbts unless $api =~ /fleshed/;
2263 for my $t (@$mbts) {
2265 if( $t->xact_type ne 'circulation' ) {
2266 push @resp, {transaction => $t};
2270 my $circ_data = flesh_circ($e, $t->id);
2271 push @resp, {transaction => $t, %$circ_data};
2279 __PACKAGE__->register_method(
2280 method => "user_perms",
2281 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2283 notes => "Returns a list of permissions"
2287 my( $self, $client, $authtoken, $user ) = @_;
2289 my( $staff, $evt ) = $apputils->checkses($authtoken);
2290 return $evt if $evt;
2292 $user ||= $staff->id;
2294 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2298 return $apputils->simple_scalar_request(
2300 "open-ils.storage.permission.user_perms.atomic",
2304 __PACKAGE__->register_method(
2305 method => "retrieve_perms",
2306 api_name => "open-ils.actor.permissions.retrieve",
2307 notes => "Returns a list of permissions"
2309 sub retrieve_perms {
2310 my( $self, $client ) = @_;
2311 return $apputils->simple_scalar_request(
2313 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2314 { id => { '!=' => undef } }
2318 __PACKAGE__->register_method(
2319 method => "retrieve_groups",
2320 api_name => "open-ils.actor.groups.retrieve",
2321 notes => "Returns a list of user groups"
2323 sub retrieve_groups {
2324 my( $self, $client ) = @_;
2325 return new_editor()->retrieve_all_permission_grp_tree();
2328 __PACKAGE__->register_method(
2329 method => "retrieve_org_address",
2330 api_name => "open-ils.actor.org_unit.address.retrieve",
2331 notes => <<' NOTES');
2332 Returns an org_unit address by ID
2333 @param An org_address ID
2335 sub retrieve_org_address {
2336 my( $self, $client, $id ) = @_;
2337 return $apputils->simple_scalar_request(
2339 "open-ils.cstore.direct.actor.org_address.retrieve",
2344 __PACKAGE__->register_method(
2345 method => "retrieve_groups_tree",
2346 api_name => "open-ils.actor.groups.tree.retrieve",
2347 notes => "Returns a list of user groups"
2350 sub retrieve_groups_tree {
2351 my( $self, $client ) = @_;
2352 return new_editor()->search_permission_grp_tree(
2357 flesh_fields => { pgt => ["children"] },
2358 order_by => { pgt => 'name'}
2365 __PACKAGE__->register_method(
2366 method => "add_user_to_groups",
2367 api_name => "open-ils.actor.user.set_groups",
2368 notes => "Adds a user to one or more permission groups"
2371 sub add_user_to_groups {
2372 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2374 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2375 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2376 return $evt if $evt;
2378 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2379 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2380 return $evt if $evt;
2382 $apputils->simplereq(
2384 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2386 for my $group (@$groups) {
2387 my $link = Fieldmapper::permission::usr_grp_map->new;
2389 $link->usr($userid);
2391 my $id = $apputils->simplereq(
2393 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2399 __PACKAGE__->register_method(
2400 method => "get_user_perm_groups",
2401 api_name => "open-ils.actor.user.get_groups",
2402 notes => "Retrieve a user's permission groups."
2406 sub get_user_perm_groups {
2407 my( $self, $client, $authtoken, $userid ) = @_;
2409 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2410 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2411 return $evt if $evt;
2413 return $apputils->simplereq(
2415 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2419 __PACKAGE__->register_method(
2420 method => "get_user_work_ous",
2421 api_name => "open-ils.actor.user.get_work_ous",
2422 notes => "Retrieve a user's work org units."
2425 __PACKAGE__->register_method(
2426 method => "get_user_work_ous",
2427 api_name => "open-ils.actor.user.get_work_ous.ids",
2428 notes => "Retrieve a user's work org units."
2431 sub get_user_work_ous {
2432 my( $self, $client, $auth, $userid ) = @_;
2433 my $e = new_editor(authtoken=>$auth);
2434 return $e->event unless $e->checkauth;
2435 $userid ||= $e->requestor->id;
2437 if($e->requestor->id != $userid) {
2438 my $user = $e->retrieve_actor_user($userid)
2439 or return $e->event;
2440 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2443 return $e->search_permission_usr_work_ou_map({usr => $userid})
2444 unless $self->api_name =~ /.ids$/;
2446 # client just wants a list of org IDs
2447 return $U->get_user_work_ou_ids($e, $userid);
2452 __PACKAGE__->register_method(
2453 method => 'register_workstation',
2454 api_name => 'open-ils.actor.workstation.register.override',
2455 signature => q/@see open-ils.actor.workstation.register/
2458 __PACKAGE__->register_method(
2459 method => 'register_workstation',
2460 api_name => 'open-ils.actor.workstation.register',
2462 Registers a new workstion in the system
2463 @param authtoken The login session key
2464 @param name The name of the workstation id
2465 @param owner The org unit that owns this workstation
2466 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2467 if the name is already in use.
2471 sub register_workstation {
2472 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2474 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2475 return $e->die_event unless $e->checkauth;
2476 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2477 my $existing = $e->search_actor_workstation({name => $name})->[0];
2478 $oargs = { all => 1 } unless defined $oargs;
2482 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2483 # workstation with the given name exists.
2485 if($owner ne $existing->owning_lib) {
2486 # if necessary, update the owning_lib of the workstation
2488 $logger->info("changing owning lib of workstation ".$existing->id.
2489 " from ".$existing->owning_lib." to $owner");
2490 return $e->die_event unless
2491 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2493 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2495 $existing->owning_lib($owner);
2496 return $e->die_event unless $e->update_actor_workstation($existing);
2502 "attempt to register an existing workstation. returning existing ID");
2505 return $existing->id;
2508 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2512 my $ws = Fieldmapper::actor::workstation->new;
2513 $ws->owning_lib($owner);
2515 $e->create_actor_workstation($ws) or return $e->die_event;
2517 return $ws->id; # note: editor sets the id on the new object for us
2520 __PACKAGE__->register_method(
2521 method => 'workstation_list',
2522 api_name => 'open-ils.actor.workstation.list',
2524 Returns a list of workstations registered at the given location
2525 @param authtoken The login session key
2526 @param ids A list of org_unit.id's for the workstation owners
2530 sub workstation_list {
2531 my( $self, $conn, $authtoken, @orgs ) = @_;
2533 my $e = new_editor(authtoken=>$authtoken);
2534 return $e->event unless $e->checkauth;
2539 unless $e->allowed('REGISTER_WORKSTATION', $o);
2540 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2546 __PACKAGE__->register_method(
2547 method => 'fetch_patron_note',
2548 api_name => 'open-ils.actor.note.retrieve.all',
2551 Returns a list of notes for a given user
2552 Requestor must have VIEW_USER permission if pub==false and
2553 @param authtoken The login session key
2554 @param args Hash of params including
2555 patronid : the patron's id
2556 pub : true if retrieving only public notes
2560 sub fetch_patron_note {
2561 my( $self, $conn, $authtoken, $args ) = @_;
2562 my $patronid = $$args{patronid};
2564 my($reqr, $evt) = $U->checkses($authtoken);
2565 return $evt if $evt;
2568 ($patron, $evt) = $U->fetch_user($patronid);
2569 return $evt if $evt;
2572 if( $patronid ne $reqr->id ) {
2573 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2574 return $evt if $evt;
2576 return $U->cstorereq(
2577 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2578 { usr => $patronid, pub => 't' } );
2581 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2582 return $evt if $evt;
2584 return $U->cstorereq(
2585 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2588 __PACKAGE__->register_method(
2589 method => 'create_user_note',
2590 api_name => 'open-ils.actor.note.create',
2592 Creates a new note for the given user
2593 @param authtoken The login session key
2594 @param note The note object
2597 sub create_user_note {
2598 my( $self, $conn, $authtoken, $note ) = @_;
2599 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2600 return $e->die_event unless $e->checkauth;
2602 my $user = $e->retrieve_actor_user($note->usr)
2603 or return $e->die_event;
2605 return $e->die_event unless
2606 $e->allowed('UPDATE_USER',$user->home_ou);
2608 $note->creator($e->requestor->id);
2609 $e->create_actor_usr_note($note) or return $e->die_event;
2615 __PACKAGE__->register_method(
2616 method => 'delete_user_note',
2617 api_name => 'open-ils.actor.note.delete',
2619 Deletes a note for the given user
2620 @param authtoken The login session key
2621 @param noteid The note id
2624 sub delete_user_note {
2625 my( $self, $conn, $authtoken, $noteid ) = @_;
2627 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2628 return $e->die_event unless $e->checkauth;
2629 my $note = $e->retrieve_actor_usr_note($noteid)
2630 or return $e->die_event;
2631 my $user = $e->retrieve_actor_user($note->usr)
2632 or return $e->die_event;
2633 return $e->die_event unless
2634 $e->allowed('UPDATE_USER', $user->home_ou);
2636 $e->delete_actor_usr_note($note) or return $e->die_event;
2642 __PACKAGE__->register_method(
2643 method => 'update_user_note',
2644 api_name => 'open-ils.actor.note.update',
2646 @param authtoken The login session key
2647 @param note The note
2651 sub update_user_note {
2652 my( $self, $conn, $auth, $note ) = @_;
2653 my $e = new_editor(authtoken=>$auth, xact=>1);
2654 return $e->die_event unless $e->checkauth;
2655 my $patron = $e->retrieve_actor_user($note->usr)
2656 or return $e->die_event;
2657 return $e->die_event unless
2658 $e->allowed('UPDATE_USER', $patron->home_ou);
2659 $e->update_actor_user_note($note)
2660 or return $e->die_event;
2665 __PACKAGE__->register_method(
2666 method => 'fetch_patron_messages',
2667 api_name => 'open-ils.actor.message.retrieve',
2670 Returns a list of notes for a given user, not
2671 including ones marked deleted
2672 @param authtoken The login session key
2673 @param patronid patron ID
2674 @param options hash containing optional limit and offset
2678 sub fetch_patron_messages {
2679 my( $self, $conn, $auth, $patronid, $options ) = @_;
2683 my $e = new_editor(authtoken => $auth);
2684 return $e->die_event unless $e->checkauth;
2686 if ($e->requestor->id ne $patronid) {
2687 return $e->die_event unless $e->allowed('VIEW_USER');
2690 my $select_clause = { usr => $patronid };
2691 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2692 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2693 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2695 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2700 __PACKAGE__->register_method(
2701 method => 'create_closed_date',
2702 api_name => 'open-ils.actor.org_unit.closed_date.create',
2704 Creates a new closing entry for the given org_unit
2705 @param authtoken The login session key
2706 @param note The closed_date object
2709 sub create_closed_date {
2710 my( $self, $conn, $authtoken, $cd ) = @_;
2712 my( $user, $evt ) = $U->checkses($authtoken);
2713 return $evt if $evt;
2715 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2716 return $evt if $evt;
2718 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2720 my $id = $U->storagereq(
2721 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2722 return $U->DB_UPDATE_FAILED($cd) unless $id;
2727 __PACKAGE__->register_method(
2728 method => 'delete_closed_date',
2729 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2731 Deletes a closing entry for the given org_unit
2732 @param authtoken The login session key
2733 @param noteid The close_date id
2736 sub delete_closed_date {
2737 my( $self, $conn, $authtoken, $cd ) = @_;
2739 my( $user, $evt ) = $U->checkses($authtoken);
2740 return $evt if $evt;
2743 ($cd_obj, $evt) = fetch_closed_date($cd);
2744 return $evt if $evt;
2746 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2747 return $evt if $evt;
2749 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2751 my $stat = $U->storagereq(
2752 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2753 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2758 __PACKAGE__->register_method(
2759 method => 'usrname_exists',
2760 api_name => 'open-ils.actor.username.exists',
2762 desc => 'Check if a username is already taken (by an undeleted patron)',
2764 {desc => 'Authentication token', type => 'string'},
2765 {desc => 'Username', type => 'string'}
2768 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2773 sub usrname_exists {
2774 my( $self, $conn, $auth, $usrname ) = @_;
2775 my $e = new_editor(authtoken=>$auth);
2776 return $e->event unless $e->checkauth;
2777 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2778 return $$a[0] if $a and @$a;
2782 __PACKAGE__->register_method(
2783 method => 'barcode_exists',
2784 api_name => 'open-ils.actor.barcode.exists',
2786 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2789 sub barcode_exists {
2790 my( $self, $conn, $auth, $barcode ) = @_;
2791 my $e = new_editor(authtoken=>$auth);
2792 return $e->event unless $e->checkauth;
2793 my $card = $e->search_actor_card({barcode => $barcode});
2799 #return undef unless @$card;
2800 #return $card->[0]->usr;
2804 __PACKAGE__->register_method(
2805 method => 'retrieve_net_levels',
2806 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2809 sub retrieve_net_levels {
2810 my( $self, $conn, $auth ) = @_;
2811 my $e = new_editor(authtoken=>$auth);
2812 return $e->event unless $e->checkauth;
2813 return $e->retrieve_all_config_net_access_level();
2816 # Retain the old typo API name just in case
2817 __PACKAGE__->register_method(
2818 method => 'fetch_org_by_shortname',
2819 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2821 __PACKAGE__->register_method(
2822 method => 'fetch_org_by_shortname',
2823 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2825 sub fetch_org_by_shortname {
2826 my( $self, $conn, $sname ) = @_;
2827 my $e = new_editor();
2828 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2829 return $e->event unless $org;
2834 __PACKAGE__->register_method(
2835 method => 'session_home_lib',
2836 api_name => 'open-ils.actor.session.home_lib',
2839 sub session_home_lib {
2840 my( $self, $conn, $auth ) = @_;
2841 my $e = new_editor(authtoken=>$auth);
2842 return undef unless $e->checkauth;
2843 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2844 return $org->shortname;
2847 __PACKAGE__->register_method(
2848 method => 'session_safe_token',
2849 api_name => 'open-ils.actor.session.safe_token',
2851 Returns a hashed session ID that is safe for export to the world.
2852 This safe token will expire after 1 hour of non-use.
2853 @param auth Active authentication token
2857 sub session_safe_token {
2858 my( $self, $conn, $auth ) = @_;
2859 my $e = new_editor(authtoken=>$auth);
2860 return undef unless $e->checkauth;
2862 my $safe_token = md5_hex($auth);
2864 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2866 # Add more like the following if needed...
2868 "safe-token-home_lib-shortname-$safe_token",
2869 $e->retrieve_actor_org_unit(
2870 $e->requestor->home_ou
2879 __PACKAGE__->register_method(
2880 method => 'safe_token_home_lib',
2881 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2883 Returns the home library shortname from the session
2884 asscociated with a safe token from generated by
2885 open-ils.actor.session.safe_token.
2886 @param safe_token Active safe token
2890 sub safe_token_home_lib {
2891 my( $self, $conn, $safe_token ) = @_;
2893 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2894 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2898 __PACKAGE__->register_method(
2899 method => "update_penalties",
2900 api_name => "open-ils.actor.user.penalties.update"
2903 sub update_penalties {
2904 my($self, $conn, $auth, $user_id) = @_;
2905 my $e = new_editor(authtoken=>$auth, xact => 1);
2906 return $e->die_event unless $e->checkauth;
2907 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2908 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2909 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2910 return $evt if $evt;
2916 __PACKAGE__->register_method(
2917 method => "apply_penalty",
2918 api_name => "open-ils.actor.user.penalty.apply"
2922 my($self, $conn, $auth, $penalty) = @_;
2924 my $e = new_editor(authtoken=>$auth, xact => 1);
2925 return $e->die_event unless $e->checkauth;
2927 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2928 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2930 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2933 (defined $ptype->org_depth) ?
2934 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2937 $penalty->org_unit($ctx_org);
2938 $penalty->staff($e->requestor->id);
2939 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2942 return $penalty->id;
2945 __PACKAGE__->register_method(
2946 method => "remove_penalty",
2947 api_name => "open-ils.actor.user.penalty.remove"
2950 sub remove_penalty {
2951 my($self, $conn, $auth, $penalty) = @_;
2952 my $e = new_editor(authtoken=>$auth, xact => 1);
2953 return $e->die_event unless $e->checkauth;
2954 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2955 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2957 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2962 __PACKAGE__->register_method(
2963 method => "update_penalty_note",
2964 api_name => "open-ils.actor.user.penalty.note.update"
2967 sub update_penalty_note {
2968 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2969 my $e = new_editor(authtoken=>$auth, xact => 1);
2970 return $e->die_event unless $e->checkauth;
2971 for my $penalty_id (@$penalty_ids) {
2972 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2973 if (! $penalty ) { return $e->die_event; }
2974 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2975 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2977 $penalty->note( $note ); $penalty->ischanged( 1 );
2979 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2985 __PACKAGE__->register_method(
2986 method => "ranged_penalty_thresholds",
2987 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2991 sub ranged_penalty_thresholds {
2992 my($self, $conn, $auth, $context_org) = @_;
2993 my $e = new_editor(authtoken=>$auth);
2994 return $e->event unless $e->checkauth;
2995 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2996 my $list = $e->search_permission_grp_penalty_threshold([
2997 {org_unit => $U->get_org_ancestors($context_org)},
2998 {order_by => {pgpt => 'id'}}
3000 $conn->respond($_) for @$list;
3006 __PACKAGE__->register_method(
3007 method => "user_retrieve_fleshed_by_id",
3009 api_name => "open-ils.actor.user.fleshed.retrieve",
3012 sub user_retrieve_fleshed_by_id {
3013 my( $self, $client, $auth, $user_id, $fields ) = @_;
3014 my $e = new_editor(authtoken => $auth);
3015 return $e->event unless $e->checkauth;
3017 if( $e->requestor->id != $user_id ) {
3018 return $e->event unless $e->allowed('VIEW_USER');
3025 "standing_penalties",
3031 return new_flesh_user($user_id, $fields, $e);
3035 sub new_flesh_user {
3038 my $fields = shift || [];
3041 my $fetch_penalties = 0;
3042 if(grep {$_ eq 'standing_penalties'} @$fields) {
3043 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3044 $fetch_penalties = 1;
3047 my $fetch_usr_act = 0;
3048 if(grep {$_ eq 'usr_activity'} @$fields) {
3049 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3053 my $user = $e->retrieve_actor_user(
3058 "flesh_fields" => { "au" => $fields }
3061 ) or return $e->die_event;
3064 if( grep { $_ eq 'addresses' } @$fields ) {
3066 $user->addresses([]) unless @{$user->addresses};
3067 # don't expose "replaced" addresses by default
3068 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3070 if( ref $user->billing_address ) {
3071 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3072 push( @{$user->addresses}, $user->billing_address );
3076 if( ref $user->mailing_address ) {
3077 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3078 push( @{$user->addresses}, $user->mailing_address );
3083 if($fetch_penalties) {
3084 # grab the user penalties ranged for this location
3085 $user->standing_penalties(
3086 $e->search_actor_user_standing_penalty([
3089 {stop_date => undef},
3090 {stop_date => {'>' => 'now'}}
3092 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3095 flesh_fields => {ausp => ['standing_penalty']}
3101 # retrieve the most recent usr_activity entry
3102 if ($fetch_usr_act) {
3104 # max number to return for simple patron fleshing
3105 my $limit = $U->ou_ancestor_setting_value(
3106 $e->requestor->ws_ou,
3107 'circ.patron.usr_activity_retrieve.max');
3111 flesh_fields => {auact => ['etype']},
3112 order_by => {auact => 'event_time DESC'},
3115 # 0 == none, <0 == return all
3116 $limit = 1 unless defined $limit;
3117 $opts->{limit} = $limit if $limit > 0;
3119 $user->usr_activity(
3121 [] : # skip the DB call
3122 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3127 $user->clear_passwd();
3134 __PACKAGE__->register_method(
3135 method => "user_retrieve_parts",
3136 api_name => "open-ils.actor.user.retrieve.parts",
3139 sub user_retrieve_parts {
3140 my( $self, $client, $auth, $user_id, $fields ) = @_;
3141 my $e = new_editor(authtoken => $auth);
3142 return $e->event unless $e->checkauth;
3143 $user_id ||= $e->requestor->id;
3144 if( $e->requestor->id != $user_id ) {
3145 return $e->event unless $e->allowed('VIEW_USER');
3148 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3149 push(@resp, $user->$_()) for(@$fields);
3155 __PACKAGE__->register_method(
3156 method => 'user_opt_in_enabled',
3157 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3158 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3161 sub user_opt_in_enabled {
3162 my($self, $conn) = @_;
3163 my $sc = OpenSRF::Utils::SettingsClient->new;
3164 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3169 __PACKAGE__->register_method(
3170 method => 'user_opt_in_at_org',
3171 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3173 @param $auth The auth token
3174 @param user_id The ID of the user to test
3175 @return 1 if the user has opted in at the specified org,
3176 event on error, and 0 otherwise. /
3178 sub user_opt_in_at_org {
3179 my($self, $conn, $auth, $user_id) = @_;
3181 # see if we even need to enforce the opt-in value
3182 return 1 unless user_opt_in_enabled($self);
3184 my $e = new_editor(authtoken => $auth);
3185 return $e->event unless $e->checkauth;
3187 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3188 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3190 my $ws_org = $e->requestor->ws_ou;
3191 # user is automatically opted-in if they are from the local org
3192 return 1 if $user->home_ou eq $ws_org;
3194 # get the boundary setting
3195 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3197 # auto opt in if user falls within the opt boundary
3198 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3200 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3202 my $vals = $e->search_actor_usr_org_unit_opt_in(
3203 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3209 __PACKAGE__->register_method(
3210 method => 'create_user_opt_in_at_org',
3211 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3213 @param $auth The auth token
3214 @param user_id The ID of the user to test
3215 @return The ID of the newly created object, event on error./
3218 sub create_user_opt_in_at_org {
3219 my($self, $conn, $auth, $user_id, $org_id) = @_;
3221 my $e = new_editor(authtoken => $auth, xact=>1);
3222 return $e->die_event unless $e->checkauth;
3224 # if a specific org unit wasn't passed in, get one based on the defaults;
3226 my $wsou = $e->requestor->ws_ou;
3227 # get the default opt depth
3228 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3229 # get the org unit at that depth
3230 my $org = $e->json_query({
3231 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3232 $org_id = $org->{id};
3235 # fall back to the workstation OU, the pre-opt-in-boundary way
3236 $org_id = $e->requestor->ws_ou;
3239 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3240 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3242 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3244 $opt_in->org_unit($org_id);
3245 $opt_in->usr($user_id);
3246 $opt_in->staff($e->requestor->id);
3247 $opt_in->opt_in_ts('now');
3248 $opt_in->opt_in_ws($e->requestor->wsid);
3250 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3251 or return $e->die_event;
3259 __PACKAGE__->register_method (
3260 method => 'retrieve_org_hours',
3261 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3263 Returns the hours of operation for a specified org unit
3264 @param authtoken The login session key
3265 @param org_id The org_unit ID
3269 sub retrieve_org_hours {
3270 my($self, $conn, $auth, $org_id) = @_;
3271 my $e = new_editor(authtoken => $auth);
3272 return $e->die_event unless $e->checkauth;
3273 $org_id ||= $e->requestor->ws_ou;
3274 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3278 __PACKAGE__->register_method (
3279 method => 'verify_user_password',
3280 api_name => 'open-ils.actor.verify_user_password',
3282 Given a barcode or username and the MD5 encoded password,
3283 returns 1 if the password is correct. Returns 0 otherwise.
3287 sub verify_user_password {
3288 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3289 my $e = new_editor(authtoken => $auth);
3290 return $e->die_event unless $e->checkauth;
3292 my $user_by_barcode;
3293 my $user_by_username;
3295 my $card = $e->search_actor_card([
3296 {barcode => $barcode},
3297 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3298 $user_by_barcode = $card->usr;
3299 $user = $user_by_barcode;
3302 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3303 $user = $user_by_username;
3305 return 0 if (!$user);
3306 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3307 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3308 return 1 if $user->passwd eq $password;
3312 __PACKAGE__->register_method (
3313 method => 'retrieve_usr_id_via_barcode_or_usrname',
3314 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3316 Given a barcode or username returns the id for the user or
3321 sub retrieve_usr_id_via_barcode_or_usrname {
3322 my($self, $conn, $auth, $barcode, $username) = @_;
3323 my $e = new_editor(authtoken => $auth);
3324 return $e->die_event unless $e->checkauth;
3325 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3327 my $user_by_barcode;
3328 my $user_by_username;
3329 $logger->info("$id_as_barcode is the ID as BARCODE");
3331 my $card = $e->search_actor_card([
3332 {barcode => $barcode},
3333 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3334 if ($id_as_barcode =~ /^t/i) {
3336 $user = $e->retrieve_actor_user($barcode);
3337 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3339 $user_by_barcode = $card->usr;
3340 $user = $user_by_barcode;
3343 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3344 $user_by_barcode = $card->usr;
3345 $user = $user_by_barcode;
3350 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3352 $user = $user_by_username;
3354 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3355 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3356 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3361 __PACKAGE__->register_method (
3362 method => 'merge_users',
3363 api_name => 'open-ils.actor.user.merge',
3366 Given a list of source users and destination user, transfer all data from the source
3367 to the dest user and delete the source user. All user related data is
3368 transferred, including circulations, holds, bookbags, etc.
3374 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3375 my $e = new_editor(xact => 1, authtoken => $auth);
3376 return $e->die_event unless $e->checkauth;
3378 # disallow the merge if any subordinate accounts are in collections
3379 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3380 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3382 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3383 my $del_addrs = ($U->ou_ancestor_setting_value(
3384 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3385 my $del_cards = ($U->ou_ancestor_setting_value(
3386 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3387 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3388 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3390 for my $src_id (@$user_ids) {
3391 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3393 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3394 if($src_user->home_ou ne $master_user->home_ou) {
3395 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3398 return $e->die_event unless
3399 $e->json_query({from => [
3414 __PACKAGE__->register_method (
3415 method => 'approve_user_address',
3416 api_name => 'open-ils.actor.user.pending_address.approve',
3423 sub approve_user_address {
3424 my($self, $conn, $auth, $addr) = @_;
3425 my $e = new_editor(xact => 1, authtoken => $auth);
3426 return $e->die_event unless $e->checkauth;
3428 # if the caller passes an address object, assume they want to
3429 # update it first before approving it
3430 $e->update_actor_user_address($addr) or return $e->die_event;
3432 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3434 my $user = $e->retrieve_actor_user($addr->usr);
3435 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3436 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3437 or return $e->die_event;
3439 return [values %$result]->[0];
3443 __PACKAGE__->register_method (
3444 method => 'retrieve_friends',
3445 api_name => 'open-ils.actor.friends.retrieve',
3448 returns { confirmed: [], pending_out: [], pending_in: []}
3449 pending_out are users I'm requesting friendship with
3450 pending_in are users requesting friendship with me
3455 sub retrieve_friends {
3456 my($self, $conn, $auth, $user_id, $options) = @_;
3457 my $e = new_editor(authtoken => $auth);
3458 return $e->event unless $e->checkauth;
3459 $user_id ||= $e->requestor->id;
3461 if($user_id != $e->requestor->id) {
3462 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3463 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3466 return OpenILS::Application::Actor::Friends->retrieve_friends(
3467 $e, $user_id, $options);
3472 __PACKAGE__->register_method (
3473 method => 'apply_friend_perms',
3474 api_name => 'open-ils.actor.friends.perms.apply',
3480 sub apply_friend_perms {
3481 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3482 my $e = new_editor(authtoken => $auth, xact => 1);
3483 return $e->die_event unless $e->checkauth;
3485 if($user_id != $e->requestor->id) {
3486 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3487 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3490 for my $perm (@perms) {
3492 OpenILS::Application::Actor::Friends->apply_friend_perm(
3493 $e, $user_id, $delegate_id, $perm);
3494 return $evt if $evt;
3502 __PACKAGE__->register_method (
3503 method => 'update_user_pending_address',
3504 api_name => 'open-ils.actor.user.address.pending.cud'
3507 sub update_user_pending_address {
3508 my($self, $conn, $auth, $addr) = @_;
3509 my $e = new_editor(authtoken => $auth, xact => 1);
3510 return $e->die_event unless $e->checkauth;
3512 if($addr->usr != $e->requestor->id) {
3513 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3514 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3518 $e->create_actor_user_address($addr) or return $e->die_event;
3519 } elsif($addr->isdeleted) {
3520 $e->delete_actor_user_address($addr) or return $e->die_event;
3522 $e->update_actor_user_address($addr) or return $e->die_event;
3530 __PACKAGE__->register_method (
3531 method => 'user_events',
3532 api_name => 'open-ils.actor.user.events.circ',
3535 __PACKAGE__->register_method (
3536 method => 'user_events',
3537 api_name => 'open-ils.actor.user.events.ahr',
3542 my($self, $conn, $auth, $user_id, $filters) = @_;
3543 my $e = new_editor(authtoken => $auth);
3544 return $e->event unless $e->checkauth;
3546 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3547 my $user_field = 'usr';
3550 $filters->{target} = {
3551 select => { $obj_type => ['id'] },
3553 where => {usr => $user_id}
3556 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3557 if($e->requestor->id != $user_id) {
3558 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3561 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3562 my $req = $ses->request('open-ils.trigger.events_by_target',
3563 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3565 while(my $resp = $req->recv) {
3566 my $val = $resp->content;
3567 my $tgt = $val->target;
3569 if($obj_type eq 'circ') {
3570 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3572 } elsif($obj_type eq 'ahr') {
3573 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3574 if $tgt->current_copy;
3577 $conn->respond($val) if $val;
3583 __PACKAGE__->register_method (
3584 method => 'copy_events',
3585 api_name => 'open-ils.actor.copy.events.circ',
3588 __PACKAGE__->register_method (
3589 method => 'copy_events',
3590 api_name => 'open-ils.actor.copy.events.ahr',
3595 my($self, $conn, $auth, $copy_id, $filters) = @_;
3596 my $e = new_editor(authtoken => $auth);
3597 return $e->event unless $e->checkauth;
3599 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3601 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3603 my $copy_field = 'target_copy';
3604 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3607 $filters->{target} = {
3608 select => { $obj_type => ['id'] },
3610 where => {$copy_field => $copy_id}
3614 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3615 my $req = $ses->request('open-ils.trigger.events_by_target',
3616 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3618 while(my $resp = $req->recv) {
3619 my $val = $resp->content;
3620 my $tgt = $val->target;
3622 my $user = $e->retrieve_actor_user($tgt->usr);
3623 if($e->requestor->id != $user->id) {
3624 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3627 $tgt->$copy_field($copy);
3630 $conn->respond($val) if $val;
3639 __PACKAGE__->register_method (
3640 method => 'update_events',
3641 api_name => 'open-ils.actor.user.event.cancel.batch',
3644 __PACKAGE__->register_method (
3645 method => 'update_events',
3646 api_name => 'open-ils.actor.user.event.reset.batch',
3651 my($self, $conn, $auth, $event_ids) = @_;
3652 my $e = new_editor(xact => 1, authtoken => $auth);
3653 return $e->die_event unless $e->checkauth;
3656 for my $id (@$event_ids) {
3658 # do a little dance to determine what user we are ultimately affecting
3659 my $event = $e->retrieve_action_trigger_event([
3662 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3664 ]) or return $e->die_event;
3667 if($event->event_def->hook->core_type eq 'circ') {
3668 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3669 } elsif($event->event_def->hook->core_type eq 'ahr') {
3670 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3675 my $user = $e->retrieve_actor_user($user_id);
3676 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3678 if($self->api_name =~ /cancel/) {
3679 $event->state('invalid');
3680 } elsif($self->api_name =~ /reset/) {
3681 $event->clear_start_time;
3682 $event->clear_update_time;
3683 $event->state('pending');
3686 $e->update_action_trigger_event($event) or return $e->die_event;
3687 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3691 return {complete => 1};
3695 __PACKAGE__->register_method (
3696 method => 'really_delete_user',
3697 api_name => 'open-ils.actor.user.delete.override',
3698 signature => q/@see open-ils.actor.user.delete/
3701 __PACKAGE__->register_method (
3702 method => 'really_delete_user',
3703 api_name => 'open-ils.actor.user.delete',
3705 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3706 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3707 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3708 dest_usr_id is only required when deleting a user that performs staff functions.
3712 sub really_delete_user {
3713 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3714 my $e = new_editor(authtoken => $auth, xact => 1);
3715 return $e->die_event unless $e->checkauth;
3716 $oargs = { all => 1 } unless defined $oargs;
3718 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3719 my $open_bills = $e->json_query({
3720 select => { mbts => ['id'] },
3723 xact_finish => { '=' => undef },
3724 usr => { '=' => $user_id },
3726 }) or return $e->die_event;
3728 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3730 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3732 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3733 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3734 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3736 # No deleting yourself - UI is supposed to stop you first, though.
3737 return $e->die_event unless $e->requestor->id != $user->id;
3738 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3739 # Check if you are allowed to mess with this patron permission group at all
3740 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3741 my $evt = group_perm_failed($session, $e->requestor, $user);
3742 return $e->die_event($evt) if $evt;
3743 my $stat = $e->json_query(
3744 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3745 or return $e->die_event;
3751 __PACKAGE__->register_method (
3752 method => 'user_payments',
3753 api_name => 'open-ils.actor.user.payments.retrieve',
3756 Returns all payments for a given user. Default order is newest payments first.
3757 @param auth Authentication token
3758 @param user_id The user ID
3759 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3764 my($self, $conn, $auth, $user_id, $filters) = @_;
3767 my $e = new_editor(authtoken => $auth);
3768 return $e->die_event unless $e->checkauth;
3770 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3771 return $e->event unless
3772 $e->requestor->id == $user_id or
3773 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3775 # Find all payments for all transactions for user $user_id
3777 select => {mp => ['id']},
3782 select => {mbt => ['id']},
3784 where => {usr => $user_id}
3789 { # by default, order newest payments first
3791 field => 'payment_ts',
3794 # secondary sort in ID as a tie-breaker, since payments created
3795 # within the same transaction will have identical payment_ts's
3802 for (qw/order_by limit offset/) {
3803 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3806 if(defined $filters->{where}) {
3807 foreach (keys %{$filters->{where}}) {
3808 # don't allow the caller to expand the result set to other users
3809 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3813 my $payment_ids = $e->json_query($query);
3814 for my $pid (@$payment_ids) {
3815 my $pay = $e->retrieve_money_payment([
3820 mbt => ['summary', 'circulation', 'grocery'],
3821 circ => ['target_copy'],
3822 acp => ['call_number'],
3830 xact_type => $pay->xact->summary->xact_type,
3831 last_billing_type => $pay->xact->summary->last_billing_type,
3834 if($pay->xact->summary->xact_type eq 'circulation') {
3835 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3836 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3839 $pay->xact($pay->xact->id); # de-flesh
3840 $conn->respond($resp);
3848 __PACKAGE__->register_method (
3849 method => 'negative_balance_users',
3850 api_name => 'open-ils.actor.users.negative_balance',
3853 Returns all users that have an overall negative balance
3854 @param auth Authentication token
3855 @param org_id The context org unit as an ID or list of IDs. This will be the home
3856 library of the user. If no org_unit is specified, no org unit filter is applied
3860 sub negative_balance_users {
3861 my($self, $conn, $auth, $org_id) = @_;
3863 my $e = new_editor(authtoken => $auth);
3864 return $e->die_event unless $e->checkauth;
3865 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3869 mous => ['usr', 'balance_owed'],
3872 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3873 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3890 where => {'+mous' => {balance_owed => {'<' => 0}}}
3893 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3895 my $list = $e->json_query($query, {timeout => 600});
3897 for my $data (@$list) {
3899 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3900 balance_owed => $data->{balance_owed},
3901 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3908 __PACKAGE__->register_method(
3909 method => "request_password_reset",
3910 api_name => "open-ils.actor.patron.password_reset.request",
3912 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3913 "method for changing a user's password. The UUID token is distributed via A/T " .
3914 "templates (i.e. email to the user).",
3916 { desc => 'user_id_type', type => 'string' },
3917 { desc => 'user_id', type => 'string' },
3918 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3920 return => {desc => '1 on success, Event on error'}
3923 sub request_password_reset {
3924 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3926 # Check to see if password reset requests are already being throttled:
3927 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3929 my $e = new_editor(xact => 1);
3932 # Get the user, if any, depending on the input value
3933 if ($user_id_type eq 'username') {
3934 $user = $e->search_actor_user({usrname => $user_id})->[0];
3937 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3939 } elsif ($user_id_type eq 'barcode') {
3940 my $card = $e->search_actor_card([
3941 {barcode => $user_id},
3942 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3945 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3950 # If the user doesn't have an email address, we can't help them
3951 if (!$user->email) {
3953 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3956 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3957 if ($email_must_match) {
3958 if ($user->email ne $email) {
3959 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3963 _reset_password_request($conn, $e, $user);
3966 # Once we have the user, we can issue the password reset request
3967 # XXX Add a wrapper method that accepts barcode + email input
3968 sub _reset_password_request {
3969 my ($conn, $e, $user) = @_;
3971 # 1. Get throttle threshold and time-to-live from OU_settings
3972 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3973 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3975 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3977 # 2. Get time of last request and number of active requests (num_active)
3978 my $active_requests = $e->json_query({
3984 transform => 'COUNT'
3987 column => 'request_time',
3993 has_been_reset => { '=' => 'f' },
3994 request_time => { '>' => $threshold_time }
3998 # Guard against no active requests
3999 if ($active_requests->[0]->{'request_time'}) {
4000 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
4001 my $now = DateTime::Format::ISO8601->new();
4003 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4004 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4005 ($last_request->add_duration('1 minute') > $now)) {
4006 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4008 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4012 # TODO Check to see if the user is in a password-reset-restricted group
4014 # Otherwise, go ahead and try to get the user.
4016 # Check the number of active requests for this user
4017 $active_requests = $e->json_query({
4023 transform => 'COUNT'
4028 usr => { '=' => $user->id },
4029 has_been_reset => { '=' => 'f' },
4030 request_time => { '>' => $threshold_time }
4034 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4036 # if less than or equal to per-user threshold, proceed; otherwise, return event
4037 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4038 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4040 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4043 # Create the aupr object and insert into the database
4044 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4045 my $uuid = create_uuid_as_string(UUID_V4);
4046 $reset_request->uuid($uuid);
4047 $reset_request->usr($user->id);
4049 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4052 # Create an event to notify user of the URL to reset their password
4054 # Can we stuff this in the user_data param for trigger autocreate?
4055 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4057 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4058 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4061 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4066 __PACKAGE__->register_method(
4067 method => "commit_password_reset",
4068 api_name => "open-ils.actor.patron.password_reset.commit",
4070 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4071 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4072 "with the supplied password.",
4074 { desc => 'uuid', type => 'string' },
4075 { desc => 'password', type => 'string' },
4077 return => {desc => '1 on success, Event on error'}
4080 sub commit_password_reset {
4081 my($self, $conn, $uuid, $password) = @_;
4083 # Check to see if password reset requests are already being throttled:
4084 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4085 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4086 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4088 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4091 my $e = new_editor(xact => 1);
4093 my $aupr = $e->search_actor_usr_password_reset({
4100 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4102 my $user_id = $aupr->[0]->usr;
4103 my $user = $e->retrieve_actor_user($user_id);
4105 # Ensure we're still within the TTL for the request
4106 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4107 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4108 if ($threshold < DateTime->now(time_zone => 'local')) {
4110 $logger->info("Password reset request needed to be submitted before $threshold");
4111 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4114 # Check complexity of password against OU-defined regex
4115 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4119 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4120 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4121 $is_strong = check_password_strength_custom($password, $pw_regex);
4123 $is_strong = check_password_strength_default($password);
4128 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4131 # All is well; update the password
4132 $user->passwd($password);
4133 $e->update_actor_user($user);
4135 # And flag that this password reset request has been honoured
4136 $aupr->[0]->has_been_reset('t');
4137 $e->update_actor_usr_password_reset($aupr->[0]);
4143 sub check_password_strength_default {
4144 my $password = shift;
4145 # Use the default set of checks
4146 if ( (length($password) < 7) or
4147 ($password !~ m/.*\d+.*/) or
4148 ($password !~ m/.*[A-Za-z]+.*/)
4155 sub check_password_strength_custom {
4156 my ($password, $pw_regex) = @_;
4158 $pw_regex = qr/$pw_regex/;
4159 if ($password !~ /$pw_regex/) {
4167 __PACKAGE__->register_method(
4168 method => "event_def_opt_in_settings",
4169 api_name => "open-ils.actor.event_def.opt_in.settings",
4172 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4174 { desc => 'Authentication token', type => 'string'},
4176 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4181 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4188 sub event_def_opt_in_settings {
4189 my($self, $conn, $auth, $org_id) = @_;
4190 my $e = new_editor(authtoken => $auth);
4191 return $e->event unless $e->checkauth;
4193 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4194 return $e->event unless
4195 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4197 $org_id = $e->requestor->home_ou;
4200 # find all config.user_setting_type's related to event_defs for the requested org unit
4201 my $types = $e->json_query({
4202 select => {cust => ['name']},
4203 from => {atevdef => 'cust'},
4206 owner => $U->get_org_ancestors($org_id), # context org plus parents
4213 $conn->respond($_) for
4214 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4221 __PACKAGE__->register_method(
4222 method => "user_visible_circs",
4223 api_name => "open-ils.actor.history.circ.visible",
4226 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4228 { desc => 'Authentication token', type => 'string'},
4229 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4230 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4233 desc => q/An object with 2 fields: circulation and summary.
4234 circulation is the "circ" object. summary is the related "accs" object/,
4240 __PACKAGE__->register_method(
4241 method => "user_visible_circs",
4242 api_name => "open-ils.actor.history.circ.visible.print",
4245 desc => 'Returns printable output for the set of opt-in visible circulations',
4247 { desc => 'Authentication token', type => 'string'},
4248 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4249 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4252 desc => q/An action_trigger.event object or error event./,
4258 __PACKAGE__->register_method(
4259 method => "user_visible_circs",
4260 api_name => "open-ils.actor.history.circ.visible.email",
4263 desc => 'Emails the set of opt-in visible circulations to the requestor',
4265 { desc => 'Authentication token', type => 'string'},
4266 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4267 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4270 desc => q/undef, or event on error/
4275 __PACKAGE__->register_method(
4276 method => "user_visible_circs",
4277 api_name => "open-ils.actor.history.hold.visible",
4280 desc => 'Returns the set of opt-in visible holds',
4282 { desc => 'Authentication token', type => 'string'},
4283 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4284 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4287 desc => q/An object with 1 field: "hold"/,
4293 __PACKAGE__->register_method(
4294 method => "user_visible_circs",
4295 api_name => "open-ils.actor.history.hold.visible.print",
4298 desc => 'Returns printable output for the set of opt-in visible holds',
4300 { desc => 'Authentication token', type => 'string'},
4301 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4302 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4305 desc => q/An action_trigger.event object or error event./,
4311 __PACKAGE__->register_method(
4312 method => "user_visible_circs",
4313 api_name => "open-ils.actor.history.hold.visible.email",
4316 desc => 'Emails the set of opt-in visible holds to the requestor',
4318 { desc => 'Authentication token', type => 'string'},
4319 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4320 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4323 desc => q/undef, or event on error/
4328 sub user_visible_circs {
4329 my($self, $conn, $auth, $user_id, $options) = @_;
4331 my $is_hold = ($self->api_name =~ /hold/);
4332 my $for_print = ($self->api_name =~ /print/);
4333 my $for_email = ($self->api_name =~ /email/);
4334 my $e = new_editor(authtoken => $auth);
4335 return $e->event unless $e->checkauth;
4337 $user_id ||= $e->requestor->id;
4339 $options->{limit} ||= 50;
4340 $options->{offset} ||= 0;
4342 if($user_id != $e->requestor->id) {
4343 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4344 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4345 return $e->event unless $e->allowed($perm, $user->home_ou);
4348 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4350 my $data = $e->json_query({
4351 from => [$db_func, $user_id],
4352 limit => $$options{limit},
4353 offset => $$options{offset}
4355 # TODO: I only want IDs. code below didn't get me there
4356 # {"select":{"au":[{"column":"id", "result_field":"id",
4357 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4362 return undef unless @$data;
4366 # collect the batch of objects
4370 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4371 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4375 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4376 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4379 } elsif ($for_email) {
4381 $conn->respond_complete(1) if $for_email; # no sense in waiting
4389 my $hold = $e->retrieve_action_hold_request($id);
4390 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4391 # events will be fired from action_trigger_runner
4395 my $circ = $e->retrieve_action_circulation($id);
4396 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4397 # events will be fired from action_trigger_runner
4401 } else { # just give me the data please
4409 my $hold = $e->retrieve_action_hold_request($id);
4410 $conn->respond({hold => $hold});
4414 my $circ = $e->retrieve_action_circulation($id);
4417 summary => $U->create_circ_chain_summary($e, $id)
4426 __PACKAGE__->register_method(
4427 method => "user_saved_search_cud",
4428 api_name => "open-ils.actor.user.saved_search.cud",
4431 desc => 'Create/Update/Delete Access to user saved searches',
4433 { desc => 'Authentication token', type => 'string' },
4434 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4437 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4443 __PACKAGE__->register_method(
4444 method => "user_saved_search_cud",
4445 api_name => "open-ils.actor.user.saved_search.retrieve",
4448 desc => 'Retrieve a saved search object',
4450 { desc => 'Authentication token', type => 'string' },
4451 { desc => 'Saved Search ID', type => 'number' }
4454 desc => q/The saved search object, Event on error/,
4460 sub user_saved_search_cud {
4461 my( $self, $client, $auth, $search ) = @_;
4462 my $e = new_editor( authtoken=>$auth );
4463 return $e->die_event unless $e->checkauth;
4465 my $o_search; # prior version of the object, if any
4466 my $res; # to be returned
4468 # branch on the operation type
4470 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4472 # Get the old version, to check ownership
4473 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4474 or return $e->die_event;
4476 # You can't read somebody else's search
4477 return OpenILS::Event->new('BAD_PARAMS')
4478 unless $o_search->owner == $e->requestor->id;
4484 $e->xact_begin; # start an editor transaction
4486 if( $search->isnew ) { # Create
4488 # You can't create a search for somebody else
4489 return OpenILS::Event->new('BAD_PARAMS')
4490 unless $search->owner == $e->requestor->id;
4492 $e->create_actor_usr_saved_search( $search )
4493 or return $e->die_event;
4497 } elsif( $search->ischanged ) { # Update
4499 # You can't change ownership of a search
4500 return OpenILS::Event->new('BAD_PARAMS')
4501 unless $search->owner == $e->requestor->id;
4503 # Get the old version, to check ownership
4504 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4505 or return $e->die_event;
4507 # You can't update somebody else's search
4508 return OpenILS::Event->new('BAD_PARAMS')
4509 unless $o_search->owner == $e->requestor->id;
4512 $e->update_actor_usr_saved_search( $search )
4513 or return $e->die_event;
4517 } elsif( $search->isdeleted ) { # Delete
4519 # Get the old version, to check ownership
4520 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4521 or return $e->die_event;
4523 # You can't delete somebody else's search
4524 return OpenILS::Event->new('BAD_PARAMS')
4525 unless $o_search->owner == $e->requestor->id;
4528 $e->delete_actor_usr_saved_search( $o_search )
4529 or return $e->die_event;
4540 __PACKAGE__->register_method(
4541 method => "get_barcodes",
4542 api_name => "open-ils.actor.get_barcodes"
4546 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4547 my $e = new_editor(authtoken => $auth);
4548 return $e->event unless $e->checkauth;
4549 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4551 my $db_result = $e->json_query(
4553 'evergreen.get_barcodes',
4554 $org_id, $context, $barcode,
4558 if($context =~ /actor/) {
4559 my $filter_result = ();
4561 foreach my $result (@$db_result) {
4562 if($result->{type} eq 'actor') {
4563 if($e->requestor->id != $result->{id}) {
4564 $patron = $e->retrieve_actor_user($result->{id});
4566 push(@$filter_result, $e->event);
4569 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4570 push(@$filter_result, $result);
4573 push(@$filter_result, $e->event);
4577 push(@$filter_result, $result);
4581 push(@$filter_result, $result);
4584 return $filter_result;
4590 __PACKAGE__->register_method(
4591 method => 'address_alert_test',
4592 api_name => 'open-ils.actor.address_alert.test',
4594 desc => "Tests a set of address fields to determine if they match with an address_alert",
4596 {desc => 'Authentication token', type => 'string'},
4597 {desc => 'Org Unit', type => 'number'},
4598 {desc => 'Fields', type => 'hash'},
4600 return => {desc => 'List of matching address_alerts'}
4604 sub address_alert_test {
4605 my ($self, $client, $auth, $org_unit, $fields) = @_;
4606 return [] unless $fields and grep {$_} values %$fields;
4608 my $e = new_editor(authtoken => $auth);
4609 return $e->event unless $e->checkauth;
4610 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4611 $org_unit ||= $e->requestor->ws_ou;
4613 my $alerts = $e->json_query({
4615 'actor.address_alert_matches',
4623 $$fields{post_code},
4624 $$fields{mailing_address},
4625 $$fields{billing_address}
4629 # map the json_query hashes to real objects
4631 map {$e->retrieve_actor_address_alert($_)}
4632 (map {$_->{id}} @$alerts)
4636 __PACKAGE__->register_method(
4637 method => "mark_users_contact_invalid",
4638 api_name => "open-ils.actor.invalidate.email",
4640 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",
4642 {desc => "Authentication token", type => "string"},
4643 {desc => "Patron ID", type => "number"},
4644 {desc => "Additional note text (optional)", type => "string"},
4645 {desc => "penalty org unit ID (optional)", type => "number"}
4647 return => {desc => "Event describing success or failure", type => "object"}
4651 __PACKAGE__->register_method(
4652 method => "mark_users_contact_invalid",
4653 api_name => "open-ils.actor.invalidate.day_phone",
4655 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",
4657 {desc => "Authentication token", type => "string"},
4658 {desc => "Patron ID", type => "number"},
4659 {desc => "Additional note text (optional)", type => "string"},
4660 {desc => "penalty org unit ID (optional)", type => "number"}
4662 return => {desc => "Event describing success or failure", type => "object"}
4666 __PACKAGE__->register_method(
4667 method => "mark_users_contact_invalid",
4668 api_name => "open-ils.actor.invalidate.evening_phone",
4670 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",
4672 {desc => "Authentication token", type => "string"},
4673 {desc => "Patron ID", type => "number"},
4674 {desc => "Additional note text (optional)", type => "string"},
4675 {desc => "penalty org unit ID (optional)", type => "number"}
4677 return => {desc => "Event describing success or failure", type => "object"}
4681 __PACKAGE__->register_method(
4682 method => "mark_users_contact_invalid",
4683 api_name => "open-ils.actor.invalidate.other_phone",
4685 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",
4687 {desc => "Authentication token", type => "string"},
4688 {desc => "Patron ID", type => "number"},
4689 {desc => "Additional note text (optional)", type => "string"},
4690 {desc => "penalty org unit ID (optional, default to top of org tree)",
4693 return => {desc => "Event describing success or failure", type => "object"}
4697 sub mark_users_contact_invalid {
4698 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4700 # This method invalidates an email address or a phone_number which
4701 # removes the bad email address or phone number, copying its contents
4702 # to a patron note, and institutes a standing penalty for "bad email"
4703 # or "bad phone number" which is cleared when the user is saved or
4704 # optionally only when the user is saved with an email address or
4705 # phone number (or staff manually delete the penalty).
4707 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4709 my $e = new_editor(authtoken => $auth, xact => 1);
4710 return $e->die_event unless $e->checkauth;
4712 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4713 $e, $contact_type, {usr => $patron_id},
4714 $addl_note, $penalty_ou, $e->requestor->id
4718 # Putting the following method in open-ils.actor is a bad fit, except in that
4719 # it serves an interface that lives under 'actor' in the templates directory,
4720 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4722 __PACKAGE__->register_method(
4723 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4724 method => "get_all_at_reactors_in_use",
4729 { name => 'authtoken', type => 'string' }
4732 desc => 'list of reactor names', type => 'array'
4737 sub get_all_at_reactors_in_use {
4738 my ($self, $conn, $auth) = @_;
4740 my $e = new_editor(authtoken => $auth);
4741 $e->checkauth or return $e->die_event;
4742 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4744 my $reactors = $e->json_query({
4746 atevdef => [{column => "reactor", transform => "distinct"}]
4748 from => {atevdef => {}}
4751 return $e->die_event unless ref $reactors eq "ARRAY";
4754 return [ map { $_->{reactor} } @$reactors ];
4757 __PACKAGE__->register_method(
4758 method => "filter_group_entry_crud",
4759 api_name => "open-ils.actor.filter_group_entry.crud",
4762 Provides CRUD access to filter group entry objects. These are not full accessible
4763 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4764 are not accessible via PCRUD (because they have no fields against which to link perms)
4767 {desc => "Authentication token", type => "string"},
4768 {desc => "Entry ID / Entry Object", type => "number"},
4769 {desc => "Additional note text (optional)", type => "string"},
4770 {desc => "penalty org unit ID (optional, default to top of org tree)",
4774 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4780 sub filter_group_entry_crud {
4781 my ($self, $conn, $auth, $arg) = @_;
4783 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4784 my $e = new_editor(authtoken => $auth, xact => 1);
4785 return $e->die_event unless $e->checkauth;
4791 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4792 or return $e->die_event;
4794 return $e->die_event unless $e->allowed(
4795 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4797 my $query = $arg->query;
4798 $query = $e->create_actor_search_query($query) or return $e->die_event;
4799 $arg->query($query->id);
4800 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4801 $entry->query($query);
4806 } elsif ($arg->ischanged) {
4808 my $entry = $e->retrieve_actor_search_filter_group_entry([
4811 flesh_fields => {asfge => ['grp']}
4813 ]) or return $e->die_event;
4815 return $e->die_event unless $e->allowed(
4816 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4818 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4819 $arg->query($arg->query->id);
4820 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4821 $arg->query($query);
4826 } elsif ($arg->isdeleted) {
4828 my $entry = $e->retrieve_actor_search_filter_group_entry([
4831 flesh_fields => {asfge => ['grp', 'query']}
4833 ]) or return $e->die_event;
4835 return $e->die_event unless $e->allowed(
4836 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4838 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4839 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4852 my $entry = $e->retrieve_actor_search_filter_group_entry([
4855 flesh_fields => {asfge => ['grp', 'query']}
4857 ]) or return $e->die_event;
4859 return $e->die_event unless $e->allowed(
4860 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4861 $entry->grp->owner);
4864 $entry->grp($entry->grp->id); # for consistency