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"
1320 sub patron_adv_search {
1321 my( $self, $client, $auth, $search_hash,
1322 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1324 my $e = new_editor(authtoken=>$auth);
1325 return $e->event unless $e->checkauth;
1326 return $e->event unless $e->allowed('VIEW_USER');
1328 # depth boundary outside of which patrons must opt-in, default to 0
1329 my $opt_boundary = 0;
1330 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1332 if (not defined $search_ou) {
1333 my $depth = $U->ou_ancestor_setting_value(
1334 $e->requestor->ws_ou,
1335 'circ.patron_edit.duplicate_patron_check_depth'
1338 if (defined $depth) {
1339 $search_ou = $U->org_unit_ancestor_at_depth(
1340 $e->requestor->ws_ou, $depth
1344 return $U->storagereq(
1345 "open-ils.storage.actor.user.crazy_search", $search_hash,
1346 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1350 __PACKAGE__->register_method(
1351 method => "update_passwd",
1352 api_name => "open-ils.actor.user.password.update",
1354 desc => "Update the operator's password",
1356 { desc => 'Authentication token', type => 'string' },
1357 { desc => 'New password', type => 'string' },
1358 { desc => 'Current password', type => 'string' }
1360 return => {desc => '1 on success, Event on error or incorrect current password'}
1364 __PACKAGE__->register_method(
1365 method => "update_passwd",
1366 api_name => "open-ils.actor.user.username.update",
1368 desc => "Update the operator's username",
1370 { desc => 'Authentication token', type => 'string' },
1371 { desc => 'New username', type => 'string' },
1372 { desc => 'Current password', type => 'string' }
1374 return => {desc => '1 on success, Event on error or incorrect current password'}
1378 __PACKAGE__->register_method(
1379 method => "update_passwd",
1380 api_name => "open-ils.actor.user.email.update",
1382 desc => "Update the operator's email address",
1384 { desc => 'Authentication token', type => 'string' },
1385 { desc => 'New email address', type => 'string' },
1386 { desc => 'Current password', type => 'string' }
1388 return => {desc => '1 on success, Event on error or incorrect current password'}
1393 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1394 my $e = new_editor(xact=>1, authtoken=>$auth);
1395 return $e->die_event unless $e->checkauth;
1397 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1398 or return $e->die_event;
1399 my $api = $self->api_name;
1401 # make sure the original password matches the in-database password
1402 if (md5_hex($orig_pw) ne $db_user->passwd) {
1404 return new OpenILS::Event('INCORRECT_PASSWORD');
1407 if( $api =~ /password/o ) {
1409 $db_user->passwd($new_val);
1413 # if we don't clear the password, the user will be updated with
1414 # a hashed version of the hashed version of their password
1415 $db_user->clear_passwd;
1417 if( $api =~ /username/o ) {
1419 # make sure no one else has this username
1420 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1423 return new OpenILS::Event('USERNAME_EXISTS');
1425 $db_user->usrname($new_val);
1427 } elsif( $api =~ /email/o ) {
1428 $db_user->email($new_val);
1432 $e->update_actor_user($db_user) or return $e->die_event;
1435 # update the cached user to pick up these changes
1436 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1442 __PACKAGE__->register_method(
1443 method => "check_user_perms",
1444 api_name => "open-ils.actor.user.perm.check",
1445 notes => <<" NOTES");
1446 Takes a login session, user id, an org id, and an array of perm type strings. For each
1447 perm type, if the user does *not* have the given permission it is added
1448 to a list which is returned from the method. If all permissions
1449 are allowed, an empty list is returned
1450 if the logged in user does not match 'user_id', then the logged in user must
1451 have VIEW_PERMISSION priveleges.
1454 sub check_user_perms {
1455 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1457 my( $staff, $evt ) = $apputils->checkses($login_session);
1458 return $evt if $evt;
1460 if($staff->id ne $user_id) {
1461 if( $evt = $apputils->check_perms(
1462 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1468 for my $perm (@$perm_types) {
1469 if($apputils->check_perms($user_id, $org_id, $perm)) {
1470 push @not_allowed, $perm;
1474 return \@not_allowed
1477 __PACKAGE__->register_method(
1478 method => "check_user_perms2",
1479 api_name => "open-ils.actor.user.perm.check.multi_org",
1481 Checks the permissions on a list of perms and orgs for a user
1482 @param authtoken The login session key
1483 @param user_id The id of the user to check
1484 @param orgs The array of org ids
1485 @param perms The array of permission names
1486 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1487 if the logged in user does not match 'user_id', then the logged in user must
1488 have VIEW_PERMISSION priveleges.
1491 sub check_user_perms2 {
1492 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1494 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1495 $authtoken, $user_id, 'VIEW_PERMISSION' );
1496 return $evt if $evt;
1499 for my $org (@$orgs) {
1500 for my $perm (@$perms) {
1501 if($apputils->check_perms($user_id, $org, $perm)) {
1502 push @not_allowed, [ $org, $perm ];
1507 return \@not_allowed
1511 __PACKAGE__->register_method(
1512 method => 'check_user_perms3',
1513 api_name => 'open-ils.actor.user.perm.highest_org',
1515 Returns the highest org unit id at which a user has a given permission
1516 If the requestor does not match the target user, the requestor must have
1517 'VIEW_PERMISSION' rights at the home org unit of the target user
1518 @param authtoken The login session key
1519 @param userid The id of the user in question
1520 @param perm The permission to check
1521 @return The org unit highest in the org tree within which the user has
1522 the requested permission
1525 sub check_user_perms3 {
1526 my($self, $client, $authtoken, $user_id, $perm) = @_;
1527 my $e = new_editor(authtoken=>$authtoken);
1528 return $e->event unless $e->checkauth;
1530 my $tree = $U->get_org_tree();
1532 unless($e->requestor->id == $user_id) {
1533 my $user = $e->retrieve_actor_user($user_id)
1534 or return $e->event;
1535 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1536 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1539 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1542 __PACKAGE__->register_method(
1543 method => 'user_has_work_perm_at',
1544 api_name => 'open-ils.actor.user.has_work_perm_at',
1548 Returns a set of org unit IDs which represent the highest orgs in
1549 the org tree where the user has the requested permission. The
1550 purpose of this method is to return the smallest set of org units
1551 which represent the full expanse of the user's ability to perform
1552 the requested action. The user whose perms this method should
1553 check is implied by the authtoken. /,
1555 {desc => 'authtoken', type => 'string'},
1556 {desc => 'permission name', type => 'string'},
1557 {desc => q/user id, optional. If present, check perms for
1558 this user instead of the logged in user/, type => 'number'},
1560 return => {desc => 'An array of org IDs'}
1564 sub user_has_work_perm_at {
1565 my($self, $conn, $auth, $perm, $user_id) = @_;
1566 my $e = new_editor(authtoken=>$auth);
1567 return $e->event unless $e->checkauth;
1568 if(defined $user_id) {
1569 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1570 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1572 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1575 __PACKAGE__->register_method(
1576 method => 'user_has_work_perm_at_batch',
1577 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1581 sub user_has_work_perm_at_batch {
1582 my($self, $conn, $auth, $perms, $user_id) = @_;
1583 my $e = new_editor(authtoken=>$auth);
1584 return $e->event unless $e->checkauth;
1585 if(defined $user_id) {
1586 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1587 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1590 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1596 __PACKAGE__->register_method(
1597 method => 'check_user_perms4',
1598 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1600 Returns the highest org unit id at which a user has a given permission
1601 If the requestor does not match the target user, the requestor must have
1602 'VIEW_PERMISSION' rights at the home org unit of the target user
1603 @param authtoken The login session key
1604 @param userid The id of the user in question
1605 @param perms An array of perm names to check
1606 @return An array of orgId's representing the org unit
1607 highest in the org tree within which the user has the requested permission
1608 The arrah of orgId's has matches the order of the perms array
1611 sub check_user_perms4 {
1612 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1614 my( $staff, $target, $org, $evt );
1616 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1617 $authtoken, $userid, 'VIEW_PERMISSION' );
1618 return $evt if $evt;
1621 return [] unless ref($perms);
1622 my $tree = $U->get_org_tree();
1624 for my $p (@$perms) {
1625 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1631 __PACKAGE__->register_method(
1632 method => "user_fines_summary",
1633 api_name => "open-ils.actor.user.fines.summary",
1636 desc => 'Returns a short summary of the users total open fines, ' .
1637 'excluding voided fines Params are login_session, user_id' ,
1639 {desc => 'Authentication token', type => 'string'},
1640 {desc => 'User ID', type => 'string'} # number?
1643 desc => "a 'mous' object, event on error",
1648 sub user_fines_summary {
1649 my( $self, $client, $auth, $user_id ) = @_;
1651 my $e = new_editor(authtoken=>$auth);
1652 return $e->event unless $e->checkauth;
1654 if( $user_id ne $e->requestor->id ) {
1655 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1656 return $e->event unless
1657 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1660 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1664 __PACKAGE__->register_method(
1665 method => "user_opac_vitals",
1666 api_name => "open-ils.actor.user.opac.vital_stats",
1670 desc => 'Returns a short summary of the users vital stats, including ' .
1671 'identification information, accumulated balance, number of holds, ' .
1672 'and current open circulation stats' ,
1674 {desc => 'Authentication token', type => 'string'},
1675 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1678 desc => "An object with four properties: user, fines, checkouts and holds."
1683 sub user_opac_vitals {
1684 my( $self, $client, $auth, $user_id ) = @_;
1686 my $e = new_editor(authtoken=>$auth);
1687 return $e->event unless $e->checkauth;
1689 $user_id ||= $e->requestor->id;
1691 my $user = $e->retrieve_actor_user( $user_id );
1694 ->method_lookup('open-ils.actor.user.fines.summary')
1695 ->run($auth => $user_id);
1696 return $fines if (defined($U->event_code($fines)));
1699 $fines = new Fieldmapper::money::open_user_summary ();
1700 $fines->balance_owed(0.00);
1701 $fines->total_owed(0.00);
1702 $fines->total_paid(0.00);
1703 $fines->usr($user_id);
1707 ->method_lookup('open-ils.actor.user.hold_requests.count')
1708 ->run($auth => $user_id);
1709 return $holds if (defined($U->event_code($holds)));
1712 ->method_lookup('open-ils.actor.user.checked_out.count')
1713 ->run($auth => $user_id);
1714 return $out if (defined($U->event_code($out)));
1716 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1720 first_given_name => $user->first_given_name,
1721 second_given_name => $user->second_given_name,
1722 family_name => $user->family_name,
1723 alias => $user->alias,
1724 usrname => $user->usrname
1726 fines => $fines->to_bare_hash,
1733 ##### a small consolidation of related method registrations
1734 my $common_params = [
1735 { desc => 'Authentication token', type => 'string' },
1736 { desc => 'User ID', type => 'string' },
1737 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1738 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1741 'open-ils.actor.user.transactions' => '',
1742 'open-ils.actor.user.transactions.fleshed' => '',
1743 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1744 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1745 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1746 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1749 foreach (keys %methods) {
1751 method => "user_transactions",
1754 desc => 'For a given user, retrieve a list of '
1755 . (/\.fleshed/ ? 'fleshed ' : '')
1756 . 'transactions' . $methods{$_}
1757 . ' optionally limited to transactions of a given type.',
1758 params => $common_params,
1760 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1761 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1765 $args{authoritative} = 1;
1766 __PACKAGE__->register_method(%args);
1769 # Now for the counts
1771 'open-ils.actor.user.transactions.count' => '',
1772 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1773 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1776 foreach (keys %methods) {
1778 method => "user_transactions",
1781 desc => 'For a given user, retrieve a count of open '
1782 . 'transactions' . $methods{$_}
1783 . ' optionally limited to transactions of a given type.',
1784 params => $common_params,
1785 return => { desc => "Integer count of transactions, or event on error" }
1788 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1789 __PACKAGE__->register_method(%args);
1792 __PACKAGE__->register_method(
1793 method => "user_transactions",
1794 api_name => "open-ils.actor.user.transactions.have_balance.total",
1797 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1798 . ' optionally limited to transactions of a given type.',
1799 params => $common_params,
1800 return => { desc => "Decimal balance value, or event on error" }
1805 sub user_transactions {
1806 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1809 my $e = new_editor(authtoken => $auth);
1810 return $e->event unless $e->checkauth;
1812 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1814 return $e->event unless
1815 $e->requestor->id == $user_id or
1816 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1818 my $api = $self->api_name();
1820 my $filter = ($api =~ /have_balance/o) ?
1821 { 'balance_owed' => { '<>' => 0 } }:
1822 { 'total_owed' => { '>' => 0 } };
1824 my $method = 'open-ils.actor.user.transactions.history.still_open';
1825 $method = "$method.authoritative" if $api =~ /authoritative/;
1826 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1828 if($api =~ /total/o) {
1830 $total += $_->balance_owed for @$trans;
1834 ($api =~ /count/o ) and return scalar @$trans;
1835 ($api !~ /fleshed/o) and return $trans;
1838 for my $t (@$trans) {
1840 if( $t->xact_type ne 'circulation' ) {
1841 push @resp, {transaction => $t};
1845 my $circ_data = flesh_circ($e, $t->id);
1846 push @resp, {transaction => $t, %$circ_data};
1853 __PACKAGE__->register_method(
1854 method => "user_transaction_retrieve",
1855 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1858 notes => "Returns a fleshed transaction record"
1861 __PACKAGE__->register_method(
1862 method => "user_transaction_retrieve",
1863 api_name => "open-ils.actor.user.transaction.retrieve",
1866 notes => "Returns a transaction record"
1869 sub user_transaction_retrieve {
1870 my($self, $client, $auth, $bill_id) = @_;
1872 my $e = new_editor(authtoken => $auth);
1873 return $e->event unless $e->checkauth;
1875 my $trans = $e->retrieve_money_billable_transaction_summary(
1876 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1878 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1880 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1882 return $trans unless $self->api_name =~ /flesh/;
1883 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1885 my $circ_data = flesh_circ($e, $trans->id, 1);
1887 return {transaction => $trans, %$circ_data};
1892 my $circ_id = shift;
1893 my $flesh_copy = shift;
1895 my $circ = $e->retrieve_action_circulation([
1899 circ => ['target_copy'],
1900 acp => ['call_number'],
1907 my $copy = $circ->target_copy;
1909 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1910 $mods = new Fieldmapper::metabib::virtual_record;
1911 $mods->doc_id(OILS_PRECAT_RECORD);
1912 $mods->title($copy->dummy_title);
1913 $mods->author($copy->dummy_author);
1916 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1920 $circ->target_copy($circ->target_copy->id);
1921 $copy->call_number($copy->call_number->id);
1923 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1927 __PACKAGE__->register_method(
1928 method => "hold_request_count",
1929 api_name => "open-ils.actor.user.hold_requests.count",
1933 Returns hold ready vs. total counts.
1934 If a context org unit is provided, a third value
1935 is returned with key 'behind_desk', which reports
1936 how many holds are ready at the pickup library
1937 with the behind_desk flag set to true.
1941 sub hold_request_count {
1942 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
1943 my $e = new_editor(authtoken => $authtoken);
1944 return $e->event unless $e->checkauth;
1946 $user_id = $e->requestor->id unless defined $user_id;
1948 if($e->requestor->id ne $user_id) {
1949 my $user = $e->retrieve_actor_user($user_id);
1950 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1953 my $holds = $e->json_query({
1954 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
1958 fulfillment_time => {"=" => undef },
1959 cancel_time => undef,
1964 $_->{current_shelf_lib} and # avoid undef warnings
1965 $_->{pickup_lib} eq $_->{current_shelf_lib}
1969 total => scalar(@$holds),
1970 ready => scalar(@ready)
1974 # count of holds ready at pickup lib with behind_desk true.
1975 $resp->{behind_desk} = scalar(
1977 $_->{pickup_lib} == $ctx_org and
1978 $U->is_true($_->{behind_desk})
1986 __PACKAGE__->register_method(
1987 method => "checked_out",
1988 api_name => "open-ils.actor.user.checked_out",
1992 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1993 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1994 . "(i.e., outstanding balance or some other pending action on the circ). "
1995 . "The .count method also includes a 'total' field which sums all open circs.",
1997 { desc => 'Authentication Token', type => 'string'},
1998 { desc => 'User ID', type => 'string'},
2001 desc => 'Returns event on error, or an object with ID lists, like: '
2002 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2007 __PACKAGE__->register_method(
2008 method => "checked_out",
2009 api_name => "open-ils.actor.user.checked_out.count",
2012 signature => q/@see open-ils.actor.user.checked_out/
2016 my( $self, $conn, $auth, $userid ) = @_;
2018 my $e = new_editor(authtoken=>$auth);
2019 return $e->event unless $e->checkauth;
2021 if( $userid ne $e->requestor->id ) {
2022 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2023 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2025 # see if there is a friend link allowing circ.view perms
2026 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2027 $e, $userid, $e->requestor->id, 'circ.view');
2028 return $e->event unless $allowed;
2032 my $count = $self->api_name =~ /count/;
2033 return _checked_out( $count, $e, $userid );
2037 my( $iscount, $e, $userid ) = @_;
2043 claims_returned => [],
2046 my $meth = 'retrieve_action_open_circ_';
2054 claims_returned => 0,
2061 my $data = $e->$meth($userid);
2065 $result{$_} += $data->$_() for (keys %result);
2066 $result{total} += $data->$_() for (keys %result);
2068 for my $k (keys %result) {
2069 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2079 __PACKAGE__->register_method(
2080 method => "checked_in_with_fines",
2081 api_name => "open-ils.actor.user.checked_in_with_fines",
2084 signature => q/@see open-ils.actor.user.checked_out/
2087 sub checked_in_with_fines {
2088 my( $self, $conn, $auth, $userid ) = @_;
2090 my $e = new_editor(authtoken=>$auth);
2091 return $e->event unless $e->checkauth;
2093 if( $userid ne $e->requestor->id ) {
2094 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2097 # money is owed on these items and they are checked in
2098 my $open = $e->search_action_circulation(
2101 xact_finish => undef,
2102 checkin_time => { "!=" => undef },
2107 my( @lost, @cr, @lo );
2108 for my $c (@$open) {
2109 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2110 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2111 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2116 claims_returned => \@cr,
2117 long_overdue => \@lo
2123 my ($api, $desc, $auth) = @_;
2124 $desc = $desc ? (" " . $desc) : '';
2125 my $ids = ($api =~ /ids$/) ? 1 : 0;
2128 method => "user_transaction_history",
2129 api_name => "open-ils.actor.user.transactions.$api",
2131 desc => "For a given User ID, returns a list of billable transaction" .
2132 ($ids ? " id" : '') .
2133 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2134 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2136 {desc => 'Authentication token', type => 'string'},
2137 {desc => 'User ID', type => 'number'},
2138 {desc => 'Transaction type (optional)', type => 'number'},
2139 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2142 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2146 $auth and push @sig, (authoritative => 1);
2150 my %auth_hist_methods = (
2152 'history.have_charge' => 'that have an initial charge',
2153 'history.still_open' => 'that are not finished',
2154 'history.have_balance' => 'that have a balance',
2155 'history.have_bill' => 'that have billings',
2156 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2157 'history.have_payment' => 'that have at least 1 payment',
2160 foreach (keys %auth_hist_methods) {
2161 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2162 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2163 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2166 sub user_transaction_history {
2167 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2171 my $e = new_editor(authtoken=>$auth);
2172 return $e->die_event unless $e->checkauth;
2174 if ($e->requestor->id ne $userid) {
2175 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2178 my $api = $self->api_name;
2179 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2181 if(defined($type)) {
2182 $filter->{'xact_type'} = $type;
2185 if($api =~ /have_bill_or_payment/o) {
2187 # transactions that have a non-zero sum across all billings or at least 1 payment
2188 $filter->{'-or'} = {
2189 'balance_owed' => { '<>' => 0 },
2190 'last_payment_ts' => { '<>' => undef }
2193 } elsif($api =~ /have_payment/) {
2195 $filter->{last_payment_ts} ||= {'<>' => undef};
2197 } elsif( $api =~ /have_balance/o) {
2199 # transactions that have a non-zero overall balance
2200 $filter->{'balance_owed'} = { '<>' => 0 };
2202 } elsif( $api =~ /have_charge/o) {
2204 # transactions that have at least 1 billing, regardless of whether it was voided
2205 $filter->{'last_billing_ts'} = { '<>' => undef };
2207 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2209 # transactions that have non-zero sum across all billings. This will exclude
2210 # xacts where all billings have been voided
2211 $filter->{'total_owed'} = { '<>' => 0 };
2214 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2215 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2216 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2218 my $mbts = $e->search_money_billable_transaction_summary(
2219 [ { usr => $userid, @xact_finish, %$filter },
2224 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2225 return $mbts unless $api =~ /fleshed/;
2228 for my $t (@$mbts) {
2230 if( $t->xact_type ne 'circulation' ) {
2231 push @resp, {transaction => $t};
2235 my $circ_data = flesh_circ($e, $t->id);
2236 push @resp, {transaction => $t, %$circ_data};
2244 __PACKAGE__->register_method(
2245 method => "user_perms",
2246 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2248 notes => "Returns a list of permissions"
2252 my( $self, $client, $authtoken, $user ) = @_;
2254 my( $staff, $evt ) = $apputils->checkses($authtoken);
2255 return $evt if $evt;
2257 $user ||= $staff->id;
2259 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2263 return $apputils->simple_scalar_request(
2265 "open-ils.storage.permission.user_perms.atomic",
2269 __PACKAGE__->register_method(
2270 method => "retrieve_perms",
2271 api_name => "open-ils.actor.permissions.retrieve",
2272 notes => "Returns a list of permissions"
2274 sub retrieve_perms {
2275 my( $self, $client ) = @_;
2276 return $apputils->simple_scalar_request(
2278 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2279 { id => { '!=' => undef } }
2283 __PACKAGE__->register_method(
2284 method => "retrieve_groups",
2285 api_name => "open-ils.actor.groups.retrieve",
2286 notes => "Returns a list of user groups"
2288 sub retrieve_groups {
2289 my( $self, $client ) = @_;
2290 return new_editor()->retrieve_all_permission_grp_tree();
2293 __PACKAGE__->register_method(
2294 method => "retrieve_org_address",
2295 api_name => "open-ils.actor.org_unit.address.retrieve",
2296 notes => <<' NOTES');
2297 Returns an org_unit address by ID
2298 @param An org_address ID
2300 sub retrieve_org_address {
2301 my( $self, $client, $id ) = @_;
2302 return $apputils->simple_scalar_request(
2304 "open-ils.cstore.direct.actor.org_address.retrieve",
2309 __PACKAGE__->register_method(
2310 method => "retrieve_groups_tree",
2311 api_name => "open-ils.actor.groups.tree.retrieve",
2312 notes => "Returns a list of user groups"
2315 sub retrieve_groups_tree {
2316 my( $self, $client ) = @_;
2317 return new_editor()->search_permission_grp_tree(
2322 flesh_fields => { pgt => ["children"] },
2323 order_by => { pgt => 'name'}
2330 __PACKAGE__->register_method(
2331 method => "add_user_to_groups",
2332 api_name => "open-ils.actor.user.set_groups",
2333 notes => "Adds a user to one or more permission groups"
2336 sub add_user_to_groups {
2337 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2339 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2340 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2341 return $evt if $evt;
2343 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2344 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2345 return $evt if $evt;
2347 $apputils->simplereq(
2349 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2351 for my $group (@$groups) {
2352 my $link = Fieldmapper::permission::usr_grp_map->new;
2354 $link->usr($userid);
2356 my $id = $apputils->simplereq(
2358 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2364 __PACKAGE__->register_method(
2365 method => "get_user_perm_groups",
2366 api_name => "open-ils.actor.user.get_groups",
2367 notes => "Retrieve a user's permission groups."
2371 sub get_user_perm_groups {
2372 my( $self, $client, $authtoken, $userid ) = @_;
2374 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2375 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2376 return $evt if $evt;
2378 return $apputils->simplereq(
2380 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2384 __PACKAGE__->register_method(
2385 method => "get_user_work_ous",
2386 api_name => "open-ils.actor.user.get_work_ous",
2387 notes => "Retrieve a user's work org units."
2390 __PACKAGE__->register_method(
2391 method => "get_user_work_ous",
2392 api_name => "open-ils.actor.user.get_work_ous.ids",
2393 notes => "Retrieve a user's work org units."
2396 sub get_user_work_ous {
2397 my( $self, $client, $auth, $userid ) = @_;
2398 my $e = new_editor(authtoken=>$auth);
2399 return $e->event unless $e->checkauth;
2400 $userid ||= $e->requestor->id;
2402 if($e->requestor->id != $userid) {
2403 my $user = $e->retrieve_actor_user($userid)
2404 or return $e->event;
2405 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2408 return $e->search_permission_usr_work_ou_map({usr => $userid})
2409 unless $self->api_name =~ /.ids$/;
2411 # client just wants a list of org IDs
2412 return $U->get_user_work_ou_ids($e, $userid);
2417 __PACKAGE__->register_method(
2418 method => 'register_workstation',
2419 api_name => 'open-ils.actor.workstation.register.override',
2420 signature => q/@see open-ils.actor.workstation.register/
2423 __PACKAGE__->register_method(
2424 method => 'register_workstation',
2425 api_name => 'open-ils.actor.workstation.register',
2427 Registers a new workstion in the system
2428 @param authtoken The login session key
2429 @param name The name of the workstation id
2430 @param owner The org unit that owns this workstation
2431 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2432 if the name is already in use.
2436 sub register_workstation {
2437 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2439 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2440 return $e->die_event unless $e->checkauth;
2441 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2442 my $existing = $e->search_actor_workstation({name => $name})->[0];
2443 $oargs = { all => 1 } unless defined $oargs;
2447 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2448 # workstation with the given name exists.
2450 if($owner ne $existing->owning_lib) {
2451 # if necessary, update the owning_lib of the workstation
2453 $logger->info("changing owning lib of workstation ".$existing->id.
2454 " from ".$existing->owning_lib." to $owner");
2455 return $e->die_event unless
2456 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2458 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2460 $existing->owning_lib($owner);
2461 return $e->die_event unless $e->update_actor_workstation($existing);
2467 "attempt to register an existing workstation. returning existing ID");
2470 return $existing->id;
2473 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2477 my $ws = Fieldmapper::actor::workstation->new;
2478 $ws->owning_lib($owner);
2480 $e->create_actor_workstation($ws) or return $e->die_event;
2482 return $ws->id; # note: editor sets the id on the new object for us
2485 __PACKAGE__->register_method(
2486 method => 'workstation_list',
2487 api_name => 'open-ils.actor.workstation.list',
2489 Returns a list of workstations registered at the given location
2490 @param authtoken The login session key
2491 @param ids A list of org_unit.id's for the workstation owners
2495 sub workstation_list {
2496 my( $self, $conn, $authtoken, @orgs ) = @_;
2498 my $e = new_editor(authtoken=>$authtoken);
2499 return $e->event unless $e->checkauth;
2504 unless $e->allowed('REGISTER_WORKSTATION', $o);
2505 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2511 __PACKAGE__->register_method(
2512 method => 'fetch_patron_note',
2513 api_name => 'open-ils.actor.note.retrieve.all',
2516 Returns a list of notes for a given user
2517 Requestor must have VIEW_USER permission if pub==false and
2518 @param authtoken The login session key
2519 @param args Hash of params including
2520 patronid : the patron's id
2521 pub : true if retrieving only public notes
2525 sub fetch_patron_note {
2526 my( $self, $conn, $authtoken, $args ) = @_;
2527 my $patronid = $$args{patronid};
2529 my($reqr, $evt) = $U->checkses($authtoken);
2530 return $evt if $evt;
2533 ($patron, $evt) = $U->fetch_user($patronid);
2534 return $evt if $evt;
2537 if( $patronid ne $reqr->id ) {
2538 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2539 return $evt if $evt;
2541 return $U->cstorereq(
2542 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2543 { usr => $patronid, pub => 't' } );
2546 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2547 return $evt if $evt;
2549 return $U->cstorereq(
2550 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2553 __PACKAGE__->register_method(
2554 method => 'create_user_note',
2555 api_name => 'open-ils.actor.note.create',
2557 Creates a new note for the given user
2558 @param authtoken The login session key
2559 @param note The note object
2562 sub create_user_note {
2563 my( $self, $conn, $authtoken, $note ) = @_;
2564 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2565 return $e->die_event unless $e->checkauth;
2567 my $user = $e->retrieve_actor_user($note->usr)
2568 or return $e->die_event;
2570 return $e->die_event unless
2571 $e->allowed('UPDATE_USER',$user->home_ou);
2573 $note->creator($e->requestor->id);
2574 $e->create_actor_usr_note($note) or return $e->die_event;
2580 __PACKAGE__->register_method(
2581 method => 'delete_user_note',
2582 api_name => 'open-ils.actor.note.delete',
2584 Deletes a note for the given user
2585 @param authtoken The login session key
2586 @param noteid The note id
2589 sub delete_user_note {
2590 my( $self, $conn, $authtoken, $noteid ) = @_;
2592 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2593 return $e->die_event unless $e->checkauth;
2594 my $note = $e->retrieve_actor_usr_note($noteid)
2595 or return $e->die_event;
2596 my $user = $e->retrieve_actor_user($note->usr)
2597 or return $e->die_event;
2598 return $e->die_event unless
2599 $e->allowed('UPDATE_USER', $user->home_ou);
2601 $e->delete_actor_usr_note($note) or return $e->die_event;
2607 __PACKAGE__->register_method(
2608 method => 'update_user_note',
2609 api_name => 'open-ils.actor.note.update',
2611 @param authtoken The login session key
2612 @param note The note
2616 sub update_user_note {
2617 my( $self, $conn, $auth, $note ) = @_;
2618 my $e = new_editor(authtoken=>$auth, xact=>1);
2619 return $e->die_event unless $e->checkauth;
2620 my $patron = $e->retrieve_actor_user($note->usr)
2621 or return $e->die_event;
2622 return $e->die_event unless
2623 $e->allowed('UPDATE_USER', $patron->home_ou);
2624 $e->update_actor_user_note($note)
2625 or return $e->die_event;
2632 __PACKAGE__->register_method(
2633 method => 'create_closed_date',
2634 api_name => 'open-ils.actor.org_unit.closed_date.create',
2636 Creates a new closing entry for the given org_unit
2637 @param authtoken The login session key
2638 @param note The closed_date object
2641 sub create_closed_date {
2642 my( $self, $conn, $authtoken, $cd ) = @_;
2644 my( $user, $evt ) = $U->checkses($authtoken);
2645 return $evt if $evt;
2647 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2648 return $evt if $evt;
2650 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2652 my $id = $U->storagereq(
2653 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2654 return $U->DB_UPDATE_FAILED($cd) unless $id;
2659 __PACKAGE__->register_method(
2660 method => 'delete_closed_date',
2661 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2663 Deletes a closing entry for the given org_unit
2664 @param authtoken The login session key
2665 @param noteid The close_date id
2668 sub delete_closed_date {
2669 my( $self, $conn, $authtoken, $cd ) = @_;
2671 my( $user, $evt ) = $U->checkses($authtoken);
2672 return $evt if $evt;
2675 ($cd_obj, $evt) = fetch_closed_date($cd);
2676 return $evt if $evt;
2678 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2679 return $evt if $evt;
2681 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2683 my $stat = $U->storagereq(
2684 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2685 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2690 __PACKAGE__->register_method(
2691 method => 'usrname_exists',
2692 api_name => 'open-ils.actor.username.exists',
2694 desc => 'Check if a username is already taken (by an undeleted patron)',
2696 {desc => 'Authentication token', type => 'string'},
2697 {desc => 'Username', type => 'string'}
2700 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2705 sub usrname_exists {
2706 my( $self, $conn, $auth, $usrname ) = @_;
2707 my $e = new_editor(authtoken=>$auth);
2708 return $e->event unless $e->checkauth;
2709 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2710 return $$a[0] if $a and @$a;
2714 __PACKAGE__->register_method(
2715 method => 'barcode_exists',
2716 api_name => 'open-ils.actor.barcode.exists',
2718 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2721 sub barcode_exists {
2722 my( $self, $conn, $auth, $barcode ) = @_;
2723 my $e = new_editor(authtoken=>$auth);
2724 return $e->event unless $e->checkauth;
2725 my $card = $e->search_actor_card({barcode => $barcode});
2731 #return undef unless @$card;
2732 #return $card->[0]->usr;
2736 __PACKAGE__->register_method(
2737 method => 'retrieve_net_levels',
2738 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2741 sub retrieve_net_levels {
2742 my( $self, $conn, $auth ) = @_;
2743 my $e = new_editor(authtoken=>$auth);
2744 return $e->event unless $e->checkauth;
2745 return $e->retrieve_all_config_net_access_level();
2748 # Retain the old typo API name just in case
2749 __PACKAGE__->register_method(
2750 method => 'fetch_org_by_shortname',
2751 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2753 __PACKAGE__->register_method(
2754 method => 'fetch_org_by_shortname',
2755 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2757 sub fetch_org_by_shortname {
2758 my( $self, $conn, $sname ) = @_;
2759 my $e = new_editor();
2760 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2761 return $e->event unless $org;
2766 __PACKAGE__->register_method(
2767 method => 'session_home_lib',
2768 api_name => 'open-ils.actor.session.home_lib',
2771 sub session_home_lib {
2772 my( $self, $conn, $auth ) = @_;
2773 my $e = new_editor(authtoken=>$auth);
2774 return undef unless $e->checkauth;
2775 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2776 return $org->shortname;
2779 __PACKAGE__->register_method(
2780 method => 'session_safe_token',
2781 api_name => 'open-ils.actor.session.safe_token',
2783 Returns a hashed session ID that is safe for export to the world.
2784 This safe token will expire after 1 hour of non-use.
2785 @param auth Active authentication token
2789 sub session_safe_token {
2790 my( $self, $conn, $auth ) = @_;
2791 my $e = new_editor(authtoken=>$auth);
2792 return undef unless $e->checkauth;
2794 my $safe_token = md5_hex($auth);
2796 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2798 # Add more like the following if needed...
2800 "safe-token-home_lib-shortname-$safe_token",
2801 $e->retrieve_actor_org_unit(
2802 $e->requestor->home_ou
2811 __PACKAGE__->register_method(
2812 method => 'safe_token_home_lib',
2813 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2815 Returns the home library shortname from the session
2816 asscociated with a safe token from generated by
2817 open-ils.actor.session.safe_token.
2818 @param safe_token Active safe token
2822 sub safe_token_home_lib {
2823 my( $self, $conn, $safe_token ) = @_;
2825 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2826 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2830 __PACKAGE__->register_method(
2831 method => "update_penalties",
2832 api_name => "open-ils.actor.user.penalties.update"
2835 sub update_penalties {
2836 my($self, $conn, $auth, $user_id) = @_;
2837 my $e = new_editor(authtoken=>$auth, xact => 1);
2838 return $e->die_event unless $e->checkauth;
2839 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2840 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2841 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2842 return $evt if $evt;
2848 __PACKAGE__->register_method(
2849 method => "apply_penalty",
2850 api_name => "open-ils.actor.user.penalty.apply"
2854 my($self, $conn, $auth, $penalty) = @_;
2856 my $e = new_editor(authtoken=>$auth, xact => 1);
2857 return $e->die_event unless $e->checkauth;
2859 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2860 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2862 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2865 (defined $ptype->org_depth) ?
2866 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2869 $penalty->org_unit($ctx_org);
2870 $penalty->staff($e->requestor->id);
2871 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2874 return $penalty->id;
2877 __PACKAGE__->register_method(
2878 method => "remove_penalty",
2879 api_name => "open-ils.actor.user.penalty.remove"
2882 sub remove_penalty {
2883 my($self, $conn, $auth, $penalty) = @_;
2884 my $e = new_editor(authtoken=>$auth, xact => 1);
2885 return $e->die_event unless $e->checkauth;
2886 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2887 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2889 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2894 __PACKAGE__->register_method(
2895 method => "update_penalty_note",
2896 api_name => "open-ils.actor.user.penalty.note.update"
2899 sub update_penalty_note {
2900 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2901 my $e = new_editor(authtoken=>$auth, xact => 1);
2902 return $e->die_event unless $e->checkauth;
2903 for my $penalty_id (@$penalty_ids) {
2904 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2905 if (! $penalty ) { return $e->die_event; }
2906 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2907 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2909 $penalty->note( $note ); $penalty->ischanged( 1 );
2911 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2917 __PACKAGE__->register_method(
2918 method => "ranged_penalty_thresholds",
2919 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2923 sub ranged_penalty_thresholds {
2924 my($self, $conn, $auth, $context_org) = @_;
2925 my $e = new_editor(authtoken=>$auth);
2926 return $e->event unless $e->checkauth;
2927 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2928 my $list = $e->search_permission_grp_penalty_threshold([
2929 {org_unit => $U->get_org_ancestors($context_org)},
2930 {order_by => {pgpt => 'id'}}
2932 $conn->respond($_) for @$list;
2938 __PACKAGE__->register_method(
2939 method => "user_retrieve_fleshed_by_id",
2941 api_name => "open-ils.actor.user.fleshed.retrieve",
2944 sub user_retrieve_fleshed_by_id {
2945 my( $self, $client, $auth, $user_id, $fields ) = @_;
2946 my $e = new_editor(authtoken => $auth);
2947 return $e->event unless $e->checkauth;
2949 if( $e->requestor->id != $user_id ) {
2950 return $e->event unless $e->allowed('VIEW_USER');
2956 "standing_penalties",
2962 return new_flesh_user($user_id, $fields, $e);
2966 sub new_flesh_user {
2969 my $fields = shift || [];
2972 my $fetch_penalties = 0;
2973 if(grep {$_ eq 'standing_penalties'} @$fields) {
2974 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2975 $fetch_penalties = 1;
2978 my $fetch_usr_act = 0;
2979 if(grep {$_ eq 'usr_activity'} @$fields) {
2980 $fields = [grep {$_ ne 'usr_activity'} @$fields];
2984 my $user = $e->retrieve_actor_user(
2989 "flesh_fields" => { "au" => $fields }
2992 ) or return $e->die_event;
2995 if( grep { $_ eq 'addresses' } @$fields ) {
2997 $user->addresses([]) unless @{$user->addresses};
2998 # don't expose "replaced" addresses by default
2999 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3001 if( ref $user->billing_address ) {
3002 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3003 push( @{$user->addresses}, $user->billing_address );
3007 if( ref $user->mailing_address ) {
3008 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3009 push( @{$user->addresses}, $user->mailing_address );
3014 if($fetch_penalties) {
3015 # grab the user penalties ranged for this location
3016 $user->standing_penalties(
3017 $e->search_actor_user_standing_penalty([
3020 {stop_date => undef},
3021 {stop_date => {'>' => 'now'}}
3023 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3026 flesh_fields => {ausp => ['standing_penalty']}
3032 # retrieve the most recent usr_activity entry
3033 if ($fetch_usr_act) {
3035 # max number to return for simple patron fleshing
3036 my $limit = $U->ou_ancestor_setting_value(
3037 $e->requestor->ws_ou,
3038 'circ.patron.usr_activity_retrieve.max');
3042 flesh_fields => {auact => ['etype']},
3043 order_by => {auact => 'event_time DESC'},
3046 # 0 == none, <0 == return all
3047 $limit = 1 unless defined $limit;
3048 $opts->{limit} = $limit if $limit > 0;
3050 $user->usr_activity(
3052 [] : # skip the DB call
3053 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3058 $user->clear_passwd();
3065 __PACKAGE__->register_method(
3066 method => "user_retrieve_parts",
3067 api_name => "open-ils.actor.user.retrieve.parts",
3070 sub user_retrieve_parts {
3071 my( $self, $client, $auth, $user_id, $fields ) = @_;
3072 my $e = new_editor(authtoken => $auth);
3073 return $e->event unless $e->checkauth;
3074 $user_id ||= $e->requestor->id;
3075 if( $e->requestor->id != $user_id ) {
3076 return $e->event unless $e->allowed('VIEW_USER');
3079 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3080 push(@resp, $user->$_()) for(@$fields);
3086 __PACKAGE__->register_method(
3087 method => 'user_opt_in_enabled',
3088 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3089 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3092 sub user_opt_in_enabled {
3093 my($self, $conn) = @_;
3094 my $sc = OpenSRF::Utils::SettingsClient->new;
3095 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3100 __PACKAGE__->register_method(
3101 method => 'user_opt_in_at_org',
3102 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3104 @param $auth The auth token
3105 @param user_id The ID of the user to test
3106 @return 1 if the user has opted in at the specified org,
3107 event on error, and 0 otherwise. /
3109 sub user_opt_in_at_org {
3110 my($self, $conn, $auth, $user_id) = @_;
3112 # see if we even need to enforce the opt-in value
3113 return 1 unless user_opt_in_enabled($self);
3115 my $e = new_editor(authtoken => $auth);
3116 return $e->event unless $e->checkauth;
3118 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3119 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3121 my $ws_org = $e->requestor->ws_ou;
3122 # user is automatically opted-in if they are from the local org
3123 return 1 if $user->home_ou eq $ws_org;
3125 # get the boundary setting
3126 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3128 # auto opt in if user falls within the opt boundary
3129 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3131 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3133 my $vals = $e->search_actor_usr_org_unit_opt_in(
3134 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3140 __PACKAGE__->register_method(
3141 method => 'create_user_opt_in_at_org',
3142 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3144 @param $auth The auth token
3145 @param user_id The ID of the user to test
3146 @return The ID of the newly created object, event on error./
3149 sub create_user_opt_in_at_org {
3150 my($self, $conn, $auth, $user_id, $org_id) = @_;
3152 my $e = new_editor(authtoken => $auth, xact=>1);
3153 return $e->die_event unless $e->checkauth;
3155 # if a specific org unit wasn't passed in, get one based on the defaults;
3157 my $wsou = $e->requestor->ws_ou;
3158 # get the default opt depth
3159 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3160 # get the org unit at that depth
3161 my $org = $e->json_query({
3162 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3163 $org_id = $org->{id};
3166 # fall back to the workstation OU, the pre-opt-in-boundary way
3167 $org_id = $e->requestor->ws_ou;
3170 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3171 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3173 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3175 $opt_in->org_unit($org_id);
3176 $opt_in->usr($user_id);
3177 $opt_in->staff($e->requestor->id);
3178 $opt_in->opt_in_ts('now');
3179 $opt_in->opt_in_ws($e->requestor->wsid);
3181 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3182 or return $e->die_event;
3190 __PACKAGE__->register_method (
3191 method => 'retrieve_org_hours',
3192 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3194 Returns the hours of operation for a specified org unit
3195 @param authtoken The login session key
3196 @param org_id The org_unit ID
3200 sub retrieve_org_hours {
3201 my($self, $conn, $auth, $org_id) = @_;
3202 my $e = new_editor(authtoken => $auth);
3203 return $e->die_event unless $e->checkauth;
3204 $org_id ||= $e->requestor->ws_ou;
3205 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3209 __PACKAGE__->register_method (
3210 method => 'verify_user_password',
3211 api_name => 'open-ils.actor.verify_user_password',
3213 Given a barcode or username and the MD5 encoded password,
3214 returns 1 if the password is correct. Returns 0 otherwise.
3218 sub verify_user_password {
3219 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3220 my $e = new_editor(authtoken => $auth);
3221 return $e->die_event unless $e->checkauth;
3223 my $user_by_barcode;
3224 my $user_by_username;
3226 my $card = $e->search_actor_card([
3227 {barcode => $barcode},
3228 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3229 $user_by_barcode = $card->usr;
3230 $user = $user_by_barcode;
3233 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3234 $user = $user_by_username;
3236 return 0 if (!$user);
3237 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3238 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3239 return 1 if $user->passwd eq $password;
3243 __PACKAGE__->register_method (
3244 method => 'retrieve_usr_id_via_barcode_or_usrname',
3245 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3247 Given a barcode or username returns the id for the user or
3252 sub retrieve_usr_id_via_barcode_or_usrname {
3253 my($self, $conn, $auth, $barcode, $username) = @_;
3254 my $e = new_editor(authtoken => $auth);
3255 return $e->die_event unless $e->checkauth;
3256 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3258 my $user_by_barcode;
3259 my $user_by_username;
3260 $logger->info("$id_as_barcode is the ID as BARCODE");
3262 my $card = $e->search_actor_card([
3263 {barcode => $barcode},
3264 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3265 if ($id_as_barcode =~ /^t/i) {
3267 $user = $e->retrieve_actor_user($barcode);
3268 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3270 $user_by_barcode = $card->usr;
3271 $user = $user_by_barcode;
3274 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3275 $user_by_barcode = $card->usr;
3276 $user = $user_by_barcode;
3281 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3283 $user = $user_by_username;
3285 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3286 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3287 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3292 __PACKAGE__->register_method (
3293 method => 'merge_users',
3294 api_name => 'open-ils.actor.user.merge',
3297 Given a list of source users and destination user, transfer all data from the source
3298 to the dest user and delete the source user. All user related data is
3299 transferred, including circulations, holds, bookbags, etc.
3305 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3306 my $e = new_editor(xact => 1, authtoken => $auth);
3307 return $e->die_event unless $e->checkauth;
3309 # disallow the merge if any subordinate accounts are in collections
3310 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3311 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3313 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3314 my $del_addrs = ($U->ou_ancestor_setting_value(
3315 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3316 my $del_cards = ($U->ou_ancestor_setting_value(
3317 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3318 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3319 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3321 for my $src_id (@$user_ids) {
3322 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3324 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3325 if($src_user->home_ou ne $master_user->home_ou) {
3326 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3329 return $e->die_event unless
3330 $e->json_query({from => [
3345 __PACKAGE__->register_method (
3346 method => 'approve_user_address',
3347 api_name => 'open-ils.actor.user.pending_address.approve',
3354 sub approve_user_address {
3355 my($self, $conn, $auth, $addr) = @_;
3356 my $e = new_editor(xact => 1, authtoken => $auth);
3357 return $e->die_event unless $e->checkauth;
3359 # if the caller passes an address object, assume they want to
3360 # update it first before approving it
3361 $e->update_actor_user_address($addr) or return $e->die_event;
3363 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3365 my $user = $e->retrieve_actor_user($addr->usr);
3366 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3367 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3368 or return $e->die_event;
3370 return [values %$result]->[0];
3374 __PACKAGE__->register_method (
3375 method => 'retrieve_friends',
3376 api_name => 'open-ils.actor.friends.retrieve',
3379 returns { confirmed: [], pending_out: [], pending_in: []}
3380 pending_out are users I'm requesting friendship with
3381 pending_in are users requesting friendship with me
3386 sub retrieve_friends {
3387 my($self, $conn, $auth, $user_id, $options) = @_;
3388 my $e = new_editor(authtoken => $auth);
3389 return $e->event unless $e->checkauth;
3390 $user_id ||= $e->requestor->id;
3392 if($user_id != $e->requestor->id) {
3393 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3394 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3397 return OpenILS::Application::Actor::Friends->retrieve_friends(
3398 $e, $user_id, $options);
3403 __PACKAGE__->register_method (
3404 method => 'apply_friend_perms',
3405 api_name => 'open-ils.actor.friends.perms.apply',
3411 sub apply_friend_perms {
3412 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3413 my $e = new_editor(authtoken => $auth, xact => 1);
3414 return $e->die_event unless $e->checkauth;
3416 if($user_id != $e->requestor->id) {
3417 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3418 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3421 for my $perm (@perms) {
3423 OpenILS::Application::Actor::Friends->apply_friend_perm(
3424 $e, $user_id, $delegate_id, $perm);
3425 return $evt if $evt;
3433 __PACKAGE__->register_method (
3434 method => 'update_user_pending_address',
3435 api_name => 'open-ils.actor.user.address.pending.cud'
3438 sub update_user_pending_address {
3439 my($self, $conn, $auth, $addr) = @_;
3440 my $e = new_editor(authtoken => $auth, xact => 1);
3441 return $e->die_event unless $e->checkauth;
3443 if($addr->usr != $e->requestor->id) {
3444 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3445 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3449 $e->create_actor_user_address($addr) or return $e->die_event;
3450 } elsif($addr->isdeleted) {
3451 $e->delete_actor_user_address($addr) or return $e->die_event;
3453 $e->update_actor_user_address($addr) or return $e->die_event;
3461 __PACKAGE__->register_method (
3462 method => 'user_events',
3463 api_name => 'open-ils.actor.user.events.circ',
3466 __PACKAGE__->register_method (
3467 method => 'user_events',
3468 api_name => 'open-ils.actor.user.events.ahr',
3473 my($self, $conn, $auth, $user_id, $filters) = @_;
3474 my $e = new_editor(authtoken => $auth);
3475 return $e->event unless $e->checkauth;
3477 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3478 my $user_field = 'usr';
3481 $filters->{target} = {
3482 select => { $obj_type => ['id'] },
3484 where => {usr => $user_id}
3487 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3488 if($e->requestor->id != $user_id) {
3489 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3492 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3493 my $req = $ses->request('open-ils.trigger.events_by_target',
3494 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3496 while(my $resp = $req->recv) {
3497 my $val = $resp->content;
3498 my $tgt = $val->target;
3500 if($obj_type eq 'circ') {
3501 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3503 } elsif($obj_type eq 'ahr') {
3504 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3505 if $tgt->current_copy;
3508 $conn->respond($val) if $val;
3514 __PACKAGE__->register_method (
3515 method => 'copy_events',
3516 api_name => 'open-ils.actor.copy.events.circ',
3519 __PACKAGE__->register_method (
3520 method => 'copy_events',
3521 api_name => 'open-ils.actor.copy.events.ahr',
3526 my($self, $conn, $auth, $copy_id, $filters) = @_;
3527 my $e = new_editor(authtoken => $auth);
3528 return $e->event unless $e->checkauth;
3530 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3532 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3534 my $copy_field = 'target_copy';
3535 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3538 $filters->{target} = {
3539 select => { $obj_type => ['id'] },
3541 where => {$copy_field => $copy_id}
3545 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3546 my $req = $ses->request('open-ils.trigger.events_by_target',
3547 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3549 while(my $resp = $req->recv) {
3550 my $val = $resp->content;
3551 my $tgt = $val->target;
3553 my $user = $e->retrieve_actor_user($tgt->usr);
3554 if($e->requestor->id != $user->id) {
3555 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3558 $tgt->$copy_field($copy);
3561 $conn->respond($val) if $val;
3570 __PACKAGE__->register_method (
3571 method => 'update_events',
3572 api_name => 'open-ils.actor.user.event.cancel.batch',
3575 __PACKAGE__->register_method (
3576 method => 'update_events',
3577 api_name => 'open-ils.actor.user.event.reset.batch',
3582 my($self, $conn, $auth, $event_ids) = @_;
3583 my $e = new_editor(xact => 1, authtoken => $auth);
3584 return $e->die_event unless $e->checkauth;
3587 for my $id (@$event_ids) {
3589 # do a little dance to determine what user we are ultimately affecting
3590 my $event = $e->retrieve_action_trigger_event([
3593 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3595 ]) or return $e->die_event;
3598 if($event->event_def->hook->core_type eq 'circ') {
3599 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3600 } elsif($event->event_def->hook->core_type eq 'ahr') {
3601 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3606 my $user = $e->retrieve_actor_user($user_id);
3607 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3609 if($self->api_name =~ /cancel/) {
3610 $event->state('invalid');
3611 } elsif($self->api_name =~ /reset/) {
3612 $event->clear_start_time;
3613 $event->clear_update_time;
3614 $event->state('pending');
3617 $e->update_action_trigger_event($event) or return $e->die_event;
3618 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3622 return {complete => 1};
3626 __PACKAGE__->register_method (
3627 method => 'really_delete_user',
3628 api_name => 'open-ils.actor.user.delete.override',
3629 signature => q/@see open-ils.actor.user.delete/
3632 __PACKAGE__->register_method (
3633 method => 'really_delete_user',
3634 api_name => 'open-ils.actor.user.delete',
3636 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3637 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3638 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3639 dest_usr_id is only required when deleting a user that performs staff functions.
3643 sub really_delete_user {
3644 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3645 my $e = new_editor(authtoken => $auth, xact => 1);
3646 return $e->die_event unless $e->checkauth;
3647 $oargs = { all => 1 } unless defined $oargs;
3649 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3650 my $open_bills = $e->json_query({
3651 select => { mbts => ['id'] },
3654 xact_finish => { '=' => undef },
3655 usr => { '=' => $user_id },
3657 }) or return $e->die_event;
3659 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3661 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3663 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3664 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3665 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3667 # No deleting yourself - UI is supposed to stop you first, though.
3668 return $e->die_event unless $e->requestor->id != $user->id;
3669 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3670 # Check if you are allowed to mess with this patron permission group at all
3671 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3672 my $evt = group_perm_failed($session, $e->requestor, $user);
3673 return $e->die_event($evt) if $evt;
3674 my $stat = $e->json_query(
3675 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3676 or return $e->die_event;
3682 __PACKAGE__->register_method (
3683 method => 'user_payments',
3684 api_name => 'open-ils.actor.user.payments.retrieve',
3687 Returns all payments for a given user. Default order is newest payments first.
3688 @param auth Authentication token
3689 @param user_id The user ID
3690 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3695 my($self, $conn, $auth, $user_id, $filters) = @_;
3698 my $e = new_editor(authtoken => $auth);
3699 return $e->die_event unless $e->checkauth;
3701 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3702 return $e->event unless
3703 $e->requestor->id == $user_id or
3704 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3706 # Find all payments for all transactions for user $user_id
3708 select => {mp => ['id']},
3713 select => {mbt => ['id']},
3715 where => {usr => $user_id}
3720 { # by default, order newest payments first
3722 field => 'payment_ts',
3725 # secondary sort in ID as a tie-breaker, since payments created
3726 # within the same transaction will have identical payment_ts's
3733 for (qw/order_by limit offset/) {
3734 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3737 if(defined $filters->{where}) {
3738 foreach (keys %{$filters->{where}}) {
3739 # don't allow the caller to expand the result set to other users
3740 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3744 my $payment_ids = $e->json_query($query);
3745 for my $pid (@$payment_ids) {
3746 my $pay = $e->retrieve_money_payment([
3751 mbt => ['summary', 'circulation', 'grocery'],
3752 circ => ['target_copy'],
3753 acp => ['call_number'],
3761 xact_type => $pay->xact->summary->xact_type,
3762 last_billing_type => $pay->xact->summary->last_billing_type,
3765 if($pay->xact->summary->xact_type eq 'circulation') {
3766 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3767 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3770 $pay->xact($pay->xact->id); # de-flesh
3771 $conn->respond($resp);
3779 __PACKAGE__->register_method (
3780 method => 'negative_balance_users',
3781 api_name => 'open-ils.actor.users.negative_balance',
3784 Returns all users that have an overall negative balance
3785 @param auth Authentication token
3786 @param org_id The context org unit as an ID or list of IDs. This will be the home
3787 library of the user. If no org_unit is specified, no org unit filter is applied
3791 sub negative_balance_users {
3792 my($self, $conn, $auth, $org_id) = @_;
3794 my $e = new_editor(authtoken => $auth);
3795 return $e->die_event unless $e->checkauth;
3796 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3800 mous => ['usr', 'balance_owed'],
3803 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3804 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3821 where => {'+mous' => {balance_owed => {'<' => 0}}}
3824 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3826 my $list = $e->json_query($query, {timeout => 600});
3828 for my $data (@$list) {
3830 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3831 balance_owed => $data->{balance_owed},
3832 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3839 __PACKAGE__->register_method(
3840 method => "request_password_reset",
3841 api_name => "open-ils.actor.patron.password_reset.request",
3843 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3844 "method for changing a user's password. The UUID token is distributed via A/T " .
3845 "templates (i.e. email to the user).",
3847 { desc => 'user_id_type', type => 'string' },
3848 { desc => 'user_id', type => 'string' },
3849 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3851 return => {desc => '1 on success, Event on error'}
3854 sub request_password_reset {
3855 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3857 # Check to see if password reset requests are already being throttled:
3858 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3860 my $e = new_editor(xact => 1);
3863 # Get the user, if any, depending on the input value
3864 if ($user_id_type eq 'username') {
3865 $user = $e->search_actor_user({usrname => $user_id})->[0];
3868 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3870 } elsif ($user_id_type eq 'barcode') {
3871 my $card = $e->search_actor_card([
3872 {barcode => $user_id},
3873 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3876 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3881 # If the user doesn't have an email address, we can't help them
3882 if (!$user->email) {
3884 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3887 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3888 if ($email_must_match) {
3889 if ($user->email ne $email) {
3890 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3894 _reset_password_request($conn, $e, $user);
3897 # Once we have the user, we can issue the password reset request
3898 # XXX Add a wrapper method that accepts barcode + email input
3899 sub _reset_password_request {
3900 my ($conn, $e, $user) = @_;
3902 # 1. Get throttle threshold and time-to-live from OU_settings
3903 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3904 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3906 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3908 # 2. Get time of last request and number of active requests (num_active)
3909 my $active_requests = $e->json_query({
3915 transform => 'COUNT'
3918 column => 'request_time',
3924 has_been_reset => { '=' => 'f' },
3925 request_time => { '>' => $threshold_time }
3929 # Guard against no active requests
3930 if ($active_requests->[0]->{'request_time'}) {
3931 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3932 my $now = DateTime::Format::ISO8601->new();
3934 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3935 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3936 ($last_request->add_duration('1 minute') > $now)) {
3937 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3939 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3943 # TODO Check to see if the user is in a password-reset-restricted group
3945 # Otherwise, go ahead and try to get the user.
3947 # Check the number of active requests for this user
3948 $active_requests = $e->json_query({
3954 transform => 'COUNT'
3959 usr => { '=' => $user->id },
3960 has_been_reset => { '=' => 'f' },
3961 request_time => { '>' => $threshold_time }
3965 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3967 # if less than or equal to per-user threshold, proceed; otherwise, return event
3968 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3969 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3971 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3974 # Create the aupr object and insert into the database
3975 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3976 my $uuid = create_uuid_as_string(UUID_V4);
3977 $reset_request->uuid($uuid);
3978 $reset_request->usr($user->id);
3980 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3983 # Create an event to notify user of the URL to reset their password
3985 # Can we stuff this in the user_data param for trigger autocreate?
3986 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3988 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3989 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3992 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3997 __PACKAGE__->register_method(
3998 method => "commit_password_reset",
3999 api_name => "open-ils.actor.patron.password_reset.commit",
4001 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4002 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4003 "with the supplied password.",
4005 { desc => 'uuid', type => 'string' },
4006 { desc => 'password', type => 'string' },
4008 return => {desc => '1 on success, Event on error'}
4011 sub commit_password_reset {
4012 my($self, $conn, $uuid, $password) = @_;
4014 # Check to see if password reset requests are already being throttled:
4015 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4016 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4017 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4019 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4022 my $e = new_editor(xact => 1);
4024 my $aupr = $e->search_actor_usr_password_reset({
4031 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4033 my $user_id = $aupr->[0]->usr;
4034 my $user = $e->retrieve_actor_user($user_id);
4036 # Ensure we're still within the TTL for the request
4037 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4038 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4039 if ($threshold < DateTime->now(time_zone => 'local')) {
4041 $logger->info("Password reset request needed to be submitted before $threshold");
4042 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4045 # Check complexity of password against OU-defined regex
4046 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4050 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4051 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4052 $is_strong = check_password_strength_custom($password, $pw_regex);
4054 $is_strong = check_password_strength_default($password);
4059 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4062 # All is well; update the password
4063 $user->passwd($password);
4064 $e->update_actor_user($user);
4066 # And flag that this password reset request has been honoured
4067 $aupr->[0]->has_been_reset('t');
4068 $e->update_actor_usr_password_reset($aupr->[0]);
4074 sub check_password_strength_default {
4075 my $password = shift;
4076 # Use the default set of checks
4077 if ( (length($password) < 7) or
4078 ($password !~ m/.*\d+.*/) or
4079 ($password !~ m/.*[A-Za-z]+.*/)
4086 sub check_password_strength_custom {
4087 my ($password, $pw_regex) = @_;
4089 $pw_regex = qr/$pw_regex/;
4090 if ($password !~ /$pw_regex/) {
4098 __PACKAGE__->register_method(
4099 method => "event_def_opt_in_settings",
4100 api_name => "open-ils.actor.event_def.opt_in.settings",
4103 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4105 { desc => 'Authentication token', type => 'string'},
4107 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4112 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4119 sub event_def_opt_in_settings {
4120 my($self, $conn, $auth, $org_id) = @_;
4121 my $e = new_editor(authtoken => $auth);
4122 return $e->event unless $e->checkauth;
4124 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4125 return $e->event unless
4126 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4128 $org_id = $e->requestor->home_ou;
4131 # find all config.user_setting_type's related to event_defs for the requested org unit
4132 my $types = $e->json_query({
4133 select => {cust => ['name']},
4134 from => {atevdef => 'cust'},
4137 owner => $U->get_org_ancestors($org_id), # context org plus parents
4144 $conn->respond($_) for
4145 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4152 __PACKAGE__->register_method(
4153 method => "user_visible_circs",
4154 api_name => "open-ils.actor.history.circ.visible",
4157 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4159 { desc => 'Authentication token', type => 'string'},
4160 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4161 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4164 desc => q/An object with 2 fields: circulation and summary.
4165 circulation is the "circ" object. summary is the related "accs" object/,
4171 __PACKAGE__->register_method(
4172 method => "user_visible_circs",
4173 api_name => "open-ils.actor.history.circ.visible.print",
4176 desc => 'Returns printable output for the set of opt-in visible circulations',
4178 { desc => 'Authentication token', type => 'string'},
4179 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4180 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4183 desc => q/An action_trigger.event object or error event./,
4189 __PACKAGE__->register_method(
4190 method => "user_visible_circs",
4191 api_name => "open-ils.actor.history.circ.visible.email",
4194 desc => 'Emails the set of opt-in visible circulations to the requestor',
4196 { desc => 'Authentication token', type => 'string'},
4197 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4198 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4201 desc => q/undef, or event on error/
4206 __PACKAGE__->register_method(
4207 method => "user_visible_circs",
4208 api_name => "open-ils.actor.history.hold.visible",
4211 desc => 'Returns the set of opt-in visible holds',
4213 { desc => 'Authentication token', type => 'string'},
4214 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4215 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4218 desc => q/An object with 1 field: "hold"/,
4224 __PACKAGE__->register_method(
4225 method => "user_visible_circs",
4226 api_name => "open-ils.actor.history.hold.visible.print",
4229 desc => 'Returns printable output for the set of opt-in visible holds',
4231 { desc => 'Authentication token', type => 'string'},
4232 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4233 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4236 desc => q/An action_trigger.event object or error event./,
4242 __PACKAGE__->register_method(
4243 method => "user_visible_circs",
4244 api_name => "open-ils.actor.history.hold.visible.email",
4247 desc => 'Emails the set of opt-in visible holds to the requestor',
4249 { desc => 'Authentication token', type => 'string'},
4250 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4251 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4254 desc => q/undef, or event on error/
4259 sub user_visible_circs {
4260 my($self, $conn, $auth, $user_id, $options) = @_;
4262 my $is_hold = ($self->api_name =~ /hold/);
4263 my $for_print = ($self->api_name =~ /print/);
4264 my $for_email = ($self->api_name =~ /email/);
4265 my $e = new_editor(authtoken => $auth);
4266 return $e->event unless $e->checkauth;
4268 $user_id ||= $e->requestor->id;
4270 $options->{limit} ||= 50;
4271 $options->{offset} ||= 0;
4273 if($user_id != $e->requestor->id) {
4274 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4275 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4276 return $e->event unless $e->allowed($perm, $user->home_ou);
4279 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4281 my $data = $e->json_query({
4282 from => [$db_func, $user_id],
4283 limit => $$options{limit},
4284 offset => $$options{offset}
4286 # TODO: I only want IDs. code below didn't get me there
4287 # {"select":{"au":[{"column":"id", "result_field":"id",
4288 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4293 return undef unless @$data;
4297 # collect the batch of objects
4301 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4302 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4306 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4307 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4310 } elsif ($for_email) {
4312 $conn->respond_complete(1) if $for_email; # no sense in waiting
4320 my $hold = $e->retrieve_action_hold_request($id);
4321 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4322 # events will be fired from action_trigger_runner
4326 my $circ = $e->retrieve_action_circulation($id);
4327 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4328 # events will be fired from action_trigger_runner
4332 } else { # just give me the data please
4340 my $hold = $e->retrieve_action_hold_request($id);
4341 $conn->respond({hold => $hold});
4345 my $circ = $e->retrieve_action_circulation($id);
4348 summary => $U->create_circ_chain_summary($e, $id)
4357 __PACKAGE__->register_method(
4358 method => "user_saved_search_cud",
4359 api_name => "open-ils.actor.user.saved_search.cud",
4362 desc => 'Create/Update/Delete Access to user saved searches',
4364 { desc => 'Authentication token', type => 'string' },
4365 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4368 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4374 __PACKAGE__->register_method(
4375 method => "user_saved_search_cud",
4376 api_name => "open-ils.actor.user.saved_search.retrieve",
4379 desc => 'Retrieve a saved search object',
4381 { desc => 'Authentication token', type => 'string' },
4382 { desc => 'Saved Search ID', type => 'number' }
4385 desc => q/The saved search object, Event on error/,
4391 sub user_saved_search_cud {
4392 my( $self, $client, $auth, $search ) = @_;
4393 my $e = new_editor( authtoken=>$auth );
4394 return $e->die_event unless $e->checkauth;
4396 my $o_search; # prior version of the object, if any
4397 my $res; # to be returned
4399 # branch on the operation type
4401 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4403 # Get the old version, to check ownership
4404 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4405 or return $e->die_event;
4407 # You can't read somebody else's search
4408 return OpenILS::Event->new('BAD_PARAMS')
4409 unless $o_search->owner == $e->requestor->id;
4415 $e->xact_begin; # start an editor transaction
4417 if( $search->isnew ) { # Create
4419 # You can't create a search for somebody else
4420 return OpenILS::Event->new('BAD_PARAMS')
4421 unless $search->owner == $e->requestor->id;
4423 $e->create_actor_usr_saved_search( $search )
4424 or return $e->die_event;
4428 } elsif( $search->ischanged ) { # Update
4430 # You can't change ownership of a search
4431 return OpenILS::Event->new('BAD_PARAMS')
4432 unless $search->owner == $e->requestor->id;
4434 # Get the old version, to check ownership
4435 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4436 or return $e->die_event;
4438 # You can't update somebody else's search
4439 return OpenILS::Event->new('BAD_PARAMS')
4440 unless $o_search->owner == $e->requestor->id;
4443 $e->update_actor_usr_saved_search( $search )
4444 or return $e->die_event;
4448 } elsif( $search->isdeleted ) { # Delete
4450 # Get the old version, to check ownership
4451 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4452 or return $e->die_event;
4454 # You can't delete somebody else's search
4455 return OpenILS::Event->new('BAD_PARAMS')
4456 unless $o_search->owner == $e->requestor->id;
4459 $e->delete_actor_usr_saved_search( $o_search )
4460 or return $e->die_event;
4471 __PACKAGE__->register_method(
4472 method => "get_barcodes",
4473 api_name => "open-ils.actor.get_barcodes"
4477 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4478 my $e = new_editor(authtoken => $auth);
4479 return $e->event unless $e->checkauth;
4480 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4482 my $db_result = $e->json_query(
4484 'evergreen.get_barcodes',
4485 $org_id, $context, $barcode,
4489 if($context =~ /actor/) {
4490 my $filter_result = ();
4492 foreach my $result (@$db_result) {
4493 if($result->{type} eq 'actor') {
4494 if($e->requestor->id != $result->{id}) {
4495 $patron = $e->retrieve_actor_user($result->{id});
4497 push(@$filter_result, $e->event);
4500 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4501 push(@$filter_result, $result);
4504 push(@$filter_result, $e->event);
4508 push(@$filter_result, $result);
4512 push(@$filter_result, $result);
4515 return $filter_result;
4521 __PACKAGE__->register_method(
4522 method => 'address_alert_test',
4523 api_name => 'open-ils.actor.address_alert.test',
4525 desc => "Tests a set of address fields to determine if they match with an address_alert",
4527 {desc => 'Authentication token', type => 'string'},
4528 {desc => 'Org Unit', type => 'number'},
4529 {desc => 'Fields', type => 'hash'},
4531 return => {desc => 'List of matching address_alerts'}
4535 sub address_alert_test {
4536 my ($self, $client, $auth, $org_unit, $fields) = @_;
4537 return [] unless $fields and grep {$_} values %$fields;
4539 my $e = new_editor(authtoken => $auth);
4540 return $e->event unless $e->checkauth;
4541 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4542 $org_unit ||= $e->requestor->ws_ou;
4544 my $alerts = $e->json_query({
4546 'actor.address_alert_matches',
4554 $$fields{post_code},
4555 $$fields{mailing_address},
4556 $$fields{billing_address}
4560 # map the json_query hashes to real objects
4562 map {$e->retrieve_actor_address_alert($_)}
4563 (map {$_->{id}} @$alerts)
4567 __PACKAGE__->register_method(
4568 method => "mark_users_contact_invalid",
4569 api_name => "open-ils.actor.invalidate.email",
4571 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",
4573 {desc => "Authentication token", type => "string"},
4574 {desc => "Patron ID", type => "number"},
4575 {desc => "Additional note text (optional)", type => "string"},
4576 {desc => "penalty org unit ID (optional)", type => "number"}
4578 return => {desc => "Event describing success or failure", type => "object"}
4582 __PACKAGE__->register_method(
4583 method => "mark_users_contact_invalid",
4584 api_name => "open-ils.actor.invalidate.day_phone",
4586 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",
4588 {desc => "Authentication token", type => "string"},
4589 {desc => "Patron ID", type => "number"},
4590 {desc => "Additional note text (optional)", type => "string"},
4591 {desc => "penalty org unit ID (optional)", type => "number"}
4593 return => {desc => "Event describing success or failure", type => "object"}
4597 __PACKAGE__->register_method(
4598 method => "mark_users_contact_invalid",
4599 api_name => "open-ils.actor.invalidate.evening_phone",
4601 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",
4603 {desc => "Authentication token", type => "string"},
4604 {desc => "Patron ID", type => "number"},
4605 {desc => "Additional note text (optional)", type => "string"},
4606 {desc => "penalty org unit ID (optional)", type => "number"}
4608 return => {desc => "Event describing success or failure", type => "object"}
4612 __PACKAGE__->register_method(
4613 method => "mark_users_contact_invalid",
4614 api_name => "open-ils.actor.invalidate.other_phone",
4616 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",
4618 {desc => "Authentication token", type => "string"},
4619 {desc => "Patron ID", type => "number"},
4620 {desc => "Additional note text (optional)", type => "string"},
4621 {desc => "penalty org unit ID (optional, default to top of org tree)",
4624 return => {desc => "Event describing success or failure", type => "object"}
4628 sub mark_users_contact_invalid {
4629 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4631 # This method invalidates an email address or a phone_number which
4632 # removes the bad email address or phone number, copying its contents
4633 # to a patron note, and institutes a standing penalty for "bad email"
4634 # or "bad phone number" which is cleared when the user is saved or
4635 # optionally only when the user is saved with an email address or
4636 # phone number (or staff manually delete the penalty).
4638 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4640 my $e = new_editor(authtoken => $auth, xact => 1);
4641 return $e->die_event unless $e->checkauth;
4643 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4644 $e, $contact_type, {usr => $patron_id},
4645 $addl_note, $penalty_ou, $e->requestor->id
4649 # Putting the following method in open-ils.actor is a bad fit, except in that
4650 # it serves an interface that lives under 'actor' in the templates directory,
4651 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4653 __PACKAGE__->register_method(
4654 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4655 method => "get_all_at_reactors_in_use",
4660 { name => 'authtoken', type => 'string' }
4663 desc => 'list of reactor names', type => 'array'
4668 sub get_all_at_reactors_in_use {
4669 my ($self, $conn, $auth) = @_;
4671 my $e = new_editor(authtoken => $auth);
4672 $e->checkauth or return $e->die_event;
4673 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4675 my $reactors = $e->json_query({
4677 atevdef => [{column => "reactor", transform => "distinct"}]
4679 from => {atevdef => {}}
4682 return $e->die_event unless ref $reactors eq "ARRAY";
4685 return [ map { $_->{reactor} } @$reactors ];
4688 __PACKAGE__->register_method(
4689 method => "filter_group_entry_crud",
4690 api_name => "open-ils.actor.filter_group_entry.crud",
4693 Provides CRUD access to filter group entry objects. These are not full accessible
4694 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4695 are not accessible via PCRUD (because they have no fields against which to link perms)
4698 {desc => "Authentication token", type => "string"},
4699 {desc => "Entry ID / Entry Object", type => "number"},
4700 {desc => "Additional note text (optional)", type => "string"},
4701 {desc => "penalty org unit ID (optional, default to top of org tree)",
4705 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4711 sub filter_group_entry_crud {
4712 my ($self, $conn, $auth, $arg) = @_;
4714 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4715 my $e = new_editor(authtoken => $auth, xact => 1);
4716 return $e->die_event unless $e->checkauth;
4722 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4723 or return $e->die_event;
4725 return $e->die_event unless $e->allowed(
4726 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4728 my $query = $arg->query;
4729 $query = $e->create_actor_search_query($query) or return $e->die_event;
4730 $arg->query($query->id);
4731 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4732 $entry->query($query);
4737 } elsif ($arg->ischanged) {
4739 my $entry = $e->retrieve_actor_search_filter_group_entry([
4742 flesh_fields => {asfge => ['grp']}
4744 ]) or return $e->die_event;
4746 return $e->die_event unless $e->allowed(
4747 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4749 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4750 $arg->query($arg->query->id);
4751 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4752 $arg->query($query);
4757 } elsif ($arg->isdeleted) {
4759 my $entry = $e->retrieve_actor_search_filter_group_entry([
4762 flesh_fields => {asfge => ['grp', 'query']}
4764 ]) or return $e->die_event;
4766 return $e->die_event unless $e->allowed(
4767 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4769 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4770 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4783 my $entry = $e->retrieve_actor_search_filter_group_entry([
4786 flesh_fields => {asfge => ['grp', 'query']}
4788 ]) or return $e->die_event;
4790 return $e->die_event unless $e->allowed(
4791 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4792 $entry->grp->owner);
4795 $entry->grp($entry->grp->id); # for consistency