1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use OpenILS::Utils::BadContact;
38 use List::Util qw/max reduce/;
40 use UUID::Tiny qw/:std/;
43 OpenILS::Application::Actor::Container->initialize();
44 OpenILS::Application::Actor::UserGroups->initialize();
45 OpenILS::Application::Actor::ClosedDates->initialize();
48 my $apputils = "OpenILS::Application::AppUtils";
51 sub _d { warn "Patron:\n" . Dumper(shift()); }
54 my $set_user_settings;
58 #__PACKAGE__->register_method(
59 # method => "allowed_test",
60 # api_name => "open-ils.actor.allowed_test",
63 # my($self, $conn, $auth, $orgid, $permcode) = @_;
64 # my $e = new_editor(authtoken => $auth);
65 # return $e->die_event unless $e->checkauth;
69 # permcode => $permcode,
70 # result => $e->allowed($permcode, $orgid)
74 __PACKAGE__->register_method(
75 method => "update_user_setting",
76 api_name => "open-ils.actor.patron.settings.update",
78 sub update_user_setting {
79 my($self, $conn, $auth, $user_id, $settings) = @_;
80 my $e = new_editor(xact => 1, authtoken => $auth);
81 return $e->die_event unless $e->checkauth;
83 $user_id = $e->requestor->id unless defined $user_id;
85 unless($e->requestor->id == $user_id) {
86 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
87 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
90 for my $name (keys %$settings) {
91 my $val = $$settings{$name};
92 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
95 $val = OpenSRF::Utils::JSON->perl2JSON($val);
98 $e->update_actor_user_setting($set) or return $e->die_event;
100 $set = Fieldmapper::actor::user_setting->new;
104 $e->create_actor_user_setting($set) or return $e->die_event;
107 $e->delete_actor_user_setting($set) or return $e->die_event;
116 __PACKAGE__->register_method(
117 method => "set_ou_settings",
118 api_name => "open-ils.actor.org_unit.settings.update",
120 desc => "Updates the value for a given org unit setting. The permission to update " .
121 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
122 "permission specified in the update_perm column of the config.org_unit_setting_type " .
123 "table's row corresponding to the setting being changed." ,
125 {desc => 'Authentication token', type => 'string'},
126 {desc => 'Org unit ID', type => 'number'},
127 {desc => 'Hash of setting name-value pairs', type => 'object'}
129 return => {desc => '1 on success, Event on error'}
133 sub set_ou_settings {
134 my( $self, $client, $auth, $org_id, $settings ) = @_;
136 my $e = new_editor(authtoken => $auth, xact => 1);
137 return $e->die_event unless $e->checkauth;
139 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
141 for my $name (keys %$settings) {
142 my $val = $$settings{$name};
144 my $type = $e->retrieve_config_org_unit_setting_type([
146 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
147 ]) or return $e->die_event;
148 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
150 # If there is no relevant permission, the default assumption will
151 # be, "no, the caller cannot change that value."
152 return $e->die_event unless ($all_allowed ||
153 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
156 $val = OpenSRF::Utils::JSON->perl2JSON($val);
159 $e->update_actor_org_unit_setting($set) or return $e->die_event;
161 $set = Fieldmapper::actor::org_unit_setting->new;
162 $set->org_unit($org_id);
165 $e->create_actor_org_unit_setting($set) or return $e->die_event;
168 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
176 __PACKAGE__->register_method(
177 method => "user_settings",
179 api_name => "open-ils.actor.patron.settings.retrieve",
182 my( $self, $client, $auth, $user_id, $setting ) = @_;
184 my $e = new_editor(authtoken => $auth);
185 return $e->event unless $e->checkauth;
186 $user_id = $e->requestor->id unless defined $user_id;
188 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
189 if($e->requestor->id != $user_id) {
190 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
194 my($e, $user_id, $setting) = @_;
195 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
196 return undef unless $val; # XXX this should really return undef, but needs testing
197 return OpenSRF::Utils::JSON->JSON2perl($val->value);
201 if(ref $setting eq 'ARRAY') {
203 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
206 return get_setting($e, $user_id, $setting);
209 my $s = $e->search_actor_user_setting({usr => $user_id});
210 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
215 __PACKAGE__->register_method(
216 method => "ranged_ou_settings",
217 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
219 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
220 "is implied for retrieving OU settings by the authenticated users' permissions.",
222 {desc => 'Authentication token', type => 'string'},
223 {desc => 'Org unit ID', type => 'number'},
225 return => {desc => 'A hashref of "ranged" settings, event on error'}
228 sub ranged_ou_settings {
229 my( $self, $client, $auth, $org_id ) = @_;
231 my $e = new_editor(authtoken => $auth);
232 return $e->event unless $e->checkauth;
235 my $org_list = $U->get_org_ancestors($org_id);
236 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
237 $org_list = [ reverse @$org_list ];
239 # start at the context org and capture the setting value
240 # without clobbering settings we've already captured
241 for my $this_org_id (@$org_list) {
243 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
245 for my $set (@sets) {
246 my $type = $e->retrieve_config_org_unit_setting_type([
248 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
251 # If there is no relevant permission, the default assumption will
252 # be, "yes, the caller can have that value."
253 if ($type && $type->view_perm) {
254 next if not $e->allowed($type->view_perm->code, $org_id);
257 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
258 unless defined $ranged_settings{$set->name};
262 return \%ranged_settings;
267 __PACKAGE__->register_method(
268 api_name => 'open-ils.actor.ou_setting.ancestor_default',
269 method => 'ou_ancestor_setting',
271 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
272 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
273 'user has permission to view that setting, if there is a permission associated with the setting.' ,
275 { desc => 'Org unit ID', type => 'number' },
276 { desc => 'setting name', type => 'string' },
277 { desc => 'authtoken (optional)', type => 'string' }
279 return => {desc => 'A value for the org unit setting, or undef'}
283 # ------------------------------------------------------------------
284 # Attempts to find the org setting value for a given org. if not
285 # found at the requested org, searches up the org tree until it
286 # finds a parent that has the requested setting.
287 # when found, returns { org => $id, value => $value }
288 # otherwise, returns NULL
289 # ------------------------------------------------------------------
290 sub ou_ancestor_setting {
291 my( $self, $client, $orgid, $name, $auth ) = @_;
292 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
295 __PACKAGE__->register_method(
296 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
297 method => 'ou_ancestor_setting_batch',
299 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
300 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
301 'user has permission to view that setting, if there is a permission associated with the setting.' ,
303 { desc => 'Org unit ID', type => 'number' },
304 { desc => 'setting name list', type => 'array' },
305 { desc => 'authtoken (optional)', type => 'string' }
307 return => {desc => 'A hash with name => value pairs for the org unit settings'}
310 sub ou_ancestor_setting_batch {
311 my( $self, $client, $orgid, $name_list, $auth ) = @_;
313 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
319 __PACKAGE__->register_method(
320 method => "update_patron",
321 api_name => "open-ils.actor.patron.update",
324 Update an existing user, or create a new one. Related objects,
325 like cards, addresses, survey responses, and stat cats,
326 can be updated by attaching them to the user object in their
327 respective fields. For examples, the billing address object
328 may be inserted into the 'billing_address' field, etc. For each
329 attached object, indicate if the object should be created,
330 updated, or deleted using the built-in 'isnew', 'ischanged',
331 and 'isdeleted' fields on the object.
334 { desc => 'Authentication token', type => 'string' },
335 { desc => 'Patron data object', type => 'object' }
337 return => {desc => 'A fleshed user object, event on error'}
342 my( $self, $client, $user_session, $patron ) = @_;
344 my $session = $apputils->start_db_session();
346 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
348 my( $user_obj, $evt ) = $U->checkses($user_session);
351 $evt = check_group_perm($session, $user_obj, $patron);
354 $apputils->set_audit_info($session, $user_session, $user_obj->id, $user_obj->wsid);
356 # $new_patron is the patron in progress. $patron is the original patron
357 # passed in with the method. new_patron will change as the components
358 # of patron are added/updated.
362 # unflesh the real items on the patron
363 $patron->card( $patron->card->id ) if(ref($patron->card));
364 $patron->billing_address( $patron->billing_address->id )
365 if(ref($patron->billing_address));
366 $patron->mailing_address( $patron->mailing_address->id )
367 if(ref($patron->mailing_address));
369 # create/update the patron first so we can use his id
371 # $patron is the obj from the client (new data) and $new_patron is the
372 # patron object properly built for db insertion, so we need a third variable
373 # if we want to represent the old patron.
376 my $barred_hook = '';
378 if($patron->isnew()) {
379 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
381 if($U->is_true($patron->barred)) {
382 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
386 $new_patron = $patron;
388 # Did auth checking above already.
390 $old_patron = $e->retrieve_actor_user($patron->id) or
391 return $e->die_event;
393 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
394 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
397 $barred_hook = $U->is_true($new_patron->barred) ?
398 'au.barred' : 'au.unbarred';
402 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
405 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
408 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
411 # re-update the patron if anything has happened to him during this process
412 if($new_patron->ischanged()) {
413 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
417 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
420 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
423 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
426 $apputils->commit_db_session($session);
428 $evt = apply_invalid_addr_penalty($patron);
431 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
433 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
435 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
437 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
438 $new_patron, $new_patron->home_ou) if $barred_hook;
441 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
444 sub apply_invalid_addr_penalty {
446 my $e = new_editor(xact => 1);
448 # grab the invalid address penalty if set
449 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
451 my ($addr_penalty) = grep
452 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
454 # do we enforce invalid address penalty
455 my $enforce = $U->ou_ancestor_setting_value(
456 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
458 my $addrs = $e->search_actor_user_address(
459 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
460 my $addr_count = scalar(@$addrs);
462 if($addr_count == 0 and $addr_penalty) {
464 # regardless of any settings, remove the penalty when the user has no invalid addresses
465 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
468 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
470 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
471 my $depth = $ptype->org_depth;
472 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
473 $ctx_org = $patron->home_ou unless defined $ctx_org;
475 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
476 $penalty->usr($patron->id);
477 $penalty->org_unit($ctx_org);
478 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
480 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
499 "standing_penalties",
507 push @$fields, "home_ou" if $home_ou;
508 return new_flesh_user($id, $fields, $e );
516 # clone and clear stuff that would break the database
520 my $new_patron = $patron->clone;
522 $new_patron->clear_billing_address();
523 $new_patron->clear_mailing_address();
524 $new_patron->clear_addresses();
525 $new_patron->clear_card();
526 $new_patron->clear_cards();
527 $new_patron->clear_id();
528 $new_patron->clear_isnew();
529 $new_patron->clear_ischanged();
530 $new_patron->clear_isdeleted();
531 $new_patron->clear_stat_cat_entries();
532 $new_patron->clear_permissions();
533 $new_patron->clear_standing_penalties();
543 my $user_obj = shift;
545 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
546 return (undef, $evt) if $evt;
548 my $ex = $session->request(
549 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
551 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
554 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
556 my $id = $session->request(
557 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
558 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
560 $logger->info("Successfully created new user [$id] in DB");
562 return ( $session->request(
563 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
567 sub check_group_perm {
568 my( $session, $requestor, $patron ) = @_;
571 # first let's see if the requestor has
572 # priveleges to update this user in any way
573 if( ! $patron->isnew ) {
574 my $p = $session->request(
575 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
577 # If we are the requestor (trying to update our own account)
578 # and we are not trying to change our profile, we're good
579 if( $p->id == $requestor->id and
580 $p->profile == $patron->profile ) {
585 $evt = group_perm_failed($session, $requestor, $p);
589 # They are allowed to edit this patron.. can they put the
590 # patron into the group requested?
591 $evt = group_perm_failed($session, $requestor, $patron);
597 sub group_perm_failed {
598 my( $session, $requestor, $patron ) = @_;
602 my $grpid = $patron->profile;
606 $logger->debug("user update looking for group perm for group $grpid");
607 $grp = $session->request(
608 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
609 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
611 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
613 $logger->info("user update checking perm $perm on user ".
614 $requestor->id." for update/create on user username=".$patron->usrname);
616 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
624 my( $session, $patron, $user_obj, $noperm) = @_;
626 $logger->info("Updating patron ".$patron->id." in DB");
631 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
632 return (undef, $evt) if $evt;
635 # update the password by itself to avoid the password protection magic
636 if( $patron->passwd ) {
637 my $s = $session->request(
638 'open-ils.storage.direct.actor.user.remote_update',
639 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
640 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
641 $patron->clear_passwd;
644 if(!$patron->ident_type) {
645 $patron->clear_ident_type;
646 $patron->clear_ident_value;
649 $evt = verify_last_xact($session, $patron);
650 return (undef, $evt) if $evt;
652 my $stat = $session->request(
653 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
654 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
659 sub verify_last_xact {
660 my( $session, $patron ) = @_;
661 return undef unless $patron->id and $patron->id > 0;
662 my $p = $session->request(
663 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
664 my $xact = $p->last_xact_id;
665 return undef unless $xact;
666 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
667 return OpenILS::Event->new('XACT_COLLISION')
668 if $xact ne $patron->last_xact_id;
673 sub _check_dup_ident {
674 my( $session, $patron ) = @_;
676 return undef unless $patron->ident_value;
679 ident_type => $patron->ident_type,
680 ident_value => $patron->ident_value,
683 $logger->debug("patron update searching for dup ident values: " .
684 $patron->ident_type . ':' . $patron->ident_value);
686 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
688 my $dups = $session->request(
689 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
692 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
699 sub _add_update_addresses {
703 my $new_patron = shift;
707 my $current_id; # id of the address before creation
709 my $addresses = $patron->addresses();
711 for my $address (@$addresses) {
713 next unless ref $address;
714 $current_id = $address->id();
716 if( $patron->billing_address() and
717 $patron->billing_address() == $current_id ) {
718 $logger->info("setting billing addr to $current_id");
719 $new_patron->billing_address($address->id());
720 $new_patron->ischanged(1);
723 if( $patron->mailing_address() and
724 $patron->mailing_address() == $current_id ) {
725 $new_patron->mailing_address($address->id());
726 $logger->info("setting mailing addr to $current_id");
727 $new_patron->ischanged(1);
731 if($address->isnew()) {
733 $address->usr($new_patron->id());
735 ($address, $evt) = _add_address($session,$address);
736 return (undef, $evt) if $evt;
738 # we need to get the new id
739 if( $patron->billing_address() and
740 $patron->billing_address() == $current_id ) {
741 $new_patron->billing_address($address->id());
742 $logger->info("setting billing addr to $current_id");
743 $new_patron->ischanged(1);
746 if( $patron->mailing_address() and
747 $patron->mailing_address() == $current_id ) {
748 $new_patron->mailing_address($address->id());
749 $logger->info("setting mailing addr to $current_id");
750 $new_patron->ischanged(1);
753 } elsif($address->ischanged() ) {
755 ($address, $evt) = _update_address($session, $address);
756 return (undef, $evt) if $evt;
758 } elsif($address->isdeleted() ) {
760 if( $address->id() == $new_patron->mailing_address() ) {
761 $new_patron->clear_mailing_address();
762 ($new_patron, $evt) = _update_patron($session, $new_patron);
763 return (undef, $evt) if $evt;
766 if( $address->id() == $new_patron->billing_address() ) {
767 $new_patron->clear_billing_address();
768 ($new_patron, $evt) = _update_patron($session, $new_patron);
769 return (undef, $evt) if $evt;
772 $evt = _delete_address($session, $address);
773 return (undef, $evt) if $evt;
777 return ( $new_patron, undef );
781 # adds an address to the db and returns the address with new id
783 my($session, $address) = @_;
784 $address->clear_id();
786 $logger->info("Creating new address at street ".$address->street1);
788 # put the address into the database
789 my $id = $session->request(
790 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
791 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
794 return ($address, undef);
798 sub _update_address {
799 my( $session, $address ) = @_;
801 $logger->info("Updating address ".$address->id." in the DB");
803 my $stat = $session->request(
804 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
806 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
807 return ($address, undef);
812 sub _add_update_cards {
816 my $new_patron = shift;
820 my $virtual_id; #id of the card before creation
822 my $cards = $patron->cards();
823 for my $card (@$cards) {
825 $card->usr($new_patron->id());
827 if(ref($card) and $card->isnew()) {
829 $virtual_id = $card->id();
830 ( $card, $evt ) = _add_card($session,$card);
831 return (undef, $evt) if $evt;
833 #if(ref($patron->card)) { $patron->card($patron->card->id); }
834 if($patron->card() == $virtual_id) {
835 $new_patron->card($card->id());
836 $new_patron->ischanged(1);
839 } elsif( ref($card) and $card->ischanged() ) {
840 $evt = _update_card($session, $card);
841 return (undef, $evt) if $evt;
845 return ( $new_patron, undef );
849 # adds an card to the db and returns the card with new id
851 my( $session, $card ) = @_;
854 $logger->info("Adding new patron card ".$card->barcode);
856 my $id = $session->request(
857 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
858 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
859 $logger->info("Successfully created patron card $id");
862 return ( $card, undef );
866 # returns event on error. returns undef otherwise
868 my( $session, $card ) = @_;
869 $logger->info("Updating patron card ".$card->id);
871 my $stat = $session->request(
872 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
873 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
880 # returns event on error. returns undef otherwise
881 sub _delete_address {
882 my( $session, $address ) = @_;
884 $logger->info("Deleting address ".$address->id." from DB");
886 my $stat = $session->request(
887 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
889 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
895 sub _add_survey_responses {
896 my ($session, $patron, $new_patron) = @_;
898 $logger->info( "Updating survey responses for patron ".$new_patron->id );
900 my $responses = $patron->survey_responses;
904 $_->usr($new_patron->id) for (@$responses);
906 my $evt = $U->simplereq( "open-ils.circ",
907 "open-ils.circ.survey.submit.user_id", $responses );
909 return (undef, $evt) if defined($U->event_code($evt));
913 return ( $new_patron, undef );
916 sub _clear_badcontact_penalties {
917 my ($session, $old_patron, $new_patron, $user_obj) = @_;
919 return ($new_patron, undef) unless $old_patron;
921 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
922 my $e = new_editor(xact => 1);
924 # This ignores whether the caller of update_patron has any permission
925 # to remove penalties, but these penalties no longer make sense
926 # if an email address field (for example) is changed (and the caller must
927 # have perms to do *that*) so there's no reason not to clear the penalties.
929 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
931 "+csp" => {"name" => [values(%$PNM)]},
932 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
934 "join" => {"csp" => {}},
936 "flesh_fields" => {"ausp" => ["standing_penalty"]}
938 ]) or return (undef, $e->die_event);
940 return ($new_patron, undef) unless @$bad_contact_penalties;
942 my @penalties_to_clear;
943 my ($field, $penalty_name);
945 # For each field that might have an associated bad contact penalty,
946 # check for such penalties and add them to the to-clear list if that
948 while (($field, $penalty_name) = each(%$PNM)) {
949 if ($old_patron->$field ne $new_patron->$field) {
950 push @penalties_to_clear, grep {
951 $_->standing_penalty->name eq $penalty_name
952 } @$bad_contact_penalties;
956 foreach (@penalties_to_clear) {
957 # Note that this "archives" penalties, in the terminology of the staff
958 # client, instead of just deleting them. This may assist reporting,
959 # or preserving old contact information when it is still potentially
961 $_->standing_penalty($_->standing_penalty->id); # deflesh
962 $_->stop_date('now');
963 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
967 return ($new_patron, undef);
971 sub _create_stat_maps {
973 my($session, $user_session, $patron, $new_patron) = @_;
975 my $maps = $patron->stat_cat_entries();
977 for my $map (@$maps) {
979 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
981 if ($map->isdeleted()) {
982 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
984 } elsif ($map->isnew()) {
985 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
990 $map->target_usr($new_patron->id);
993 $logger->info("Updating stat entry with method $method and map $map");
995 my $stat = $session->request($method, $map)->gather(1);
996 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1000 return ($new_patron, undef);
1003 sub _create_perm_maps {
1005 my($session, $user_session, $patron, $new_patron) = @_;
1007 my $maps = $patron->permissions;
1009 for my $map (@$maps) {
1011 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1012 if ($map->isdeleted()) {
1013 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1014 } elsif ($map->isnew()) {
1015 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1020 $map->usr($new_patron->id);
1022 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1023 $logger->info( "Updating permissions with method $method and map $map" );
1025 my $stat = $session->request($method, $map)->gather(1);
1026 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1030 return ($new_patron, undef);
1034 __PACKAGE__->register_method(
1035 method => "set_user_work_ous",
1036 api_name => "open-ils.actor.user.work_ous.update",
1039 sub set_user_work_ous {
1045 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1046 return $evt if $evt;
1048 my $session = $apputils->start_db_session();
1049 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1051 for my $map (@$maps) {
1053 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1054 if ($map->isdeleted()) {
1055 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1056 } elsif ($map->isnew()) {
1057 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1061 #warn( "Updating permissions with method $method and session $ses and map $map" );
1062 $logger->info( "Updating work_ou map with method $method and map $map" );
1064 my $stat = $session->request($method, $map)->gather(1);
1065 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1069 $apputils->commit_db_session($session);
1071 return scalar(@$maps);
1075 __PACKAGE__->register_method(
1076 method => "set_user_perms",
1077 api_name => "open-ils.actor.user.permissions.update",
1080 sub set_user_perms {
1086 my $session = $apputils->start_db_session();
1088 my( $user_obj, $evt ) = $U->checkses($ses);
1089 return $evt if $evt;
1090 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1092 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1095 $all = 1 if ($U->is_true($user_obj->super_user()));
1096 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1098 for my $map (@$maps) {
1100 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1101 if ($map->isdeleted()) {
1102 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1103 } elsif ($map->isnew()) {
1104 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1108 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1109 #warn( "Updating permissions with method $method and session $ses and map $map" );
1110 $logger->info( "Updating permissions with method $method and map $map" );
1112 my $stat = $session->request($method, $map)->gather(1);
1113 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1117 $apputils->commit_db_session($session);
1119 return scalar(@$maps);
1123 __PACKAGE__->register_method(
1124 method => "user_retrieve_by_barcode",
1126 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1128 sub user_retrieve_by_barcode {
1129 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1131 my $e = new_editor(authtoken => $auth);
1132 return $e->event unless $e->checkauth;
1134 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1135 or return $e->event;
1137 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1138 return $e->event unless $e->allowed(
1139 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1146 __PACKAGE__->register_method(
1147 method => "get_user_by_id",
1149 api_name => "open-ils.actor.user.retrieve",
1152 sub get_user_by_id {
1153 my ($self, $client, $auth, $id) = @_;
1154 my $e = new_editor(authtoken=>$auth);
1155 return $e->event unless $e->checkauth;
1156 my $user = $e->retrieve_actor_user($id) or return $e->event;
1157 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1162 __PACKAGE__->register_method(
1163 method => "get_org_types",
1164 api_name => "open-ils.actor.org_types.retrieve",
1167 return $U->get_org_types();
1171 __PACKAGE__->register_method(
1172 method => "get_user_ident_types",
1173 api_name => "open-ils.actor.user.ident_types.retrieve",
1176 sub get_user_ident_types {
1177 return $ident_types if $ident_types;
1178 return $ident_types =
1179 new_editor()->retrieve_all_config_identification_type();
1183 __PACKAGE__->register_method(
1184 method => "get_org_unit",
1185 api_name => "open-ils.actor.org_unit.retrieve",
1189 my( $self, $client, $user_session, $org_id ) = @_;
1190 my $e = new_editor(authtoken => $user_session);
1192 return $e->event unless $e->checkauth;
1193 $org_id = $e->requestor->ws_ou;
1195 my $o = $e->retrieve_actor_org_unit($org_id)
1196 or return $e->event;
1200 __PACKAGE__->register_method(
1201 method => "search_org_unit",
1202 api_name => "open-ils.actor.org_unit_list.search",
1205 sub search_org_unit {
1207 my( $self, $client, $field, $value ) = @_;
1209 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1211 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1212 { $field => $value } );
1218 # build the org tree
1220 __PACKAGE__->register_method(
1221 method => "get_org_tree",
1222 api_name => "open-ils.actor.org_tree.retrieve",
1224 note => "Returns the entire org tree structure",
1230 return $U->get_org_tree($client->session->session_locale);
1234 __PACKAGE__->register_method(
1235 method => "get_org_descendants",
1236 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1239 # depth is optional. org_unit is the id
1240 sub get_org_descendants {
1241 my( $self, $client, $org_unit, $depth ) = @_;
1243 if(ref $org_unit eq 'ARRAY') {
1246 for my $i (0..scalar(@$org_unit)-1) {
1247 my $list = $U->simple_scalar_request(
1249 "open-ils.storage.actor.org_unit.descendants.atomic",
1250 $org_unit->[$i], $depth->[$i] );
1251 push(@trees, $U->build_org_tree($list));
1256 my $orglist = $apputils->simple_scalar_request(
1258 "open-ils.storage.actor.org_unit.descendants.atomic",
1259 $org_unit, $depth );
1260 return $U->build_org_tree($orglist);
1265 __PACKAGE__->register_method(
1266 method => "get_org_ancestors",
1267 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1270 # depth is optional. org_unit is the id
1271 sub get_org_ancestors {
1272 my( $self, $client, $org_unit, $depth ) = @_;
1273 my $orglist = $apputils->simple_scalar_request(
1275 "open-ils.storage.actor.org_unit.ancestors.atomic",
1276 $org_unit, $depth );
1277 return $U->build_org_tree($orglist);
1281 __PACKAGE__->register_method(
1282 method => "get_standings",
1283 api_name => "open-ils.actor.standings.retrieve"
1288 return $user_standings if $user_standings;
1289 return $user_standings =
1290 $apputils->simple_scalar_request(
1292 "open-ils.cstore.direct.config.standing.search.atomic",
1293 { id => { "!=" => undef } }
1298 __PACKAGE__->register_method(
1299 method => "get_my_org_path",
1300 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1303 sub get_my_org_path {
1304 my( $self, $client, $auth, $org_id ) = @_;
1305 my $e = new_editor(authtoken=>$auth);
1306 return $e->event unless $e->checkauth;
1307 $org_id = $e->requestor->ws_ou unless defined $org_id;
1309 return $apputils->simple_scalar_request(
1311 "open-ils.storage.actor.org_unit.full_path.atomic",
1316 __PACKAGE__->register_method(
1317 method => "patron_adv_search",
1318 api_name => "open-ils.actor.patron.search.advanced"
1320 sub patron_adv_search {
1321 my( $self, $client, $auth, $search_hash,
1322 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1324 my $e = new_editor(authtoken=>$auth);
1325 return $e->event unless $e->checkauth;
1326 return $e->event unless $e->allowed('VIEW_USER');
1328 # depth boundary outside of which patrons must opt-in, default to 0
1329 my $opt_boundary = 0;
1330 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1332 #Only set search_ou if it is undef. Set search_ou to Consortium (1) or the workstation org unit depending on Library Setting
1333 $search_ou = ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'opac.duplicate_patron_check_use_consortium') == 1) ? 1 : $e->requestor->ws_ou if $search_ou == undef;
1335 return $U->storagereq(
1336 "open-ils.storage.actor.user.crazy_search", $search_hash,
1337 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1341 __PACKAGE__->register_method(
1342 method => "update_passwd",
1343 api_name => "open-ils.actor.user.password.update",
1345 desc => "Update the operator's password",
1347 { desc => 'Authentication token', type => 'string' },
1348 { desc => 'New password', type => 'string' },
1349 { desc => 'Current password', type => 'string' }
1351 return => {desc => '1 on success, Event on error or incorrect current password'}
1355 __PACKAGE__->register_method(
1356 method => "update_passwd",
1357 api_name => "open-ils.actor.user.username.update",
1359 desc => "Update the operator's username",
1361 { desc => 'Authentication token', type => 'string' },
1362 { desc => 'New username', type => 'string' },
1363 { desc => 'Current password', type => 'string' }
1365 return => {desc => '1 on success, Event on error or incorrect current password'}
1369 __PACKAGE__->register_method(
1370 method => "update_passwd",
1371 api_name => "open-ils.actor.user.email.update",
1373 desc => "Update the operator's email address",
1375 { desc => 'Authentication token', type => 'string' },
1376 { desc => 'New email address', type => 'string' },
1377 { desc => 'Current password', type => 'string' }
1379 return => {desc => '1 on success, Event on error or incorrect current password'}
1384 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1385 my $e = new_editor(xact=>1, authtoken=>$auth);
1386 return $e->die_event unless $e->checkauth;
1388 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1389 or return $e->die_event;
1390 my $api = $self->api_name;
1392 # make sure the original password matches the in-database password
1393 if (md5_hex($orig_pw) ne $db_user->passwd) {
1395 return new OpenILS::Event('INCORRECT_PASSWORD');
1398 if( $api =~ /password/o ) {
1400 $db_user->passwd($new_val);
1404 # if we don't clear the password, the user will be updated with
1405 # a hashed version of the hashed version of their password
1406 $db_user->clear_passwd;
1408 if( $api =~ /username/o ) {
1410 # make sure no one else has this username
1411 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1414 return new OpenILS::Event('USERNAME_EXISTS');
1416 $db_user->usrname($new_val);
1418 } elsif( $api =~ /email/o ) {
1419 $db_user->email($new_val);
1423 $e->update_actor_user($db_user) or return $e->die_event;
1426 # update the cached user to pick up these changes
1427 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1433 __PACKAGE__->register_method(
1434 method => "check_user_perms",
1435 api_name => "open-ils.actor.user.perm.check",
1436 notes => <<" NOTES");
1437 Takes a login session, user id, an org id, and an array of perm type strings. For each
1438 perm type, if the user does *not* have the given permission it is added
1439 to a list which is returned from the method. If all permissions
1440 are allowed, an empty list is returned
1441 if the logged in user does not match 'user_id', then the logged in user must
1442 have VIEW_PERMISSION priveleges.
1445 sub check_user_perms {
1446 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1448 my( $staff, $evt ) = $apputils->checkses($login_session);
1449 return $evt if $evt;
1451 if($staff->id ne $user_id) {
1452 if( $evt = $apputils->check_perms(
1453 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1459 for my $perm (@$perm_types) {
1460 if($apputils->check_perms($user_id, $org_id, $perm)) {
1461 push @not_allowed, $perm;
1465 return \@not_allowed
1468 __PACKAGE__->register_method(
1469 method => "check_user_perms2",
1470 api_name => "open-ils.actor.user.perm.check.multi_org",
1472 Checks the permissions on a list of perms and orgs for a user
1473 @param authtoken The login session key
1474 @param user_id The id of the user to check
1475 @param orgs The array of org ids
1476 @param perms The array of permission names
1477 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1478 if the logged in user does not match 'user_id', then the logged in user must
1479 have VIEW_PERMISSION priveleges.
1482 sub check_user_perms2 {
1483 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1485 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1486 $authtoken, $user_id, 'VIEW_PERMISSION' );
1487 return $evt if $evt;
1490 for my $org (@$orgs) {
1491 for my $perm (@$perms) {
1492 if($apputils->check_perms($user_id, $org, $perm)) {
1493 push @not_allowed, [ $org, $perm ];
1498 return \@not_allowed
1502 __PACKAGE__->register_method(
1503 method => 'check_user_perms3',
1504 api_name => 'open-ils.actor.user.perm.highest_org',
1506 Returns the highest org unit id at which a user has a given permission
1507 If the requestor does not match the target user, the requestor must have
1508 'VIEW_PERMISSION' rights at the home org unit of the target user
1509 @param authtoken The login session key
1510 @param userid The id of the user in question
1511 @param perm The permission to check
1512 @return The org unit highest in the org tree within which the user has
1513 the requested permission
1516 sub check_user_perms3 {
1517 my($self, $client, $authtoken, $user_id, $perm) = @_;
1518 my $e = new_editor(authtoken=>$authtoken);
1519 return $e->event unless $e->checkauth;
1521 my $tree = $U->get_org_tree();
1523 unless($e->requestor->id == $user_id) {
1524 my $user = $e->retrieve_actor_user($user_id)
1525 or return $e->event;
1526 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1527 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1530 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1533 __PACKAGE__->register_method(
1534 method => 'user_has_work_perm_at',
1535 api_name => 'open-ils.actor.user.has_work_perm_at',
1539 Returns a set of org unit IDs which represent the highest orgs in
1540 the org tree where the user has the requested permission. The
1541 purpose of this method is to return the smallest set of org units
1542 which represent the full expanse of the user's ability to perform
1543 the requested action. The user whose perms this method should
1544 check is implied by the authtoken. /,
1546 {desc => 'authtoken', type => 'string'},
1547 {desc => 'permission name', type => 'string'},
1548 {desc => q/user id, optional. If present, check perms for
1549 this user instead of the logged in user/, type => 'number'},
1551 return => {desc => 'An array of org IDs'}
1555 sub user_has_work_perm_at {
1556 my($self, $conn, $auth, $perm, $user_id) = @_;
1557 my $e = new_editor(authtoken=>$auth);
1558 return $e->event unless $e->checkauth;
1559 if(defined $user_id) {
1560 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1561 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1563 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1566 __PACKAGE__->register_method(
1567 method => 'user_has_work_perm_at_batch',
1568 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1572 sub user_has_work_perm_at_batch {
1573 my($self, $conn, $auth, $perms, $user_id) = @_;
1574 my $e = new_editor(authtoken=>$auth);
1575 return $e->event unless $e->checkauth;
1576 if(defined $user_id) {
1577 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1578 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1581 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1587 __PACKAGE__->register_method(
1588 method => 'check_user_perms4',
1589 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1591 Returns the highest org unit id at which a user has a given permission
1592 If the requestor does not match the target user, the requestor must have
1593 'VIEW_PERMISSION' rights at the home org unit of the target user
1594 @param authtoken The login session key
1595 @param userid The id of the user in question
1596 @param perms An array of perm names to check
1597 @return An array of orgId's representing the org unit
1598 highest in the org tree within which the user has the requested permission
1599 The arrah of orgId's has matches the order of the perms array
1602 sub check_user_perms4 {
1603 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1605 my( $staff, $target, $org, $evt );
1607 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1608 $authtoken, $userid, 'VIEW_PERMISSION' );
1609 return $evt if $evt;
1612 return [] unless ref($perms);
1613 my $tree = $U->get_org_tree();
1615 for my $p (@$perms) {
1616 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1622 __PACKAGE__->register_method(
1623 method => "user_fines_summary",
1624 api_name => "open-ils.actor.user.fines.summary",
1627 desc => 'Returns a short summary of the users total open fines, ' .
1628 'excluding voided fines Params are login_session, user_id' ,
1630 {desc => 'Authentication token', type => 'string'},
1631 {desc => 'User ID', type => 'string'} # number?
1634 desc => "a 'mous' object, event on error",
1639 sub user_fines_summary {
1640 my( $self, $client, $auth, $user_id ) = @_;
1642 my $e = new_editor(authtoken=>$auth);
1643 return $e->event unless $e->checkauth;
1645 if( $user_id ne $e->requestor->id ) {
1646 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1647 return $e->event unless
1648 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1651 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1655 __PACKAGE__->register_method(
1656 method => "user_opac_vitals",
1657 api_name => "open-ils.actor.user.opac.vital_stats",
1661 desc => 'Returns a short summary of the users vital stats, including ' .
1662 'identification information, accumulated balance, number of holds, ' .
1663 'and current open circulation stats' ,
1665 {desc => 'Authentication token', type => 'string'},
1666 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1669 desc => "An object with four properties: user, fines, checkouts and holds."
1674 sub user_opac_vitals {
1675 my( $self, $client, $auth, $user_id ) = @_;
1677 my $e = new_editor(authtoken=>$auth);
1678 return $e->event unless $e->checkauth;
1680 $user_id ||= $e->requestor->id;
1682 my $user = $e->retrieve_actor_user( $user_id );
1685 ->method_lookup('open-ils.actor.user.fines.summary')
1686 ->run($auth => $user_id);
1687 return $fines if (defined($U->event_code($fines)));
1690 $fines = new Fieldmapper::money::open_user_summary ();
1691 $fines->balance_owed(0.00);
1692 $fines->total_owed(0.00);
1693 $fines->total_paid(0.00);
1694 $fines->usr($user_id);
1698 ->method_lookup('open-ils.actor.user.hold_requests.count')
1699 ->run($auth => $user_id);
1700 return $holds if (defined($U->event_code($holds)));
1703 ->method_lookup('open-ils.actor.user.checked_out.count')
1704 ->run($auth => $user_id);
1705 return $out if (defined($U->event_code($out)));
1707 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1711 first_given_name => $user->first_given_name,
1712 second_given_name => $user->second_given_name,
1713 family_name => $user->family_name,
1714 alias => $user->alias,
1715 usrname => $user->usrname
1717 fines => $fines->to_bare_hash,
1724 ##### a small consolidation of related method registrations
1725 my $common_params = [
1726 { desc => 'Authentication token', type => 'string' },
1727 { desc => 'User ID', type => 'string' },
1728 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1729 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1732 'open-ils.actor.user.transactions' => '',
1733 'open-ils.actor.user.transactions.fleshed' => '',
1734 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1735 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1736 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1737 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1740 foreach (keys %methods) {
1742 method => "user_transactions",
1745 desc => 'For a given user, retrieve a list of '
1746 . (/\.fleshed/ ? 'fleshed ' : '')
1747 . 'transactions' . $methods{$_}
1748 . ' optionally limited to transactions of a given type.',
1749 params => $common_params,
1751 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1752 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1756 $args{authoritative} = 1;
1757 __PACKAGE__->register_method(%args);
1760 # Now for the counts
1762 'open-ils.actor.user.transactions.count' => '',
1763 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1764 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1767 foreach (keys %methods) {
1769 method => "user_transactions",
1772 desc => 'For a given user, retrieve a count of open '
1773 . 'transactions' . $methods{$_}
1774 . ' optionally limited to transactions of a given type.',
1775 params => $common_params,
1776 return => { desc => "Integer count of transactions, or event on error" }
1779 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1780 __PACKAGE__->register_method(%args);
1783 __PACKAGE__->register_method(
1784 method => "user_transactions",
1785 api_name => "open-ils.actor.user.transactions.have_balance.total",
1788 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1789 . ' optionally limited to transactions of a given type.',
1790 params => $common_params,
1791 return => { desc => "Decimal balance value, or event on error" }
1796 sub user_transactions {
1797 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1800 my $e = new_editor(authtoken => $auth);
1801 return $e->event unless $e->checkauth;
1803 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1805 return $e->event unless
1806 $e->requestor->id == $user_id or
1807 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1809 my $api = $self->api_name();
1811 my $filter = ($api =~ /have_balance/o) ?
1812 { 'balance_owed' => { '<>' => 0 } }:
1813 { 'total_owed' => { '>' => 0 } };
1815 my $method = 'open-ils.actor.user.transactions.history.still_open';
1816 $method = "$method.authoritative" if $api =~ /authoritative/;
1817 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1819 if($api =~ /total/o) {
1821 $total += $_->balance_owed for @$trans;
1825 ($api =~ /count/o ) and return scalar @$trans;
1826 ($api !~ /fleshed/o) and return $trans;
1829 for my $t (@$trans) {
1831 if( $t->xact_type ne 'circulation' ) {
1832 push @resp, {transaction => $t};
1836 my $circ_data = flesh_circ($e, $t->id);
1837 push @resp, {transaction => $t, %$circ_data};
1844 __PACKAGE__->register_method(
1845 method => "user_transaction_retrieve",
1846 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1849 notes => "Returns a fleshed transaction record"
1852 __PACKAGE__->register_method(
1853 method => "user_transaction_retrieve",
1854 api_name => "open-ils.actor.user.transaction.retrieve",
1857 notes => "Returns a transaction record"
1860 sub user_transaction_retrieve {
1861 my($self, $client, $auth, $bill_id) = @_;
1863 my $e = new_editor(authtoken => $auth);
1864 return $e->event unless $e->checkauth;
1866 my $trans = $e->retrieve_money_billable_transaction_summary(
1867 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1869 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1871 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1873 return $trans unless $self->api_name =~ /flesh/;
1874 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1876 my $circ_data = flesh_circ($e, $trans->id, 1);
1878 return {transaction => $trans, %$circ_data};
1883 my $circ_id = shift;
1884 my $flesh_copy = shift;
1886 my $circ = $e->retrieve_action_circulation([
1890 circ => ['target_copy'],
1891 acp => ['call_number'],
1898 my $copy = $circ->target_copy;
1900 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1901 $mods = new Fieldmapper::metabib::virtual_record;
1902 $mods->doc_id(OILS_PRECAT_RECORD);
1903 $mods->title($copy->dummy_title);
1904 $mods->author($copy->dummy_author);
1907 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1911 $circ->target_copy($circ->target_copy->id);
1912 $copy->call_number($copy->call_number->id);
1914 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1918 __PACKAGE__->register_method(
1919 method => "hold_request_count",
1920 api_name => "open-ils.actor.user.hold_requests.count",
1923 notes => 'Returns hold ready/total counts'
1926 sub hold_request_count {
1927 my( $self, $client, $authtoken, $user_id ) = @_;
1928 my $e = new_editor(authtoken => $authtoken);
1929 return $e->event unless $e->checkauth;
1931 $user_id = $e->requestor->id unless defined $user_id;
1933 if($e->requestor->id ne $user_id) {
1934 my $user = $e->retrieve_actor_user($user_id);
1935 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1938 my $holds = $e->json_query({
1939 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1943 fulfillment_time => {"=" => undef },
1944 cancel_time => undef,
1949 total => scalar(@$holds),
1952 $_->{current_shelf_lib} and # avoid undef warnings
1953 $_->{pickup_lib} eq $_->{current_shelf_lib}
1959 __PACKAGE__->register_method(
1960 method => "checked_out",
1961 api_name => "open-ils.actor.user.checked_out",
1965 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1966 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1967 . "(i.e., outstanding balance or some other pending action on the circ). "
1968 . "The .count method also includes a 'total' field which sums all open circs.",
1970 { desc => 'Authentication Token', type => 'string'},
1971 { desc => 'User ID', type => 'string'},
1974 desc => 'Returns event on error, or an object with ID lists, like: '
1975 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1980 __PACKAGE__->register_method(
1981 method => "checked_out",
1982 api_name => "open-ils.actor.user.checked_out.count",
1985 signature => q/@see open-ils.actor.user.checked_out/
1989 my( $self, $conn, $auth, $userid ) = @_;
1991 my $e = new_editor(authtoken=>$auth);
1992 return $e->event unless $e->checkauth;
1994 if( $userid ne $e->requestor->id ) {
1995 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1996 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1998 # see if there is a friend link allowing circ.view perms
1999 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2000 $e, $userid, $e->requestor->id, 'circ.view');
2001 return $e->event unless $allowed;
2005 my $count = $self->api_name =~ /count/;
2006 return _checked_out( $count, $e, $userid );
2010 my( $iscount, $e, $userid ) = @_;
2016 claims_returned => [],
2019 my $meth = 'retrieve_action_open_circ_';
2027 claims_returned => 0,
2034 my $data = $e->$meth($userid);
2038 $result{$_} += $data->$_() for (keys %result);
2039 $result{total} += $data->$_() for (keys %result);
2041 for my $k (keys %result) {
2042 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2052 __PACKAGE__->register_method(
2053 method => "checked_in_with_fines",
2054 api_name => "open-ils.actor.user.checked_in_with_fines",
2057 signature => q/@see open-ils.actor.user.checked_out/
2060 sub checked_in_with_fines {
2061 my( $self, $conn, $auth, $userid ) = @_;
2063 my $e = new_editor(authtoken=>$auth);
2064 return $e->event unless $e->checkauth;
2066 if( $userid ne $e->requestor->id ) {
2067 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2070 # money is owed on these items and they are checked in
2071 my $open = $e->search_action_circulation(
2074 xact_finish => undef,
2075 checkin_time => { "!=" => undef },
2080 my( @lost, @cr, @lo );
2081 for my $c (@$open) {
2082 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2083 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2084 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2089 claims_returned => \@cr,
2090 long_overdue => \@lo
2096 my ($api, $desc, $auth) = @_;
2097 $desc = $desc ? (" " . $desc) : '';
2098 my $ids = ($api =~ /ids$/) ? 1 : 0;
2101 method => "user_transaction_history",
2102 api_name => "open-ils.actor.user.transactions.$api",
2104 desc => "For a given User ID, returns a list of billable transaction" .
2105 ($ids ? " id" : '') .
2106 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2107 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2109 {desc => 'Authentication token', type => 'string'},
2110 {desc => 'User ID', type => 'number'},
2111 {desc => 'Transaction type (optional)', type => 'number'},
2112 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2115 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2119 $auth and push @sig, (authoritative => 1);
2123 my %auth_hist_methods = (
2125 'history.have_charge' => 'that have an initial charge',
2126 'history.still_open' => 'that are not finished',
2127 'history.have_balance' => 'that have a balance',
2128 'history.have_bill' => 'that have billings',
2129 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2130 'history.have_payment' => 'that have at least 1 payment',
2133 foreach (keys %auth_hist_methods) {
2134 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2135 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2136 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2139 sub user_transaction_history {
2140 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2144 my $e = new_editor(authtoken=>$auth);
2145 return $e->die_event unless $e->checkauth;
2147 if ($e->requestor->id ne $userid) {
2148 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2151 my $api = $self->api_name;
2152 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2154 if(defined($type)) {
2155 $filter->{'xact_type'} = $type;
2158 if($api =~ /have_bill_or_payment/o) {
2160 # transactions that have a non-zero sum across all billings or at least 1 payment
2161 $filter->{'-or'} = {
2162 'balance_owed' => { '<>' => 0 },
2163 'last_payment_ts' => { '<>' => undef }
2166 } elsif($api =~ /have_payment/) {
2168 $filter->{last_payment_ts} ||= {'<>' => undef};
2170 } elsif( $api =~ /have_balance/o) {
2172 # transactions that have a non-zero overall balance
2173 $filter->{'balance_owed'} = { '<>' => 0 };
2175 } elsif( $api =~ /have_charge/o) {
2177 # transactions that have at least 1 billing, regardless of whether it was voided
2178 $filter->{'last_billing_ts'} = { '<>' => undef };
2180 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2182 # transactions that have non-zero sum across all billings. This will exclude
2183 # xacts where all billings have been voided
2184 $filter->{'total_owed'} = { '<>' => 0 };
2187 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2188 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2189 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2191 my $mbts = $e->search_money_billable_transaction_summary(
2192 [ { usr => $userid, @xact_finish, %$filter },
2197 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2198 return $mbts unless $api =~ /fleshed/;
2201 for my $t (@$mbts) {
2203 if( $t->xact_type ne 'circulation' ) {
2204 push @resp, {transaction => $t};
2208 my $circ_data = flesh_circ($e, $t->id);
2209 push @resp, {transaction => $t, %$circ_data};
2217 __PACKAGE__->register_method(
2218 method => "user_perms",
2219 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2221 notes => "Returns a list of permissions"
2225 my( $self, $client, $authtoken, $user ) = @_;
2227 my( $staff, $evt ) = $apputils->checkses($authtoken);
2228 return $evt if $evt;
2230 $user ||= $staff->id;
2232 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2236 return $apputils->simple_scalar_request(
2238 "open-ils.storage.permission.user_perms.atomic",
2242 __PACKAGE__->register_method(
2243 method => "retrieve_perms",
2244 api_name => "open-ils.actor.permissions.retrieve",
2245 notes => "Returns a list of permissions"
2247 sub retrieve_perms {
2248 my( $self, $client ) = @_;
2249 return $apputils->simple_scalar_request(
2251 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2252 { id => { '!=' => undef } }
2256 __PACKAGE__->register_method(
2257 method => "retrieve_groups",
2258 api_name => "open-ils.actor.groups.retrieve",
2259 notes => "Returns a list of user groups"
2261 sub retrieve_groups {
2262 my( $self, $client ) = @_;
2263 return new_editor()->retrieve_all_permission_grp_tree();
2266 __PACKAGE__->register_method(
2267 method => "retrieve_org_address",
2268 api_name => "open-ils.actor.org_unit.address.retrieve",
2269 notes => <<' NOTES');
2270 Returns an org_unit address by ID
2271 @param An org_address ID
2273 sub retrieve_org_address {
2274 my( $self, $client, $id ) = @_;
2275 return $apputils->simple_scalar_request(
2277 "open-ils.cstore.direct.actor.org_address.retrieve",
2282 __PACKAGE__->register_method(
2283 method => "retrieve_groups_tree",
2284 api_name => "open-ils.actor.groups.tree.retrieve",
2285 notes => "Returns a list of user groups"
2288 sub retrieve_groups_tree {
2289 my( $self, $client ) = @_;
2290 return new_editor()->search_permission_grp_tree(
2295 flesh_fields => { pgt => ["children"] },
2296 order_by => { pgt => 'name'}
2303 __PACKAGE__->register_method(
2304 method => "add_user_to_groups",
2305 api_name => "open-ils.actor.user.set_groups",
2306 notes => "Adds a user to one or more permission groups"
2309 sub add_user_to_groups {
2310 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2312 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2313 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2314 return $evt if $evt;
2316 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2317 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2318 return $evt if $evt;
2320 $apputils->simplereq(
2322 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2324 for my $group (@$groups) {
2325 my $link = Fieldmapper::permission::usr_grp_map->new;
2327 $link->usr($userid);
2329 my $id = $apputils->simplereq(
2331 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2337 __PACKAGE__->register_method(
2338 method => "get_user_perm_groups",
2339 api_name => "open-ils.actor.user.get_groups",
2340 notes => "Retrieve a user's permission groups."
2344 sub get_user_perm_groups {
2345 my( $self, $client, $authtoken, $userid ) = @_;
2347 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2348 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2349 return $evt if $evt;
2351 return $apputils->simplereq(
2353 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2357 __PACKAGE__->register_method(
2358 method => "get_user_work_ous",
2359 api_name => "open-ils.actor.user.get_work_ous",
2360 notes => "Retrieve a user's work org units."
2363 __PACKAGE__->register_method(
2364 method => "get_user_work_ous",
2365 api_name => "open-ils.actor.user.get_work_ous.ids",
2366 notes => "Retrieve a user's work org units."
2369 sub get_user_work_ous {
2370 my( $self, $client, $auth, $userid ) = @_;
2371 my $e = new_editor(authtoken=>$auth);
2372 return $e->event unless $e->checkauth;
2373 $userid ||= $e->requestor->id;
2375 if($e->requestor->id != $userid) {
2376 my $user = $e->retrieve_actor_user($userid)
2377 or return $e->event;
2378 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2381 return $e->search_permission_usr_work_ou_map({usr => $userid})
2382 unless $self->api_name =~ /.ids$/;
2384 # client just wants a list of org IDs
2385 return $U->get_user_work_ou_ids($e, $userid);
2390 __PACKAGE__->register_method(
2391 method => 'register_workstation',
2392 api_name => 'open-ils.actor.workstation.register.override',
2393 signature => q/@see open-ils.actor.workstation.register/
2396 __PACKAGE__->register_method(
2397 method => 'register_workstation',
2398 api_name => 'open-ils.actor.workstation.register',
2400 Registers a new workstion in the system
2401 @param authtoken The login session key
2402 @param name The name of the workstation id
2403 @param owner The org unit that owns this workstation
2404 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2405 if the name is already in use.
2409 sub register_workstation {
2410 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2412 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2413 return $e->die_event unless $e->checkauth;
2414 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2415 my $existing = $e->search_actor_workstation({name => $name})->[0];
2416 $oargs = { all => 1 } unless defined $oargs;
2420 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2421 # workstation with the given name exists.
2423 if($owner ne $existing->owning_lib) {
2424 # if necessary, update the owning_lib of the workstation
2426 $logger->info("changing owning lib of workstation ".$existing->id.
2427 " from ".$existing->owning_lib." to $owner");
2428 return $e->die_event unless
2429 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2431 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2433 $existing->owning_lib($owner);
2434 return $e->die_event unless $e->update_actor_workstation($existing);
2440 "attempt to register an existing workstation. returning existing ID");
2443 return $existing->id;
2446 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2450 my $ws = Fieldmapper::actor::workstation->new;
2451 $ws->owning_lib($owner);
2453 $e->create_actor_workstation($ws) or return $e->die_event;
2455 return $ws->id; # note: editor sets the id on the new object for us
2458 __PACKAGE__->register_method(
2459 method => 'workstation_list',
2460 api_name => 'open-ils.actor.workstation.list',
2462 Returns a list of workstations registered at the given location
2463 @param authtoken The login session key
2464 @param ids A list of org_unit.id's for the workstation owners
2468 sub workstation_list {
2469 my( $self, $conn, $authtoken, @orgs ) = @_;
2471 my $e = new_editor(authtoken=>$authtoken);
2472 return $e->event unless $e->checkauth;
2477 unless $e->allowed('REGISTER_WORKSTATION', $o);
2478 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2484 __PACKAGE__->register_method(
2485 method => 'fetch_patron_note',
2486 api_name => 'open-ils.actor.note.retrieve.all',
2489 Returns a list of notes for a given user
2490 Requestor must have VIEW_USER permission if pub==false and
2491 @param authtoken The login session key
2492 @param args Hash of params including
2493 patronid : the patron's id
2494 pub : true if retrieving only public notes
2498 sub fetch_patron_note {
2499 my( $self, $conn, $authtoken, $args ) = @_;
2500 my $patronid = $$args{patronid};
2502 my($reqr, $evt) = $U->checkses($authtoken);
2503 return $evt if $evt;
2506 ($patron, $evt) = $U->fetch_user($patronid);
2507 return $evt if $evt;
2510 if( $patronid ne $reqr->id ) {
2511 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2512 return $evt if $evt;
2514 return $U->cstorereq(
2515 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2516 { usr => $patronid, pub => 't' } );
2519 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2520 return $evt if $evt;
2522 return $U->cstorereq(
2523 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2526 __PACKAGE__->register_method(
2527 method => 'create_user_note',
2528 api_name => 'open-ils.actor.note.create',
2530 Creates a new note for the given user
2531 @param authtoken The login session key
2532 @param note The note object
2535 sub create_user_note {
2536 my( $self, $conn, $authtoken, $note ) = @_;
2537 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2538 return $e->die_event unless $e->checkauth;
2540 my $user = $e->retrieve_actor_user($note->usr)
2541 or return $e->die_event;
2543 return $e->die_event unless
2544 $e->allowed('UPDATE_USER',$user->home_ou);
2546 $note->creator($e->requestor->id);
2547 $e->create_actor_usr_note($note) or return $e->die_event;
2553 __PACKAGE__->register_method(
2554 method => 'delete_user_note',
2555 api_name => 'open-ils.actor.note.delete',
2557 Deletes a note for the given user
2558 @param authtoken The login session key
2559 @param noteid The note id
2562 sub delete_user_note {
2563 my( $self, $conn, $authtoken, $noteid ) = @_;
2565 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2566 return $e->die_event unless $e->checkauth;
2567 my $note = $e->retrieve_actor_usr_note($noteid)
2568 or return $e->die_event;
2569 my $user = $e->retrieve_actor_user($note->usr)
2570 or return $e->die_event;
2571 return $e->die_event unless
2572 $e->allowed('UPDATE_USER', $user->home_ou);
2574 $e->delete_actor_usr_note($note) or return $e->die_event;
2580 __PACKAGE__->register_method(
2581 method => 'update_user_note',
2582 api_name => 'open-ils.actor.note.update',
2584 @param authtoken The login session key
2585 @param note The note
2589 sub update_user_note {
2590 my( $self, $conn, $auth, $note ) = @_;
2591 my $e = new_editor(authtoken=>$auth, xact=>1);
2592 return $e->die_event unless $e->checkauth;
2593 my $patron = $e->retrieve_actor_user($note->usr)
2594 or return $e->die_event;
2595 return $e->die_event unless
2596 $e->allowed('UPDATE_USER', $patron->home_ou);
2597 $e->update_actor_user_note($note)
2598 or return $e->die_event;
2605 __PACKAGE__->register_method(
2606 method => 'create_closed_date',
2607 api_name => 'open-ils.actor.org_unit.closed_date.create',
2609 Creates a new closing entry for the given org_unit
2610 @param authtoken The login session key
2611 @param note The closed_date object
2614 sub create_closed_date {
2615 my( $self, $conn, $authtoken, $cd ) = @_;
2617 my( $user, $evt ) = $U->checkses($authtoken);
2618 return $evt if $evt;
2620 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2621 return $evt if $evt;
2623 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2625 my $id = $U->storagereq(
2626 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2627 return $U->DB_UPDATE_FAILED($cd) unless $id;
2632 __PACKAGE__->register_method(
2633 method => 'delete_closed_date',
2634 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2636 Deletes a closing entry for the given org_unit
2637 @param authtoken The login session key
2638 @param noteid The close_date id
2641 sub delete_closed_date {
2642 my( $self, $conn, $authtoken, $cd ) = @_;
2644 my( $user, $evt ) = $U->checkses($authtoken);
2645 return $evt if $evt;
2648 ($cd_obj, $evt) = fetch_closed_date($cd);
2649 return $evt if $evt;
2651 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2652 return $evt if $evt;
2654 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2656 my $stat = $U->storagereq(
2657 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2658 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2663 __PACKAGE__->register_method(
2664 method => 'usrname_exists',
2665 api_name => 'open-ils.actor.username.exists',
2667 desc => 'Check if a username is already taken (by an undeleted patron)',
2669 {desc => 'Authentication token', type => 'string'},
2670 {desc => 'Username', type => 'string'}
2673 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2678 sub usrname_exists {
2679 my( $self, $conn, $auth, $usrname ) = @_;
2680 my $e = new_editor(authtoken=>$auth);
2681 return $e->event unless $e->checkauth;
2682 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2683 return $$a[0] if $a and @$a;
2687 __PACKAGE__->register_method(
2688 method => 'barcode_exists',
2689 api_name => 'open-ils.actor.barcode.exists',
2691 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2694 sub barcode_exists {
2695 my( $self, $conn, $auth, $barcode ) = @_;
2696 my $e = new_editor(authtoken=>$auth);
2697 return $e->event unless $e->checkauth;
2698 my $card = $e->search_actor_card({barcode => $barcode});
2704 #return undef unless @$card;
2705 #return $card->[0]->usr;
2709 __PACKAGE__->register_method(
2710 method => 'retrieve_net_levels',
2711 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2714 sub retrieve_net_levels {
2715 my( $self, $conn, $auth ) = @_;
2716 my $e = new_editor(authtoken=>$auth);
2717 return $e->event unless $e->checkauth;
2718 return $e->retrieve_all_config_net_access_level();
2721 # Retain the old typo API name just in case
2722 __PACKAGE__->register_method(
2723 method => 'fetch_org_by_shortname',
2724 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2726 __PACKAGE__->register_method(
2727 method => 'fetch_org_by_shortname',
2728 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2730 sub fetch_org_by_shortname {
2731 my( $self, $conn, $sname ) = @_;
2732 my $e = new_editor();
2733 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2734 return $e->event unless $org;
2739 __PACKAGE__->register_method(
2740 method => 'session_home_lib',
2741 api_name => 'open-ils.actor.session.home_lib',
2744 sub session_home_lib {
2745 my( $self, $conn, $auth ) = @_;
2746 my $e = new_editor(authtoken=>$auth);
2747 return undef unless $e->checkauth;
2748 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2749 return $org->shortname;
2752 __PACKAGE__->register_method(
2753 method => 'session_safe_token',
2754 api_name => 'open-ils.actor.session.safe_token',
2756 Returns a hashed session ID that is safe for export to the world.
2757 This safe token will expire after 1 hour of non-use.
2758 @param auth Active authentication token
2762 sub session_safe_token {
2763 my( $self, $conn, $auth ) = @_;
2764 my $e = new_editor(authtoken=>$auth);
2765 return undef unless $e->checkauth;
2767 my $safe_token = md5_hex($auth);
2769 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2771 # Add more like the following if needed...
2773 "safe-token-home_lib-shortname-$safe_token",
2774 $e->retrieve_actor_org_unit(
2775 $e->requestor->home_ou
2784 __PACKAGE__->register_method(
2785 method => 'safe_token_home_lib',
2786 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2788 Returns the home library shortname from the session
2789 asscociated with a safe token from generated by
2790 open-ils.actor.session.safe_token.
2791 @param safe_token Active safe token
2795 sub safe_token_home_lib {
2796 my( $self, $conn, $safe_token ) = @_;
2798 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2799 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2803 __PACKAGE__->register_method(
2804 method => "update_penalties",
2805 api_name => "open-ils.actor.user.penalties.update"
2808 sub update_penalties {
2809 my($self, $conn, $auth, $user_id) = @_;
2810 my $e = new_editor(authtoken=>$auth, xact => 1);
2811 return $e->die_event unless $e->checkauth;
2812 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2813 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2814 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2815 return $evt if $evt;
2821 __PACKAGE__->register_method(
2822 method => "apply_penalty",
2823 api_name => "open-ils.actor.user.penalty.apply"
2827 my($self, $conn, $auth, $penalty) = @_;
2829 my $e = new_editor(authtoken=>$auth, xact => 1);
2830 return $e->die_event unless $e->checkauth;
2832 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2833 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2835 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2838 (defined $ptype->org_depth) ?
2839 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2842 $penalty->org_unit($ctx_org);
2843 $penalty->staff($e->requestor->id);
2844 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2847 return $penalty->id;
2850 __PACKAGE__->register_method(
2851 method => "remove_penalty",
2852 api_name => "open-ils.actor.user.penalty.remove"
2855 sub remove_penalty {
2856 my($self, $conn, $auth, $penalty) = @_;
2857 my $e = new_editor(authtoken=>$auth, xact => 1);
2858 return $e->die_event unless $e->checkauth;
2859 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2860 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2862 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2867 __PACKAGE__->register_method(
2868 method => "update_penalty_note",
2869 api_name => "open-ils.actor.user.penalty.note.update"
2872 sub update_penalty_note {
2873 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2874 my $e = new_editor(authtoken=>$auth, xact => 1);
2875 return $e->die_event unless $e->checkauth;
2876 for my $penalty_id (@$penalty_ids) {
2877 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2878 if (! $penalty ) { return $e->die_event; }
2879 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2880 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2882 $penalty->note( $note ); $penalty->ischanged( 1 );
2884 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2890 __PACKAGE__->register_method(
2891 method => "ranged_penalty_thresholds",
2892 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2896 sub ranged_penalty_thresholds {
2897 my($self, $conn, $auth, $context_org) = @_;
2898 my $e = new_editor(authtoken=>$auth);
2899 return $e->event unless $e->checkauth;
2900 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2901 my $list = $e->search_permission_grp_penalty_threshold([
2902 {org_unit => $U->get_org_ancestors($context_org)},
2903 {order_by => {pgpt => 'id'}}
2905 $conn->respond($_) for @$list;
2911 __PACKAGE__->register_method(
2912 method => "user_retrieve_fleshed_by_id",
2914 api_name => "open-ils.actor.user.fleshed.retrieve",
2917 sub user_retrieve_fleshed_by_id {
2918 my( $self, $client, $auth, $user_id, $fields ) = @_;
2919 my $e = new_editor(authtoken => $auth);
2920 return $e->event unless $e->checkauth;
2922 if( $e->requestor->id != $user_id ) {
2923 return $e->event unless $e->allowed('VIEW_USER');
2929 "standing_penalties",
2935 return new_flesh_user($user_id, $fields, $e);
2939 sub new_flesh_user {
2942 my $fields = shift || [];
2945 my $fetch_penalties = 0;
2946 if(grep {$_ eq 'standing_penalties'} @$fields) {
2947 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2948 $fetch_penalties = 1;
2951 my $fetch_usr_act = 0;
2952 if(grep {$_ eq 'usr_activity'} @$fields) {
2953 $fields = [grep {$_ ne 'usr_activity'} @$fields];
2957 my $user = $e->retrieve_actor_user(
2962 "flesh_fields" => { "au" => $fields }
2965 ) or return $e->die_event;
2968 if( grep { $_ eq 'addresses' } @$fields ) {
2970 $user->addresses([]) unless @{$user->addresses};
2971 # don't expose "replaced" addresses by default
2972 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2974 if( ref $user->billing_address ) {
2975 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2976 push( @{$user->addresses}, $user->billing_address );
2980 if( ref $user->mailing_address ) {
2981 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2982 push( @{$user->addresses}, $user->mailing_address );
2987 if($fetch_penalties) {
2988 # grab the user penalties ranged for this location
2989 $user->standing_penalties(
2990 $e->search_actor_user_standing_penalty([
2993 {stop_date => undef},
2994 {stop_date => {'>' => 'now'}}
2996 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
2999 flesh_fields => {ausp => ['standing_penalty']}
3005 # retrieve the most recent usr_activity entry
3006 if ($fetch_usr_act) {
3008 # max number to return for simple patron fleshing
3009 my $limit = $U->ou_ancestor_setting_value(
3010 $e->requestor->ws_ou,
3011 'circ.patron.usr_activity_retrieve.max');
3015 flesh_fields => {auact => ['etype']},
3016 order_by => {auact => 'event_time DESC'},
3019 # 0 == none, <0 == return all
3020 $limit = 1 unless defined $limit;
3021 $opts->{limit} = $limit if $limit > 0;
3023 $user->usr_activity(
3025 [] : # skip the DB call
3026 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3031 $user->clear_passwd();
3038 __PACKAGE__->register_method(
3039 method => "user_retrieve_parts",
3040 api_name => "open-ils.actor.user.retrieve.parts",
3043 sub user_retrieve_parts {
3044 my( $self, $client, $auth, $user_id, $fields ) = @_;
3045 my $e = new_editor(authtoken => $auth);
3046 return $e->event unless $e->checkauth;
3047 $user_id ||= $e->requestor->id;
3048 if( $e->requestor->id != $user_id ) {
3049 return $e->event unless $e->allowed('VIEW_USER');
3052 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3053 push(@resp, $user->$_()) for(@$fields);
3059 __PACKAGE__->register_method(
3060 method => 'user_opt_in_enabled',
3061 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3062 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3065 sub user_opt_in_enabled {
3066 my($self, $conn) = @_;
3067 my $sc = OpenSRF::Utils::SettingsClient->new;
3068 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3073 __PACKAGE__->register_method(
3074 method => 'user_opt_in_at_org',
3075 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3077 @param $auth The auth token
3078 @param user_id The ID of the user to test
3079 @return 1 if the user has opted in at the specified org,
3080 event on error, and 0 otherwise. /
3082 sub user_opt_in_at_org {
3083 my($self, $conn, $auth, $user_id) = @_;
3085 # see if we even need to enforce the opt-in value
3086 return 1 unless user_opt_in_enabled($self);
3088 my $e = new_editor(authtoken => $auth);
3089 return $e->event unless $e->checkauth;
3091 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3092 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3094 my $ws_org = $e->requestor->ws_ou;
3095 # user is automatically opted-in if they are from the local org
3096 return 1 if $user->home_ou eq $ws_org;
3098 # get the boundary setting
3099 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3101 # auto opt in if user falls within the opt boundary
3102 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3104 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3106 my $vals = $e->search_actor_usr_org_unit_opt_in(
3107 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3113 __PACKAGE__->register_method(
3114 method => 'create_user_opt_in_at_org',
3115 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3117 @param $auth The auth token
3118 @param user_id The ID of the user to test
3119 @return The ID of the newly created object, event on error./
3122 sub create_user_opt_in_at_org {
3123 my($self, $conn, $auth, $user_id, $org_id) = @_;
3125 my $e = new_editor(authtoken => $auth, xact=>1);
3126 return $e->die_event unless $e->checkauth;
3128 # if a specific org unit wasn't passed in, get one based on the defaults;
3130 my $wsou = $e->requestor->ws_ou;
3131 # get the default opt depth
3132 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3133 # get the org unit at that depth
3134 my $org = $e->json_query({
3135 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3136 $org_id = $org->{id};
3139 # fall back to the workstation OU, the pre-opt-in-boundary way
3140 $org_id = $e->requestor->ws_ou;
3143 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3144 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3146 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3148 $opt_in->org_unit($org_id);
3149 $opt_in->usr($user_id);
3150 $opt_in->staff($e->requestor->id);
3151 $opt_in->opt_in_ts('now');
3152 $opt_in->opt_in_ws($e->requestor->wsid);
3154 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3155 or return $e->die_event;
3163 __PACKAGE__->register_method (
3164 method => 'retrieve_org_hours',
3165 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3167 Returns the hours of operation for a specified org unit
3168 @param authtoken The login session key
3169 @param org_id The org_unit ID
3173 sub retrieve_org_hours {
3174 my($self, $conn, $auth, $org_id) = @_;
3175 my $e = new_editor(authtoken => $auth);
3176 return $e->die_event unless $e->checkauth;
3177 $org_id ||= $e->requestor->ws_ou;
3178 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3182 __PACKAGE__->register_method (
3183 method => 'verify_user_password',
3184 api_name => 'open-ils.actor.verify_user_password',
3186 Given a barcode or username and the MD5 encoded password,
3187 returns 1 if the password is correct. Returns 0 otherwise.
3191 sub verify_user_password {
3192 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3193 my $e = new_editor(authtoken => $auth);
3194 return $e->die_event unless $e->checkauth;
3196 my $user_by_barcode;
3197 my $user_by_username;
3199 my $card = $e->search_actor_card([
3200 {barcode => $barcode},
3201 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3202 $user_by_barcode = $card->usr;
3203 $user = $user_by_barcode;
3206 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3207 $user = $user_by_username;
3209 return 0 if (!$user);
3210 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3211 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3212 return 1 if $user->passwd eq $password;
3216 __PACKAGE__->register_method (
3217 method => 'retrieve_usr_id_via_barcode_or_usrname',
3218 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3220 Given a barcode or username returns the id for the user or
3225 sub retrieve_usr_id_via_barcode_or_usrname {
3226 my($self, $conn, $auth, $barcode, $username) = @_;
3227 my $e = new_editor(authtoken => $auth);
3228 return $e->die_event unless $e->checkauth;
3229 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3231 my $user_by_barcode;
3232 my $user_by_username;
3233 $logger->info("$id_as_barcode is the ID as BARCODE");
3235 my $card = $e->search_actor_card([
3236 {barcode => $barcode},
3237 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3238 if ($id_as_barcode =~ /^t/i) {
3240 $user = $e->retrieve_actor_user($barcode);
3241 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3243 $user_by_barcode = $card->usr;
3244 $user = $user_by_barcode;
3247 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3248 $user_by_barcode = $card->usr;
3249 $user = $user_by_barcode;
3254 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3256 $user = $user_by_username;
3258 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3259 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3260 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3265 __PACKAGE__->register_method (
3266 method => 'merge_users',
3267 api_name => 'open-ils.actor.user.merge',
3270 Given a list of source users and destination user, transfer all data from the source
3271 to the dest user and delete the source user. All user related data is
3272 transferred, including circulations, holds, bookbags, etc.
3278 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3279 my $e = new_editor(xact => 1, authtoken => $auth);
3280 return $e->die_event unless $e->checkauth;
3282 # disallow the merge if any subordinate accounts are in collections
3283 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3284 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3286 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3287 my $del_addrs = ($U->ou_ancestor_setting_value(
3288 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3289 my $del_cards = ($U->ou_ancestor_setting_value(
3290 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3291 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3292 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3294 for my $src_id (@$user_ids) {
3295 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3297 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3298 if($src_user->home_ou ne $master_user->home_ou) {
3299 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3302 return $e->die_event unless
3303 $e->json_query({from => [
3318 __PACKAGE__->register_method (
3319 method => 'approve_user_address',
3320 api_name => 'open-ils.actor.user.pending_address.approve',
3327 sub approve_user_address {
3328 my($self, $conn, $auth, $addr) = @_;
3329 my $e = new_editor(xact => 1, authtoken => $auth);
3330 return $e->die_event unless $e->checkauth;
3332 # if the caller passes an address object, assume they want to
3333 # update it first before approving it
3334 $e->update_actor_user_address($addr) or return $e->die_event;
3336 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3338 my $user = $e->retrieve_actor_user($addr->usr);
3339 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3340 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3341 or return $e->die_event;
3343 return [values %$result]->[0];
3347 __PACKAGE__->register_method (
3348 method => 'retrieve_friends',
3349 api_name => 'open-ils.actor.friends.retrieve',
3352 returns { confirmed: [], pending_out: [], pending_in: []}
3353 pending_out are users I'm requesting friendship with
3354 pending_in are users requesting friendship with me
3359 sub retrieve_friends {
3360 my($self, $conn, $auth, $user_id, $options) = @_;
3361 my $e = new_editor(authtoken => $auth);
3362 return $e->event unless $e->checkauth;
3363 $user_id ||= $e->requestor->id;
3365 if($user_id != $e->requestor->id) {
3366 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3367 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3370 return OpenILS::Application::Actor::Friends->retrieve_friends(
3371 $e, $user_id, $options);
3376 __PACKAGE__->register_method (
3377 method => 'apply_friend_perms',
3378 api_name => 'open-ils.actor.friends.perms.apply',
3384 sub apply_friend_perms {
3385 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3386 my $e = new_editor(authtoken => $auth, xact => 1);
3387 return $e->die_event unless $e->checkauth;
3389 if($user_id != $e->requestor->id) {
3390 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3391 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3394 for my $perm (@perms) {
3396 OpenILS::Application::Actor::Friends->apply_friend_perm(
3397 $e, $user_id, $delegate_id, $perm);
3398 return $evt if $evt;
3406 __PACKAGE__->register_method (
3407 method => 'update_user_pending_address',
3408 api_name => 'open-ils.actor.user.address.pending.cud'
3411 sub update_user_pending_address {
3412 my($self, $conn, $auth, $addr) = @_;
3413 my $e = new_editor(authtoken => $auth, xact => 1);
3414 return $e->die_event unless $e->checkauth;
3416 if($addr->usr != $e->requestor->id) {
3417 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3418 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3422 $e->create_actor_user_address($addr) or return $e->die_event;
3423 } elsif($addr->isdeleted) {
3424 $e->delete_actor_user_address($addr) or return $e->die_event;
3426 $e->update_actor_user_address($addr) or return $e->die_event;
3434 __PACKAGE__->register_method (
3435 method => 'user_events',
3436 api_name => 'open-ils.actor.user.events.circ',
3439 __PACKAGE__->register_method (
3440 method => 'user_events',
3441 api_name => 'open-ils.actor.user.events.ahr',
3446 my($self, $conn, $auth, $user_id, $filters) = @_;
3447 my $e = new_editor(authtoken => $auth);
3448 return $e->event unless $e->checkauth;
3450 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3451 my $user_field = 'usr';
3454 $filters->{target} = {
3455 select => { $obj_type => ['id'] },
3457 where => {usr => $user_id}
3460 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3461 if($e->requestor->id != $user_id) {
3462 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3465 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3466 my $req = $ses->request('open-ils.trigger.events_by_target',
3467 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3469 while(my $resp = $req->recv) {
3470 my $val = $resp->content;
3471 my $tgt = $val->target;
3473 if($obj_type eq 'circ') {
3474 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3476 } elsif($obj_type eq 'ahr') {
3477 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3478 if $tgt->current_copy;
3481 $conn->respond($val) if $val;
3487 __PACKAGE__->register_method (
3488 method => 'copy_events',
3489 api_name => 'open-ils.actor.copy.events.circ',
3492 __PACKAGE__->register_method (
3493 method => 'copy_events',
3494 api_name => 'open-ils.actor.copy.events.ahr',
3499 my($self, $conn, $auth, $copy_id, $filters) = @_;
3500 my $e = new_editor(authtoken => $auth);
3501 return $e->event unless $e->checkauth;
3503 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3505 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3507 my $copy_field = 'target_copy';
3508 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3511 $filters->{target} = {
3512 select => { $obj_type => ['id'] },
3514 where => {$copy_field => $copy_id}
3518 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3519 my $req = $ses->request('open-ils.trigger.events_by_target',
3520 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3522 while(my $resp = $req->recv) {
3523 my $val = $resp->content;
3524 my $tgt = $val->target;
3526 my $user = $e->retrieve_actor_user($tgt->usr);
3527 if($e->requestor->id != $user->id) {
3528 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3531 $tgt->$copy_field($copy);
3534 $conn->respond($val) if $val;
3543 __PACKAGE__->register_method (
3544 method => 'update_events',
3545 api_name => 'open-ils.actor.user.event.cancel.batch',
3548 __PACKAGE__->register_method (
3549 method => 'update_events',
3550 api_name => 'open-ils.actor.user.event.reset.batch',
3555 my($self, $conn, $auth, $event_ids) = @_;
3556 my $e = new_editor(xact => 1, authtoken => $auth);
3557 return $e->die_event unless $e->checkauth;
3560 for my $id (@$event_ids) {
3562 # do a little dance to determine what user we are ultimately affecting
3563 my $event = $e->retrieve_action_trigger_event([
3566 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3568 ]) or return $e->die_event;
3571 if($event->event_def->hook->core_type eq 'circ') {
3572 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3573 } elsif($event->event_def->hook->core_type eq 'ahr') {
3574 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3579 my $user = $e->retrieve_actor_user($user_id);
3580 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3582 if($self->api_name =~ /cancel/) {
3583 $event->state('invalid');
3584 } elsif($self->api_name =~ /reset/) {
3585 $event->clear_start_time;
3586 $event->clear_update_time;
3587 $event->state('pending');
3590 $e->update_action_trigger_event($event) or return $e->die_event;
3591 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3595 return {complete => 1};
3599 __PACKAGE__->register_method (
3600 method => 'really_delete_user',
3601 api_name => 'open-ils.actor.user.delete.override',
3602 signature => q/@see open-ils.actor.user.delete/
3605 __PACKAGE__->register_method (
3606 method => 'really_delete_user',
3607 api_name => 'open-ils.actor.user.delete',
3609 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3610 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3611 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3612 dest_usr_id is only required when deleting a user that performs staff functions.
3616 sub really_delete_user {
3617 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3618 my $e = new_editor(authtoken => $auth, xact => 1);
3619 return $e->die_event unless $e->checkauth;
3620 $oargs = { all => 1 } unless defined $oargs;
3622 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3623 my $open_bills = $e->json_query({
3624 select => { mbts => ['id'] },
3627 xact_finish => { '=' => undef },
3628 usr => { '=' => $user_id },
3630 }) or return $e->die_event;
3632 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3634 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3636 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3637 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3638 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3640 # No deleting yourself - UI is supposed to stop you first, though.
3641 return $e->die_event unless $e->requestor->id != $user->id;
3642 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3643 # Check if you are allowed to mess with this patron permission group at all
3644 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3645 my $evt = group_perm_failed($session, $e->requestor, $user);
3646 return $e->die_event($evt) if $evt;
3647 my $stat = $e->json_query(
3648 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3649 or return $e->die_event;
3655 __PACKAGE__->register_method (
3656 method => 'user_payments',
3657 api_name => 'open-ils.actor.user.payments.retrieve',
3660 Returns all payments for a given user. Default order is newest payments first.
3661 @param auth Authentication token
3662 @param user_id The user ID
3663 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3668 my($self, $conn, $auth, $user_id, $filters) = @_;
3671 my $e = new_editor(authtoken => $auth);
3672 return $e->die_event unless $e->checkauth;
3674 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3675 return $e->event unless
3676 $e->requestor->id == $user_id or
3677 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3679 # Find all payments for all transactions for user $user_id
3681 select => {mp => ['id']},
3686 select => {mbt => ['id']},
3688 where => {usr => $user_id}
3693 { # by default, order newest payments first
3695 field => 'payment_ts',
3698 # secondary sort in ID as a tie-breaker, since payments created
3699 # within the same transaction will have identical payment_ts's
3706 for (qw/order_by limit offset/) {
3707 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3710 if(defined $filters->{where}) {
3711 foreach (keys %{$filters->{where}}) {
3712 # don't allow the caller to expand the result set to other users
3713 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3717 my $payment_ids = $e->json_query($query);
3718 for my $pid (@$payment_ids) {
3719 my $pay = $e->retrieve_money_payment([
3724 mbt => ['summary', 'circulation', 'grocery'],
3725 circ => ['target_copy'],
3726 acp => ['call_number'],
3734 xact_type => $pay->xact->summary->xact_type,
3735 last_billing_type => $pay->xact->summary->last_billing_type,
3738 if($pay->xact->summary->xact_type eq 'circulation') {
3739 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3740 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3743 $pay->xact($pay->xact->id); # de-flesh
3744 $conn->respond($resp);
3752 __PACKAGE__->register_method (
3753 method => 'negative_balance_users',
3754 api_name => 'open-ils.actor.users.negative_balance',
3757 Returns all users that have an overall negative balance
3758 @param auth Authentication token
3759 @param org_id The context org unit as an ID or list of IDs. This will be the home
3760 library of the user. If no org_unit is specified, no org unit filter is applied
3764 sub negative_balance_users {
3765 my($self, $conn, $auth, $org_id) = @_;
3767 my $e = new_editor(authtoken => $auth);
3768 return $e->die_event unless $e->checkauth;
3769 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3773 mous => ['usr', 'balance_owed'],
3776 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3777 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3794 where => {'+mous' => {balance_owed => {'<' => 0}}}
3797 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3799 my $list = $e->json_query($query, {timeout => 600});
3801 for my $data (@$list) {
3803 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3804 balance_owed => $data->{balance_owed},
3805 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3812 __PACKAGE__->register_method(
3813 method => "request_password_reset",
3814 api_name => "open-ils.actor.patron.password_reset.request",
3816 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3817 "method for changing a user's password. The UUID token is distributed via A/T " .
3818 "templates (i.e. email to the user).",
3820 { desc => 'user_id_type', type => 'string' },
3821 { desc => 'user_id', type => 'string' },
3822 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3824 return => {desc => '1 on success, Event on error'}
3827 sub request_password_reset {
3828 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3830 # Check to see if password reset requests are already being throttled:
3831 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3833 my $e = new_editor(xact => 1);
3836 # Get the user, if any, depending on the input value
3837 if ($user_id_type eq 'username') {
3838 $user = $e->search_actor_user({usrname => $user_id})->[0];
3841 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3843 } elsif ($user_id_type eq 'barcode') {
3844 my $card = $e->search_actor_card([
3845 {barcode => $user_id},
3846 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3849 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3854 # If the user doesn't have an email address, we can't help them
3855 if (!$user->email) {
3857 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3860 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3861 if ($email_must_match) {
3862 if ($user->email ne $email) {
3863 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3867 _reset_password_request($conn, $e, $user);
3870 # Once we have the user, we can issue the password reset request
3871 # XXX Add a wrapper method that accepts barcode + email input
3872 sub _reset_password_request {
3873 my ($conn, $e, $user) = @_;
3875 # 1. Get throttle threshold and time-to-live from OU_settings
3876 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3877 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3879 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3881 # 2. Get time of last request and number of active requests (num_active)
3882 my $active_requests = $e->json_query({
3888 transform => 'COUNT'
3891 column => 'request_time',
3897 has_been_reset => { '=' => 'f' },
3898 request_time => { '>' => $threshold_time }
3902 # Guard against no active requests
3903 if ($active_requests->[0]->{'request_time'}) {
3904 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3905 my $now = DateTime::Format::ISO8601->new();
3907 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3908 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3909 ($last_request->add_duration('1 minute') > $now)) {
3910 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3912 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3916 # TODO Check to see if the user is in a password-reset-restricted group
3918 # Otherwise, go ahead and try to get the user.
3920 # Check the number of active requests for this user
3921 $active_requests = $e->json_query({
3927 transform => 'COUNT'
3932 usr => { '=' => $user->id },
3933 has_been_reset => { '=' => 'f' },
3934 request_time => { '>' => $threshold_time }
3938 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3940 # if less than or equal to per-user threshold, proceed; otherwise, return event
3941 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3942 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3944 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3947 # Create the aupr object and insert into the database
3948 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3949 my $uuid = create_uuid_as_string(UUID_V4);
3950 $reset_request->uuid($uuid);
3951 $reset_request->usr($user->id);
3953 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3956 # Create an event to notify user of the URL to reset their password
3958 # Can we stuff this in the user_data param for trigger autocreate?
3959 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3961 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3962 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3965 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3970 __PACKAGE__->register_method(
3971 method => "commit_password_reset",
3972 api_name => "open-ils.actor.patron.password_reset.commit",
3974 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3975 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3976 "with the supplied password.",
3978 { desc => 'uuid', type => 'string' },
3979 { desc => 'password', type => 'string' },
3981 return => {desc => '1 on success, Event on error'}
3984 sub commit_password_reset {
3985 my($self, $conn, $uuid, $password) = @_;
3987 # Check to see if password reset requests are already being throttled:
3988 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3989 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3990 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3992 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3995 my $e = new_editor(xact => 1);
3997 my $aupr = $e->search_actor_usr_password_reset({
4004 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4006 my $user_id = $aupr->[0]->usr;
4007 my $user = $e->retrieve_actor_user($user_id);
4009 # Ensure we're still within the TTL for the request
4010 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4011 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4012 if ($threshold < DateTime->now(time_zone => 'local')) {
4014 $logger->info("Password reset request needed to be submitted before $threshold");
4015 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4018 # Check complexity of password against OU-defined regex
4019 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4023 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4024 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4025 $is_strong = check_password_strength_custom($password, $pw_regex);
4027 $is_strong = check_password_strength_default($password);
4032 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4035 # All is well; update the password
4036 $user->passwd($password);
4037 $e->update_actor_user($user);
4039 # And flag that this password reset request has been honoured
4040 $aupr->[0]->has_been_reset('t');
4041 $e->update_actor_usr_password_reset($aupr->[0]);
4047 sub check_password_strength_default {
4048 my $password = shift;
4049 # Use the default set of checks
4050 if ( (length($password) < 7) or
4051 ($password !~ m/.*\d+.*/) or
4052 ($password !~ m/.*[A-Za-z]+.*/)
4059 sub check_password_strength_custom {
4060 my ($password, $pw_regex) = @_;
4062 $pw_regex = qr/$pw_regex/;
4063 if ($password !~ /$pw_regex/) {
4071 __PACKAGE__->register_method(
4072 method => "event_def_opt_in_settings",
4073 api_name => "open-ils.actor.event_def.opt_in.settings",
4076 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4078 { desc => 'Authentication token', type => 'string'},
4080 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4085 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4092 sub event_def_opt_in_settings {
4093 my($self, $conn, $auth, $org_id) = @_;
4094 my $e = new_editor(authtoken => $auth);
4095 return $e->event unless $e->checkauth;
4097 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4098 return $e->event unless
4099 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4101 $org_id = $e->requestor->home_ou;
4104 # find all config.user_setting_type's related to event_defs for the requested org unit
4105 my $types = $e->json_query({
4106 select => {cust => ['name']},
4107 from => {atevdef => 'cust'},
4110 owner => $U->get_org_ancestors($org_id), # context org plus parents
4117 $conn->respond($_) for
4118 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4125 __PACKAGE__->register_method(
4126 method => "user_visible_circs",
4127 api_name => "open-ils.actor.history.circ.visible",
4130 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4132 { desc => 'Authentication token', type => 'string'},
4133 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4134 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4137 desc => q/An object with 2 fields: circulation and summary.
4138 circulation is the "circ" object. summary is the related "accs" object/,
4144 __PACKAGE__->register_method(
4145 method => "user_visible_circs",
4146 api_name => "open-ils.actor.history.circ.visible.print",
4149 desc => 'Returns printable output for the set of opt-in visible circulations',
4151 { desc => 'Authentication token', type => 'string'},
4152 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4153 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4156 desc => q/An action_trigger.event object or error event./,
4162 __PACKAGE__->register_method(
4163 method => "user_visible_circs",
4164 api_name => "open-ils.actor.history.circ.visible.email",
4167 desc => 'Emails the set of opt-in visible circulations to the requestor',
4169 { desc => 'Authentication token', type => 'string'},
4170 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4171 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4174 desc => q/undef, or event on error/
4179 __PACKAGE__->register_method(
4180 method => "user_visible_circs",
4181 api_name => "open-ils.actor.history.hold.visible",
4184 desc => 'Returns the set of opt-in visible holds',
4186 { desc => 'Authentication token', type => 'string'},
4187 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4188 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4191 desc => q/An object with 1 field: "hold"/,
4197 __PACKAGE__->register_method(
4198 method => "user_visible_circs",
4199 api_name => "open-ils.actor.history.hold.visible.print",
4202 desc => 'Returns printable output for the set of opt-in visible holds',
4204 { desc => 'Authentication token', type => 'string'},
4205 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4206 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4209 desc => q/An action_trigger.event object or error event./,
4215 __PACKAGE__->register_method(
4216 method => "user_visible_circs",
4217 api_name => "open-ils.actor.history.hold.visible.email",
4220 desc => 'Emails the set of opt-in visible holds to the requestor',
4222 { desc => 'Authentication token', type => 'string'},
4223 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4224 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4227 desc => q/undef, or event on error/
4232 sub user_visible_circs {
4233 my($self, $conn, $auth, $user_id, $options) = @_;
4235 my $is_hold = ($self->api_name =~ /hold/);
4236 my $for_print = ($self->api_name =~ /print/);
4237 my $for_email = ($self->api_name =~ /email/);
4238 my $e = new_editor(authtoken => $auth);
4239 return $e->event unless $e->checkauth;
4241 $user_id ||= $e->requestor->id;
4243 $options->{limit} ||= 50;
4244 $options->{offset} ||= 0;
4246 if($user_id != $e->requestor->id) {
4247 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4248 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4249 return $e->event unless $e->allowed($perm, $user->home_ou);
4252 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4254 my $data = $e->json_query({
4255 from => [$db_func, $user_id],
4256 limit => $$options{limit},
4257 offset => $$options{offset}
4259 # TODO: I only want IDs. code below didn't get me there
4260 # {"select":{"au":[{"column":"id", "result_field":"id",
4261 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4266 return undef unless @$data;
4270 # collect the batch of objects
4274 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4275 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4279 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4280 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4283 } elsif ($for_email) {
4285 $conn->respond_complete(1) if $for_email; # no sense in waiting
4293 my $hold = $e->retrieve_action_hold_request($id);
4294 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4295 # events will be fired from action_trigger_runner
4299 my $circ = $e->retrieve_action_circulation($id);
4300 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4301 # events will be fired from action_trigger_runner
4305 } else { # just give me the data please
4313 my $hold = $e->retrieve_action_hold_request($id);
4314 $conn->respond({hold => $hold});
4318 my $circ = $e->retrieve_action_circulation($id);
4321 summary => $U->create_circ_chain_summary($e, $id)
4330 __PACKAGE__->register_method(
4331 method => "user_saved_search_cud",
4332 api_name => "open-ils.actor.user.saved_search.cud",
4335 desc => 'Create/Update/Delete Access to user saved searches',
4337 { desc => 'Authentication token', type => 'string' },
4338 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4341 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4347 __PACKAGE__->register_method(
4348 method => "user_saved_search_cud",
4349 api_name => "open-ils.actor.user.saved_search.retrieve",
4352 desc => 'Retrieve a saved search object',
4354 { desc => 'Authentication token', type => 'string' },
4355 { desc => 'Saved Search ID', type => 'number' }
4358 desc => q/The saved search object, Event on error/,
4364 sub user_saved_search_cud {
4365 my( $self, $client, $auth, $search ) = @_;
4366 my $e = new_editor( authtoken=>$auth );
4367 return $e->die_event unless $e->checkauth;
4369 my $o_search; # prior version of the object, if any
4370 my $res; # to be returned
4372 # branch on the operation type
4374 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4376 # Get the old version, to check ownership
4377 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4378 or return $e->die_event;
4380 # You can't read somebody else's search
4381 return OpenILS::Event->new('BAD_PARAMS')
4382 unless $o_search->owner == $e->requestor->id;
4388 $e->xact_begin; # start an editor transaction
4390 if( $search->isnew ) { # Create
4392 # You can't create a search for somebody else
4393 return OpenILS::Event->new('BAD_PARAMS')
4394 unless $search->owner == $e->requestor->id;
4396 $e->create_actor_usr_saved_search( $search )
4397 or return $e->die_event;
4401 } elsif( $search->ischanged ) { # Update
4403 # You can't change ownership of a search
4404 return OpenILS::Event->new('BAD_PARAMS')
4405 unless $search->owner == $e->requestor->id;
4407 # Get the old version, to check ownership
4408 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4409 or return $e->die_event;
4411 # You can't update somebody else's search
4412 return OpenILS::Event->new('BAD_PARAMS')
4413 unless $o_search->owner == $e->requestor->id;
4416 $e->update_actor_usr_saved_search( $search )
4417 or return $e->die_event;
4421 } elsif( $search->isdeleted ) { # Delete
4423 # Get the old version, to check ownership
4424 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4425 or return $e->die_event;
4427 # You can't delete somebody else's search
4428 return OpenILS::Event->new('BAD_PARAMS')
4429 unless $o_search->owner == $e->requestor->id;
4432 $e->delete_actor_usr_saved_search( $o_search )
4433 or return $e->die_event;
4444 __PACKAGE__->register_method(
4445 method => "get_barcodes",
4446 api_name => "open-ils.actor.get_barcodes"
4450 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4451 my $e = new_editor(authtoken => $auth);
4452 return $e->event unless $e->checkauth;
4453 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4455 my $db_result = $e->json_query(
4457 'evergreen.get_barcodes',
4458 $org_id, $context, $barcode,
4462 if($context =~ /actor/) {
4463 my $filter_result = ();
4465 foreach my $result (@$db_result) {
4466 if($result->{type} eq 'actor') {
4467 if($e->requestor->id != $result->{id}) {
4468 $patron = $e->retrieve_actor_user($result->{id});
4470 push(@$filter_result, $e->event);
4473 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4474 push(@$filter_result, $result);
4477 push(@$filter_result, $e->event);
4481 push(@$filter_result, $result);
4485 push(@$filter_result, $result);
4488 return $filter_result;
4494 __PACKAGE__->register_method(
4495 method => 'address_alert_test',
4496 api_name => 'open-ils.actor.address_alert.test',
4498 desc => "Tests a set of address fields to determine if they match with an address_alert",
4500 {desc => 'Authentication token', type => 'string'},
4501 {desc => 'Org Unit', type => 'number'},
4502 {desc => 'Fields', type => 'hash'},
4504 return => {desc => 'List of matching address_alerts'}
4508 sub address_alert_test {
4509 my ($self, $client, $auth, $org_unit, $fields) = @_;
4510 return [] unless $fields and grep {$_} values %$fields;
4512 my $e = new_editor(authtoken => $auth);
4513 return $e->event unless $e->checkauth;
4514 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4515 $org_unit ||= $e->requestor->ws_ou;
4517 my $alerts = $e->json_query({
4519 'actor.address_alert_matches',
4527 $$fields{post_code},
4528 $$fields{mailing_address},
4529 $$fields{billing_address}
4533 # map the json_query hashes to real objects
4535 map {$e->retrieve_actor_address_alert($_)}
4536 (map {$_->{id}} @$alerts)
4540 __PACKAGE__->register_method(
4541 method => "mark_users_contact_invalid",
4542 api_name => "open-ils.actor.invalidate.email",
4544 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",
4546 {desc => "Authentication token", type => "string"},
4547 {desc => "Patron ID", type => "number"},
4548 {desc => "Additional note text (optional)", type => "string"},
4549 {desc => "penalty org unit ID (optional)", type => "number"}
4551 return => {desc => "Event describing success or failure", type => "object"}
4555 __PACKAGE__->register_method(
4556 method => "mark_users_contact_invalid",
4557 api_name => "open-ils.actor.invalidate.day_phone",
4559 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",
4561 {desc => "Authentication token", type => "string"},
4562 {desc => "Patron ID", type => "number"},
4563 {desc => "Additional note text (optional)", type => "string"},
4564 {desc => "penalty org unit ID (optional)", type => "number"}
4566 return => {desc => "Event describing success or failure", type => "object"}
4570 __PACKAGE__->register_method(
4571 method => "mark_users_contact_invalid",
4572 api_name => "open-ils.actor.invalidate.evening_phone",
4574 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",
4576 {desc => "Authentication token", type => "string"},
4577 {desc => "Patron ID", type => "number"},
4578 {desc => "Additional note text (optional)", type => "string"},
4579 {desc => "penalty org unit ID (optional)", type => "number"}
4581 return => {desc => "Event describing success or failure", type => "object"}
4585 __PACKAGE__->register_method(
4586 method => "mark_users_contact_invalid",
4587 api_name => "open-ils.actor.invalidate.other_phone",
4589 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",
4591 {desc => "Authentication token", type => "string"},
4592 {desc => "Patron ID", type => "number"},
4593 {desc => "Additional note text (optional)", type => "string"},
4594 {desc => "penalty org unit ID (optional, default to top of org tree)",
4597 return => {desc => "Event describing success or failure", type => "object"}
4601 sub mark_users_contact_invalid {
4602 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4604 # This method invalidates an email address or a phone_number which
4605 # removes the bad email address or phone number, copying its contents
4606 # to a patron note, and institutes a standing penalty for "bad email"
4607 # or "bad phone number" which is cleared when the user is saved or
4608 # optionally only when the user is saved with an email address or
4609 # phone number (or staff manually delete the penalty).
4611 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4613 my $e = new_editor(authtoken => $auth, xact => 1);
4614 return $e->die_event unless $e->checkauth;
4616 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4617 $e, $contact_type, {usr => $patron_id},
4618 $addl_note, $penalty_ou, $e->requestor->id
4622 # Putting the following method in open-ils.actor is a bad fit, except in that
4623 # it serves an interface that lives under 'actor' in the templates directory,
4624 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4626 __PACKAGE__->register_method(
4627 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4628 method => "get_all_at_reactors_in_use",
4633 { name => 'authtoken', type => 'string' }
4636 desc => 'list of reactor names', type => 'array'
4641 sub get_all_at_reactors_in_use {
4642 my ($self, $conn, $auth) = @_;
4644 my $e = new_editor(authtoken => $auth);
4645 $e->checkauth or return $e->die_event;
4646 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4648 my $reactors = $e->json_query({
4650 atevdef => [{column => "reactor", transform => "distinct"}]
4652 from => {atevdef => {}}
4655 return $e->die_event unless ref $reactors eq "ARRAY";
4658 return [ map { $_->{reactor} } @$reactors ];
4661 __PACKAGE__->register_method(
4662 method => "filter_group_entry_crud",
4663 api_name => "open-ils.actor.filter_group_entry.crud",
4666 Provides CRUD access to filter group entry objects. These are not full accessible
4667 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4668 are not accessible via PCRUD (because they have no fields against which to link perms)
4671 {desc => "Authentication token", type => "string"},
4672 {desc => "Entry ID / Entry Object", type => "number"},
4673 {desc => "Additional note text (optional)", type => "string"},
4674 {desc => "penalty org unit ID (optional, default to top of org tree)",
4678 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4684 sub filter_group_entry_crud {
4685 my ($self, $conn, $auth, $arg) = @_;
4687 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4688 my $e = new_editor(authtoken => $auth, xact => 1);
4689 return $e->die_event unless $e->checkauth;
4695 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4696 or return $e->die_event;
4698 return $e->die_event unless $e->allowed(
4699 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4701 my $query = $arg->query;
4702 $query = $e->create_actor_search_query($query) or return $e->die_event;
4703 $arg->query($query->id);
4704 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4705 $entry->query($query);
4710 } elsif ($arg->ischanged) {
4712 my $entry = $e->retrieve_actor_search_filter_group_entry([
4715 flesh_fields => {asfge => ['grp']}
4717 ]) or return $e->die_event;
4719 return $e->die_event unless $e->allowed(
4720 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4722 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4723 $arg->query($arg->query->id);
4724 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4725 $arg->query($query);
4730 } elsif ($arg->isdeleted) {
4732 my $entry = $e->retrieve_actor_search_filter_group_entry([
4735 flesh_fields => {asfge => ['grp', 'query']}
4737 ]) or return $e->die_event;
4739 return $e->die_event unless $e->allowed(
4740 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4742 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4743 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4756 my $entry = $e->retrieve_actor_search_filter_group_entry([
4759 flesh_fields => {asfge => ['grp', 'query']}
4761 ]) or return $e->die_event;
4763 return $e->die_event unless $e->allowed(
4764 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4765 $entry->grp->owner);
4768 $entry->grp($entry->grp->id); # for consistency