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');
2957 "standing_penalties",
2963 return new_flesh_user($user_id, $fields, $e);
2967 sub new_flesh_user {
2970 my $fields = shift || [];
2973 my $fetch_penalties = 0;
2974 if(grep {$_ eq 'standing_penalties'} @$fields) {
2975 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2976 $fetch_penalties = 1;
2979 my $fetch_usr_act = 0;
2980 if(grep {$_ eq 'usr_activity'} @$fields) {
2981 $fields = [grep {$_ ne 'usr_activity'} @$fields];
2985 my $user = $e->retrieve_actor_user(
2990 "flesh_fields" => { "au" => $fields }
2993 ) or return $e->die_event;
2996 if( grep { $_ eq 'addresses' } @$fields ) {
2998 $user->addresses([]) unless @{$user->addresses};
2999 # don't expose "replaced" addresses by default
3000 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3002 if( ref $user->billing_address ) {
3003 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3004 push( @{$user->addresses}, $user->billing_address );
3008 if( ref $user->mailing_address ) {
3009 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3010 push( @{$user->addresses}, $user->mailing_address );
3015 if($fetch_penalties) {
3016 # grab the user penalties ranged for this location
3017 $user->standing_penalties(
3018 $e->search_actor_user_standing_penalty([
3021 {stop_date => undef},
3022 {stop_date => {'>' => 'now'}}
3024 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3027 flesh_fields => {ausp => ['standing_penalty']}
3033 # retrieve the most recent usr_activity entry
3034 if ($fetch_usr_act) {
3036 # max number to return for simple patron fleshing
3037 my $limit = $U->ou_ancestor_setting_value(
3038 $e->requestor->ws_ou,
3039 'circ.patron.usr_activity_retrieve.max');
3043 flesh_fields => {auact => ['etype']},
3044 order_by => {auact => 'event_time DESC'},
3047 # 0 == none, <0 == return all
3048 $limit = 1 unless defined $limit;
3049 $opts->{limit} = $limit if $limit > 0;
3051 $user->usr_activity(
3053 [] : # skip the DB call
3054 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3059 $user->clear_passwd();
3066 __PACKAGE__->register_method(
3067 method => "user_retrieve_parts",
3068 api_name => "open-ils.actor.user.retrieve.parts",
3071 sub user_retrieve_parts {
3072 my( $self, $client, $auth, $user_id, $fields ) = @_;
3073 my $e = new_editor(authtoken => $auth);
3074 return $e->event unless $e->checkauth;
3075 $user_id ||= $e->requestor->id;
3076 if( $e->requestor->id != $user_id ) {
3077 return $e->event unless $e->allowed('VIEW_USER');
3080 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3081 push(@resp, $user->$_()) for(@$fields);
3087 __PACKAGE__->register_method(
3088 method => 'user_opt_in_enabled',
3089 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3090 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3093 sub user_opt_in_enabled {
3094 my($self, $conn) = @_;
3095 my $sc = OpenSRF::Utils::SettingsClient->new;
3096 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3101 __PACKAGE__->register_method(
3102 method => 'user_opt_in_at_org',
3103 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3105 @param $auth The auth token
3106 @param user_id The ID of the user to test
3107 @return 1 if the user has opted in at the specified org,
3108 event on error, and 0 otherwise. /
3110 sub user_opt_in_at_org {
3111 my($self, $conn, $auth, $user_id) = @_;
3113 # see if we even need to enforce the opt-in value
3114 return 1 unless user_opt_in_enabled($self);
3116 my $e = new_editor(authtoken => $auth);
3117 return $e->event unless $e->checkauth;
3119 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3120 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3122 my $ws_org = $e->requestor->ws_ou;
3123 # user is automatically opted-in if they are from the local org
3124 return 1 if $user->home_ou eq $ws_org;
3126 # get the boundary setting
3127 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3129 # auto opt in if user falls within the opt boundary
3130 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3132 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3134 my $vals = $e->search_actor_usr_org_unit_opt_in(
3135 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3141 __PACKAGE__->register_method(
3142 method => 'create_user_opt_in_at_org',
3143 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3145 @param $auth The auth token
3146 @param user_id The ID of the user to test
3147 @return The ID of the newly created object, event on error./
3150 sub create_user_opt_in_at_org {
3151 my($self, $conn, $auth, $user_id, $org_id) = @_;
3153 my $e = new_editor(authtoken => $auth, xact=>1);
3154 return $e->die_event unless $e->checkauth;
3156 # if a specific org unit wasn't passed in, get one based on the defaults;
3158 my $wsou = $e->requestor->ws_ou;
3159 # get the default opt depth
3160 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3161 # get the org unit at that depth
3162 my $org = $e->json_query({
3163 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3164 $org_id = $org->{id};
3167 # fall back to the workstation OU, the pre-opt-in-boundary way
3168 $org_id = $e->requestor->ws_ou;
3171 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3172 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3174 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3176 $opt_in->org_unit($org_id);
3177 $opt_in->usr($user_id);
3178 $opt_in->staff($e->requestor->id);
3179 $opt_in->opt_in_ts('now');
3180 $opt_in->opt_in_ws($e->requestor->wsid);
3182 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3183 or return $e->die_event;
3191 __PACKAGE__->register_method (
3192 method => 'retrieve_org_hours',
3193 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3195 Returns the hours of operation for a specified org unit
3196 @param authtoken The login session key
3197 @param org_id The org_unit ID
3201 sub retrieve_org_hours {
3202 my($self, $conn, $auth, $org_id) = @_;
3203 my $e = new_editor(authtoken => $auth);
3204 return $e->die_event unless $e->checkauth;
3205 $org_id ||= $e->requestor->ws_ou;
3206 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3210 __PACKAGE__->register_method (
3211 method => 'verify_user_password',
3212 api_name => 'open-ils.actor.verify_user_password',
3214 Given a barcode or username and the MD5 encoded password,
3215 returns 1 if the password is correct. Returns 0 otherwise.
3219 sub verify_user_password {
3220 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3221 my $e = new_editor(authtoken => $auth);
3222 return $e->die_event unless $e->checkauth;
3224 my $user_by_barcode;
3225 my $user_by_username;
3227 my $card = $e->search_actor_card([
3228 {barcode => $barcode},
3229 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3230 $user_by_barcode = $card->usr;
3231 $user = $user_by_barcode;
3234 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3235 $user = $user_by_username;
3237 return 0 if (!$user);
3238 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3239 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3240 return 1 if $user->passwd eq $password;
3244 __PACKAGE__->register_method (
3245 method => 'retrieve_usr_id_via_barcode_or_usrname',
3246 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3248 Given a barcode or username returns the id for the user or
3253 sub retrieve_usr_id_via_barcode_or_usrname {
3254 my($self, $conn, $auth, $barcode, $username) = @_;
3255 my $e = new_editor(authtoken => $auth);
3256 return $e->die_event unless $e->checkauth;
3257 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3259 my $user_by_barcode;
3260 my $user_by_username;
3261 $logger->info("$id_as_barcode is the ID as BARCODE");
3263 my $card = $e->search_actor_card([
3264 {barcode => $barcode},
3265 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3266 if ($id_as_barcode =~ /^t/i) {
3268 $user = $e->retrieve_actor_user($barcode);
3269 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3271 $user_by_barcode = $card->usr;
3272 $user = $user_by_barcode;
3275 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3276 $user_by_barcode = $card->usr;
3277 $user = $user_by_barcode;
3282 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3284 $user = $user_by_username;
3286 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3287 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3288 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3293 __PACKAGE__->register_method (
3294 method => 'merge_users',
3295 api_name => 'open-ils.actor.user.merge',
3298 Given a list of source users and destination user, transfer all data from the source
3299 to the dest user and delete the source user. All user related data is
3300 transferred, including circulations, holds, bookbags, etc.
3306 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3307 my $e = new_editor(xact => 1, authtoken => $auth);
3308 return $e->die_event unless $e->checkauth;
3310 # disallow the merge if any subordinate accounts are in collections
3311 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3312 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3314 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3315 my $del_addrs = ($U->ou_ancestor_setting_value(
3316 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3317 my $del_cards = ($U->ou_ancestor_setting_value(
3318 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3319 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3320 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3322 for my $src_id (@$user_ids) {
3323 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3325 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3326 if($src_user->home_ou ne $master_user->home_ou) {
3327 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3330 return $e->die_event unless
3331 $e->json_query({from => [
3346 __PACKAGE__->register_method (
3347 method => 'approve_user_address',
3348 api_name => 'open-ils.actor.user.pending_address.approve',
3355 sub approve_user_address {
3356 my($self, $conn, $auth, $addr) = @_;
3357 my $e = new_editor(xact => 1, authtoken => $auth);
3358 return $e->die_event unless $e->checkauth;
3360 # if the caller passes an address object, assume they want to
3361 # update it first before approving it
3362 $e->update_actor_user_address($addr) or return $e->die_event;
3364 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3366 my $user = $e->retrieve_actor_user($addr->usr);
3367 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3368 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3369 or return $e->die_event;
3371 return [values %$result]->[0];
3375 __PACKAGE__->register_method (
3376 method => 'retrieve_friends',
3377 api_name => 'open-ils.actor.friends.retrieve',
3380 returns { confirmed: [], pending_out: [], pending_in: []}
3381 pending_out are users I'm requesting friendship with
3382 pending_in are users requesting friendship with me
3387 sub retrieve_friends {
3388 my($self, $conn, $auth, $user_id, $options) = @_;
3389 my $e = new_editor(authtoken => $auth);
3390 return $e->event unless $e->checkauth;
3391 $user_id ||= $e->requestor->id;
3393 if($user_id != $e->requestor->id) {
3394 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3395 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3398 return OpenILS::Application::Actor::Friends->retrieve_friends(
3399 $e, $user_id, $options);
3404 __PACKAGE__->register_method (
3405 method => 'apply_friend_perms',
3406 api_name => 'open-ils.actor.friends.perms.apply',
3412 sub apply_friend_perms {
3413 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3414 my $e = new_editor(authtoken => $auth, xact => 1);
3415 return $e->die_event unless $e->checkauth;
3417 if($user_id != $e->requestor->id) {
3418 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3419 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3422 for my $perm (@perms) {
3424 OpenILS::Application::Actor::Friends->apply_friend_perm(
3425 $e, $user_id, $delegate_id, $perm);
3426 return $evt if $evt;
3434 __PACKAGE__->register_method (
3435 method => 'update_user_pending_address',
3436 api_name => 'open-ils.actor.user.address.pending.cud'
3439 sub update_user_pending_address {
3440 my($self, $conn, $auth, $addr) = @_;
3441 my $e = new_editor(authtoken => $auth, xact => 1);
3442 return $e->die_event unless $e->checkauth;
3444 if($addr->usr != $e->requestor->id) {
3445 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3446 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3450 $e->create_actor_user_address($addr) or return $e->die_event;
3451 } elsif($addr->isdeleted) {
3452 $e->delete_actor_user_address($addr) or return $e->die_event;
3454 $e->update_actor_user_address($addr) or return $e->die_event;
3462 __PACKAGE__->register_method (
3463 method => 'user_events',
3464 api_name => 'open-ils.actor.user.events.circ',
3467 __PACKAGE__->register_method (
3468 method => 'user_events',
3469 api_name => 'open-ils.actor.user.events.ahr',
3474 my($self, $conn, $auth, $user_id, $filters) = @_;
3475 my $e = new_editor(authtoken => $auth);
3476 return $e->event unless $e->checkauth;
3478 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3479 my $user_field = 'usr';
3482 $filters->{target} = {
3483 select => { $obj_type => ['id'] },
3485 where => {usr => $user_id}
3488 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3489 if($e->requestor->id != $user_id) {
3490 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3493 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3494 my $req = $ses->request('open-ils.trigger.events_by_target',
3495 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3497 while(my $resp = $req->recv) {
3498 my $val = $resp->content;
3499 my $tgt = $val->target;
3501 if($obj_type eq 'circ') {
3502 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3504 } elsif($obj_type eq 'ahr') {
3505 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3506 if $tgt->current_copy;
3509 $conn->respond($val) if $val;
3515 __PACKAGE__->register_method (
3516 method => 'copy_events',
3517 api_name => 'open-ils.actor.copy.events.circ',
3520 __PACKAGE__->register_method (
3521 method => 'copy_events',
3522 api_name => 'open-ils.actor.copy.events.ahr',
3527 my($self, $conn, $auth, $copy_id, $filters) = @_;
3528 my $e = new_editor(authtoken => $auth);
3529 return $e->event unless $e->checkauth;
3531 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3533 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3535 my $copy_field = 'target_copy';
3536 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3539 $filters->{target} = {
3540 select => { $obj_type => ['id'] },
3542 where => {$copy_field => $copy_id}
3546 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3547 my $req = $ses->request('open-ils.trigger.events_by_target',
3548 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3550 while(my $resp = $req->recv) {
3551 my $val = $resp->content;
3552 my $tgt = $val->target;
3554 my $user = $e->retrieve_actor_user($tgt->usr);
3555 if($e->requestor->id != $user->id) {
3556 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3559 $tgt->$copy_field($copy);
3562 $conn->respond($val) if $val;
3571 __PACKAGE__->register_method (
3572 method => 'update_events',
3573 api_name => 'open-ils.actor.user.event.cancel.batch',
3576 __PACKAGE__->register_method (
3577 method => 'update_events',
3578 api_name => 'open-ils.actor.user.event.reset.batch',
3583 my($self, $conn, $auth, $event_ids) = @_;
3584 my $e = new_editor(xact => 1, authtoken => $auth);
3585 return $e->die_event unless $e->checkauth;
3588 for my $id (@$event_ids) {
3590 # do a little dance to determine what user we are ultimately affecting
3591 my $event = $e->retrieve_action_trigger_event([
3594 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3596 ]) or return $e->die_event;
3599 if($event->event_def->hook->core_type eq 'circ') {
3600 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3601 } elsif($event->event_def->hook->core_type eq 'ahr') {
3602 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3607 my $user = $e->retrieve_actor_user($user_id);
3608 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3610 if($self->api_name =~ /cancel/) {
3611 $event->state('invalid');
3612 } elsif($self->api_name =~ /reset/) {
3613 $event->clear_start_time;
3614 $event->clear_update_time;
3615 $event->state('pending');
3618 $e->update_action_trigger_event($event) or return $e->die_event;
3619 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3623 return {complete => 1};
3627 __PACKAGE__->register_method (
3628 method => 'really_delete_user',
3629 api_name => 'open-ils.actor.user.delete.override',
3630 signature => q/@see open-ils.actor.user.delete/
3633 __PACKAGE__->register_method (
3634 method => 'really_delete_user',
3635 api_name => 'open-ils.actor.user.delete',
3637 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3638 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3639 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3640 dest_usr_id is only required when deleting a user that performs staff functions.
3644 sub really_delete_user {
3645 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3646 my $e = new_editor(authtoken => $auth, xact => 1);
3647 return $e->die_event unless $e->checkauth;
3648 $oargs = { all => 1 } unless defined $oargs;
3650 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3651 my $open_bills = $e->json_query({
3652 select => { mbts => ['id'] },
3655 xact_finish => { '=' => undef },
3656 usr => { '=' => $user_id },
3658 }) or return $e->die_event;
3660 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3662 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3664 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3665 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3666 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3668 # No deleting yourself - UI is supposed to stop you first, though.
3669 return $e->die_event unless $e->requestor->id != $user->id;
3670 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3671 # Check if you are allowed to mess with this patron permission group at all
3672 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3673 my $evt = group_perm_failed($session, $e->requestor, $user);
3674 return $e->die_event($evt) if $evt;
3675 my $stat = $e->json_query(
3676 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3677 or return $e->die_event;
3683 __PACKAGE__->register_method (
3684 method => 'user_payments',
3685 api_name => 'open-ils.actor.user.payments.retrieve',
3688 Returns all payments for a given user. Default order is newest payments first.
3689 @param auth Authentication token
3690 @param user_id The user ID
3691 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3696 my($self, $conn, $auth, $user_id, $filters) = @_;
3699 my $e = new_editor(authtoken => $auth);
3700 return $e->die_event unless $e->checkauth;
3702 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3703 return $e->event unless
3704 $e->requestor->id == $user_id or
3705 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3707 # Find all payments for all transactions for user $user_id
3709 select => {mp => ['id']},
3714 select => {mbt => ['id']},
3716 where => {usr => $user_id}
3721 { # by default, order newest payments first
3723 field => 'payment_ts',
3726 # secondary sort in ID as a tie-breaker, since payments created
3727 # within the same transaction will have identical payment_ts's
3734 for (qw/order_by limit offset/) {
3735 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3738 if(defined $filters->{where}) {
3739 foreach (keys %{$filters->{where}}) {
3740 # don't allow the caller to expand the result set to other users
3741 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3745 my $payment_ids = $e->json_query($query);
3746 for my $pid (@$payment_ids) {
3747 my $pay = $e->retrieve_money_payment([
3752 mbt => ['summary', 'circulation', 'grocery'],
3753 circ => ['target_copy'],
3754 acp => ['call_number'],
3762 xact_type => $pay->xact->summary->xact_type,
3763 last_billing_type => $pay->xact->summary->last_billing_type,
3766 if($pay->xact->summary->xact_type eq 'circulation') {
3767 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3768 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3771 $pay->xact($pay->xact->id); # de-flesh
3772 $conn->respond($resp);
3780 __PACKAGE__->register_method (
3781 method => 'negative_balance_users',
3782 api_name => 'open-ils.actor.users.negative_balance',
3785 Returns all users that have an overall negative balance
3786 @param auth Authentication token
3787 @param org_id The context org unit as an ID or list of IDs. This will be the home
3788 library of the user. If no org_unit is specified, no org unit filter is applied
3792 sub negative_balance_users {
3793 my($self, $conn, $auth, $org_id) = @_;
3795 my $e = new_editor(authtoken => $auth);
3796 return $e->die_event unless $e->checkauth;
3797 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3801 mous => ['usr', 'balance_owed'],
3804 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3805 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3822 where => {'+mous' => {balance_owed => {'<' => 0}}}
3825 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3827 my $list = $e->json_query($query, {timeout => 600});
3829 for my $data (@$list) {
3831 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3832 balance_owed => $data->{balance_owed},
3833 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3840 __PACKAGE__->register_method(
3841 method => "request_password_reset",
3842 api_name => "open-ils.actor.patron.password_reset.request",
3844 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3845 "method for changing a user's password. The UUID token is distributed via A/T " .
3846 "templates (i.e. email to the user).",
3848 { desc => 'user_id_type', type => 'string' },
3849 { desc => 'user_id', type => 'string' },
3850 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3852 return => {desc => '1 on success, Event on error'}
3855 sub request_password_reset {
3856 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3858 # Check to see if password reset requests are already being throttled:
3859 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3861 my $e = new_editor(xact => 1);
3864 # Get the user, if any, depending on the input value
3865 if ($user_id_type eq 'username') {
3866 $user = $e->search_actor_user({usrname => $user_id})->[0];
3869 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3871 } elsif ($user_id_type eq 'barcode') {
3872 my $card = $e->search_actor_card([
3873 {barcode => $user_id},
3874 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3877 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3882 # If the user doesn't have an email address, we can't help them
3883 if (!$user->email) {
3885 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3888 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3889 if ($email_must_match) {
3890 if ($user->email ne $email) {
3891 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3895 _reset_password_request($conn, $e, $user);
3898 # Once we have the user, we can issue the password reset request
3899 # XXX Add a wrapper method that accepts barcode + email input
3900 sub _reset_password_request {
3901 my ($conn, $e, $user) = @_;
3903 # 1. Get throttle threshold and time-to-live from OU_settings
3904 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3905 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3907 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3909 # 2. Get time of last request and number of active requests (num_active)
3910 my $active_requests = $e->json_query({
3916 transform => 'COUNT'
3919 column => 'request_time',
3925 has_been_reset => { '=' => 'f' },
3926 request_time => { '>' => $threshold_time }
3930 # Guard against no active requests
3931 if ($active_requests->[0]->{'request_time'}) {
3932 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3933 my $now = DateTime::Format::ISO8601->new();
3935 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3936 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3937 ($last_request->add_duration('1 minute') > $now)) {
3938 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3940 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3944 # TODO Check to see if the user is in a password-reset-restricted group
3946 # Otherwise, go ahead and try to get the user.
3948 # Check the number of active requests for this user
3949 $active_requests = $e->json_query({
3955 transform => 'COUNT'
3960 usr => { '=' => $user->id },
3961 has_been_reset => { '=' => 'f' },
3962 request_time => { '>' => $threshold_time }
3966 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3968 # if less than or equal to per-user threshold, proceed; otherwise, return event
3969 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3970 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3972 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3975 # Create the aupr object and insert into the database
3976 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3977 my $uuid = create_uuid_as_string(UUID_V4);
3978 $reset_request->uuid($uuid);
3979 $reset_request->usr($user->id);
3981 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3984 # Create an event to notify user of the URL to reset their password
3986 # Can we stuff this in the user_data param for trigger autocreate?
3987 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3989 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3990 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3993 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3998 __PACKAGE__->register_method(
3999 method => "commit_password_reset",
4000 api_name => "open-ils.actor.patron.password_reset.commit",
4002 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4003 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4004 "with the supplied password.",
4006 { desc => 'uuid', type => 'string' },
4007 { desc => 'password', type => 'string' },
4009 return => {desc => '1 on success, Event on error'}
4012 sub commit_password_reset {
4013 my($self, $conn, $uuid, $password) = @_;
4015 # Check to see if password reset requests are already being throttled:
4016 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4017 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4018 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4020 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4023 my $e = new_editor(xact => 1);
4025 my $aupr = $e->search_actor_usr_password_reset({
4032 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4034 my $user_id = $aupr->[0]->usr;
4035 my $user = $e->retrieve_actor_user($user_id);
4037 # Ensure we're still within the TTL for the request
4038 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4039 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4040 if ($threshold < DateTime->now(time_zone => 'local')) {
4042 $logger->info("Password reset request needed to be submitted before $threshold");
4043 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4046 # Check complexity of password against OU-defined regex
4047 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4051 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4052 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4053 $is_strong = check_password_strength_custom($password, $pw_regex);
4055 $is_strong = check_password_strength_default($password);
4060 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4063 # All is well; update the password
4064 $user->passwd($password);
4065 $e->update_actor_user($user);
4067 # And flag that this password reset request has been honoured
4068 $aupr->[0]->has_been_reset('t');
4069 $e->update_actor_usr_password_reset($aupr->[0]);
4075 sub check_password_strength_default {
4076 my $password = shift;
4077 # Use the default set of checks
4078 if ( (length($password) < 7) or
4079 ($password !~ m/.*\d+.*/) or
4080 ($password !~ m/.*[A-Za-z]+.*/)
4087 sub check_password_strength_custom {
4088 my ($password, $pw_regex) = @_;
4090 $pw_regex = qr/$pw_regex/;
4091 if ($password !~ /$pw_regex/) {
4099 __PACKAGE__->register_method(
4100 method => "event_def_opt_in_settings",
4101 api_name => "open-ils.actor.event_def.opt_in.settings",
4104 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4106 { desc => 'Authentication token', type => 'string'},
4108 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4113 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4120 sub event_def_opt_in_settings {
4121 my($self, $conn, $auth, $org_id) = @_;
4122 my $e = new_editor(authtoken => $auth);
4123 return $e->event unless $e->checkauth;
4125 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4126 return $e->event unless
4127 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4129 $org_id = $e->requestor->home_ou;
4132 # find all config.user_setting_type's related to event_defs for the requested org unit
4133 my $types = $e->json_query({
4134 select => {cust => ['name']},
4135 from => {atevdef => 'cust'},
4138 owner => $U->get_org_ancestors($org_id), # context org plus parents
4145 $conn->respond($_) for
4146 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4153 __PACKAGE__->register_method(
4154 method => "user_visible_circs",
4155 api_name => "open-ils.actor.history.circ.visible",
4158 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4160 { desc => 'Authentication token', type => 'string'},
4161 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4162 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4165 desc => q/An object with 2 fields: circulation and summary.
4166 circulation is the "circ" object. summary is the related "accs" object/,
4172 __PACKAGE__->register_method(
4173 method => "user_visible_circs",
4174 api_name => "open-ils.actor.history.circ.visible.print",
4177 desc => 'Returns printable output for the set of opt-in visible circulations',
4179 { desc => 'Authentication token', type => 'string'},
4180 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4181 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4184 desc => q/An action_trigger.event object or error event./,
4190 __PACKAGE__->register_method(
4191 method => "user_visible_circs",
4192 api_name => "open-ils.actor.history.circ.visible.email",
4195 desc => 'Emails the set of opt-in visible circulations to the requestor',
4197 { desc => 'Authentication token', type => 'string'},
4198 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4199 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4202 desc => q/undef, or event on error/
4207 __PACKAGE__->register_method(
4208 method => "user_visible_circs",
4209 api_name => "open-ils.actor.history.hold.visible",
4212 desc => 'Returns the set of opt-in visible holds',
4214 { desc => 'Authentication token', type => 'string'},
4215 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4216 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4219 desc => q/An object with 1 field: "hold"/,
4225 __PACKAGE__->register_method(
4226 method => "user_visible_circs",
4227 api_name => "open-ils.actor.history.hold.visible.print",
4230 desc => 'Returns printable output for the set of opt-in visible holds',
4232 { desc => 'Authentication token', type => 'string'},
4233 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4234 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4237 desc => q/An action_trigger.event object or error event./,
4243 __PACKAGE__->register_method(
4244 method => "user_visible_circs",
4245 api_name => "open-ils.actor.history.hold.visible.email",
4248 desc => 'Emails the set of opt-in visible holds to the requestor',
4250 { desc => 'Authentication token', type => 'string'},
4251 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4252 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4255 desc => q/undef, or event on error/
4260 sub user_visible_circs {
4261 my($self, $conn, $auth, $user_id, $options) = @_;
4263 my $is_hold = ($self->api_name =~ /hold/);
4264 my $for_print = ($self->api_name =~ /print/);
4265 my $for_email = ($self->api_name =~ /email/);
4266 my $e = new_editor(authtoken => $auth);
4267 return $e->event unless $e->checkauth;
4269 $user_id ||= $e->requestor->id;
4271 $options->{limit} ||= 50;
4272 $options->{offset} ||= 0;
4274 if($user_id != $e->requestor->id) {
4275 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4276 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4277 return $e->event unless $e->allowed($perm, $user->home_ou);
4280 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4282 my $data = $e->json_query({
4283 from => [$db_func, $user_id],
4284 limit => $$options{limit},
4285 offset => $$options{offset}
4287 # TODO: I only want IDs. code below didn't get me there
4288 # {"select":{"au":[{"column":"id", "result_field":"id",
4289 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4294 return undef unless @$data;
4298 # collect the batch of objects
4302 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4303 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4307 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4308 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4311 } elsif ($for_email) {
4313 $conn->respond_complete(1) if $for_email; # no sense in waiting
4321 my $hold = $e->retrieve_action_hold_request($id);
4322 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4323 # events will be fired from action_trigger_runner
4327 my $circ = $e->retrieve_action_circulation($id);
4328 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4329 # events will be fired from action_trigger_runner
4333 } else { # just give me the data please
4341 my $hold = $e->retrieve_action_hold_request($id);
4342 $conn->respond({hold => $hold});
4346 my $circ = $e->retrieve_action_circulation($id);
4349 summary => $U->create_circ_chain_summary($e, $id)
4358 __PACKAGE__->register_method(
4359 method => "user_saved_search_cud",
4360 api_name => "open-ils.actor.user.saved_search.cud",
4363 desc => 'Create/Update/Delete Access to user saved searches',
4365 { desc => 'Authentication token', type => 'string' },
4366 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4369 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4375 __PACKAGE__->register_method(
4376 method => "user_saved_search_cud",
4377 api_name => "open-ils.actor.user.saved_search.retrieve",
4380 desc => 'Retrieve a saved search object',
4382 { desc => 'Authentication token', type => 'string' },
4383 { desc => 'Saved Search ID', type => 'number' }
4386 desc => q/The saved search object, Event on error/,
4392 sub user_saved_search_cud {
4393 my( $self, $client, $auth, $search ) = @_;
4394 my $e = new_editor( authtoken=>$auth );
4395 return $e->die_event unless $e->checkauth;
4397 my $o_search; # prior version of the object, if any
4398 my $res; # to be returned
4400 # branch on the operation type
4402 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4404 # Get the old version, to check ownership
4405 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4406 or return $e->die_event;
4408 # You can't read somebody else's search
4409 return OpenILS::Event->new('BAD_PARAMS')
4410 unless $o_search->owner == $e->requestor->id;
4416 $e->xact_begin; # start an editor transaction
4418 if( $search->isnew ) { # Create
4420 # You can't create a search for somebody else
4421 return OpenILS::Event->new('BAD_PARAMS')
4422 unless $search->owner == $e->requestor->id;
4424 $e->create_actor_usr_saved_search( $search )
4425 or return $e->die_event;
4429 } elsif( $search->ischanged ) { # Update
4431 # You can't change ownership of a search
4432 return OpenILS::Event->new('BAD_PARAMS')
4433 unless $search->owner == $e->requestor->id;
4435 # Get the old version, to check ownership
4436 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4437 or return $e->die_event;
4439 # You can't update somebody else's search
4440 return OpenILS::Event->new('BAD_PARAMS')
4441 unless $o_search->owner == $e->requestor->id;
4444 $e->update_actor_usr_saved_search( $search )
4445 or return $e->die_event;
4449 } elsif( $search->isdeleted ) { # Delete
4451 # Get the old version, to check ownership
4452 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4453 or return $e->die_event;
4455 # You can't delete somebody else's search
4456 return OpenILS::Event->new('BAD_PARAMS')
4457 unless $o_search->owner == $e->requestor->id;
4460 $e->delete_actor_usr_saved_search( $o_search )
4461 or return $e->die_event;
4472 __PACKAGE__->register_method(
4473 method => "get_barcodes",
4474 api_name => "open-ils.actor.get_barcodes"
4478 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4479 my $e = new_editor(authtoken => $auth);
4480 return $e->event unless $e->checkauth;
4481 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4483 my $db_result = $e->json_query(
4485 'evergreen.get_barcodes',
4486 $org_id, $context, $barcode,
4490 if($context =~ /actor/) {
4491 my $filter_result = ();
4493 foreach my $result (@$db_result) {
4494 if($result->{type} eq 'actor') {
4495 if($e->requestor->id != $result->{id}) {
4496 $patron = $e->retrieve_actor_user($result->{id});
4498 push(@$filter_result, $e->event);
4501 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4502 push(@$filter_result, $result);
4505 push(@$filter_result, $e->event);
4509 push(@$filter_result, $result);
4513 push(@$filter_result, $result);
4516 return $filter_result;
4522 __PACKAGE__->register_method(
4523 method => 'address_alert_test',
4524 api_name => 'open-ils.actor.address_alert.test',
4526 desc => "Tests a set of address fields to determine if they match with an address_alert",
4528 {desc => 'Authentication token', type => 'string'},
4529 {desc => 'Org Unit', type => 'number'},
4530 {desc => 'Fields', type => 'hash'},
4532 return => {desc => 'List of matching address_alerts'}
4536 sub address_alert_test {
4537 my ($self, $client, $auth, $org_unit, $fields) = @_;
4538 return [] unless $fields and grep {$_} values %$fields;
4540 my $e = new_editor(authtoken => $auth);
4541 return $e->event unless $e->checkauth;
4542 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4543 $org_unit ||= $e->requestor->ws_ou;
4545 my $alerts = $e->json_query({
4547 'actor.address_alert_matches',
4555 $$fields{post_code},
4556 $$fields{mailing_address},
4557 $$fields{billing_address}
4561 # map the json_query hashes to real objects
4563 map {$e->retrieve_actor_address_alert($_)}
4564 (map {$_->{id}} @$alerts)
4568 __PACKAGE__->register_method(
4569 method => "mark_users_contact_invalid",
4570 api_name => "open-ils.actor.invalidate.email",
4572 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",
4574 {desc => "Authentication token", type => "string"},
4575 {desc => "Patron ID", type => "number"},
4576 {desc => "Additional note text (optional)", type => "string"},
4577 {desc => "penalty org unit ID (optional)", type => "number"}
4579 return => {desc => "Event describing success or failure", type => "object"}
4583 __PACKAGE__->register_method(
4584 method => "mark_users_contact_invalid",
4585 api_name => "open-ils.actor.invalidate.day_phone",
4587 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",
4589 {desc => "Authentication token", type => "string"},
4590 {desc => "Patron ID", type => "number"},
4591 {desc => "Additional note text (optional)", type => "string"},
4592 {desc => "penalty org unit ID (optional)", type => "number"}
4594 return => {desc => "Event describing success or failure", type => "object"}
4598 __PACKAGE__->register_method(
4599 method => "mark_users_contact_invalid",
4600 api_name => "open-ils.actor.invalidate.evening_phone",
4602 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",
4604 {desc => "Authentication token", type => "string"},
4605 {desc => "Patron ID", type => "number"},
4606 {desc => "Additional note text (optional)", type => "string"},
4607 {desc => "penalty org unit ID (optional)", type => "number"}
4609 return => {desc => "Event describing success or failure", type => "object"}
4613 __PACKAGE__->register_method(
4614 method => "mark_users_contact_invalid",
4615 api_name => "open-ils.actor.invalidate.other_phone",
4617 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",
4619 {desc => "Authentication token", type => "string"},
4620 {desc => "Patron ID", type => "number"},
4621 {desc => "Additional note text (optional)", type => "string"},
4622 {desc => "penalty org unit ID (optional, default to top of org tree)",
4625 return => {desc => "Event describing success or failure", type => "object"}
4629 sub mark_users_contact_invalid {
4630 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4632 # This method invalidates an email address or a phone_number which
4633 # removes the bad email address or phone number, copying its contents
4634 # to a patron note, and institutes a standing penalty for "bad email"
4635 # or "bad phone number" which is cleared when the user is saved or
4636 # optionally only when the user is saved with an email address or
4637 # phone number (or staff manually delete the penalty).
4639 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4641 my $e = new_editor(authtoken => $auth, xact => 1);
4642 return $e->die_event unless $e->checkauth;
4644 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4645 $e, $contact_type, {usr => $patron_id},
4646 $addl_note, $penalty_ou, $e->requestor->id
4650 # Putting the following method in open-ils.actor is a bad fit, except in that
4651 # it serves an interface that lives under 'actor' in the templates directory,
4652 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4654 __PACKAGE__->register_method(
4655 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4656 method => "get_all_at_reactors_in_use",
4661 { name => 'authtoken', type => 'string' }
4664 desc => 'list of reactor names', type => 'array'
4669 sub get_all_at_reactors_in_use {
4670 my ($self, $conn, $auth) = @_;
4672 my $e = new_editor(authtoken => $auth);
4673 $e->checkauth or return $e->die_event;
4674 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4676 my $reactors = $e->json_query({
4678 atevdef => [{column => "reactor", transform => "distinct"}]
4680 from => {atevdef => {}}
4683 return $e->die_event unless ref $reactors eq "ARRAY";
4686 return [ map { $_->{reactor} } @$reactors ];
4689 __PACKAGE__->register_method(
4690 method => "filter_group_entry_crud",
4691 api_name => "open-ils.actor.filter_group_entry.crud",
4694 Provides CRUD access to filter group entry objects. These are not full accessible
4695 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4696 are not accessible via PCRUD (because they have no fields against which to link perms)
4699 {desc => "Authentication token", type => "string"},
4700 {desc => "Entry ID / Entry Object", type => "number"},
4701 {desc => "Additional note text (optional)", type => "string"},
4702 {desc => "penalty org unit ID (optional, default to top of org tree)",
4706 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4712 sub filter_group_entry_crud {
4713 my ($self, $conn, $auth, $arg) = @_;
4715 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4716 my $e = new_editor(authtoken => $auth, xact => 1);
4717 return $e->die_event unless $e->checkauth;
4723 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4724 or return $e->die_event;
4726 return $e->die_event unless $e->allowed(
4727 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4729 my $query = $arg->query;
4730 $query = $e->create_actor_search_query($query) or return $e->die_event;
4731 $arg->query($query->id);
4732 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4733 $entry->query($query);
4738 } elsif ($arg->ischanged) {
4740 my $entry = $e->retrieve_actor_search_filter_group_entry([
4743 flesh_fields => {asfge => ['grp']}
4745 ]) or return $e->die_event;
4747 return $e->die_event unless $e->allowed(
4748 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4750 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4751 $arg->query($arg->query->id);
4752 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4753 $arg->query($query);
4758 } elsif ($arg->isdeleted) {
4760 my $entry = $e->retrieve_actor_search_filter_group_entry([
4763 flesh_fields => {asfge => ['grp', 'query']}
4765 ]) or return $e->die_event;
4767 return $e->die_event unless $e->allowed(
4768 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4770 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4771 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4784 my $entry = $e->retrieve_actor_search_filter_group_entry([
4787 flesh_fields => {asfge => ['grp', 'query']}
4789 ]) or return $e->die_event;
4791 return $e->die_event unless $e->allowed(
4792 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4793 $entry->grp->owner);
4796 $entry->grp($entry->grp->id); # for consistency