1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use OpenILS::Utils::BadContact;
38 use List::Util qw/max reduce/;
40 use UUID::Tiny qw/:std/;
43 OpenILS::Application::Actor::Container->initialize();
44 OpenILS::Application::Actor::UserGroups->initialize();
45 OpenILS::Application::Actor::ClosedDates->initialize();
48 my $apputils = "OpenILS::Application::AppUtils";
51 sub _d { warn "Patron:\n" . Dumper(shift()); }
54 my $set_user_settings;
58 #__PACKAGE__->register_method(
59 # method => "allowed_test",
60 # api_name => "open-ils.actor.allowed_test",
63 # my($self, $conn, $auth, $orgid, $permcode) = @_;
64 # my $e = new_editor(authtoken => $auth);
65 # return $e->die_event unless $e->checkauth;
69 # permcode => $permcode,
70 # result => $e->allowed($permcode, $orgid)
74 __PACKAGE__->register_method(
75 method => "update_user_setting",
76 api_name => "open-ils.actor.patron.settings.update",
78 sub update_user_setting {
79 my($self, $conn, $auth, $user_id, $settings) = @_;
80 my $e = new_editor(xact => 1, authtoken => $auth);
81 return $e->die_event unless $e->checkauth;
83 $user_id = $e->requestor->id unless defined $user_id;
85 unless($e->requestor->id == $user_id) {
86 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
87 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
90 for my $name (keys %$settings) {
91 my $val = $$settings{$name};
92 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
95 $val = OpenSRF::Utils::JSON->perl2JSON($val);
98 $e->update_actor_user_setting($set) or return $e->die_event;
100 $set = Fieldmapper::actor::user_setting->new;
104 $e->create_actor_user_setting($set) or return $e->die_event;
107 $e->delete_actor_user_setting($set) or return $e->die_event;
116 __PACKAGE__->register_method(
117 method => "set_ou_settings",
118 api_name => "open-ils.actor.org_unit.settings.update",
120 desc => "Updates the value for a given org unit setting. The permission to update " .
121 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
122 "permission specified in the update_perm column of the config.org_unit_setting_type " .
123 "table's row corresponding to the setting being changed." ,
125 {desc => 'Authentication token', type => 'string'},
126 {desc => 'Org unit ID', type => 'number'},
127 {desc => 'Hash of setting name-value pairs', type => 'object'}
129 return => {desc => '1 on success, Event on error'}
133 sub set_ou_settings {
134 my( $self, $client, $auth, $org_id, $settings ) = @_;
136 my $e = new_editor(authtoken => $auth, xact => 1);
137 return $e->die_event unless $e->checkauth;
139 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
141 for my $name (keys %$settings) {
142 my $val = $$settings{$name};
144 my $type = $e->retrieve_config_org_unit_setting_type([
146 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
147 ]) or return $e->die_event;
148 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
150 # If there is no relevant permission, the default assumption will
151 # be, "no, the caller cannot change that value."
152 return $e->die_event unless ($all_allowed ||
153 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
156 $val = OpenSRF::Utils::JSON->perl2JSON($val);
159 $e->update_actor_org_unit_setting($set) or return $e->die_event;
161 $set = Fieldmapper::actor::org_unit_setting->new;
162 $set->org_unit($org_id);
165 $e->create_actor_org_unit_setting($set) or return $e->die_event;
168 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
176 __PACKAGE__->register_method(
177 method => "user_settings",
179 api_name => "open-ils.actor.patron.settings.retrieve",
182 my( $self, $client, $auth, $user_id, $setting ) = @_;
184 my $e = new_editor(authtoken => $auth);
185 return $e->event unless $e->checkauth;
186 $user_id = $e->requestor->id unless defined $user_id;
188 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
189 if($e->requestor->id != $user_id) {
190 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
194 my($e, $user_id, $setting) = @_;
195 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
196 return undef unless $val; # XXX this should really return undef, but needs testing
197 return OpenSRF::Utils::JSON->JSON2perl($val->value);
201 if(ref $setting eq 'ARRAY') {
203 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
206 return get_setting($e, $user_id, $setting);
209 my $s = $e->search_actor_user_setting({usr => $user_id});
210 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
215 __PACKAGE__->register_method(
216 method => "ranged_ou_settings",
217 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
219 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
220 "is implied for retrieving OU settings by the authenticated users' permissions.",
222 {desc => 'Authentication token', type => 'string'},
223 {desc => 'Org unit ID', type => 'number'},
225 return => {desc => 'A hashref of "ranged" settings, event on error'}
228 sub ranged_ou_settings {
229 my( $self, $client, $auth, $org_id ) = @_;
231 my $e = new_editor(authtoken => $auth);
232 return $e->event unless $e->checkauth;
235 my $org_list = $U->get_org_ancestors($org_id);
236 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
237 $org_list = [ reverse @$org_list ];
239 # start at the context org and capture the setting value
240 # without clobbering settings we've already captured
241 for my $this_org_id (@$org_list) {
243 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
245 for my $set (@sets) {
246 my $type = $e->retrieve_config_org_unit_setting_type([
248 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
251 # If there is no relevant permission, the default assumption will
252 # be, "yes, the caller can have that value."
253 if ($type && $type->view_perm) {
254 next if not $e->allowed($type->view_perm->code, $org_id);
257 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
258 unless defined $ranged_settings{$set->name};
262 return \%ranged_settings;
267 __PACKAGE__->register_method(
268 api_name => 'open-ils.actor.ou_setting.ancestor_default',
269 method => 'ou_ancestor_setting',
271 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
272 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
273 'user has permission to view that setting, if there is a permission associated with the setting.' ,
275 { desc => 'Org unit ID', type => 'number' },
276 { desc => 'setting name', type => 'string' },
277 { desc => 'authtoken (optional)', type => 'string' }
279 return => {desc => 'A value for the org unit setting, or undef'}
283 # ------------------------------------------------------------------
284 # Attempts to find the org setting value for a given org. if not
285 # found at the requested org, searches up the org tree until it
286 # finds a parent that has the requested setting.
287 # when found, returns { org => $id, value => $value }
288 # otherwise, returns NULL
289 # ------------------------------------------------------------------
290 sub ou_ancestor_setting {
291 my( $self, $client, $orgid, $name, $auth ) = @_;
292 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
295 __PACKAGE__->register_method(
296 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
297 method => 'ou_ancestor_setting_batch',
299 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
300 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
301 'user has permission to view that setting, if there is a permission associated with the setting.' ,
303 { desc => 'Org unit ID', type => 'number' },
304 { desc => 'setting name list', type => 'array' },
305 { desc => 'authtoken (optional)', type => 'string' }
307 return => {desc => 'A hash with name => value pairs for the org unit settings'}
310 sub ou_ancestor_setting_batch {
311 my( $self, $client, $orgid, $name_list, $auth ) = @_;
313 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
319 __PACKAGE__->register_method(
320 method => "update_patron",
321 api_name => "open-ils.actor.patron.update",
324 Update an existing user, or create a new one. Related objects,
325 like cards, addresses, survey responses, and stat cats,
326 can be updated by attaching them to the user object in their
327 respective fields. For examples, the billing address object
328 may be inserted into the 'billing_address' field, etc. For each
329 attached object, indicate if the object should be created,
330 updated, or deleted using the built-in 'isnew', 'ischanged',
331 and 'isdeleted' fields on the object.
334 { desc => 'Authentication token', type => 'string' },
335 { desc => 'Patron data object', type => 'object' }
337 return => {desc => 'A fleshed user object, event on error'}
342 my( $self, $client, $user_session, $patron ) = @_;
344 my $session = $apputils->start_db_session();
346 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
348 my( $user_obj, $evt ) = $U->checkses($user_session);
351 $evt = check_group_perm($session, $user_obj, $patron);
354 $apputils->set_audit_info($session, $user_session, $user_obj->id, $user_obj->wsid);
356 # $new_patron is the patron in progress. $patron is the original patron
357 # passed in with the method. new_patron will change as the components
358 # of patron are added/updated.
362 # unflesh the real items on the patron
363 $patron->card( $patron->card->id ) if(ref($patron->card));
364 $patron->billing_address( $patron->billing_address->id )
365 if(ref($patron->billing_address));
366 $patron->mailing_address( $patron->mailing_address->id )
367 if(ref($patron->mailing_address));
369 # create/update the patron first so we can use his id
371 # $patron is the obj from the client (new data) and $new_patron is the
372 # patron object properly built for db insertion, so we need a third variable
373 # if we want to represent the old patron.
376 my $barred_hook = '';
378 if($patron->isnew()) {
379 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
381 if($U->is_true($patron->barred)) {
382 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
386 $new_patron = $patron;
388 # Did auth checking above already.
390 $old_patron = $e->retrieve_actor_user($patron->id) or
391 return $e->die_event;
393 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
394 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
397 $barred_hook = $U->is_true($new_patron->barred) ?
398 'au.barred' : 'au.unbarred';
402 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
405 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
408 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
411 # re-update the patron if anything has happened to him during this process
412 if($new_patron->ischanged()) {
413 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
417 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
420 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
423 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
426 $apputils->commit_db_session($session);
428 $evt = apply_invalid_addr_penalty($patron);
431 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
433 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
435 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
437 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
438 $new_patron, $new_patron->home_ou) if $barred_hook;
441 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
444 sub apply_invalid_addr_penalty {
446 my $e = new_editor(xact => 1);
448 # grab the invalid address penalty if set
449 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
451 my ($addr_penalty) = grep
452 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
454 # do we enforce invalid address penalty
455 my $enforce = $U->ou_ancestor_setting_value(
456 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
458 my $addrs = $e->search_actor_user_address(
459 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
460 my $addr_count = scalar(@$addrs);
462 if($addr_count == 0 and $addr_penalty) {
464 # regardless of any settings, remove the penalty when the user has no invalid addresses
465 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
468 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
470 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
471 my $depth = $ptype->org_depth;
472 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
473 $ctx_org = $patron->home_ou unless defined $ctx_org;
475 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
476 $penalty->usr($patron->id);
477 $penalty->org_unit($ctx_org);
478 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
480 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
499 "standing_penalties",
507 push @$fields, "home_ou" if $home_ou;
508 return new_flesh_user($id, $fields, $e );
516 # clone and clear stuff that would break the database
520 my $new_patron = $patron->clone;
522 $new_patron->clear_billing_address();
523 $new_patron->clear_mailing_address();
524 $new_patron->clear_addresses();
525 $new_patron->clear_card();
526 $new_patron->clear_cards();
527 $new_patron->clear_id();
528 $new_patron->clear_isnew();
529 $new_patron->clear_ischanged();
530 $new_patron->clear_isdeleted();
531 $new_patron->clear_stat_cat_entries();
532 $new_patron->clear_permissions();
533 $new_patron->clear_standing_penalties();
543 my $user_obj = shift;
545 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
546 return (undef, $evt) if $evt;
548 my $ex = $session->request(
549 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
551 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
554 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
556 my $id = $session->request(
557 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
558 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
560 $logger->info("Successfully created new user [$id] in DB");
562 return ( $session->request(
563 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
567 sub check_group_perm {
568 my( $session, $requestor, $patron ) = @_;
571 # first let's see if the requestor has
572 # priveleges to update this user in any way
573 if( ! $patron->isnew ) {
574 my $p = $session->request(
575 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
577 # If we are the requestor (trying to update our own account)
578 # and we are not trying to change our profile, we're good
579 if( $p->id == $requestor->id and
580 $p->profile == $patron->profile ) {
585 $evt = group_perm_failed($session, $requestor, $p);
589 # They are allowed to edit this patron.. can they put the
590 # patron into the group requested?
591 $evt = group_perm_failed($session, $requestor, $patron);
597 sub group_perm_failed {
598 my( $session, $requestor, $patron ) = @_;
602 my $grpid = $patron->profile;
606 $logger->debug("user update looking for group perm for group $grpid");
607 $grp = $session->request(
608 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
609 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
611 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
613 $logger->info("user update checking perm $perm on user ".
614 $requestor->id." for update/create on user username=".$patron->usrname);
616 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
624 my( $session, $patron, $user_obj, $noperm) = @_;
626 $logger->info("Updating patron ".$patron->id." in DB");
631 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
632 return (undef, $evt) if $evt;
635 # update the password by itself to avoid the password protection magic
636 if( $patron->passwd ) {
637 my $s = $session->request(
638 'open-ils.storage.direct.actor.user.remote_update',
639 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
640 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
641 $patron->clear_passwd;
644 if(!$patron->ident_type) {
645 $patron->clear_ident_type;
646 $patron->clear_ident_value;
649 $evt = verify_last_xact($session, $patron);
650 return (undef, $evt) if $evt;
652 my $stat = $session->request(
653 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
654 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
659 sub verify_last_xact {
660 my( $session, $patron ) = @_;
661 return undef unless $patron->id and $patron->id > 0;
662 my $p = $session->request(
663 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
664 my $xact = $p->last_xact_id;
665 return undef unless $xact;
666 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
667 return OpenILS::Event->new('XACT_COLLISION')
668 if $xact ne $patron->last_xact_id;
673 sub _check_dup_ident {
674 my( $session, $patron ) = @_;
676 return undef unless $patron->ident_value;
679 ident_type => $patron->ident_type,
680 ident_value => $patron->ident_value,
683 $logger->debug("patron update searching for dup ident values: " .
684 $patron->ident_type . ':' . $patron->ident_value);
686 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
688 my $dups = $session->request(
689 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
692 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
699 sub _add_update_addresses {
703 my $new_patron = shift;
707 my $current_id; # id of the address before creation
709 my $addresses = $patron->addresses();
711 for my $address (@$addresses) {
713 next unless ref $address;
714 $current_id = $address->id();
716 if( $patron->billing_address() and
717 $patron->billing_address() == $current_id ) {
718 $logger->info("setting billing addr to $current_id");
719 $new_patron->billing_address($address->id());
720 $new_patron->ischanged(1);
723 if( $patron->mailing_address() and
724 $patron->mailing_address() == $current_id ) {
725 $new_patron->mailing_address($address->id());
726 $logger->info("setting mailing addr to $current_id");
727 $new_patron->ischanged(1);
731 if($address->isnew()) {
733 $address->usr($new_patron->id());
735 ($address, $evt) = _add_address($session,$address);
736 return (undef, $evt) if $evt;
738 # we need to get the new id
739 if( $patron->billing_address() and
740 $patron->billing_address() == $current_id ) {
741 $new_patron->billing_address($address->id());
742 $logger->info("setting billing addr to $current_id");
743 $new_patron->ischanged(1);
746 if( $patron->mailing_address() and
747 $patron->mailing_address() == $current_id ) {
748 $new_patron->mailing_address($address->id());
749 $logger->info("setting mailing addr to $current_id");
750 $new_patron->ischanged(1);
753 } elsif($address->ischanged() ) {
755 ($address, $evt) = _update_address($session, $address);
756 return (undef, $evt) if $evt;
758 } elsif($address->isdeleted() ) {
760 if( $address->id() == $new_patron->mailing_address() ) {
761 $new_patron->clear_mailing_address();
762 ($new_patron, $evt) = _update_patron($session, $new_patron);
763 return (undef, $evt) if $evt;
766 if( $address->id() == $new_patron->billing_address() ) {
767 $new_patron->clear_billing_address();
768 ($new_patron, $evt) = _update_patron($session, $new_patron);
769 return (undef, $evt) if $evt;
772 $evt = _delete_address($session, $address);
773 return (undef, $evt) if $evt;
777 return ( $new_patron, undef );
781 # adds an address to the db and returns the address with new id
783 my($session, $address) = @_;
784 $address->clear_id();
786 $logger->info("Creating new address at street ".$address->street1);
788 # put the address into the database
789 my $id = $session->request(
790 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
791 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
794 return ($address, undef);
798 sub _update_address {
799 my( $session, $address ) = @_;
801 $logger->info("Updating address ".$address->id." in the DB");
803 my $stat = $session->request(
804 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
806 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
807 return ($address, undef);
812 sub _add_update_cards {
816 my $new_patron = shift;
820 my $virtual_id; #id of the card before creation
822 my $cards = $patron->cards();
823 for my $card (@$cards) {
825 $card->usr($new_patron->id());
827 if(ref($card) and $card->isnew()) {
829 $virtual_id = $card->id();
830 ( $card, $evt ) = _add_card($session,$card);
831 return (undef, $evt) if $evt;
833 #if(ref($patron->card)) { $patron->card($patron->card->id); }
834 if($patron->card() == $virtual_id) {
835 $new_patron->card($card->id());
836 $new_patron->ischanged(1);
839 } elsif( ref($card) and $card->ischanged() ) {
840 $evt = _update_card($session, $card);
841 return (undef, $evt) if $evt;
845 return ( $new_patron, undef );
849 # adds an card to the db and returns the card with new id
851 my( $session, $card ) = @_;
854 $logger->info("Adding new patron card ".$card->barcode);
856 my $id = $session->request(
857 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
858 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
859 $logger->info("Successfully created patron card $id");
862 return ( $card, undef );
866 # returns event on error. returns undef otherwise
868 my( $session, $card ) = @_;
869 $logger->info("Updating patron card ".$card->id);
871 my $stat = $session->request(
872 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
873 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
880 # returns event on error. returns undef otherwise
881 sub _delete_address {
882 my( $session, $address ) = @_;
884 $logger->info("Deleting address ".$address->id." from DB");
886 my $stat = $session->request(
887 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
889 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
895 sub _add_survey_responses {
896 my ($session, $patron, $new_patron) = @_;
898 $logger->info( "Updating survey responses for patron ".$new_patron->id );
900 my $responses = $patron->survey_responses;
904 $_->usr($new_patron->id) for (@$responses);
906 my $evt = $U->simplereq( "open-ils.circ",
907 "open-ils.circ.survey.submit.user_id", $responses );
909 return (undef, $evt) if defined($U->event_code($evt));
913 return ( $new_patron, undef );
916 sub _clear_badcontact_penalties {
917 my ($session, $old_patron, $new_patron, $user_obj) = @_;
919 return ($new_patron, undef) unless $old_patron;
921 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
922 my $e = new_editor(xact => 1);
924 # This ignores whether the caller of update_patron has any permission
925 # to remove penalties, but these penalties no longer make sense
926 # if an email address field (for example) is changed (and the caller must
927 # have perms to do *that*) so there's no reason not to clear the penalties.
929 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
931 "+csp" => {"name" => [values(%$PNM)]},
932 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
934 "join" => {"csp" => {}},
936 "flesh_fields" => {"ausp" => ["standing_penalty"]}
938 ]) or return (undef, $e->die_event);
940 return ($new_patron, undef) unless @$bad_contact_penalties;
942 my @penalties_to_clear;
943 my ($field, $penalty_name);
945 # For each field that might have an associated bad contact penalty,
946 # check for such penalties and add them to the to-clear list if that
948 while (($field, $penalty_name) = each(%$PNM)) {
949 if ($old_patron->$field ne $new_patron->$field) {
950 push @penalties_to_clear, grep {
951 $_->standing_penalty->name eq $penalty_name
952 } @$bad_contact_penalties;
956 foreach (@penalties_to_clear) {
957 # Note that this "archives" penalties, in the terminology of the staff
958 # client, instead of just deleting them. This may assist reporting,
959 # or preserving old contact information when it is still potentially
961 $_->standing_penalty($_->standing_penalty->id); # deflesh
962 $_->stop_date('now');
963 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
967 return ($new_patron, undef);
971 sub _create_stat_maps {
973 my($session, $user_session, $patron, $new_patron) = @_;
975 my $maps = $patron->stat_cat_entries();
977 for my $map (@$maps) {
979 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
981 if ($map->isdeleted()) {
982 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
984 } elsif ($map->isnew()) {
985 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
990 $map->target_usr($new_patron->id);
993 $logger->info("Updating stat entry with method $method and map $map");
995 my $stat = $session->request($method, $map)->gather(1);
996 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1000 return ($new_patron, undef);
1003 sub _create_perm_maps {
1005 my($session, $user_session, $patron, $new_patron) = @_;
1007 my $maps = $patron->permissions;
1009 for my $map (@$maps) {
1011 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1012 if ($map->isdeleted()) {
1013 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1014 } elsif ($map->isnew()) {
1015 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1020 $map->usr($new_patron->id);
1022 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1023 $logger->info( "Updating permissions with method $method and map $map" );
1025 my $stat = $session->request($method, $map)->gather(1);
1026 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1030 return ($new_patron, undef);
1034 __PACKAGE__->register_method(
1035 method => "set_user_work_ous",
1036 api_name => "open-ils.actor.user.work_ous.update",
1039 sub set_user_work_ous {
1045 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1046 return $evt if $evt;
1048 my $session = $apputils->start_db_session();
1049 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1051 for my $map (@$maps) {
1053 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1054 if ($map->isdeleted()) {
1055 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1056 } elsif ($map->isnew()) {
1057 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1061 #warn( "Updating permissions with method $method and session $ses and map $map" );
1062 $logger->info( "Updating work_ou map with method $method and map $map" );
1064 my $stat = $session->request($method, $map)->gather(1);
1065 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1069 $apputils->commit_db_session($session);
1071 return scalar(@$maps);
1075 __PACKAGE__->register_method(
1076 method => "set_user_perms",
1077 api_name => "open-ils.actor.user.permissions.update",
1080 sub set_user_perms {
1086 my $session = $apputils->start_db_session();
1088 my( $user_obj, $evt ) = $U->checkses($ses);
1089 return $evt if $evt;
1090 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1092 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1095 $all = 1 if ($U->is_true($user_obj->super_user()));
1096 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1098 for my $map (@$maps) {
1100 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1101 if ($map->isdeleted()) {
1102 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1103 } elsif ($map->isnew()) {
1104 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1108 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1109 #warn( "Updating permissions with method $method and session $ses and map $map" );
1110 $logger->info( "Updating permissions with method $method and map $map" );
1112 my $stat = $session->request($method, $map)->gather(1);
1113 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1117 $apputils->commit_db_session($session);
1119 return scalar(@$maps);
1123 __PACKAGE__->register_method(
1124 method => "user_retrieve_by_barcode",
1126 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1128 sub user_retrieve_by_barcode {
1129 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1131 my $e = new_editor(authtoken => $auth);
1132 return $e->event unless $e->checkauth;
1134 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1135 or return $e->event;
1137 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1138 return $e->event unless $e->allowed(
1139 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1146 __PACKAGE__->register_method(
1147 method => "get_user_by_id",
1149 api_name => "open-ils.actor.user.retrieve",
1152 sub get_user_by_id {
1153 my ($self, $client, $auth, $id) = @_;
1154 my $e = new_editor(authtoken=>$auth);
1155 return $e->event unless $e->checkauth;
1156 my $user = $e->retrieve_actor_user($id) or return $e->event;
1157 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1162 __PACKAGE__->register_method(
1163 method => "get_org_types",
1164 api_name => "open-ils.actor.org_types.retrieve",
1167 return $U->get_org_types();
1171 __PACKAGE__->register_method(
1172 method => "get_user_ident_types",
1173 api_name => "open-ils.actor.user.ident_types.retrieve",
1176 sub get_user_ident_types {
1177 return $ident_types if $ident_types;
1178 return $ident_types =
1179 new_editor()->retrieve_all_config_identification_type();
1183 __PACKAGE__->register_method(
1184 method => "get_org_unit",
1185 api_name => "open-ils.actor.org_unit.retrieve",
1189 my( $self, $client, $user_session, $org_id ) = @_;
1190 my $e = new_editor(authtoken => $user_session);
1192 return $e->event unless $e->checkauth;
1193 $org_id = $e->requestor->ws_ou;
1195 my $o = $e->retrieve_actor_org_unit($org_id)
1196 or return $e->event;
1200 __PACKAGE__->register_method(
1201 method => "search_org_unit",
1202 api_name => "open-ils.actor.org_unit_list.search",
1205 sub search_org_unit {
1207 my( $self, $client, $field, $value ) = @_;
1209 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1211 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1212 { $field => $value } );
1218 # build the org tree
1220 __PACKAGE__->register_method(
1221 method => "get_org_tree",
1222 api_name => "open-ils.actor.org_tree.retrieve",
1224 note => "Returns the entire org tree structure",
1230 return $U->get_org_tree($client->session->session_locale);
1234 __PACKAGE__->register_method(
1235 method => "get_org_descendants",
1236 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1239 # depth is optional. org_unit is the id
1240 sub get_org_descendants {
1241 my( $self, $client, $org_unit, $depth ) = @_;
1243 if(ref $org_unit eq 'ARRAY') {
1246 for my $i (0..scalar(@$org_unit)-1) {
1247 my $list = $U->simple_scalar_request(
1249 "open-ils.storage.actor.org_unit.descendants.atomic",
1250 $org_unit->[$i], $depth->[$i] );
1251 push(@trees, $U->build_org_tree($list));
1256 my $orglist = $apputils->simple_scalar_request(
1258 "open-ils.storage.actor.org_unit.descendants.atomic",
1259 $org_unit, $depth );
1260 return $U->build_org_tree($orglist);
1265 __PACKAGE__->register_method(
1266 method => "get_org_ancestors",
1267 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1270 # depth is optional. org_unit is the id
1271 sub get_org_ancestors {
1272 my( $self, $client, $org_unit, $depth ) = @_;
1273 my $orglist = $apputils->simple_scalar_request(
1275 "open-ils.storage.actor.org_unit.ancestors.atomic",
1276 $org_unit, $depth );
1277 return $U->build_org_tree($orglist);
1281 __PACKAGE__->register_method(
1282 method => "get_standings",
1283 api_name => "open-ils.actor.standings.retrieve"
1288 return $user_standings if $user_standings;
1289 return $user_standings =
1290 $apputils->simple_scalar_request(
1292 "open-ils.cstore.direct.config.standing.search.atomic",
1293 { id => { "!=" => undef } }
1298 __PACKAGE__->register_method(
1299 method => "get_my_org_path",
1300 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1303 sub get_my_org_path {
1304 my( $self, $client, $auth, $org_id ) = @_;
1305 my $e = new_editor(authtoken=>$auth);
1306 return $e->event unless $e->checkauth;
1307 $org_id = $e->requestor->ws_ou unless defined $org_id;
1309 return $apputils->simple_scalar_request(
1311 "open-ils.storage.actor.org_unit.full_path.atomic",
1316 __PACKAGE__->register_method(
1317 method => "patron_adv_search",
1318 api_name => "open-ils.actor.patron.search.advanced"
1321 __PACKAGE__->register_method(
1322 method => "patron_adv_search",
1323 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1325 # TODO: change when opensrf 'bundling' is merged.
1326 # set a relatively small bundle size so the caller can start
1327 # seeing results fairly quickly
1328 max_chunk_size => 4096, # bundling
1331 # pending opensrf work -- also, not sure if needed since we're not
1332 # actaully creating an alternate vesrion, only offering to return a
1336 desc => q/Returns a stream of fleshed user objects instead of
1337 a pile of identifiers/
1341 sub patron_adv_search {
1342 my( $self, $client, $auth, $search_hash, $search_limit,
1343 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1345 my $e = new_editor(authtoken=>$auth);
1346 return $e->event unless $e->checkauth;
1347 return $e->event unless $e->allowed('VIEW_USER');
1349 # depth boundary outside of which patrons must opt-in, default to 0
1350 my $opt_boundary = 0;
1351 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1353 if (not defined $search_ou) {
1354 my $depth = $U->ou_ancestor_setting_value(
1355 $e->requestor->ws_ou,
1356 'circ.patron_edit.duplicate_patron_check_depth'
1359 if (defined $depth) {
1360 $search_ou = $U->org_unit_ancestor_at_depth(
1361 $e->requestor->ws_ou, $depth
1366 my $ids = $U->storagereq(
1367 "open-ils.storage.actor.user.crazy_search", $search_hash,
1368 $search_limit, $search_sort, $include_inactive,
1369 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1371 return $ids unless $self->api_name =~ /fleshed/;
1373 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1379 __PACKAGE__->register_method(
1380 method => "update_passwd",
1381 api_name => "open-ils.actor.user.password.update",
1383 desc => "Update the operator's password",
1385 { desc => 'Authentication token', type => 'string' },
1386 { desc => 'New password', type => 'string' },
1387 { desc => 'Current password', type => 'string' }
1389 return => {desc => '1 on success, Event on error or incorrect current password'}
1393 __PACKAGE__->register_method(
1394 method => "update_passwd",
1395 api_name => "open-ils.actor.user.username.update",
1397 desc => "Update the operator's username",
1399 { desc => 'Authentication token', type => 'string' },
1400 { desc => 'New username', type => 'string' },
1401 { desc => 'Current password', type => 'string' }
1403 return => {desc => '1 on success, Event on error or incorrect current password'}
1407 __PACKAGE__->register_method(
1408 method => "update_passwd",
1409 api_name => "open-ils.actor.user.email.update",
1411 desc => "Update the operator's email address",
1413 { desc => 'Authentication token', type => 'string' },
1414 { desc => 'New email address', type => 'string' },
1415 { desc => 'Current password', type => 'string' }
1417 return => {desc => '1 on success, Event on error or incorrect current password'}
1422 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1423 my $e = new_editor(xact=>1, authtoken=>$auth);
1424 return $e->die_event unless $e->checkauth;
1426 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1427 or return $e->die_event;
1428 my $api = $self->api_name;
1430 # make sure the original password matches the in-database password
1431 if (md5_hex($orig_pw) ne $db_user->passwd) {
1433 return new OpenILS::Event('INCORRECT_PASSWORD');
1436 if( $api =~ /password/o ) {
1438 $db_user->passwd($new_val);
1442 # if we don't clear the password, the user will be updated with
1443 # a hashed version of the hashed version of their password
1444 $db_user->clear_passwd;
1446 if( $api =~ /username/o ) {
1448 # make sure no one else has this username
1449 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1452 return new OpenILS::Event('USERNAME_EXISTS');
1454 $db_user->usrname($new_val);
1456 } elsif( $api =~ /email/o ) {
1457 $db_user->email($new_val);
1461 $e->update_actor_user($db_user) or return $e->die_event;
1464 # update the cached user to pick up these changes
1465 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1471 __PACKAGE__->register_method(
1472 method => "check_user_perms",
1473 api_name => "open-ils.actor.user.perm.check",
1474 notes => <<" NOTES");
1475 Takes a login session, user id, an org id, and an array of perm type strings. For each
1476 perm type, if the user does *not* have the given permission it is added
1477 to a list which is returned from the method. If all permissions
1478 are allowed, an empty list is returned
1479 if the logged in user does not match 'user_id', then the logged in user must
1480 have VIEW_PERMISSION priveleges.
1483 sub check_user_perms {
1484 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1486 my( $staff, $evt ) = $apputils->checkses($login_session);
1487 return $evt if $evt;
1489 if($staff->id ne $user_id) {
1490 if( $evt = $apputils->check_perms(
1491 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1497 for my $perm (@$perm_types) {
1498 if($apputils->check_perms($user_id, $org_id, $perm)) {
1499 push @not_allowed, $perm;
1503 return \@not_allowed
1506 __PACKAGE__->register_method(
1507 method => "check_user_perms2",
1508 api_name => "open-ils.actor.user.perm.check.multi_org",
1510 Checks the permissions on a list of perms and orgs for a user
1511 @param authtoken The login session key
1512 @param user_id The id of the user to check
1513 @param orgs The array of org ids
1514 @param perms The array of permission names
1515 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1516 if the logged in user does not match 'user_id', then the logged in user must
1517 have VIEW_PERMISSION priveleges.
1520 sub check_user_perms2 {
1521 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1523 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1524 $authtoken, $user_id, 'VIEW_PERMISSION' );
1525 return $evt if $evt;
1528 for my $org (@$orgs) {
1529 for my $perm (@$perms) {
1530 if($apputils->check_perms($user_id, $org, $perm)) {
1531 push @not_allowed, [ $org, $perm ];
1536 return \@not_allowed
1540 __PACKAGE__->register_method(
1541 method => 'check_user_perms3',
1542 api_name => 'open-ils.actor.user.perm.highest_org',
1544 Returns the highest org unit id at which a user has a given permission
1545 If the requestor does not match the target user, the requestor must have
1546 'VIEW_PERMISSION' rights at the home org unit of the target user
1547 @param authtoken The login session key
1548 @param userid The id of the user in question
1549 @param perm The permission to check
1550 @return The org unit highest in the org tree within which the user has
1551 the requested permission
1554 sub check_user_perms3 {
1555 my($self, $client, $authtoken, $user_id, $perm) = @_;
1556 my $e = new_editor(authtoken=>$authtoken);
1557 return $e->event unless $e->checkauth;
1559 my $tree = $U->get_org_tree();
1561 unless($e->requestor->id == $user_id) {
1562 my $user = $e->retrieve_actor_user($user_id)
1563 or return $e->event;
1564 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1565 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1568 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1571 __PACKAGE__->register_method(
1572 method => 'user_has_work_perm_at',
1573 api_name => 'open-ils.actor.user.has_work_perm_at',
1577 Returns a set of org unit IDs which represent the highest orgs in
1578 the org tree where the user has the requested permission. The
1579 purpose of this method is to return the smallest set of org units
1580 which represent the full expanse of the user's ability to perform
1581 the requested action. The user whose perms this method should
1582 check is implied by the authtoken. /,
1584 {desc => 'authtoken', type => 'string'},
1585 {desc => 'permission name', type => 'string'},
1586 {desc => q/user id, optional. If present, check perms for
1587 this user instead of the logged in user/, type => 'number'},
1589 return => {desc => 'An array of org IDs'}
1593 sub user_has_work_perm_at {
1594 my($self, $conn, $auth, $perm, $user_id) = @_;
1595 my $e = new_editor(authtoken=>$auth);
1596 return $e->event unless $e->checkauth;
1597 if(defined $user_id) {
1598 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1599 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1601 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1604 __PACKAGE__->register_method(
1605 method => 'user_has_work_perm_at_batch',
1606 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1610 sub user_has_work_perm_at_batch {
1611 my($self, $conn, $auth, $perms, $user_id) = @_;
1612 my $e = new_editor(authtoken=>$auth);
1613 return $e->event unless $e->checkauth;
1614 if(defined $user_id) {
1615 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1616 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1619 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1625 __PACKAGE__->register_method(
1626 method => 'check_user_perms4',
1627 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1629 Returns the highest org unit id at which a user has a given permission
1630 If the requestor does not match the target user, the requestor must have
1631 'VIEW_PERMISSION' rights at the home org unit of the target user
1632 @param authtoken The login session key
1633 @param userid The id of the user in question
1634 @param perms An array of perm names to check
1635 @return An array of orgId's representing the org unit
1636 highest in the org tree within which the user has the requested permission
1637 The arrah of orgId's has matches the order of the perms array
1640 sub check_user_perms4 {
1641 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1643 my( $staff, $target, $org, $evt );
1645 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1646 $authtoken, $userid, 'VIEW_PERMISSION' );
1647 return $evt if $evt;
1650 return [] unless ref($perms);
1651 my $tree = $U->get_org_tree();
1653 for my $p (@$perms) {
1654 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1660 __PACKAGE__->register_method(
1661 method => "user_fines_summary",
1662 api_name => "open-ils.actor.user.fines.summary",
1665 desc => 'Returns a short summary of the users total open fines, ' .
1666 'excluding voided fines Params are login_session, user_id' ,
1668 {desc => 'Authentication token', type => 'string'},
1669 {desc => 'User ID', type => 'string'} # number?
1672 desc => "a 'mous' object, event on error",
1677 sub user_fines_summary {
1678 my( $self, $client, $auth, $user_id ) = @_;
1680 my $e = new_editor(authtoken=>$auth);
1681 return $e->event unless $e->checkauth;
1683 if( $user_id ne $e->requestor->id ) {
1684 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1685 return $e->event unless
1686 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1689 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1693 __PACKAGE__->register_method(
1694 method => "user_opac_vitals",
1695 api_name => "open-ils.actor.user.opac.vital_stats",
1699 desc => 'Returns a short summary of the users vital stats, including ' .
1700 'identification information, accumulated balance, number of holds, ' .
1701 'and current open circulation stats' ,
1703 {desc => 'Authentication token', type => 'string'},
1704 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1707 desc => "An object with four properties: user, fines, checkouts and holds."
1712 sub user_opac_vitals {
1713 my( $self, $client, $auth, $user_id ) = @_;
1715 my $e = new_editor(authtoken=>$auth);
1716 return $e->event unless $e->checkauth;
1718 $user_id ||= $e->requestor->id;
1720 my $user = $e->retrieve_actor_user( $user_id );
1723 ->method_lookup('open-ils.actor.user.fines.summary')
1724 ->run($auth => $user_id);
1725 return $fines if (defined($U->event_code($fines)));
1728 $fines = new Fieldmapper::money::open_user_summary ();
1729 $fines->balance_owed(0.00);
1730 $fines->total_owed(0.00);
1731 $fines->total_paid(0.00);
1732 $fines->usr($user_id);
1736 ->method_lookup('open-ils.actor.user.hold_requests.count')
1737 ->run($auth => $user_id);
1738 return $holds if (defined($U->event_code($holds)));
1741 ->method_lookup('open-ils.actor.user.checked_out.count')
1742 ->run($auth => $user_id);
1743 return $out if (defined($U->event_code($out)));
1745 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1749 first_given_name => $user->first_given_name,
1750 second_given_name => $user->second_given_name,
1751 family_name => $user->family_name,
1752 alias => $user->alias,
1753 usrname => $user->usrname
1755 fines => $fines->to_bare_hash,
1762 ##### a small consolidation of related method registrations
1763 my $common_params = [
1764 { desc => 'Authentication token', type => 'string' },
1765 { desc => 'User ID', type => 'string' },
1766 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1767 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1770 'open-ils.actor.user.transactions' => '',
1771 'open-ils.actor.user.transactions.fleshed' => '',
1772 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1773 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1774 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1775 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1778 foreach (keys %methods) {
1780 method => "user_transactions",
1783 desc => 'For a given user, retrieve a list of '
1784 . (/\.fleshed/ ? 'fleshed ' : '')
1785 . 'transactions' . $methods{$_}
1786 . ' optionally limited to transactions of a given type.',
1787 params => $common_params,
1789 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1790 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1794 $args{authoritative} = 1;
1795 __PACKAGE__->register_method(%args);
1798 # Now for the counts
1800 'open-ils.actor.user.transactions.count' => '',
1801 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1802 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1805 foreach (keys %methods) {
1807 method => "user_transactions",
1810 desc => 'For a given user, retrieve a count of open '
1811 . 'transactions' . $methods{$_}
1812 . ' optionally limited to transactions of a given type.',
1813 params => $common_params,
1814 return => { desc => "Integer count of transactions, or event on error" }
1817 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1818 __PACKAGE__->register_method(%args);
1821 __PACKAGE__->register_method(
1822 method => "user_transactions",
1823 api_name => "open-ils.actor.user.transactions.have_balance.total",
1826 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1827 . ' optionally limited to transactions of a given type.',
1828 params => $common_params,
1829 return => { desc => "Decimal balance value, or event on error" }
1834 sub user_transactions {
1835 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1838 my $e = new_editor(authtoken => $auth);
1839 return $e->event unless $e->checkauth;
1841 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1843 return $e->event unless
1844 $e->requestor->id == $user_id or
1845 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1847 my $api = $self->api_name();
1849 my $filter = ($api =~ /have_balance/o) ?
1850 { 'balance_owed' => { '<>' => 0 } }:
1851 { 'total_owed' => { '>' => 0 } };
1853 my $method = 'open-ils.actor.user.transactions.history.still_open';
1854 $method = "$method.authoritative" if $api =~ /authoritative/;
1855 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1857 if($api =~ /total/o) {
1859 $total += $_->balance_owed for @$trans;
1863 ($api =~ /count/o ) and return scalar @$trans;
1864 ($api !~ /fleshed/o) and return $trans;
1867 for my $t (@$trans) {
1869 if( $t->xact_type ne 'circulation' ) {
1870 push @resp, {transaction => $t};
1874 my $circ_data = flesh_circ($e, $t->id);
1875 push @resp, {transaction => $t, %$circ_data};
1882 __PACKAGE__->register_method(
1883 method => "user_transaction_retrieve",
1884 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1887 notes => "Returns a fleshed transaction record"
1890 __PACKAGE__->register_method(
1891 method => "user_transaction_retrieve",
1892 api_name => "open-ils.actor.user.transaction.retrieve",
1895 notes => "Returns a transaction record"
1898 sub user_transaction_retrieve {
1899 my($self, $client, $auth, $bill_id) = @_;
1901 my $e = new_editor(authtoken => $auth);
1902 return $e->event unless $e->checkauth;
1904 my $trans = $e->retrieve_money_billable_transaction_summary(
1905 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1907 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1909 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1911 return $trans unless $self->api_name =~ /flesh/;
1912 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1914 my $circ_data = flesh_circ($e, $trans->id, 1);
1916 return {transaction => $trans, %$circ_data};
1921 my $circ_id = shift;
1922 my $flesh_copy = shift;
1924 my $circ = $e->retrieve_action_circulation([
1928 circ => ['target_copy'],
1929 acp => ['call_number'],
1936 my $copy = $circ->target_copy;
1938 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1939 $mods = new Fieldmapper::metabib::virtual_record;
1940 $mods->doc_id(OILS_PRECAT_RECORD);
1941 $mods->title($copy->dummy_title);
1942 $mods->author($copy->dummy_author);
1945 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1949 $circ->target_copy($circ->target_copy->id);
1950 $copy->call_number($copy->call_number->id);
1952 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1956 __PACKAGE__->register_method(
1957 method => "hold_request_count",
1958 api_name => "open-ils.actor.user.hold_requests.count",
1962 Returns hold ready vs. total counts.
1963 If a context org unit is provided, a third value
1964 is returned with key 'behind_desk', which reports
1965 how many holds are ready at the pickup library
1966 with the behind_desk flag set to true.
1970 sub hold_request_count {
1971 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
1972 my $e = new_editor(authtoken => $authtoken);
1973 return $e->event unless $e->checkauth;
1975 $user_id = $e->requestor->id unless defined $user_id;
1977 if($e->requestor->id ne $user_id) {
1978 my $user = $e->retrieve_actor_user($user_id);
1979 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1982 my $holds = $e->json_query({
1983 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
1987 fulfillment_time => {"=" => undef },
1988 cancel_time => undef,
1993 $_->{current_shelf_lib} and # avoid undef warnings
1994 $_->{pickup_lib} eq $_->{current_shelf_lib}
1998 total => scalar(@$holds),
1999 ready => scalar(@ready)
2003 # count of holds ready at pickup lib with behind_desk true.
2004 $resp->{behind_desk} = scalar(
2006 $_->{pickup_lib} == $ctx_org and
2007 $U->is_true($_->{behind_desk})
2015 __PACKAGE__->register_method(
2016 method => "checked_out",
2017 api_name => "open-ils.actor.user.checked_out",
2021 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2022 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2023 . "(i.e., outstanding balance or some other pending action on the circ). "
2024 . "The .count method also includes a 'total' field which sums all open circs.",
2026 { desc => 'Authentication Token', type => 'string'},
2027 { desc => 'User ID', type => 'string'},
2030 desc => 'Returns event on error, or an object with ID lists, like: '
2031 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2036 __PACKAGE__->register_method(
2037 method => "checked_out",
2038 api_name => "open-ils.actor.user.checked_out.count",
2041 signature => q/@see open-ils.actor.user.checked_out/
2045 my( $self, $conn, $auth, $userid ) = @_;
2047 my $e = new_editor(authtoken=>$auth);
2048 return $e->event unless $e->checkauth;
2050 if( $userid ne $e->requestor->id ) {
2051 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2052 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2054 # see if there is a friend link allowing circ.view perms
2055 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2056 $e, $userid, $e->requestor->id, 'circ.view');
2057 return $e->event unless $allowed;
2061 my $count = $self->api_name =~ /count/;
2062 return _checked_out( $count, $e, $userid );
2066 my( $iscount, $e, $userid ) = @_;
2072 claims_returned => [],
2075 my $meth = 'retrieve_action_open_circ_';
2083 claims_returned => 0,
2090 my $data = $e->$meth($userid);
2094 $result{$_} += $data->$_() for (keys %result);
2095 $result{total} += $data->$_() for (keys %result);
2097 for my $k (keys %result) {
2098 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2108 __PACKAGE__->register_method(
2109 method => "checked_in_with_fines",
2110 api_name => "open-ils.actor.user.checked_in_with_fines",
2113 signature => q/@see open-ils.actor.user.checked_out/
2116 sub checked_in_with_fines {
2117 my( $self, $conn, $auth, $userid ) = @_;
2119 my $e = new_editor(authtoken=>$auth);
2120 return $e->event unless $e->checkauth;
2122 if( $userid ne $e->requestor->id ) {
2123 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2126 # money is owed on these items and they are checked in
2127 my $open = $e->search_action_circulation(
2130 xact_finish => undef,
2131 checkin_time => { "!=" => undef },
2136 my( @lost, @cr, @lo );
2137 for my $c (@$open) {
2138 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2139 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2140 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2145 claims_returned => \@cr,
2146 long_overdue => \@lo
2152 my ($api, $desc, $auth) = @_;
2153 $desc = $desc ? (" " . $desc) : '';
2154 my $ids = ($api =~ /ids$/) ? 1 : 0;
2157 method => "user_transaction_history",
2158 api_name => "open-ils.actor.user.transactions.$api",
2160 desc => "For a given User ID, returns a list of billable transaction" .
2161 ($ids ? " id" : '') .
2162 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2163 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2165 {desc => 'Authentication token', type => 'string'},
2166 {desc => 'User ID', type => 'number'},
2167 {desc => 'Transaction type (optional)', type => 'number'},
2168 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2171 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2175 $auth and push @sig, (authoritative => 1);
2179 my %auth_hist_methods = (
2181 'history.have_charge' => 'that have an initial charge',
2182 'history.still_open' => 'that are not finished',
2183 'history.have_balance' => 'that have a balance',
2184 'history.have_bill' => 'that have billings',
2185 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2186 'history.have_payment' => 'that have at least 1 payment',
2189 foreach (keys %auth_hist_methods) {
2190 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2191 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2192 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2195 sub user_transaction_history {
2196 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2200 my $e = new_editor(authtoken=>$auth);
2201 return $e->die_event unless $e->checkauth;
2203 if ($e->requestor->id ne $userid) {
2204 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2207 my $api = $self->api_name;
2208 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2210 if(defined($type)) {
2211 $filter->{'xact_type'} = $type;
2214 if($api =~ /have_bill_or_payment/o) {
2216 # transactions that have a non-zero sum across all billings or at least 1 payment
2217 $filter->{'-or'} = {
2218 'balance_owed' => { '<>' => 0 },
2219 'last_payment_ts' => { '<>' => undef }
2222 } elsif($api =~ /have_payment/) {
2224 $filter->{last_payment_ts} ||= {'<>' => undef};
2226 } elsif( $api =~ /have_balance/o) {
2228 # transactions that have a non-zero overall balance
2229 $filter->{'balance_owed'} = { '<>' => 0 };
2231 } elsif( $api =~ /have_charge/o) {
2233 # transactions that have at least 1 billing, regardless of whether it was voided
2234 $filter->{'last_billing_ts'} = { '<>' => undef };
2236 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2238 # transactions that have non-zero sum across all billings. This will exclude
2239 # xacts where all billings have been voided
2240 $filter->{'total_owed'} = { '<>' => 0 };
2243 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2244 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2245 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2247 my $mbts = $e->search_money_billable_transaction_summary(
2248 [ { usr => $userid, @xact_finish, %$filter },
2253 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2254 return $mbts unless $api =~ /fleshed/;
2257 for my $t (@$mbts) {
2259 if( $t->xact_type ne 'circulation' ) {
2260 push @resp, {transaction => $t};
2264 my $circ_data = flesh_circ($e, $t->id);
2265 push @resp, {transaction => $t, %$circ_data};
2273 __PACKAGE__->register_method(
2274 method => "user_perms",
2275 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2277 notes => "Returns a list of permissions"
2281 my( $self, $client, $authtoken, $user ) = @_;
2283 my( $staff, $evt ) = $apputils->checkses($authtoken);
2284 return $evt if $evt;
2286 $user ||= $staff->id;
2288 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2292 return $apputils->simple_scalar_request(
2294 "open-ils.storage.permission.user_perms.atomic",
2298 __PACKAGE__->register_method(
2299 method => "retrieve_perms",
2300 api_name => "open-ils.actor.permissions.retrieve",
2301 notes => "Returns a list of permissions"
2303 sub retrieve_perms {
2304 my( $self, $client ) = @_;
2305 return $apputils->simple_scalar_request(
2307 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2308 { id => { '!=' => undef } }
2312 __PACKAGE__->register_method(
2313 method => "retrieve_groups",
2314 api_name => "open-ils.actor.groups.retrieve",
2315 notes => "Returns a list of user groups"
2317 sub retrieve_groups {
2318 my( $self, $client ) = @_;
2319 return new_editor()->retrieve_all_permission_grp_tree();
2322 __PACKAGE__->register_method(
2323 method => "retrieve_org_address",
2324 api_name => "open-ils.actor.org_unit.address.retrieve",
2325 notes => <<' NOTES');
2326 Returns an org_unit address by ID
2327 @param An org_address ID
2329 sub retrieve_org_address {
2330 my( $self, $client, $id ) = @_;
2331 return $apputils->simple_scalar_request(
2333 "open-ils.cstore.direct.actor.org_address.retrieve",
2338 __PACKAGE__->register_method(
2339 method => "retrieve_groups_tree",
2340 api_name => "open-ils.actor.groups.tree.retrieve",
2341 notes => "Returns a list of user groups"
2344 sub retrieve_groups_tree {
2345 my( $self, $client ) = @_;
2346 return new_editor()->search_permission_grp_tree(
2351 flesh_fields => { pgt => ["children"] },
2352 order_by => { pgt => 'name'}
2359 __PACKAGE__->register_method(
2360 method => "add_user_to_groups",
2361 api_name => "open-ils.actor.user.set_groups",
2362 notes => "Adds a user to one or more permission groups"
2365 sub add_user_to_groups {
2366 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2368 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2369 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2370 return $evt if $evt;
2372 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2373 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2374 return $evt if $evt;
2376 $apputils->simplereq(
2378 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2380 for my $group (@$groups) {
2381 my $link = Fieldmapper::permission::usr_grp_map->new;
2383 $link->usr($userid);
2385 my $id = $apputils->simplereq(
2387 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2393 __PACKAGE__->register_method(
2394 method => "get_user_perm_groups",
2395 api_name => "open-ils.actor.user.get_groups",
2396 notes => "Retrieve a user's permission groups."
2400 sub get_user_perm_groups {
2401 my( $self, $client, $authtoken, $userid ) = @_;
2403 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2404 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2405 return $evt if $evt;
2407 return $apputils->simplereq(
2409 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2413 __PACKAGE__->register_method(
2414 method => "get_user_work_ous",
2415 api_name => "open-ils.actor.user.get_work_ous",
2416 notes => "Retrieve a user's work org units."
2419 __PACKAGE__->register_method(
2420 method => "get_user_work_ous",
2421 api_name => "open-ils.actor.user.get_work_ous.ids",
2422 notes => "Retrieve a user's work org units."
2425 sub get_user_work_ous {
2426 my( $self, $client, $auth, $userid ) = @_;
2427 my $e = new_editor(authtoken=>$auth);
2428 return $e->event unless $e->checkauth;
2429 $userid ||= $e->requestor->id;
2431 if($e->requestor->id != $userid) {
2432 my $user = $e->retrieve_actor_user($userid)
2433 or return $e->event;
2434 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2437 return $e->search_permission_usr_work_ou_map({usr => $userid})
2438 unless $self->api_name =~ /.ids$/;
2440 # client just wants a list of org IDs
2441 return $U->get_user_work_ou_ids($e, $userid);
2446 __PACKAGE__->register_method(
2447 method => 'register_workstation',
2448 api_name => 'open-ils.actor.workstation.register.override',
2449 signature => q/@see open-ils.actor.workstation.register/
2452 __PACKAGE__->register_method(
2453 method => 'register_workstation',
2454 api_name => 'open-ils.actor.workstation.register',
2456 Registers a new workstion in the system
2457 @param authtoken The login session key
2458 @param name The name of the workstation id
2459 @param owner The org unit that owns this workstation
2460 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2461 if the name is already in use.
2465 sub register_workstation {
2466 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2468 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2469 return $e->die_event unless $e->checkauth;
2470 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2471 my $existing = $e->search_actor_workstation({name => $name})->[0];
2472 $oargs = { all => 1 } unless defined $oargs;
2476 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2477 # workstation with the given name exists.
2479 if($owner ne $existing->owning_lib) {
2480 # if necessary, update the owning_lib of the workstation
2482 $logger->info("changing owning lib of workstation ".$existing->id.
2483 " from ".$existing->owning_lib." to $owner");
2484 return $e->die_event unless
2485 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2487 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2489 $existing->owning_lib($owner);
2490 return $e->die_event unless $e->update_actor_workstation($existing);
2496 "attempt to register an existing workstation. returning existing ID");
2499 return $existing->id;
2502 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2506 my $ws = Fieldmapper::actor::workstation->new;
2507 $ws->owning_lib($owner);
2509 $e->create_actor_workstation($ws) or return $e->die_event;
2511 return $ws->id; # note: editor sets the id on the new object for us
2514 __PACKAGE__->register_method(
2515 method => 'workstation_list',
2516 api_name => 'open-ils.actor.workstation.list',
2518 Returns a list of workstations registered at the given location
2519 @param authtoken The login session key
2520 @param ids A list of org_unit.id's for the workstation owners
2524 sub workstation_list {
2525 my( $self, $conn, $authtoken, @orgs ) = @_;
2527 my $e = new_editor(authtoken=>$authtoken);
2528 return $e->event unless $e->checkauth;
2533 unless $e->allowed('REGISTER_WORKSTATION', $o);
2534 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2540 __PACKAGE__->register_method(
2541 method => 'fetch_patron_note',
2542 api_name => 'open-ils.actor.note.retrieve.all',
2545 Returns a list of notes for a given user
2546 Requestor must have VIEW_USER permission if pub==false and
2547 @param authtoken The login session key
2548 @param args Hash of params including
2549 patronid : the patron's id
2550 pub : true if retrieving only public notes
2554 sub fetch_patron_note {
2555 my( $self, $conn, $authtoken, $args ) = @_;
2556 my $patronid = $$args{patronid};
2558 my($reqr, $evt) = $U->checkses($authtoken);
2559 return $evt if $evt;
2562 ($patron, $evt) = $U->fetch_user($patronid);
2563 return $evt if $evt;
2566 if( $patronid ne $reqr->id ) {
2567 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2568 return $evt if $evt;
2570 return $U->cstorereq(
2571 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2572 { usr => $patronid, pub => 't' } );
2575 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2576 return $evt if $evt;
2578 return $U->cstorereq(
2579 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2582 __PACKAGE__->register_method(
2583 method => 'create_user_note',
2584 api_name => 'open-ils.actor.note.create',
2586 Creates a new note for the given user
2587 @param authtoken The login session key
2588 @param note The note object
2591 sub create_user_note {
2592 my( $self, $conn, $authtoken, $note ) = @_;
2593 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2594 return $e->die_event unless $e->checkauth;
2596 my $user = $e->retrieve_actor_user($note->usr)
2597 or return $e->die_event;
2599 return $e->die_event unless
2600 $e->allowed('UPDATE_USER',$user->home_ou);
2602 $note->creator($e->requestor->id);
2603 $e->create_actor_usr_note($note) or return $e->die_event;
2609 __PACKAGE__->register_method(
2610 method => 'delete_user_note',
2611 api_name => 'open-ils.actor.note.delete',
2613 Deletes a note for the given user
2614 @param authtoken The login session key
2615 @param noteid The note id
2618 sub delete_user_note {
2619 my( $self, $conn, $authtoken, $noteid ) = @_;
2621 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2622 return $e->die_event unless $e->checkauth;
2623 my $note = $e->retrieve_actor_usr_note($noteid)
2624 or return $e->die_event;
2625 my $user = $e->retrieve_actor_user($note->usr)
2626 or return $e->die_event;
2627 return $e->die_event unless
2628 $e->allowed('UPDATE_USER', $user->home_ou);
2630 $e->delete_actor_usr_note($note) or return $e->die_event;
2636 __PACKAGE__->register_method(
2637 method => 'update_user_note',
2638 api_name => 'open-ils.actor.note.update',
2640 @param authtoken The login session key
2641 @param note The note
2645 sub update_user_note {
2646 my( $self, $conn, $auth, $note ) = @_;
2647 my $e = new_editor(authtoken=>$auth, xact=>1);
2648 return $e->die_event unless $e->checkauth;
2649 my $patron = $e->retrieve_actor_user($note->usr)
2650 or return $e->die_event;
2651 return $e->die_event unless
2652 $e->allowed('UPDATE_USER', $patron->home_ou);
2653 $e->update_actor_user_note($note)
2654 or return $e->die_event;
2661 __PACKAGE__->register_method(
2662 method => 'create_closed_date',
2663 api_name => 'open-ils.actor.org_unit.closed_date.create',
2665 Creates a new closing entry for the given org_unit
2666 @param authtoken The login session key
2667 @param note The closed_date object
2670 sub create_closed_date {
2671 my( $self, $conn, $authtoken, $cd ) = @_;
2673 my( $user, $evt ) = $U->checkses($authtoken);
2674 return $evt if $evt;
2676 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2677 return $evt if $evt;
2679 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2681 my $id = $U->storagereq(
2682 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2683 return $U->DB_UPDATE_FAILED($cd) unless $id;
2688 __PACKAGE__->register_method(
2689 method => 'delete_closed_date',
2690 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2692 Deletes a closing entry for the given org_unit
2693 @param authtoken The login session key
2694 @param noteid The close_date id
2697 sub delete_closed_date {
2698 my( $self, $conn, $authtoken, $cd ) = @_;
2700 my( $user, $evt ) = $U->checkses($authtoken);
2701 return $evt if $evt;
2704 ($cd_obj, $evt) = fetch_closed_date($cd);
2705 return $evt if $evt;
2707 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2708 return $evt if $evt;
2710 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2712 my $stat = $U->storagereq(
2713 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2714 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2719 __PACKAGE__->register_method(
2720 method => 'usrname_exists',
2721 api_name => 'open-ils.actor.username.exists',
2723 desc => 'Check if a username is already taken (by an undeleted patron)',
2725 {desc => 'Authentication token', type => 'string'},
2726 {desc => 'Username', type => 'string'}
2729 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2734 sub usrname_exists {
2735 my( $self, $conn, $auth, $usrname ) = @_;
2736 my $e = new_editor(authtoken=>$auth);
2737 return $e->event unless $e->checkauth;
2738 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2739 return $$a[0] if $a and @$a;
2743 __PACKAGE__->register_method(
2744 method => 'barcode_exists',
2745 api_name => 'open-ils.actor.barcode.exists',
2747 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2750 sub barcode_exists {
2751 my( $self, $conn, $auth, $barcode ) = @_;
2752 my $e = new_editor(authtoken=>$auth);
2753 return $e->event unless $e->checkauth;
2754 my $card = $e->search_actor_card({barcode => $barcode});
2760 #return undef unless @$card;
2761 #return $card->[0]->usr;
2765 __PACKAGE__->register_method(
2766 method => 'retrieve_net_levels',
2767 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2770 sub retrieve_net_levels {
2771 my( $self, $conn, $auth ) = @_;
2772 my $e = new_editor(authtoken=>$auth);
2773 return $e->event unless $e->checkauth;
2774 return $e->retrieve_all_config_net_access_level();
2777 # Retain the old typo API name just in case
2778 __PACKAGE__->register_method(
2779 method => 'fetch_org_by_shortname',
2780 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2782 __PACKAGE__->register_method(
2783 method => 'fetch_org_by_shortname',
2784 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2786 sub fetch_org_by_shortname {
2787 my( $self, $conn, $sname ) = @_;
2788 my $e = new_editor();
2789 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2790 return $e->event unless $org;
2795 __PACKAGE__->register_method(
2796 method => 'session_home_lib',
2797 api_name => 'open-ils.actor.session.home_lib',
2800 sub session_home_lib {
2801 my( $self, $conn, $auth ) = @_;
2802 my $e = new_editor(authtoken=>$auth);
2803 return undef unless $e->checkauth;
2804 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2805 return $org->shortname;
2808 __PACKAGE__->register_method(
2809 method => 'session_safe_token',
2810 api_name => 'open-ils.actor.session.safe_token',
2812 Returns a hashed session ID that is safe for export to the world.
2813 This safe token will expire after 1 hour of non-use.
2814 @param auth Active authentication token
2818 sub session_safe_token {
2819 my( $self, $conn, $auth ) = @_;
2820 my $e = new_editor(authtoken=>$auth);
2821 return undef unless $e->checkauth;
2823 my $safe_token = md5_hex($auth);
2825 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2827 # Add more like the following if needed...
2829 "safe-token-home_lib-shortname-$safe_token",
2830 $e->retrieve_actor_org_unit(
2831 $e->requestor->home_ou
2840 __PACKAGE__->register_method(
2841 method => 'safe_token_home_lib',
2842 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2844 Returns the home library shortname from the session
2845 asscociated with a safe token from generated by
2846 open-ils.actor.session.safe_token.
2847 @param safe_token Active safe token
2851 sub safe_token_home_lib {
2852 my( $self, $conn, $safe_token ) = @_;
2854 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2855 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2859 __PACKAGE__->register_method(
2860 method => "update_penalties",
2861 api_name => "open-ils.actor.user.penalties.update"
2864 sub update_penalties {
2865 my($self, $conn, $auth, $user_id) = @_;
2866 my $e = new_editor(authtoken=>$auth, xact => 1);
2867 return $e->die_event unless $e->checkauth;
2868 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2869 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2870 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2871 return $evt if $evt;
2877 __PACKAGE__->register_method(
2878 method => "apply_penalty",
2879 api_name => "open-ils.actor.user.penalty.apply"
2883 my($self, $conn, $auth, $penalty) = @_;
2885 my $e = new_editor(authtoken=>$auth, xact => 1);
2886 return $e->die_event unless $e->checkauth;
2888 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2889 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2891 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2894 (defined $ptype->org_depth) ?
2895 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2898 $penalty->org_unit($ctx_org);
2899 $penalty->staff($e->requestor->id);
2900 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2903 return $penalty->id;
2906 __PACKAGE__->register_method(
2907 method => "remove_penalty",
2908 api_name => "open-ils.actor.user.penalty.remove"
2911 sub remove_penalty {
2912 my($self, $conn, $auth, $penalty) = @_;
2913 my $e = new_editor(authtoken=>$auth, xact => 1);
2914 return $e->die_event unless $e->checkauth;
2915 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2916 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2918 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2923 __PACKAGE__->register_method(
2924 method => "update_penalty_note",
2925 api_name => "open-ils.actor.user.penalty.note.update"
2928 sub update_penalty_note {
2929 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2930 my $e = new_editor(authtoken=>$auth, xact => 1);
2931 return $e->die_event unless $e->checkauth;
2932 for my $penalty_id (@$penalty_ids) {
2933 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2934 if (! $penalty ) { return $e->die_event; }
2935 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2936 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2938 $penalty->note( $note ); $penalty->ischanged( 1 );
2940 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2946 __PACKAGE__->register_method(
2947 method => "ranged_penalty_thresholds",
2948 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2952 sub ranged_penalty_thresholds {
2953 my($self, $conn, $auth, $context_org) = @_;
2954 my $e = new_editor(authtoken=>$auth);
2955 return $e->event unless $e->checkauth;
2956 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2957 my $list = $e->search_permission_grp_penalty_threshold([
2958 {org_unit => $U->get_org_ancestors($context_org)},
2959 {order_by => {pgpt => 'id'}}
2961 $conn->respond($_) for @$list;
2967 __PACKAGE__->register_method(
2968 method => "user_retrieve_fleshed_by_id",
2970 api_name => "open-ils.actor.user.fleshed.retrieve",
2973 sub user_retrieve_fleshed_by_id {
2974 my( $self, $client, $auth, $user_id, $fields ) = @_;
2975 my $e = new_editor(authtoken => $auth);
2976 return $e->event unless $e->checkauth;
2978 if( $e->requestor->id != $user_id ) {
2979 return $e->event unless $e->allowed('VIEW_USER');
2986 "standing_penalties",
2992 return new_flesh_user($user_id, $fields, $e);
2996 sub new_flesh_user {
2999 my $fields = shift || [];
3002 my $fetch_penalties = 0;
3003 if(grep {$_ eq 'standing_penalties'} @$fields) {
3004 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3005 $fetch_penalties = 1;
3008 my $fetch_usr_act = 0;
3009 if(grep {$_ eq 'usr_activity'} @$fields) {
3010 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3014 my $user = $e->retrieve_actor_user(
3019 "flesh_fields" => { "au" => $fields }
3022 ) or return $e->die_event;
3025 if( grep { $_ eq 'addresses' } @$fields ) {
3027 $user->addresses([]) unless @{$user->addresses};
3028 # don't expose "replaced" addresses by default
3029 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3031 if( ref $user->billing_address ) {
3032 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3033 push( @{$user->addresses}, $user->billing_address );
3037 if( ref $user->mailing_address ) {
3038 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3039 push( @{$user->addresses}, $user->mailing_address );
3044 if($fetch_penalties) {
3045 # grab the user penalties ranged for this location
3046 $user->standing_penalties(
3047 $e->search_actor_user_standing_penalty([
3050 {stop_date => undef},
3051 {stop_date => {'>' => 'now'}}
3053 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3056 flesh_fields => {ausp => ['standing_penalty']}
3062 # retrieve the most recent usr_activity entry
3063 if ($fetch_usr_act) {
3065 # max number to return for simple patron fleshing
3066 my $limit = $U->ou_ancestor_setting_value(
3067 $e->requestor->ws_ou,
3068 'circ.patron.usr_activity_retrieve.max');
3072 flesh_fields => {auact => ['etype']},
3073 order_by => {auact => 'event_time DESC'},
3076 # 0 == none, <0 == return all
3077 $limit = 1 unless defined $limit;
3078 $opts->{limit} = $limit if $limit > 0;
3080 $user->usr_activity(
3082 [] : # skip the DB call
3083 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3088 $user->clear_passwd();
3095 __PACKAGE__->register_method(
3096 method => "user_retrieve_parts",
3097 api_name => "open-ils.actor.user.retrieve.parts",
3100 sub user_retrieve_parts {
3101 my( $self, $client, $auth, $user_id, $fields ) = @_;
3102 my $e = new_editor(authtoken => $auth);
3103 return $e->event unless $e->checkauth;
3104 $user_id ||= $e->requestor->id;
3105 if( $e->requestor->id != $user_id ) {
3106 return $e->event unless $e->allowed('VIEW_USER');
3109 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3110 push(@resp, $user->$_()) for(@$fields);
3116 __PACKAGE__->register_method(
3117 method => 'user_opt_in_enabled',
3118 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3119 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3122 sub user_opt_in_enabled {
3123 my($self, $conn) = @_;
3124 my $sc = OpenSRF::Utils::SettingsClient->new;
3125 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3130 __PACKAGE__->register_method(
3131 method => 'user_opt_in_at_org',
3132 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3134 @param $auth The auth token
3135 @param user_id The ID of the user to test
3136 @return 1 if the user has opted in at the specified org,
3137 event on error, and 0 otherwise. /
3139 sub user_opt_in_at_org {
3140 my($self, $conn, $auth, $user_id) = @_;
3142 # see if we even need to enforce the opt-in value
3143 return 1 unless user_opt_in_enabled($self);
3145 my $e = new_editor(authtoken => $auth);
3146 return $e->event unless $e->checkauth;
3148 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3149 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3151 my $ws_org = $e->requestor->ws_ou;
3152 # user is automatically opted-in if they are from the local org
3153 return 1 if $user->home_ou eq $ws_org;
3155 # get the boundary setting
3156 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3158 # auto opt in if user falls within the opt boundary
3159 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3161 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3163 my $vals = $e->search_actor_usr_org_unit_opt_in(
3164 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3170 __PACKAGE__->register_method(
3171 method => 'create_user_opt_in_at_org',
3172 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3174 @param $auth The auth token
3175 @param user_id The ID of the user to test
3176 @return The ID of the newly created object, event on error./
3179 sub create_user_opt_in_at_org {
3180 my($self, $conn, $auth, $user_id, $org_id) = @_;
3182 my $e = new_editor(authtoken => $auth, xact=>1);
3183 return $e->die_event unless $e->checkauth;
3185 # if a specific org unit wasn't passed in, get one based on the defaults;
3187 my $wsou = $e->requestor->ws_ou;
3188 # get the default opt depth
3189 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3190 # get the org unit at that depth
3191 my $org = $e->json_query({
3192 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3193 $org_id = $org->{id};
3196 # fall back to the workstation OU, the pre-opt-in-boundary way
3197 $org_id = $e->requestor->ws_ou;
3200 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3201 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3203 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3205 $opt_in->org_unit($org_id);
3206 $opt_in->usr($user_id);
3207 $opt_in->staff($e->requestor->id);
3208 $opt_in->opt_in_ts('now');
3209 $opt_in->opt_in_ws($e->requestor->wsid);
3211 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3212 or return $e->die_event;
3220 __PACKAGE__->register_method (
3221 method => 'retrieve_org_hours',
3222 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3224 Returns the hours of operation for a specified org unit
3225 @param authtoken The login session key
3226 @param org_id The org_unit ID
3230 sub retrieve_org_hours {
3231 my($self, $conn, $auth, $org_id) = @_;
3232 my $e = new_editor(authtoken => $auth);
3233 return $e->die_event unless $e->checkauth;
3234 $org_id ||= $e->requestor->ws_ou;
3235 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3239 __PACKAGE__->register_method (
3240 method => 'verify_user_password',
3241 api_name => 'open-ils.actor.verify_user_password',
3243 Given a barcode or username and the MD5 encoded password,
3244 returns 1 if the password is correct. Returns 0 otherwise.
3248 sub verify_user_password {
3249 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3250 my $e = new_editor(authtoken => $auth);
3251 return $e->die_event unless $e->checkauth;
3253 my $user_by_barcode;
3254 my $user_by_username;
3256 my $card = $e->search_actor_card([
3257 {barcode => $barcode},
3258 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3259 $user_by_barcode = $card->usr;
3260 $user = $user_by_barcode;
3263 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3264 $user = $user_by_username;
3266 return 0 if (!$user);
3267 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3268 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3269 return 1 if $user->passwd eq $password;
3273 __PACKAGE__->register_method (
3274 method => 'retrieve_usr_id_via_barcode_or_usrname',
3275 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3277 Given a barcode or username returns the id for the user or
3282 sub retrieve_usr_id_via_barcode_or_usrname {
3283 my($self, $conn, $auth, $barcode, $username) = @_;
3284 my $e = new_editor(authtoken => $auth);
3285 return $e->die_event unless $e->checkauth;
3286 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3288 my $user_by_barcode;
3289 my $user_by_username;
3290 $logger->info("$id_as_barcode is the ID as BARCODE");
3292 my $card = $e->search_actor_card([
3293 {barcode => $barcode},
3294 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3295 if ($id_as_barcode =~ /^t/i) {
3297 $user = $e->retrieve_actor_user($barcode);
3298 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3300 $user_by_barcode = $card->usr;
3301 $user = $user_by_barcode;
3304 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3305 $user_by_barcode = $card->usr;
3306 $user = $user_by_barcode;
3311 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3313 $user = $user_by_username;
3315 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3316 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3317 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3322 __PACKAGE__->register_method (
3323 method => 'merge_users',
3324 api_name => 'open-ils.actor.user.merge',
3327 Given a list of source users and destination user, transfer all data from the source
3328 to the dest user and delete the source user. All user related data is
3329 transferred, including circulations, holds, bookbags, etc.
3335 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3336 my $e = new_editor(xact => 1, authtoken => $auth);
3337 return $e->die_event unless $e->checkauth;
3339 # disallow the merge if any subordinate accounts are in collections
3340 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3341 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3343 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3344 my $del_addrs = ($U->ou_ancestor_setting_value(
3345 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3346 my $del_cards = ($U->ou_ancestor_setting_value(
3347 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3348 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3349 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3351 for my $src_id (@$user_ids) {
3352 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3354 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3355 if($src_user->home_ou ne $master_user->home_ou) {
3356 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3359 return $e->die_event unless
3360 $e->json_query({from => [
3375 __PACKAGE__->register_method (
3376 method => 'approve_user_address',
3377 api_name => 'open-ils.actor.user.pending_address.approve',
3384 sub approve_user_address {
3385 my($self, $conn, $auth, $addr) = @_;
3386 my $e = new_editor(xact => 1, authtoken => $auth);
3387 return $e->die_event unless $e->checkauth;
3389 # if the caller passes an address object, assume they want to
3390 # update it first before approving it
3391 $e->update_actor_user_address($addr) or return $e->die_event;
3393 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3395 my $user = $e->retrieve_actor_user($addr->usr);
3396 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3397 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3398 or return $e->die_event;
3400 return [values %$result]->[0];
3404 __PACKAGE__->register_method (
3405 method => 'retrieve_friends',
3406 api_name => 'open-ils.actor.friends.retrieve',
3409 returns { confirmed: [], pending_out: [], pending_in: []}
3410 pending_out are users I'm requesting friendship with
3411 pending_in are users requesting friendship with me
3416 sub retrieve_friends {
3417 my($self, $conn, $auth, $user_id, $options) = @_;
3418 my $e = new_editor(authtoken => $auth);
3419 return $e->event unless $e->checkauth;
3420 $user_id ||= $e->requestor->id;
3422 if($user_id != $e->requestor->id) {
3423 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3424 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3427 return OpenILS::Application::Actor::Friends->retrieve_friends(
3428 $e, $user_id, $options);
3433 __PACKAGE__->register_method (
3434 method => 'apply_friend_perms',
3435 api_name => 'open-ils.actor.friends.perms.apply',
3441 sub apply_friend_perms {
3442 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3443 my $e = new_editor(authtoken => $auth, xact => 1);
3444 return $e->die_event unless $e->checkauth;
3446 if($user_id != $e->requestor->id) {
3447 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3448 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3451 for my $perm (@perms) {
3453 OpenILS::Application::Actor::Friends->apply_friend_perm(
3454 $e, $user_id, $delegate_id, $perm);
3455 return $evt if $evt;
3463 __PACKAGE__->register_method (
3464 method => 'update_user_pending_address',
3465 api_name => 'open-ils.actor.user.address.pending.cud'
3468 sub update_user_pending_address {
3469 my($self, $conn, $auth, $addr) = @_;
3470 my $e = new_editor(authtoken => $auth, xact => 1);
3471 return $e->die_event unless $e->checkauth;
3473 if($addr->usr != $e->requestor->id) {
3474 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3475 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3479 $e->create_actor_user_address($addr) or return $e->die_event;
3480 } elsif($addr->isdeleted) {
3481 $e->delete_actor_user_address($addr) or return $e->die_event;
3483 $e->update_actor_user_address($addr) or return $e->die_event;
3491 __PACKAGE__->register_method (
3492 method => 'user_events',
3493 api_name => 'open-ils.actor.user.events.circ',
3496 __PACKAGE__->register_method (
3497 method => 'user_events',
3498 api_name => 'open-ils.actor.user.events.ahr',
3503 my($self, $conn, $auth, $user_id, $filters) = @_;
3504 my $e = new_editor(authtoken => $auth);
3505 return $e->event unless $e->checkauth;
3507 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3508 my $user_field = 'usr';
3511 $filters->{target} = {
3512 select => { $obj_type => ['id'] },
3514 where => {usr => $user_id}
3517 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3518 if($e->requestor->id != $user_id) {
3519 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3522 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3523 my $req = $ses->request('open-ils.trigger.events_by_target',
3524 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3526 while(my $resp = $req->recv) {
3527 my $val = $resp->content;
3528 my $tgt = $val->target;
3530 if($obj_type eq 'circ') {
3531 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3533 } elsif($obj_type eq 'ahr') {
3534 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3535 if $tgt->current_copy;
3538 $conn->respond($val) if $val;
3544 __PACKAGE__->register_method (
3545 method => 'copy_events',
3546 api_name => 'open-ils.actor.copy.events.circ',
3549 __PACKAGE__->register_method (
3550 method => 'copy_events',
3551 api_name => 'open-ils.actor.copy.events.ahr',
3556 my($self, $conn, $auth, $copy_id, $filters) = @_;
3557 my $e = new_editor(authtoken => $auth);
3558 return $e->event unless $e->checkauth;
3560 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3562 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3564 my $copy_field = 'target_copy';
3565 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3568 $filters->{target} = {
3569 select => { $obj_type => ['id'] },
3571 where => {$copy_field => $copy_id}
3575 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3576 my $req = $ses->request('open-ils.trigger.events_by_target',
3577 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3579 while(my $resp = $req->recv) {
3580 my $val = $resp->content;
3581 my $tgt = $val->target;
3583 my $user = $e->retrieve_actor_user($tgt->usr);
3584 if($e->requestor->id != $user->id) {
3585 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3588 $tgt->$copy_field($copy);
3591 $conn->respond($val) if $val;
3600 __PACKAGE__->register_method (
3601 method => 'update_events',
3602 api_name => 'open-ils.actor.user.event.cancel.batch',
3605 __PACKAGE__->register_method (
3606 method => 'update_events',
3607 api_name => 'open-ils.actor.user.event.reset.batch',
3612 my($self, $conn, $auth, $event_ids) = @_;
3613 my $e = new_editor(xact => 1, authtoken => $auth);
3614 return $e->die_event unless $e->checkauth;
3617 for my $id (@$event_ids) {
3619 # do a little dance to determine what user we are ultimately affecting
3620 my $event = $e->retrieve_action_trigger_event([
3623 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3625 ]) or return $e->die_event;
3628 if($event->event_def->hook->core_type eq 'circ') {
3629 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3630 } elsif($event->event_def->hook->core_type eq 'ahr') {
3631 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3636 my $user = $e->retrieve_actor_user($user_id);
3637 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3639 if($self->api_name =~ /cancel/) {
3640 $event->state('invalid');
3641 } elsif($self->api_name =~ /reset/) {
3642 $event->clear_start_time;
3643 $event->clear_update_time;
3644 $event->state('pending');
3647 $e->update_action_trigger_event($event) or return $e->die_event;
3648 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3652 return {complete => 1};
3656 __PACKAGE__->register_method (
3657 method => 'really_delete_user',
3658 api_name => 'open-ils.actor.user.delete.override',
3659 signature => q/@see open-ils.actor.user.delete/
3662 __PACKAGE__->register_method (
3663 method => 'really_delete_user',
3664 api_name => 'open-ils.actor.user.delete',
3666 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3667 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3668 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3669 dest_usr_id is only required when deleting a user that performs staff functions.
3673 sub really_delete_user {
3674 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3675 my $e = new_editor(authtoken => $auth, xact => 1);
3676 return $e->die_event unless $e->checkauth;
3677 $oargs = { all => 1 } unless defined $oargs;
3679 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3680 my $open_bills = $e->json_query({
3681 select => { mbts => ['id'] },
3684 xact_finish => { '=' => undef },
3685 usr => { '=' => $user_id },
3687 }) or return $e->die_event;
3689 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3691 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3693 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3694 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3695 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3697 # No deleting yourself - UI is supposed to stop you first, though.
3698 return $e->die_event unless $e->requestor->id != $user->id;
3699 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3700 # Check if you are allowed to mess with this patron permission group at all
3701 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3702 my $evt = group_perm_failed($session, $e->requestor, $user);
3703 return $e->die_event($evt) if $evt;
3704 my $stat = $e->json_query(
3705 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3706 or return $e->die_event;
3712 __PACKAGE__->register_method (
3713 method => 'user_payments',
3714 api_name => 'open-ils.actor.user.payments.retrieve',
3717 Returns all payments for a given user. Default order is newest payments first.
3718 @param auth Authentication token
3719 @param user_id The user ID
3720 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3725 my($self, $conn, $auth, $user_id, $filters) = @_;
3728 my $e = new_editor(authtoken => $auth);
3729 return $e->die_event unless $e->checkauth;
3731 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3732 return $e->event unless
3733 $e->requestor->id == $user_id or
3734 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3736 # Find all payments for all transactions for user $user_id
3738 select => {mp => ['id']},
3743 select => {mbt => ['id']},
3745 where => {usr => $user_id}
3750 { # by default, order newest payments first
3752 field => 'payment_ts',
3755 # secondary sort in ID as a tie-breaker, since payments created
3756 # within the same transaction will have identical payment_ts's
3763 for (qw/order_by limit offset/) {
3764 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3767 if(defined $filters->{where}) {
3768 foreach (keys %{$filters->{where}}) {
3769 # don't allow the caller to expand the result set to other users
3770 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3774 my $payment_ids = $e->json_query($query);
3775 for my $pid (@$payment_ids) {
3776 my $pay = $e->retrieve_money_payment([
3781 mbt => ['summary', 'circulation', 'grocery'],
3782 circ => ['target_copy'],
3783 acp => ['call_number'],
3791 xact_type => $pay->xact->summary->xact_type,
3792 last_billing_type => $pay->xact->summary->last_billing_type,
3795 if($pay->xact->summary->xact_type eq 'circulation') {
3796 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3797 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3800 $pay->xact($pay->xact->id); # de-flesh
3801 $conn->respond($resp);
3809 __PACKAGE__->register_method (
3810 method => 'negative_balance_users',
3811 api_name => 'open-ils.actor.users.negative_balance',
3814 Returns all users that have an overall negative balance
3815 @param auth Authentication token
3816 @param org_id The context org unit as an ID or list of IDs. This will be the home
3817 library of the user. If no org_unit is specified, no org unit filter is applied
3821 sub negative_balance_users {
3822 my($self, $conn, $auth, $org_id) = @_;
3824 my $e = new_editor(authtoken => $auth);
3825 return $e->die_event unless $e->checkauth;
3826 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3830 mous => ['usr', 'balance_owed'],
3833 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3834 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3851 where => {'+mous' => {balance_owed => {'<' => 0}}}
3854 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3856 my $list = $e->json_query($query, {timeout => 600});
3858 for my $data (@$list) {
3860 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3861 balance_owed => $data->{balance_owed},
3862 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3869 __PACKAGE__->register_method(
3870 method => "request_password_reset",
3871 api_name => "open-ils.actor.patron.password_reset.request",
3873 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3874 "method for changing a user's password. The UUID token is distributed via A/T " .
3875 "templates (i.e. email to the user).",
3877 { desc => 'user_id_type', type => 'string' },
3878 { desc => 'user_id', type => 'string' },
3879 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3881 return => {desc => '1 on success, Event on error'}
3884 sub request_password_reset {
3885 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3887 # Check to see if password reset requests are already being throttled:
3888 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3890 my $e = new_editor(xact => 1);
3893 # Get the user, if any, depending on the input value
3894 if ($user_id_type eq 'username') {
3895 $user = $e->search_actor_user({usrname => $user_id})->[0];
3898 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3900 } elsif ($user_id_type eq 'barcode') {
3901 my $card = $e->search_actor_card([
3902 {barcode => $user_id},
3903 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3906 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3911 # If the user doesn't have an email address, we can't help them
3912 if (!$user->email) {
3914 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3917 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3918 if ($email_must_match) {
3919 if ($user->email ne $email) {
3920 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3924 _reset_password_request($conn, $e, $user);
3927 # Once we have the user, we can issue the password reset request
3928 # XXX Add a wrapper method that accepts barcode + email input
3929 sub _reset_password_request {
3930 my ($conn, $e, $user) = @_;
3932 # 1. Get throttle threshold and time-to-live from OU_settings
3933 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3934 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3936 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3938 # 2. Get time of last request and number of active requests (num_active)
3939 my $active_requests = $e->json_query({
3945 transform => 'COUNT'
3948 column => 'request_time',
3954 has_been_reset => { '=' => 'f' },
3955 request_time => { '>' => $threshold_time }
3959 # Guard against no active requests
3960 if ($active_requests->[0]->{'request_time'}) {
3961 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3962 my $now = DateTime::Format::ISO8601->new();
3964 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3965 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3966 ($last_request->add_duration('1 minute') > $now)) {
3967 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3969 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3973 # TODO Check to see if the user is in a password-reset-restricted group
3975 # Otherwise, go ahead and try to get the user.
3977 # Check the number of active requests for this user
3978 $active_requests = $e->json_query({
3984 transform => 'COUNT'
3989 usr => { '=' => $user->id },
3990 has_been_reset => { '=' => 'f' },
3991 request_time => { '>' => $threshold_time }
3995 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3997 # if less than or equal to per-user threshold, proceed; otherwise, return event
3998 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3999 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4001 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4004 # Create the aupr object and insert into the database
4005 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4006 my $uuid = create_uuid_as_string(UUID_V4);
4007 $reset_request->uuid($uuid);
4008 $reset_request->usr($user->id);
4010 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4013 # Create an event to notify user of the URL to reset their password
4015 # Can we stuff this in the user_data param for trigger autocreate?
4016 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4018 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4019 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4022 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4027 __PACKAGE__->register_method(
4028 method => "commit_password_reset",
4029 api_name => "open-ils.actor.patron.password_reset.commit",
4031 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4032 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4033 "with the supplied password.",
4035 { desc => 'uuid', type => 'string' },
4036 { desc => 'password', type => 'string' },
4038 return => {desc => '1 on success, Event on error'}
4041 sub commit_password_reset {
4042 my($self, $conn, $uuid, $password) = @_;
4044 # Check to see if password reset requests are already being throttled:
4045 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4046 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4047 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4049 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4052 my $e = new_editor(xact => 1);
4054 my $aupr = $e->search_actor_usr_password_reset({
4061 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4063 my $user_id = $aupr->[0]->usr;
4064 my $user = $e->retrieve_actor_user($user_id);
4066 # Ensure we're still within the TTL for the request
4067 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4068 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4069 if ($threshold < DateTime->now(time_zone => 'local')) {
4071 $logger->info("Password reset request needed to be submitted before $threshold");
4072 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4075 # Check complexity of password against OU-defined regex
4076 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4080 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4081 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4082 $is_strong = check_password_strength_custom($password, $pw_regex);
4084 $is_strong = check_password_strength_default($password);
4089 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4092 # All is well; update the password
4093 $user->passwd($password);
4094 $e->update_actor_user($user);
4096 # And flag that this password reset request has been honoured
4097 $aupr->[0]->has_been_reset('t');
4098 $e->update_actor_usr_password_reset($aupr->[0]);
4104 sub check_password_strength_default {
4105 my $password = shift;
4106 # Use the default set of checks
4107 if ( (length($password) < 7) or
4108 ($password !~ m/.*\d+.*/) or
4109 ($password !~ m/.*[A-Za-z]+.*/)
4116 sub check_password_strength_custom {
4117 my ($password, $pw_regex) = @_;
4119 $pw_regex = qr/$pw_regex/;
4120 if ($password !~ /$pw_regex/) {
4128 __PACKAGE__->register_method(
4129 method => "event_def_opt_in_settings",
4130 api_name => "open-ils.actor.event_def.opt_in.settings",
4133 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4135 { desc => 'Authentication token', type => 'string'},
4137 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4142 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4149 sub event_def_opt_in_settings {
4150 my($self, $conn, $auth, $org_id) = @_;
4151 my $e = new_editor(authtoken => $auth);
4152 return $e->event unless $e->checkauth;
4154 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4155 return $e->event unless
4156 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4158 $org_id = $e->requestor->home_ou;
4161 # find all config.user_setting_type's related to event_defs for the requested org unit
4162 my $types = $e->json_query({
4163 select => {cust => ['name']},
4164 from => {atevdef => 'cust'},
4167 owner => $U->get_org_ancestors($org_id), # context org plus parents
4174 $conn->respond($_) for
4175 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4182 __PACKAGE__->register_method(
4183 method => "user_visible_circs",
4184 api_name => "open-ils.actor.history.circ.visible",
4187 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4189 { desc => 'Authentication token', type => 'string'},
4190 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4191 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4194 desc => q/An object with 2 fields: circulation and summary.
4195 circulation is the "circ" object. summary is the related "accs" object/,
4201 __PACKAGE__->register_method(
4202 method => "user_visible_circs",
4203 api_name => "open-ils.actor.history.circ.visible.print",
4206 desc => 'Returns printable output for the set of opt-in visible circulations',
4208 { desc => 'Authentication token', type => 'string'},
4209 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4210 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4213 desc => q/An action_trigger.event object or error event./,
4219 __PACKAGE__->register_method(
4220 method => "user_visible_circs",
4221 api_name => "open-ils.actor.history.circ.visible.email",
4224 desc => 'Emails the set of opt-in visible circulations to the requestor',
4226 { desc => 'Authentication token', type => 'string'},
4227 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4228 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4231 desc => q/undef, or event on error/
4236 __PACKAGE__->register_method(
4237 method => "user_visible_circs",
4238 api_name => "open-ils.actor.history.hold.visible",
4241 desc => 'Returns the set of opt-in visible holds',
4243 { desc => 'Authentication token', type => 'string'},
4244 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4245 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4248 desc => q/An object with 1 field: "hold"/,
4254 __PACKAGE__->register_method(
4255 method => "user_visible_circs",
4256 api_name => "open-ils.actor.history.hold.visible.print",
4259 desc => 'Returns printable output for the set of opt-in visible holds',
4261 { desc => 'Authentication token', type => 'string'},
4262 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4263 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4266 desc => q/An action_trigger.event object or error event./,
4272 __PACKAGE__->register_method(
4273 method => "user_visible_circs",
4274 api_name => "open-ils.actor.history.hold.visible.email",
4277 desc => 'Emails the set of opt-in visible holds to the requestor',
4279 { desc => 'Authentication token', type => 'string'},
4280 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4281 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4284 desc => q/undef, or event on error/
4289 sub user_visible_circs {
4290 my($self, $conn, $auth, $user_id, $options) = @_;
4292 my $is_hold = ($self->api_name =~ /hold/);
4293 my $for_print = ($self->api_name =~ /print/);
4294 my $for_email = ($self->api_name =~ /email/);
4295 my $e = new_editor(authtoken => $auth);
4296 return $e->event unless $e->checkauth;
4298 $user_id ||= $e->requestor->id;
4300 $options->{limit} ||= 50;
4301 $options->{offset} ||= 0;
4303 if($user_id != $e->requestor->id) {
4304 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4305 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4306 return $e->event unless $e->allowed($perm, $user->home_ou);
4309 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4311 my $data = $e->json_query({
4312 from => [$db_func, $user_id],
4313 limit => $$options{limit},
4314 offset => $$options{offset}
4316 # TODO: I only want IDs. code below didn't get me there
4317 # {"select":{"au":[{"column":"id", "result_field":"id",
4318 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4323 return undef unless @$data;
4327 # collect the batch of objects
4331 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4332 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4336 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4337 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4340 } elsif ($for_email) {
4342 $conn->respond_complete(1) if $for_email; # no sense in waiting
4350 my $hold = $e->retrieve_action_hold_request($id);
4351 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4352 # events will be fired from action_trigger_runner
4356 my $circ = $e->retrieve_action_circulation($id);
4357 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4358 # events will be fired from action_trigger_runner
4362 } else { # just give me the data please
4370 my $hold = $e->retrieve_action_hold_request($id);
4371 $conn->respond({hold => $hold});
4375 my $circ = $e->retrieve_action_circulation($id);
4378 summary => $U->create_circ_chain_summary($e, $id)
4387 __PACKAGE__->register_method(
4388 method => "user_saved_search_cud",
4389 api_name => "open-ils.actor.user.saved_search.cud",
4392 desc => 'Create/Update/Delete Access to user saved searches',
4394 { desc => 'Authentication token', type => 'string' },
4395 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4398 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4404 __PACKAGE__->register_method(
4405 method => "user_saved_search_cud",
4406 api_name => "open-ils.actor.user.saved_search.retrieve",
4409 desc => 'Retrieve a saved search object',
4411 { desc => 'Authentication token', type => 'string' },
4412 { desc => 'Saved Search ID', type => 'number' }
4415 desc => q/The saved search object, Event on error/,
4421 sub user_saved_search_cud {
4422 my( $self, $client, $auth, $search ) = @_;
4423 my $e = new_editor( authtoken=>$auth );
4424 return $e->die_event unless $e->checkauth;
4426 my $o_search; # prior version of the object, if any
4427 my $res; # to be returned
4429 # branch on the operation type
4431 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4433 # Get the old version, to check ownership
4434 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4435 or return $e->die_event;
4437 # You can't read somebody else's search
4438 return OpenILS::Event->new('BAD_PARAMS')
4439 unless $o_search->owner == $e->requestor->id;
4445 $e->xact_begin; # start an editor transaction
4447 if( $search->isnew ) { # Create
4449 # You can't create a search for somebody else
4450 return OpenILS::Event->new('BAD_PARAMS')
4451 unless $search->owner == $e->requestor->id;
4453 $e->create_actor_usr_saved_search( $search )
4454 or return $e->die_event;
4458 } elsif( $search->ischanged ) { # Update
4460 # You can't change ownership of a search
4461 return OpenILS::Event->new('BAD_PARAMS')
4462 unless $search->owner == $e->requestor->id;
4464 # Get the old version, to check ownership
4465 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4466 or return $e->die_event;
4468 # You can't update somebody else's search
4469 return OpenILS::Event->new('BAD_PARAMS')
4470 unless $o_search->owner == $e->requestor->id;
4473 $e->update_actor_usr_saved_search( $search )
4474 or return $e->die_event;
4478 } elsif( $search->isdeleted ) { # Delete
4480 # Get the old version, to check ownership
4481 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4482 or return $e->die_event;
4484 # You can't delete somebody else's search
4485 return OpenILS::Event->new('BAD_PARAMS')
4486 unless $o_search->owner == $e->requestor->id;
4489 $e->delete_actor_usr_saved_search( $o_search )
4490 or return $e->die_event;
4501 __PACKAGE__->register_method(
4502 method => "get_barcodes",
4503 api_name => "open-ils.actor.get_barcodes"
4507 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4508 my $e = new_editor(authtoken => $auth);
4509 return $e->event unless $e->checkauth;
4510 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4512 my $db_result = $e->json_query(
4514 'evergreen.get_barcodes',
4515 $org_id, $context, $barcode,
4519 if($context =~ /actor/) {
4520 my $filter_result = ();
4522 foreach my $result (@$db_result) {
4523 if($result->{type} eq 'actor') {
4524 if($e->requestor->id != $result->{id}) {
4525 $patron = $e->retrieve_actor_user($result->{id});
4527 push(@$filter_result, $e->event);
4530 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4531 push(@$filter_result, $result);
4534 push(@$filter_result, $e->event);
4538 push(@$filter_result, $result);
4542 push(@$filter_result, $result);
4545 return $filter_result;
4551 __PACKAGE__->register_method(
4552 method => 'address_alert_test',
4553 api_name => 'open-ils.actor.address_alert.test',
4555 desc => "Tests a set of address fields to determine if they match with an address_alert",
4557 {desc => 'Authentication token', type => 'string'},
4558 {desc => 'Org Unit', type => 'number'},
4559 {desc => 'Fields', type => 'hash'},
4561 return => {desc => 'List of matching address_alerts'}
4565 sub address_alert_test {
4566 my ($self, $client, $auth, $org_unit, $fields) = @_;
4567 return [] unless $fields and grep {$_} values %$fields;
4569 my $e = new_editor(authtoken => $auth);
4570 return $e->event unless $e->checkauth;
4571 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4572 $org_unit ||= $e->requestor->ws_ou;
4574 my $alerts = $e->json_query({
4576 'actor.address_alert_matches',
4584 $$fields{post_code},
4585 $$fields{mailing_address},
4586 $$fields{billing_address}
4590 # map the json_query hashes to real objects
4592 map {$e->retrieve_actor_address_alert($_)}
4593 (map {$_->{id}} @$alerts)
4597 __PACKAGE__->register_method(
4598 method => "mark_users_contact_invalid",
4599 api_name => "open-ils.actor.invalidate.email",
4601 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",
4603 {desc => "Authentication token", type => "string"},
4604 {desc => "Patron ID", type => "number"},
4605 {desc => "Additional note text (optional)", type => "string"},
4606 {desc => "penalty org unit ID (optional)", type => "number"}
4608 return => {desc => "Event describing success or failure", type => "object"}
4612 __PACKAGE__->register_method(
4613 method => "mark_users_contact_invalid",
4614 api_name => "open-ils.actor.invalidate.day_phone",
4616 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",
4618 {desc => "Authentication token", type => "string"},
4619 {desc => "Patron ID", type => "number"},
4620 {desc => "Additional note text (optional)", type => "string"},
4621 {desc => "penalty org unit ID (optional)", type => "number"}
4623 return => {desc => "Event describing success or failure", type => "object"}
4627 __PACKAGE__->register_method(
4628 method => "mark_users_contact_invalid",
4629 api_name => "open-ils.actor.invalidate.evening_phone",
4631 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",
4633 {desc => "Authentication token", type => "string"},
4634 {desc => "Patron ID", type => "number"},
4635 {desc => "Additional note text (optional)", type => "string"},
4636 {desc => "penalty org unit ID (optional)", type => "number"}
4638 return => {desc => "Event describing success or failure", type => "object"}
4642 __PACKAGE__->register_method(
4643 method => "mark_users_contact_invalid",
4644 api_name => "open-ils.actor.invalidate.other_phone",
4646 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",
4648 {desc => "Authentication token", type => "string"},
4649 {desc => "Patron ID", type => "number"},
4650 {desc => "Additional note text (optional)", type => "string"},
4651 {desc => "penalty org unit ID (optional, default to top of org tree)",
4654 return => {desc => "Event describing success or failure", type => "object"}
4658 sub mark_users_contact_invalid {
4659 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4661 # This method invalidates an email address or a phone_number which
4662 # removes the bad email address or phone number, copying its contents
4663 # to a patron note, and institutes a standing penalty for "bad email"
4664 # or "bad phone number" which is cleared when the user is saved or
4665 # optionally only when the user is saved with an email address or
4666 # phone number (or staff manually delete the penalty).
4668 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4670 my $e = new_editor(authtoken => $auth, xact => 1);
4671 return $e->die_event unless $e->checkauth;
4673 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4674 $e, $contact_type, {usr => $patron_id},
4675 $addl_note, $penalty_ou, $e->requestor->id
4679 # Putting the following method in open-ils.actor is a bad fit, except in that
4680 # it serves an interface that lives under 'actor' in the templates directory,
4681 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4683 __PACKAGE__->register_method(
4684 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4685 method => "get_all_at_reactors_in_use",
4690 { name => 'authtoken', type => 'string' }
4693 desc => 'list of reactor names', type => 'array'
4698 sub get_all_at_reactors_in_use {
4699 my ($self, $conn, $auth) = @_;
4701 my $e = new_editor(authtoken => $auth);
4702 $e->checkauth or return $e->die_event;
4703 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4705 my $reactors = $e->json_query({
4707 atevdef => [{column => "reactor", transform => "distinct"}]
4709 from => {atevdef => {}}
4712 return $e->die_event unless ref $reactors eq "ARRAY";
4715 return [ map { $_->{reactor} } @$reactors ];
4718 __PACKAGE__->register_method(
4719 method => "filter_group_entry_crud",
4720 api_name => "open-ils.actor.filter_group_entry.crud",
4723 Provides CRUD access to filter group entry objects. These are not full accessible
4724 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4725 are not accessible via PCRUD (because they have no fields against which to link perms)
4728 {desc => "Authentication token", type => "string"},
4729 {desc => "Entry ID / Entry Object", type => "number"},
4730 {desc => "Additional note text (optional)", type => "string"},
4731 {desc => "penalty org unit ID (optional, default to top of org tree)",
4735 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4741 sub filter_group_entry_crud {
4742 my ($self, $conn, $auth, $arg) = @_;
4744 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4745 my $e = new_editor(authtoken => $auth, xact => 1);
4746 return $e->die_event unless $e->checkauth;
4752 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4753 or return $e->die_event;
4755 return $e->die_event unless $e->allowed(
4756 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4758 my $query = $arg->query;
4759 $query = $e->create_actor_search_query($query) or return $e->die_event;
4760 $arg->query($query->id);
4761 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4762 $entry->query($query);
4767 } elsif ($arg->ischanged) {
4769 my $entry = $e->retrieve_actor_search_filter_group_entry([
4772 flesh_fields => {asfge => ['grp']}
4774 ]) or return $e->die_event;
4776 return $e->die_event unless $e->allowed(
4777 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4779 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4780 $arg->query($arg->query->id);
4781 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4782 $arg->query($query);
4787 } elsif ($arg->isdeleted) {
4789 my $entry = $e->retrieve_actor_search_filter_group_entry([
4792 flesh_fields => {asfge => ['grp', 'query']}
4794 ]) or return $e->die_event;
4796 return $e->die_event unless $e->allowed(
4797 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4799 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4800 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4813 my $entry = $e->retrieve_actor_search_filter_group_entry([
4816 flesh_fields => {asfge => ['grp', 'query']}
4818 ]) or return $e->die_event;
4820 return $e->die_event unless $e->allowed(
4821 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4822 $entry->grp->owner);
4825 $entry->grp($entry->grp->id); # for consistency