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 return $U->storagereq(
1333 "open-ils.storage.actor.user.crazy_search", $search_hash,
1334 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1338 __PACKAGE__->register_method(
1339 method => "update_passwd",
1340 api_name => "open-ils.actor.user.password.update",
1342 desc => "Update the operator's password",
1344 { desc => 'Authentication token', type => 'string' },
1345 { desc => 'New password', type => 'string' },
1346 { desc => 'Current password', type => 'string' }
1348 return => {desc => '1 on success, Event on error or incorrect current password'}
1352 __PACKAGE__->register_method(
1353 method => "update_passwd",
1354 api_name => "open-ils.actor.user.username.update",
1356 desc => "Update the operator's username",
1358 { desc => 'Authentication token', type => 'string' },
1359 { desc => 'New username', type => 'string' },
1360 { desc => 'Current password', type => 'string' }
1362 return => {desc => '1 on success, Event on error or incorrect current password'}
1366 __PACKAGE__->register_method(
1367 method => "update_passwd",
1368 api_name => "open-ils.actor.user.email.update",
1370 desc => "Update the operator's email address",
1372 { desc => 'Authentication token', type => 'string' },
1373 { desc => 'New email address', type => 'string' },
1374 { desc => 'Current password', type => 'string' }
1376 return => {desc => '1 on success, Event on error or incorrect current password'}
1381 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1382 my $e = new_editor(xact=>1, authtoken=>$auth);
1383 return $e->die_event unless $e->checkauth;
1385 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1386 or return $e->die_event;
1387 my $api = $self->api_name;
1389 # make sure the original password matches the in-database password
1390 if (md5_hex($orig_pw) ne $db_user->passwd) {
1392 return new OpenILS::Event('INCORRECT_PASSWORD');
1395 if( $api =~ /password/o ) {
1397 $db_user->passwd($new_val);
1401 # if we don't clear the password, the user will be updated with
1402 # a hashed version of the hashed version of their password
1403 $db_user->clear_passwd;
1405 if( $api =~ /username/o ) {
1407 # make sure no one else has this username
1408 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1411 return new OpenILS::Event('USERNAME_EXISTS');
1413 $db_user->usrname($new_val);
1415 } elsif( $api =~ /email/o ) {
1416 $db_user->email($new_val);
1420 $e->update_actor_user($db_user) or return $e->die_event;
1423 # update the cached user to pick up these changes
1424 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1430 __PACKAGE__->register_method(
1431 method => "check_user_perms",
1432 api_name => "open-ils.actor.user.perm.check",
1433 notes => <<" NOTES");
1434 Takes a login session, user id, an org id, and an array of perm type strings. For each
1435 perm type, if the user does *not* have the given permission it is added
1436 to a list which is returned from the method. If all permissions
1437 are allowed, an empty list is returned
1438 if the logged in user does not match 'user_id', then the logged in user must
1439 have VIEW_PERMISSION priveleges.
1442 sub check_user_perms {
1443 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1445 my( $staff, $evt ) = $apputils->checkses($login_session);
1446 return $evt if $evt;
1448 if($staff->id ne $user_id) {
1449 if( $evt = $apputils->check_perms(
1450 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1456 for my $perm (@$perm_types) {
1457 if($apputils->check_perms($user_id, $org_id, $perm)) {
1458 push @not_allowed, $perm;
1462 return \@not_allowed
1465 __PACKAGE__->register_method(
1466 method => "check_user_perms2",
1467 api_name => "open-ils.actor.user.perm.check.multi_org",
1469 Checks the permissions on a list of perms and orgs for a user
1470 @param authtoken The login session key
1471 @param user_id The id of the user to check
1472 @param orgs The array of org ids
1473 @param perms The array of permission names
1474 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1475 if the logged in user does not match 'user_id', then the logged in user must
1476 have VIEW_PERMISSION priveleges.
1479 sub check_user_perms2 {
1480 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1482 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1483 $authtoken, $user_id, 'VIEW_PERMISSION' );
1484 return $evt if $evt;
1487 for my $org (@$orgs) {
1488 for my $perm (@$perms) {
1489 if($apputils->check_perms($user_id, $org, $perm)) {
1490 push @not_allowed, [ $org, $perm ];
1495 return \@not_allowed
1499 __PACKAGE__->register_method(
1500 method => 'check_user_perms3',
1501 api_name => 'open-ils.actor.user.perm.highest_org',
1503 Returns the highest org unit id at which a user has a given permission
1504 If the requestor does not match the target user, the requestor must have
1505 'VIEW_PERMISSION' rights at the home org unit of the target user
1506 @param authtoken The login session key
1507 @param userid The id of the user in question
1508 @param perm The permission to check
1509 @return The org unit highest in the org tree within which the user has
1510 the requested permission
1513 sub check_user_perms3 {
1514 my($self, $client, $authtoken, $user_id, $perm) = @_;
1515 my $e = new_editor(authtoken=>$authtoken);
1516 return $e->event unless $e->checkauth;
1518 my $tree = $U->get_org_tree();
1520 unless($e->requestor->id == $user_id) {
1521 my $user = $e->retrieve_actor_user($user_id)
1522 or return $e->event;
1523 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1524 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1527 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1530 __PACKAGE__->register_method(
1531 method => 'user_has_work_perm_at',
1532 api_name => 'open-ils.actor.user.has_work_perm_at',
1536 Returns a set of org unit IDs which represent the highest orgs in
1537 the org tree where the user has the requested permission. The
1538 purpose of this method is to return the smallest set of org units
1539 which represent the full expanse of the user's ability to perform
1540 the requested action. The user whose perms this method should
1541 check is implied by the authtoken. /,
1543 {desc => 'authtoken', type => 'string'},
1544 {desc => 'permission name', type => 'string'},
1545 {desc => q/user id, optional. If present, check perms for
1546 this user instead of the logged in user/, type => 'number'},
1548 return => {desc => 'An array of org IDs'}
1552 sub user_has_work_perm_at {
1553 my($self, $conn, $auth, $perm, $user_id) = @_;
1554 my $e = new_editor(authtoken=>$auth);
1555 return $e->event unless $e->checkauth;
1556 if(defined $user_id) {
1557 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1558 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1560 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1563 __PACKAGE__->register_method(
1564 method => 'user_has_work_perm_at_batch',
1565 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1569 sub user_has_work_perm_at_batch {
1570 my($self, $conn, $auth, $perms, $user_id) = @_;
1571 my $e = new_editor(authtoken=>$auth);
1572 return $e->event unless $e->checkauth;
1573 if(defined $user_id) {
1574 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1575 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1578 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1584 __PACKAGE__->register_method(
1585 method => 'check_user_perms4',
1586 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1588 Returns the highest org unit id at which a user has a given permission
1589 If the requestor does not match the target user, the requestor must have
1590 'VIEW_PERMISSION' rights at the home org unit of the target user
1591 @param authtoken The login session key
1592 @param userid The id of the user in question
1593 @param perms An array of perm names to check
1594 @return An array of orgId's representing the org unit
1595 highest in the org tree within which the user has the requested permission
1596 The arrah of orgId's has matches the order of the perms array
1599 sub check_user_perms4 {
1600 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1602 my( $staff, $target, $org, $evt );
1604 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1605 $authtoken, $userid, 'VIEW_PERMISSION' );
1606 return $evt if $evt;
1609 return [] unless ref($perms);
1610 my $tree = $U->get_org_tree();
1612 for my $p (@$perms) {
1613 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1619 __PACKAGE__->register_method(
1620 method => "user_fines_summary",
1621 api_name => "open-ils.actor.user.fines.summary",
1624 desc => 'Returns a short summary of the users total open fines, ' .
1625 'excluding voided fines Params are login_session, user_id' ,
1627 {desc => 'Authentication token', type => 'string'},
1628 {desc => 'User ID', type => 'string'} # number?
1631 desc => "a 'mous' object, event on error",
1636 sub user_fines_summary {
1637 my( $self, $client, $auth, $user_id ) = @_;
1639 my $e = new_editor(authtoken=>$auth);
1640 return $e->event unless $e->checkauth;
1642 if( $user_id ne $e->requestor->id ) {
1643 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1644 return $e->event unless
1645 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1648 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1652 __PACKAGE__->register_method(
1653 method => "user_opac_vitals",
1654 api_name => "open-ils.actor.user.opac.vital_stats",
1658 desc => 'Returns a short summary of the users vital stats, including ' .
1659 'identification information, accumulated balance, number of holds, ' .
1660 'and current open circulation stats' ,
1662 {desc => 'Authentication token', type => 'string'},
1663 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1666 desc => "An object with four properties: user, fines, checkouts and holds."
1671 sub user_opac_vitals {
1672 my( $self, $client, $auth, $user_id ) = @_;
1674 my $e = new_editor(authtoken=>$auth);
1675 return $e->event unless $e->checkauth;
1677 $user_id ||= $e->requestor->id;
1679 my $user = $e->retrieve_actor_user( $user_id );
1682 ->method_lookup('open-ils.actor.user.fines.summary')
1683 ->run($auth => $user_id);
1684 return $fines if (defined($U->event_code($fines)));
1687 $fines = new Fieldmapper::money::open_user_summary ();
1688 $fines->balance_owed(0.00);
1689 $fines->total_owed(0.00);
1690 $fines->total_paid(0.00);
1691 $fines->usr($user_id);
1695 ->method_lookup('open-ils.actor.user.hold_requests.count')
1696 ->run($auth => $user_id);
1697 return $holds if (defined($U->event_code($holds)));
1700 ->method_lookup('open-ils.actor.user.checked_out.count')
1701 ->run($auth => $user_id);
1702 return $out if (defined($U->event_code($out)));
1704 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1708 first_given_name => $user->first_given_name,
1709 second_given_name => $user->second_given_name,
1710 family_name => $user->family_name,
1711 alias => $user->alias,
1712 usrname => $user->usrname
1714 fines => $fines->to_bare_hash,
1721 ##### a small consolidation of related method registrations
1722 my $common_params = [
1723 { desc => 'Authentication token', type => 'string' },
1724 { desc => 'User ID', type => 'string' },
1725 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1726 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1729 'open-ils.actor.user.transactions' => '',
1730 'open-ils.actor.user.transactions.fleshed' => '',
1731 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1732 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1733 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1734 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1737 foreach (keys %methods) {
1739 method => "user_transactions",
1742 desc => 'For a given user, retrieve a list of '
1743 . (/\.fleshed/ ? 'fleshed ' : '')
1744 . 'transactions' . $methods{$_}
1745 . ' optionally limited to transactions of a given type.',
1746 params => $common_params,
1748 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1749 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1753 $args{authoritative} = 1;
1754 __PACKAGE__->register_method(%args);
1757 # Now for the counts
1759 'open-ils.actor.user.transactions.count' => '',
1760 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1761 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1764 foreach (keys %methods) {
1766 method => "user_transactions",
1769 desc => 'For a given user, retrieve a count of open '
1770 . 'transactions' . $methods{$_}
1771 . ' optionally limited to transactions of a given type.',
1772 params => $common_params,
1773 return => { desc => "Integer count of transactions, or event on error" }
1776 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1777 __PACKAGE__->register_method(%args);
1780 __PACKAGE__->register_method(
1781 method => "user_transactions",
1782 api_name => "open-ils.actor.user.transactions.have_balance.total",
1785 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1786 . ' optionally limited to transactions of a given type.',
1787 params => $common_params,
1788 return => { desc => "Decimal balance value, or event on error" }
1793 sub user_transactions {
1794 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1797 my $e = new_editor(authtoken => $auth);
1798 return $e->event unless $e->checkauth;
1800 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1802 return $e->event unless
1803 $e->requestor->id == $user_id or
1804 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1806 my $api = $self->api_name();
1808 my $filter = ($api =~ /have_balance/o) ?
1809 { 'balance_owed' => { '<>' => 0 } }:
1810 { 'total_owed' => { '>' => 0 } };
1812 my $method = 'open-ils.actor.user.transactions.history.still_open';
1813 $method = "$method.authoritative" if $api =~ /authoritative/;
1814 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1816 if($api =~ /total/o) {
1818 $total += $_->balance_owed for @$trans;
1822 ($api =~ /count/o ) and return scalar @$trans;
1823 ($api !~ /fleshed/o) and return $trans;
1826 for my $t (@$trans) {
1828 if( $t->xact_type ne 'circulation' ) {
1829 push @resp, {transaction => $t};
1833 my $circ_data = flesh_circ($e, $t->id);
1834 push @resp, {transaction => $t, %$circ_data};
1841 __PACKAGE__->register_method(
1842 method => "user_transaction_retrieve",
1843 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1846 notes => "Returns a fleshed transaction record"
1849 __PACKAGE__->register_method(
1850 method => "user_transaction_retrieve",
1851 api_name => "open-ils.actor.user.transaction.retrieve",
1854 notes => "Returns a transaction record"
1857 sub user_transaction_retrieve {
1858 my($self, $client, $auth, $bill_id) = @_;
1860 my $e = new_editor(authtoken => $auth);
1861 return $e->event unless $e->checkauth;
1863 my $trans = $e->retrieve_money_billable_transaction_summary(
1864 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1866 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1868 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1870 return $trans unless $self->api_name =~ /flesh/;
1871 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1873 my $circ_data = flesh_circ($e, $trans->id, 1);
1875 return {transaction => $trans, %$circ_data};
1880 my $circ_id = shift;
1881 my $flesh_copy = shift;
1883 my $circ = $e->retrieve_action_circulation([
1887 circ => ['target_copy'],
1888 acp => ['call_number'],
1895 my $copy = $circ->target_copy;
1897 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1898 $mods = new Fieldmapper::metabib::virtual_record;
1899 $mods->doc_id(OILS_PRECAT_RECORD);
1900 $mods->title($copy->dummy_title);
1901 $mods->author($copy->dummy_author);
1904 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1908 $circ->target_copy($circ->target_copy->id);
1909 $copy->call_number($copy->call_number->id);
1911 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1915 __PACKAGE__->register_method(
1916 method => "hold_request_count",
1917 api_name => "open-ils.actor.user.hold_requests.count",
1920 notes => 'Returns hold ready/total counts'
1923 sub hold_request_count {
1924 my( $self, $client, $authtoken, $user_id ) = @_;
1925 my $e = new_editor(authtoken => $authtoken);
1926 return $e->event unless $e->checkauth;
1928 $user_id = $e->requestor->id unless defined $user_id;
1930 if($e->requestor->id ne $user_id) {
1931 my $user = $e->retrieve_actor_user($user_id);
1932 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1935 my $holds = $e->json_query({
1936 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1940 fulfillment_time => {"=" => undef },
1941 cancel_time => undef,
1946 total => scalar(@$holds),
1949 $_->{current_shelf_lib} and # avoid undef warnings
1950 $_->{pickup_lib} eq $_->{current_shelf_lib}
1956 __PACKAGE__->register_method(
1957 method => "checked_out",
1958 api_name => "open-ils.actor.user.checked_out",
1962 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1963 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1964 . "(i.e., outstanding balance or some other pending action on the circ). "
1965 . "The .count method also includes a 'total' field which sums all open circs.",
1967 { desc => 'Authentication Token', type => 'string'},
1968 { desc => 'User ID', type => 'string'},
1971 desc => 'Returns event on error, or an object with ID lists, like: '
1972 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1977 __PACKAGE__->register_method(
1978 method => "checked_out",
1979 api_name => "open-ils.actor.user.checked_out.count",
1982 signature => q/@see open-ils.actor.user.checked_out/
1986 my( $self, $conn, $auth, $userid ) = @_;
1988 my $e = new_editor(authtoken=>$auth);
1989 return $e->event unless $e->checkauth;
1991 if( $userid ne $e->requestor->id ) {
1992 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1993 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1995 # see if there is a friend link allowing circ.view perms
1996 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1997 $e, $userid, $e->requestor->id, 'circ.view');
1998 return $e->event unless $allowed;
2002 my $count = $self->api_name =~ /count/;
2003 return _checked_out( $count, $e, $userid );
2007 my( $iscount, $e, $userid ) = @_;
2013 claims_returned => [],
2016 my $meth = 'retrieve_action_open_circ_';
2024 claims_returned => 0,
2031 my $data = $e->$meth($userid);
2035 $result{$_} += $data->$_() for (keys %result);
2036 $result{total} += $data->$_() for (keys %result);
2038 for my $k (keys %result) {
2039 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2049 __PACKAGE__->register_method(
2050 method => "checked_in_with_fines",
2051 api_name => "open-ils.actor.user.checked_in_with_fines",
2054 signature => q/@see open-ils.actor.user.checked_out/
2057 sub checked_in_with_fines {
2058 my( $self, $conn, $auth, $userid ) = @_;
2060 my $e = new_editor(authtoken=>$auth);
2061 return $e->event unless $e->checkauth;
2063 if( $userid ne $e->requestor->id ) {
2064 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2067 # money is owed on these items and they are checked in
2068 my $open = $e->search_action_circulation(
2071 xact_finish => undef,
2072 checkin_time => { "!=" => undef },
2077 my( @lost, @cr, @lo );
2078 for my $c (@$open) {
2079 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2080 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2081 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2086 claims_returned => \@cr,
2087 long_overdue => \@lo
2093 my ($api, $desc, $auth) = @_;
2094 $desc = $desc ? (" " . $desc) : '';
2095 my $ids = ($api =~ /ids$/) ? 1 : 0;
2098 method => "user_transaction_history",
2099 api_name => "open-ils.actor.user.transactions.$api",
2101 desc => "For a given User ID, returns a list of billable transaction" .
2102 ($ids ? " id" : '') .
2103 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2104 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2106 {desc => 'Authentication token', type => 'string'},
2107 {desc => 'User ID', type => 'number'},
2108 {desc => 'Transaction type (optional)', type => 'number'},
2109 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2112 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2116 $auth and push @sig, (authoritative => 1);
2120 my %auth_hist_methods = (
2122 'history.have_charge' => 'that have an initial charge',
2123 'history.still_open' => 'that are not finished',
2124 'history.have_balance' => 'that have a balance',
2125 'history.have_bill' => 'that have billings',
2126 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2127 'history.have_payment' => 'that have at least 1 payment',
2130 foreach (keys %auth_hist_methods) {
2131 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2132 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2133 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2136 sub user_transaction_history {
2137 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2141 my $e = new_editor(authtoken=>$auth);
2142 return $e->die_event unless $e->checkauth;
2144 if ($e->requestor->id ne $userid) {
2145 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2148 my $api = $self->api_name;
2149 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2151 if(defined($type)) {
2152 $filter->{'xact_type'} = $type;
2155 if($api =~ /have_bill_or_payment/o) {
2157 # transactions that have a non-zero sum across all billings or at least 1 payment
2158 $filter->{'-or'} = {
2159 'balance_owed' => { '<>' => 0 },
2160 'last_payment_ts' => { '<>' => undef }
2163 } elsif($api =~ /have_payment/) {
2165 $filter->{last_payment_ts} ||= {'<>' => undef};
2167 } elsif( $api =~ /have_balance/o) {
2169 # transactions that have a non-zero overall balance
2170 $filter->{'balance_owed'} = { '<>' => 0 };
2172 } elsif( $api =~ /have_charge/o) {
2174 # transactions that have at least 1 billing, regardless of whether it was voided
2175 $filter->{'last_billing_ts'} = { '<>' => undef };
2177 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2179 # transactions that have non-zero sum across all billings. This will exclude
2180 # xacts where all billings have been voided
2181 $filter->{'total_owed'} = { '<>' => 0 };
2184 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2185 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2186 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2188 my $mbts = $e->search_money_billable_transaction_summary(
2189 [ { usr => $userid, @xact_finish, %$filter },
2194 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2195 return $mbts unless $api =~ /fleshed/;
2198 for my $t (@$mbts) {
2200 if( $t->xact_type ne 'circulation' ) {
2201 push @resp, {transaction => $t};
2205 my $circ_data = flesh_circ($e, $t->id);
2206 push @resp, {transaction => $t, %$circ_data};
2214 __PACKAGE__->register_method(
2215 method => "user_perms",
2216 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2218 notes => "Returns a list of permissions"
2222 my( $self, $client, $authtoken, $user ) = @_;
2224 my( $staff, $evt ) = $apputils->checkses($authtoken);
2225 return $evt if $evt;
2227 $user ||= $staff->id;
2229 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2233 return $apputils->simple_scalar_request(
2235 "open-ils.storage.permission.user_perms.atomic",
2239 __PACKAGE__->register_method(
2240 method => "retrieve_perms",
2241 api_name => "open-ils.actor.permissions.retrieve",
2242 notes => "Returns a list of permissions"
2244 sub retrieve_perms {
2245 my( $self, $client ) = @_;
2246 return $apputils->simple_scalar_request(
2248 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2249 { id => { '!=' => undef } }
2253 __PACKAGE__->register_method(
2254 method => "retrieve_groups",
2255 api_name => "open-ils.actor.groups.retrieve",
2256 notes => "Returns a list of user groups"
2258 sub retrieve_groups {
2259 my( $self, $client ) = @_;
2260 return new_editor()->retrieve_all_permission_grp_tree();
2263 __PACKAGE__->register_method(
2264 method => "retrieve_org_address",
2265 api_name => "open-ils.actor.org_unit.address.retrieve",
2266 notes => <<' NOTES');
2267 Returns an org_unit address by ID
2268 @param An org_address ID
2270 sub retrieve_org_address {
2271 my( $self, $client, $id ) = @_;
2272 return $apputils->simple_scalar_request(
2274 "open-ils.cstore.direct.actor.org_address.retrieve",
2279 __PACKAGE__->register_method(
2280 method => "retrieve_groups_tree",
2281 api_name => "open-ils.actor.groups.tree.retrieve",
2282 notes => "Returns a list of user groups"
2285 sub retrieve_groups_tree {
2286 my( $self, $client ) = @_;
2287 return new_editor()->search_permission_grp_tree(
2292 flesh_fields => { pgt => ["children"] },
2293 order_by => { pgt => 'name'}
2300 __PACKAGE__->register_method(
2301 method => "add_user_to_groups",
2302 api_name => "open-ils.actor.user.set_groups",
2303 notes => "Adds a user to one or more permission groups"
2306 sub add_user_to_groups {
2307 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2309 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2310 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2311 return $evt if $evt;
2313 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2314 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2315 return $evt if $evt;
2317 $apputils->simplereq(
2319 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2321 for my $group (@$groups) {
2322 my $link = Fieldmapper::permission::usr_grp_map->new;
2324 $link->usr($userid);
2326 my $id = $apputils->simplereq(
2328 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2334 __PACKAGE__->register_method(
2335 method => "get_user_perm_groups",
2336 api_name => "open-ils.actor.user.get_groups",
2337 notes => "Retrieve a user's permission groups."
2341 sub get_user_perm_groups {
2342 my( $self, $client, $authtoken, $userid ) = @_;
2344 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2345 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2346 return $evt if $evt;
2348 return $apputils->simplereq(
2350 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2354 __PACKAGE__->register_method(
2355 method => "get_user_work_ous",
2356 api_name => "open-ils.actor.user.get_work_ous",
2357 notes => "Retrieve a user's work org units."
2360 __PACKAGE__->register_method(
2361 method => "get_user_work_ous",
2362 api_name => "open-ils.actor.user.get_work_ous.ids",
2363 notes => "Retrieve a user's work org units."
2366 sub get_user_work_ous {
2367 my( $self, $client, $auth, $userid ) = @_;
2368 my $e = new_editor(authtoken=>$auth);
2369 return $e->event unless $e->checkauth;
2370 $userid ||= $e->requestor->id;
2372 if($e->requestor->id != $userid) {
2373 my $user = $e->retrieve_actor_user($userid)
2374 or return $e->event;
2375 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2378 return $e->search_permission_usr_work_ou_map({usr => $userid})
2379 unless $self->api_name =~ /.ids$/;
2381 # client just wants a list of org IDs
2382 return $U->get_user_work_ou_ids($e, $userid);
2387 __PACKAGE__->register_method(
2388 method => 'register_workstation',
2389 api_name => 'open-ils.actor.workstation.register.override',
2390 signature => q/@see open-ils.actor.workstation.register/
2393 __PACKAGE__->register_method(
2394 method => 'register_workstation',
2395 api_name => 'open-ils.actor.workstation.register',
2397 Registers a new workstion in the system
2398 @param authtoken The login session key
2399 @param name The name of the workstation id
2400 @param owner The org unit that owns this workstation
2401 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2402 if the name is already in use.
2406 sub register_workstation {
2407 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2409 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2410 return $e->die_event unless $e->checkauth;
2411 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2412 my $existing = $e->search_actor_workstation({name => $name})->[0];
2413 $oargs = { all => 1 } unless defined $oargs;
2417 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2418 # workstation with the given name exists.
2420 if($owner ne $existing->owning_lib) {
2421 # if necessary, update the owning_lib of the workstation
2423 $logger->info("changing owning lib of workstation ".$existing->id.
2424 " from ".$existing->owning_lib." to $owner");
2425 return $e->die_event unless
2426 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2428 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2430 $existing->owning_lib($owner);
2431 return $e->die_event unless $e->update_actor_workstation($existing);
2437 "attempt to register an existing workstation. returning existing ID");
2440 return $existing->id;
2443 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2447 my $ws = Fieldmapper::actor::workstation->new;
2448 $ws->owning_lib($owner);
2450 $e->create_actor_workstation($ws) or return $e->die_event;
2452 return $ws->id; # note: editor sets the id on the new object for us
2455 __PACKAGE__->register_method(
2456 method => 'workstation_list',
2457 api_name => 'open-ils.actor.workstation.list',
2459 Returns a list of workstations registered at the given location
2460 @param authtoken The login session key
2461 @param ids A list of org_unit.id's for the workstation owners
2465 sub workstation_list {
2466 my( $self, $conn, $authtoken, @orgs ) = @_;
2468 my $e = new_editor(authtoken=>$authtoken);
2469 return $e->event unless $e->checkauth;
2474 unless $e->allowed('REGISTER_WORKSTATION', $o);
2475 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2481 __PACKAGE__->register_method(
2482 method => 'fetch_patron_note',
2483 api_name => 'open-ils.actor.note.retrieve.all',
2486 Returns a list of notes for a given user
2487 Requestor must have VIEW_USER permission if pub==false and
2488 @param authtoken The login session key
2489 @param args Hash of params including
2490 patronid : the patron's id
2491 pub : true if retrieving only public notes
2495 sub fetch_patron_note {
2496 my( $self, $conn, $authtoken, $args ) = @_;
2497 my $patronid = $$args{patronid};
2499 my($reqr, $evt) = $U->checkses($authtoken);
2500 return $evt if $evt;
2503 ($patron, $evt) = $U->fetch_user($patronid);
2504 return $evt if $evt;
2507 if( $patronid ne $reqr->id ) {
2508 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2509 return $evt if $evt;
2511 return $U->cstorereq(
2512 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2513 { usr => $patronid, pub => 't' } );
2516 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2517 return $evt if $evt;
2519 return $U->cstorereq(
2520 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2523 __PACKAGE__->register_method(
2524 method => 'create_user_note',
2525 api_name => 'open-ils.actor.note.create',
2527 Creates a new note for the given user
2528 @param authtoken The login session key
2529 @param note The note object
2532 sub create_user_note {
2533 my( $self, $conn, $authtoken, $note ) = @_;
2534 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2535 return $e->die_event unless $e->checkauth;
2537 my $user = $e->retrieve_actor_user($note->usr)
2538 or return $e->die_event;
2540 return $e->die_event unless
2541 $e->allowed('UPDATE_USER',$user->home_ou);
2543 $note->creator($e->requestor->id);
2544 $e->create_actor_usr_note($note) or return $e->die_event;
2550 __PACKAGE__->register_method(
2551 method => 'delete_user_note',
2552 api_name => 'open-ils.actor.note.delete',
2554 Deletes a note for the given user
2555 @param authtoken The login session key
2556 @param noteid The note id
2559 sub delete_user_note {
2560 my( $self, $conn, $authtoken, $noteid ) = @_;
2562 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2563 return $e->die_event unless $e->checkauth;
2564 my $note = $e->retrieve_actor_usr_note($noteid)
2565 or return $e->die_event;
2566 my $user = $e->retrieve_actor_user($note->usr)
2567 or return $e->die_event;
2568 return $e->die_event unless
2569 $e->allowed('UPDATE_USER', $user->home_ou);
2571 $e->delete_actor_usr_note($note) or return $e->die_event;
2577 __PACKAGE__->register_method(
2578 method => 'update_user_note',
2579 api_name => 'open-ils.actor.note.update',
2581 @param authtoken The login session key
2582 @param note The note
2586 sub update_user_note {
2587 my( $self, $conn, $auth, $note ) = @_;
2588 my $e = new_editor(authtoken=>$auth, xact=>1);
2589 return $e->die_event unless $e->checkauth;
2590 my $patron = $e->retrieve_actor_user($note->usr)
2591 or return $e->die_event;
2592 return $e->die_event unless
2593 $e->allowed('UPDATE_USER', $patron->home_ou);
2594 $e->update_actor_user_note($note)
2595 or return $e->die_event;
2602 __PACKAGE__->register_method(
2603 method => 'create_closed_date',
2604 api_name => 'open-ils.actor.org_unit.closed_date.create',
2606 Creates a new closing entry for the given org_unit
2607 @param authtoken The login session key
2608 @param note The closed_date object
2611 sub create_closed_date {
2612 my( $self, $conn, $authtoken, $cd ) = @_;
2614 my( $user, $evt ) = $U->checkses($authtoken);
2615 return $evt if $evt;
2617 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2618 return $evt if $evt;
2620 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2622 my $id = $U->storagereq(
2623 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2624 return $U->DB_UPDATE_FAILED($cd) unless $id;
2629 __PACKAGE__->register_method(
2630 method => 'delete_closed_date',
2631 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2633 Deletes a closing entry for the given org_unit
2634 @param authtoken The login session key
2635 @param noteid The close_date id
2638 sub delete_closed_date {
2639 my( $self, $conn, $authtoken, $cd ) = @_;
2641 my( $user, $evt ) = $U->checkses($authtoken);
2642 return $evt if $evt;
2645 ($cd_obj, $evt) = fetch_closed_date($cd);
2646 return $evt if $evt;
2648 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2649 return $evt if $evt;
2651 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2653 my $stat = $U->storagereq(
2654 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2655 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2660 __PACKAGE__->register_method(
2661 method => 'usrname_exists',
2662 api_name => 'open-ils.actor.username.exists',
2664 desc => 'Check if a username is already taken (by an undeleted patron)',
2666 {desc => 'Authentication token', type => 'string'},
2667 {desc => 'Username', type => 'string'}
2670 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2675 sub usrname_exists {
2676 my( $self, $conn, $auth, $usrname ) = @_;
2677 my $e = new_editor(authtoken=>$auth);
2678 return $e->event unless $e->checkauth;
2679 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2680 return $$a[0] if $a and @$a;
2684 __PACKAGE__->register_method(
2685 method => 'barcode_exists',
2686 api_name => 'open-ils.actor.barcode.exists',
2688 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2691 sub barcode_exists {
2692 my( $self, $conn, $auth, $barcode ) = @_;
2693 my $e = new_editor(authtoken=>$auth);
2694 return $e->event unless $e->checkauth;
2695 my $card = $e->search_actor_card({barcode => $barcode});
2701 #return undef unless @$card;
2702 #return $card->[0]->usr;
2706 __PACKAGE__->register_method(
2707 method => 'retrieve_net_levels',
2708 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2711 sub retrieve_net_levels {
2712 my( $self, $conn, $auth ) = @_;
2713 my $e = new_editor(authtoken=>$auth);
2714 return $e->event unless $e->checkauth;
2715 return $e->retrieve_all_config_net_access_level();
2718 # Retain the old typo API name just in case
2719 __PACKAGE__->register_method(
2720 method => 'fetch_org_by_shortname',
2721 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2723 __PACKAGE__->register_method(
2724 method => 'fetch_org_by_shortname',
2725 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2727 sub fetch_org_by_shortname {
2728 my( $self, $conn, $sname ) = @_;
2729 my $e = new_editor();
2730 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2731 return $e->event unless $org;
2736 __PACKAGE__->register_method(
2737 method => 'session_home_lib',
2738 api_name => 'open-ils.actor.session.home_lib',
2741 sub session_home_lib {
2742 my( $self, $conn, $auth ) = @_;
2743 my $e = new_editor(authtoken=>$auth);
2744 return undef unless $e->checkauth;
2745 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2746 return $org->shortname;
2749 __PACKAGE__->register_method(
2750 method => 'session_safe_token',
2751 api_name => 'open-ils.actor.session.safe_token',
2753 Returns a hashed session ID that is safe for export to the world.
2754 This safe token will expire after 1 hour of non-use.
2755 @param auth Active authentication token
2759 sub session_safe_token {
2760 my( $self, $conn, $auth ) = @_;
2761 my $e = new_editor(authtoken=>$auth);
2762 return undef unless $e->checkauth;
2764 my $safe_token = md5_hex($auth);
2766 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2768 # Add more like the following if needed...
2770 "safe-token-home_lib-shortname-$safe_token",
2771 $e->retrieve_actor_org_unit(
2772 $e->requestor->home_ou
2781 __PACKAGE__->register_method(
2782 method => 'safe_token_home_lib',
2783 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2785 Returns the home library shortname from the session
2786 asscociated with a safe token from generated by
2787 open-ils.actor.session.safe_token.
2788 @param safe_token Active safe token
2792 sub safe_token_home_lib {
2793 my( $self, $conn, $safe_token ) = @_;
2795 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2796 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2800 __PACKAGE__->register_method(
2801 method => "update_penalties",
2802 api_name => "open-ils.actor.user.penalties.update"
2805 sub update_penalties {
2806 my($self, $conn, $auth, $user_id) = @_;
2807 my $e = new_editor(authtoken=>$auth, xact => 1);
2808 return $e->die_event unless $e->checkauth;
2809 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2810 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2811 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2812 return $evt if $evt;
2818 __PACKAGE__->register_method(
2819 method => "apply_penalty",
2820 api_name => "open-ils.actor.user.penalty.apply"
2824 my($self, $conn, $auth, $penalty) = @_;
2826 my $e = new_editor(authtoken=>$auth, xact => 1);
2827 return $e->die_event unless $e->checkauth;
2829 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2830 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2832 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2835 (defined $ptype->org_depth) ?
2836 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2839 $penalty->org_unit($ctx_org);
2840 $penalty->staff($e->requestor->id);
2841 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2844 return $penalty->id;
2847 __PACKAGE__->register_method(
2848 method => "remove_penalty",
2849 api_name => "open-ils.actor.user.penalty.remove"
2852 sub remove_penalty {
2853 my($self, $conn, $auth, $penalty) = @_;
2854 my $e = new_editor(authtoken=>$auth, xact => 1);
2855 return $e->die_event unless $e->checkauth;
2856 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2857 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2859 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2864 __PACKAGE__->register_method(
2865 method => "update_penalty_note",
2866 api_name => "open-ils.actor.user.penalty.note.update"
2869 sub update_penalty_note {
2870 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2871 my $e = new_editor(authtoken=>$auth, xact => 1);
2872 return $e->die_event unless $e->checkauth;
2873 for my $penalty_id (@$penalty_ids) {
2874 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2875 if (! $penalty ) { return $e->die_event; }
2876 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2877 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2879 $penalty->note( $note ); $penalty->ischanged( 1 );
2881 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2887 __PACKAGE__->register_method(
2888 method => "ranged_penalty_thresholds",
2889 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2893 sub ranged_penalty_thresholds {
2894 my($self, $conn, $auth, $context_org) = @_;
2895 my $e = new_editor(authtoken=>$auth);
2896 return $e->event unless $e->checkauth;
2897 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2898 my $list = $e->search_permission_grp_penalty_threshold([
2899 {org_unit => $U->get_org_ancestors($context_org)},
2900 {order_by => {pgpt => 'id'}}
2902 $conn->respond($_) for @$list;
2908 __PACKAGE__->register_method(
2909 method => "user_retrieve_fleshed_by_id",
2911 api_name => "open-ils.actor.user.fleshed.retrieve",
2914 sub user_retrieve_fleshed_by_id {
2915 my( $self, $client, $auth, $user_id, $fields ) = @_;
2916 my $e = new_editor(authtoken => $auth);
2917 return $e->event unless $e->checkauth;
2919 if( $e->requestor->id != $user_id ) {
2920 return $e->event unless $e->allowed('VIEW_USER');
2926 "standing_penalties",
2932 return new_flesh_user($user_id, $fields, $e);
2936 sub new_flesh_user {
2939 my $fields = shift || [];
2942 my $fetch_penalties = 0;
2943 if(grep {$_ eq 'standing_penalties'} @$fields) {
2944 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2945 $fetch_penalties = 1;
2948 my $fetch_usr_act = 0;
2949 if(grep {$_ eq 'usr_activity'} @$fields) {
2950 $fields = [grep {$_ ne 'usr_activity'} @$fields];
2954 my $user = $e->retrieve_actor_user(
2959 "flesh_fields" => { "au" => $fields }
2962 ) or return $e->die_event;
2965 if( grep { $_ eq 'addresses' } @$fields ) {
2967 $user->addresses([]) unless @{$user->addresses};
2968 # don't expose "replaced" addresses by default
2969 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2971 if( ref $user->billing_address ) {
2972 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2973 push( @{$user->addresses}, $user->billing_address );
2977 if( ref $user->mailing_address ) {
2978 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2979 push( @{$user->addresses}, $user->mailing_address );
2984 if($fetch_penalties) {
2985 # grab the user penalties ranged for this location
2986 $user->standing_penalties(
2987 $e->search_actor_user_standing_penalty([
2990 {stop_date => undef},
2991 {stop_date => {'>' => 'now'}}
2993 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
2996 flesh_fields => {ausp => ['standing_penalty']}
3002 # retrieve the most recent usr_activity entry
3003 if ($fetch_usr_act) {
3005 # max number to return for simple patron fleshing
3006 my $limit = $U->ou_ancestor_setting_value(
3007 $e->requestor->ws_ou,
3008 'circ.patron.usr_activity_retrieve.max');
3012 flesh_fields => {auact => ['etype']},
3013 order_by => {auact => 'event_time DESC'},
3016 # 0 == none, <0 == return all
3017 $limit = 1 unless defined $limit;
3018 $opts->{limit} = $limit if $limit > 0;
3020 $user->usr_activity(
3022 [] : # skip the DB call
3023 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3028 $user->clear_passwd();
3035 __PACKAGE__->register_method(
3036 method => "user_retrieve_parts",
3037 api_name => "open-ils.actor.user.retrieve.parts",
3040 sub user_retrieve_parts {
3041 my( $self, $client, $auth, $user_id, $fields ) = @_;
3042 my $e = new_editor(authtoken => $auth);
3043 return $e->event unless $e->checkauth;
3044 $user_id ||= $e->requestor->id;
3045 if( $e->requestor->id != $user_id ) {
3046 return $e->event unless $e->allowed('VIEW_USER');
3049 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3050 push(@resp, $user->$_()) for(@$fields);
3056 __PACKAGE__->register_method(
3057 method => 'user_opt_in_enabled',
3058 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3059 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3062 sub user_opt_in_enabled {
3063 my($self, $conn) = @_;
3064 my $sc = OpenSRF::Utils::SettingsClient->new;
3065 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3070 __PACKAGE__->register_method(
3071 method => 'user_opt_in_at_org',
3072 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3074 @param $auth The auth token
3075 @param user_id The ID of the user to test
3076 @return 1 if the user has opted in at the specified org,
3077 event on error, and 0 otherwise. /
3079 sub user_opt_in_at_org {
3080 my($self, $conn, $auth, $user_id) = @_;
3082 # see if we even need to enforce the opt-in value
3083 return 1 unless user_opt_in_enabled($self);
3085 my $e = new_editor(authtoken => $auth);
3086 return $e->event unless $e->checkauth;
3088 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3089 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3091 my $ws_org = $e->requestor->ws_ou;
3092 # user is automatically opted-in if they are from the local org
3093 return 1 if $user->home_ou eq $ws_org;
3095 # get the boundary setting
3096 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3098 # auto opt in if user falls within the opt boundary
3099 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3101 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3103 my $vals = $e->search_actor_usr_org_unit_opt_in(
3104 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3110 __PACKAGE__->register_method(
3111 method => 'create_user_opt_in_at_org',
3112 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3114 @param $auth The auth token
3115 @param user_id The ID of the user to test
3116 @return The ID of the newly created object, event on error./
3119 sub create_user_opt_in_at_org {
3120 my($self, $conn, $auth, $user_id, $org_id) = @_;
3122 my $e = new_editor(authtoken => $auth, xact=>1);
3123 return $e->die_event unless $e->checkauth;
3125 # if a specific org unit wasn't passed in, get one based on the defaults;
3127 my $wsou = $e->requestor->ws_ou;
3128 # get the default opt depth
3129 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3130 # get the org unit at that depth
3131 my $org = $e->json_query({
3132 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3133 $org_id = $org->{id};
3136 # fall back to the workstation OU, the pre-opt-in-boundary way
3137 $org_id = $e->requestor->ws_ou;
3140 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3141 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3143 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3145 $opt_in->org_unit($org_id);
3146 $opt_in->usr($user_id);
3147 $opt_in->staff($e->requestor->id);
3148 $opt_in->opt_in_ts('now');
3149 $opt_in->opt_in_ws($e->requestor->wsid);
3151 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3152 or return $e->die_event;
3160 __PACKAGE__->register_method (
3161 method => 'retrieve_org_hours',
3162 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3164 Returns the hours of operation for a specified org unit
3165 @param authtoken The login session key
3166 @param org_id The org_unit ID
3170 sub retrieve_org_hours {
3171 my($self, $conn, $auth, $org_id) = @_;
3172 my $e = new_editor(authtoken => $auth);
3173 return $e->die_event unless $e->checkauth;
3174 $org_id ||= $e->requestor->ws_ou;
3175 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3179 __PACKAGE__->register_method (
3180 method => 'verify_user_password',
3181 api_name => 'open-ils.actor.verify_user_password',
3183 Given a barcode or username and the MD5 encoded password,
3184 returns 1 if the password is correct. Returns 0 otherwise.
3188 sub verify_user_password {
3189 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3190 my $e = new_editor(authtoken => $auth);
3191 return $e->die_event unless $e->checkauth;
3193 my $user_by_barcode;
3194 my $user_by_username;
3196 my $card = $e->search_actor_card([
3197 {barcode => $barcode},
3198 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3199 $user_by_barcode = $card->usr;
3200 $user = $user_by_barcode;
3203 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3204 $user = $user_by_username;
3206 return 0 if (!$user);
3207 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3208 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3209 return 1 if $user->passwd eq $password;
3213 __PACKAGE__->register_method (
3214 method => 'retrieve_usr_id_via_barcode_or_usrname',
3215 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3217 Given a barcode or username returns the id for the user or
3222 sub retrieve_usr_id_via_barcode_or_usrname {
3223 my($self, $conn, $auth, $barcode, $username) = @_;
3224 my $e = new_editor(authtoken => $auth);
3225 return $e->die_event unless $e->checkauth;
3226 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3228 my $user_by_barcode;
3229 my $user_by_username;
3230 $logger->info("$id_as_barcode is the ID as BARCODE");
3232 my $card = $e->search_actor_card([
3233 {barcode => $barcode},
3234 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3235 if ($id_as_barcode =~ /^t/i) {
3237 $user = $e->retrieve_actor_user($barcode);
3238 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3240 $user_by_barcode = $card->usr;
3241 $user = $user_by_barcode;
3244 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3245 $user_by_barcode = $card->usr;
3246 $user = $user_by_barcode;
3251 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3253 $user = $user_by_username;
3255 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3256 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3257 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3262 __PACKAGE__->register_method (
3263 method => 'merge_users',
3264 api_name => 'open-ils.actor.user.merge',
3267 Given a list of source users and destination user, transfer all data from the source
3268 to the dest user and delete the source user. All user related data is
3269 transferred, including circulations, holds, bookbags, etc.
3275 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3276 my $e = new_editor(xact => 1, authtoken => $auth);
3277 return $e->die_event unless $e->checkauth;
3279 # disallow the merge if any subordinate accounts are in collections
3280 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3281 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3283 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3284 my $del_addrs = ($U->ou_ancestor_setting_value(
3285 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3286 my $del_cards = ($U->ou_ancestor_setting_value(
3287 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3288 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3289 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3291 for my $src_id (@$user_ids) {
3292 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3294 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3295 if($src_user->home_ou ne $master_user->home_ou) {
3296 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3299 return $e->die_event unless
3300 $e->json_query({from => [
3315 __PACKAGE__->register_method (
3316 method => 'approve_user_address',
3317 api_name => 'open-ils.actor.user.pending_address.approve',
3324 sub approve_user_address {
3325 my($self, $conn, $auth, $addr) = @_;
3326 my $e = new_editor(xact => 1, authtoken => $auth);
3327 return $e->die_event unless $e->checkauth;
3329 # if the caller passes an address object, assume they want to
3330 # update it first before approving it
3331 $e->update_actor_user_address($addr) or return $e->die_event;
3333 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3335 my $user = $e->retrieve_actor_user($addr->usr);
3336 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3337 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3338 or return $e->die_event;
3340 return [values %$result]->[0];
3344 __PACKAGE__->register_method (
3345 method => 'retrieve_friends',
3346 api_name => 'open-ils.actor.friends.retrieve',
3349 returns { confirmed: [], pending_out: [], pending_in: []}
3350 pending_out are users I'm requesting friendship with
3351 pending_in are users requesting friendship with me
3356 sub retrieve_friends {
3357 my($self, $conn, $auth, $user_id, $options) = @_;
3358 my $e = new_editor(authtoken => $auth);
3359 return $e->event unless $e->checkauth;
3360 $user_id ||= $e->requestor->id;
3362 if($user_id != $e->requestor->id) {
3363 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3364 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3367 return OpenILS::Application::Actor::Friends->retrieve_friends(
3368 $e, $user_id, $options);
3373 __PACKAGE__->register_method (
3374 method => 'apply_friend_perms',
3375 api_name => 'open-ils.actor.friends.perms.apply',
3381 sub apply_friend_perms {
3382 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3383 my $e = new_editor(authtoken => $auth, xact => 1);
3384 return $e->die_event unless $e->checkauth;
3386 if($user_id != $e->requestor->id) {
3387 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3388 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3391 for my $perm (@perms) {
3393 OpenILS::Application::Actor::Friends->apply_friend_perm(
3394 $e, $user_id, $delegate_id, $perm);
3395 return $evt if $evt;
3403 __PACKAGE__->register_method (
3404 method => 'update_user_pending_address',
3405 api_name => 'open-ils.actor.user.address.pending.cud'
3408 sub update_user_pending_address {
3409 my($self, $conn, $auth, $addr) = @_;
3410 my $e = new_editor(authtoken => $auth, xact => 1);
3411 return $e->die_event unless $e->checkauth;
3413 if($addr->usr != $e->requestor->id) {
3414 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3415 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3419 $e->create_actor_user_address($addr) or return $e->die_event;
3420 } elsif($addr->isdeleted) {
3421 $e->delete_actor_user_address($addr) or return $e->die_event;
3423 $e->update_actor_user_address($addr) or return $e->die_event;
3431 __PACKAGE__->register_method (
3432 method => 'user_events',
3433 api_name => 'open-ils.actor.user.events.circ',
3436 __PACKAGE__->register_method (
3437 method => 'user_events',
3438 api_name => 'open-ils.actor.user.events.ahr',
3443 my($self, $conn, $auth, $user_id, $filters) = @_;
3444 my $e = new_editor(authtoken => $auth);
3445 return $e->event unless $e->checkauth;
3447 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3448 my $user_field = 'usr';
3451 $filters->{target} = {
3452 select => { $obj_type => ['id'] },
3454 where => {usr => $user_id}
3457 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3458 if($e->requestor->id != $user_id) {
3459 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3462 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3463 my $req = $ses->request('open-ils.trigger.events_by_target',
3464 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3466 while(my $resp = $req->recv) {
3467 my $val = $resp->content;
3468 my $tgt = $val->target;
3470 if($obj_type eq 'circ') {
3471 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3473 } elsif($obj_type eq 'ahr') {
3474 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3475 if $tgt->current_copy;
3478 $conn->respond($val) if $val;
3484 __PACKAGE__->register_method (
3485 method => 'copy_events',
3486 api_name => 'open-ils.actor.copy.events.circ',
3489 __PACKAGE__->register_method (
3490 method => 'copy_events',
3491 api_name => 'open-ils.actor.copy.events.ahr',
3496 my($self, $conn, $auth, $copy_id, $filters) = @_;
3497 my $e = new_editor(authtoken => $auth);
3498 return $e->event unless $e->checkauth;
3500 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3502 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3504 my $copy_field = 'target_copy';
3505 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3508 $filters->{target} = {
3509 select => { $obj_type => ['id'] },
3511 where => {$copy_field => $copy_id}
3515 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3516 my $req = $ses->request('open-ils.trigger.events_by_target',
3517 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3519 while(my $resp = $req->recv) {
3520 my $val = $resp->content;
3521 my $tgt = $val->target;
3523 my $user = $e->retrieve_actor_user($tgt->usr);
3524 if($e->requestor->id != $user->id) {
3525 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3528 $tgt->$copy_field($copy);
3531 $conn->respond($val) if $val;
3540 __PACKAGE__->register_method (
3541 method => 'update_events',
3542 api_name => 'open-ils.actor.user.event.cancel.batch',
3545 __PACKAGE__->register_method (
3546 method => 'update_events',
3547 api_name => 'open-ils.actor.user.event.reset.batch',
3552 my($self, $conn, $auth, $event_ids) = @_;
3553 my $e = new_editor(xact => 1, authtoken => $auth);
3554 return $e->die_event unless $e->checkauth;
3557 for my $id (@$event_ids) {
3559 # do a little dance to determine what user we are ultimately affecting
3560 my $event = $e->retrieve_action_trigger_event([
3563 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3565 ]) or return $e->die_event;
3568 if($event->event_def->hook->core_type eq 'circ') {
3569 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3570 } elsif($event->event_def->hook->core_type eq 'ahr') {
3571 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3576 my $user = $e->retrieve_actor_user($user_id);
3577 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3579 if($self->api_name =~ /cancel/) {
3580 $event->state('invalid');
3581 } elsif($self->api_name =~ /reset/) {
3582 $event->clear_start_time;
3583 $event->clear_update_time;
3584 $event->state('pending');
3587 $e->update_action_trigger_event($event) or return $e->die_event;
3588 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3592 return {complete => 1};
3596 __PACKAGE__->register_method (
3597 method => 'really_delete_user',
3598 api_name => 'open-ils.actor.user.delete.override',
3599 signature => q/@see open-ils.actor.user.delete/
3602 __PACKAGE__->register_method (
3603 method => 'really_delete_user',
3604 api_name => 'open-ils.actor.user.delete',
3606 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3607 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3608 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3609 dest_usr_id is only required when deleting a user that performs staff functions.
3613 sub really_delete_user {
3614 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3615 my $e = new_editor(authtoken => $auth, xact => 1);
3616 return $e->die_event unless $e->checkauth;
3617 $oargs = { all => 1 } unless defined $oargs;
3619 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3620 my $open_bills = $e->json_query({
3621 select => { mbts => ['id'] },
3624 xact_finish => { '=' => undef },
3625 usr => { '=' => $user_id },
3627 }) or return $e->die_event;
3629 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3631 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3633 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3634 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3635 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3637 # No deleting yourself - UI is supposed to stop you first, though.
3638 return $e->die_event unless $e->requestor->id != $user->id;
3639 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3640 # Check if you are allowed to mess with this patron permission group at all
3641 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3642 my $evt = group_perm_failed($session, $e->requestor, $user);
3643 return $e->die_event($evt) if $evt;
3644 my $stat = $e->json_query(
3645 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3646 or return $e->die_event;
3652 __PACKAGE__->register_method (
3653 method => 'user_payments',
3654 api_name => 'open-ils.actor.user.payments.retrieve',
3657 Returns all payments for a given user. Default order is newest payments first.
3658 @param auth Authentication token
3659 @param user_id The user ID
3660 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3665 my($self, $conn, $auth, $user_id, $filters) = @_;
3668 my $e = new_editor(authtoken => $auth);
3669 return $e->die_event unless $e->checkauth;
3671 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3672 return $e->event unless
3673 $e->requestor->id == $user_id or
3674 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3676 # Find all payments for all transactions for user $user_id
3678 select => {mp => ['id']},
3683 select => {mbt => ['id']},
3685 where => {usr => $user_id}
3690 { # by default, order newest payments first
3692 field => 'payment_ts',
3695 # secondary sort in ID as a tie-breaker, since payments created
3696 # within the same transaction will have identical payment_ts's
3703 for (qw/order_by limit offset/) {
3704 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3707 if(defined $filters->{where}) {
3708 foreach (keys %{$filters->{where}}) {
3709 # don't allow the caller to expand the result set to other users
3710 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3714 my $payment_ids = $e->json_query($query);
3715 for my $pid (@$payment_ids) {
3716 my $pay = $e->retrieve_money_payment([
3721 mbt => ['summary', 'circulation', 'grocery'],
3722 circ => ['target_copy'],
3723 acp => ['call_number'],
3731 xact_type => $pay->xact->summary->xact_type,
3732 last_billing_type => $pay->xact->summary->last_billing_type,
3735 if($pay->xact->summary->xact_type eq 'circulation') {
3736 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3737 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3740 $pay->xact($pay->xact->id); # de-flesh
3741 $conn->respond($resp);
3749 __PACKAGE__->register_method (
3750 method => 'negative_balance_users',
3751 api_name => 'open-ils.actor.users.negative_balance',
3754 Returns all users that have an overall negative balance
3755 @param auth Authentication token
3756 @param org_id The context org unit as an ID or list of IDs. This will be the home
3757 library of the user. If no org_unit is specified, no org unit filter is applied
3761 sub negative_balance_users {
3762 my($self, $conn, $auth, $org_id) = @_;
3764 my $e = new_editor(authtoken => $auth);
3765 return $e->die_event unless $e->checkauth;
3766 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3770 mous => ['usr', 'balance_owed'],
3773 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3774 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3791 where => {'+mous' => {balance_owed => {'<' => 0}}}
3794 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3796 my $list = $e->json_query($query, {timeout => 600});
3798 for my $data (@$list) {
3800 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3801 balance_owed => $data->{balance_owed},
3802 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3809 __PACKAGE__->register_method(
3810 method => "request_password_reset",
3811 api_name => "open-ils.actor.patron.password_reset.request",
3813 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3814 "method for changing a user's password. The UUID token is distributed via A/T " .
3815 "templates (i.e. email to the user).",
3817 { desc => 'user_id_type', type => 'string' },
3818 { desc => 'user_id', type => 'string' },
3819 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3821 return => {desc => '1 on success, Event on error'}
3824 sub request_password_reset {
3825 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3827 # Check to see if password reset requests are already being throttled:
3828 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3830 my $e = new_editor(xact => 1);
3833 # Get the user, if any, depending on the input value
3834 if ($user_id_type eq 'username') {
3835 $user = $e->search_actor_user({usrname => $user_id})->[0];
3838 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3840 } elsif ($user_id_type eq 'barcode') {
3841 my $card = $e->search_actor_card([
3842 {barcode => $user_id},
3843 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3846 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3851 # If the user doesn't have an email address, we can't help them
3852 if (!$user->email) {
3854 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3857 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3858 if ($email_must_match) {
3859 if ($user->email ne $email) {
3860 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3864 _reset_password_request($conn, $e, $user);
3867 # Once we have the user, we can issue the password reset request
3868 # XXX Add a wrapper method that accepts barcode + email input
3869 sub _reset_password_request {
3870 my ($conn, $e, $user) = @_;
3872 # 1. Get throttle threshold and time-to-live from OU_settings
3873 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3874 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3876 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3878 # 2. Get time of last request and number of active requests (num_active)
3879 my $active_requests = $e->json_query({
3885 transform => 'COUNT'
3888 column => 'request_time',
3894 has_been_reset => { '=' => 'f' },
3895 request_time => { '>' => $threshold_time }
3899 # Guard against no active requests
3900 if ($active_requests->[0]->{'request_time'}) {
3901 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3902 my $now = DateTime::Format::ISO8601->new();
3904 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3905 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3906 ($last_request->add_duration('1 minute') > $now)) {
3907 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3909 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3913 # TODO Check to see if the user is in a password-reset-restricted group
3915 # Otherwise, go ahead and try to get the user.
3917 # Check the number of active requests for this user
3918 $active_requests = $e->json_query({
3924 transform => 'COUNT'
3929 usr => { '=' => $user->id },
3930 has_been_reset => { '=' => 'f' },
3931 request_time => { '>' => $threshold_time }
3935 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3937 # if less than or equal to per-user threshold, proceed; otherwise, return event
3938 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3939 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3941 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3944 # Create the aupr object and insert into the database
3945 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3946 my $uuid = create_uuid_as_string(UUID_V4);
3947 $reset_request->uuid($uuid);
3948 $reset_request->usr($user->id);
3950 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3953 # Create an event to notify user of the URL to reset their password
3955 # Can we stuff this in the user_data param for trigger autocreate?
3956 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3958 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3959 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3962 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3967 __PACKAGE__->register_method(
3968 method => "commit_password_reset",
3969 api_name => "open-ils.actor.patron.password_reset.commit",
3971 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3972 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3973 "with the supplied password.",
3975 { desc => 'uuid', type => 'string' },
3976 { desc => 'password', type => 'string' },
3978 return => {desc => '1 on success, Event on error'}
3981 sub commit_password_reset {
3982 my($self, $conn, $uuid, $password) = @_;
3984 # Check to see if password reset requests are already being throttled:
3985 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3986 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3987 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3989 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3992 my $e = new_editor(xact => 1);
3994 my $aupr = $e->search_actor_usr_password_reset({
4001 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4003 my $user_id = $aupr->[0]->usr;
4004 my $user = $e->retrieve_actor_user($user_id);
4006 # Ensure we're still within the TTL for the request
4007 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4008 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4009 if ($threshold < DateTime->now(time_zone => 'local')) {
4011 $logger->info("Password reset request needed to be submitted before $threshold");
4012 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4015 # Check complexity of password against OU-defined regex
4016 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4020 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4021 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4022 $is_strong = check_password_strength_custom($password, $pw_regex);
4024 $is_strong = check_password_strength_default($password);
4029 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4032 # All is well; update the password
4033 $user->passwd($password);
4034 $e->update_actor_user($user);
4036 # And flag that this password reset request has been honoured
4037 $aupr->[0]->has_been_reset('t');
4038 $e->update_actor_usr_password_reset($aupr->[0]);
4044 sub check_password_strength_default {
4045 my $password = shift;
4046 # Use the default set of checks
4047 if ( (length($password) < 7) or
4048 ($password !~ m/.*\d+.*/) or
4049 ($password !~ m/.*[A-Za-z]+.*/)
4056 sub check_password_strength_custom {
4057 my ($password, $pw_regex) = @_;
4059 $pw_regex = qr/$pw_regex/;
4060 if ($password !~ /$pw_regex/) {
4068 __PACKAGE__->register_method(
4069 method => "event_def_opt_in_settings",
4070 api_name => "open-ils.actor.event_def.opt_in.settings",
4073 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4075 { desc => 'Authentication token', type => 'string'},
4077 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4082 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4089 sub event_def_opt_in_settings {
4090 my($self, $conn, $auth, $org_id) = @_;
4091 my $e = new_editor(authtoken => $auth);
4092 return $e->event unless $e->checkauth;
4094 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4095 return $e->event unless
4096 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4098 $org_id = $e->requestor->home_ou;
4101 # find all config.user_setting_type's related to event_defs for the requested org unit
4102 my $types = $e->json_query({
4103 select => {cust => ['name']},
4104 from => {atevdef => 'cust'},
4107 owner => $U->get_org_ancestors($org_id), # context org plus parents
4114 $conn->respond($_) for
4115 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4122 __PACKAGE__->register_method(
4123 method => "user_visible_circs",
4124 api_name => "open-ils.actor.history.circ.visible",
4127 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4129 { desc => 'Authentication token', type => 'string'},
4130 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4131 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4134 desc => q/An object with 2 fields: circulation and summary.
4135 circulation is the "circ" object. summary is the related "accs" object/,
4141 __PACKAGE__->register_method(
4142 method => "user_visible_circs",
4143 api_name => "open-ils.actor.history.circ.visible.print",
4146 desc => 'Returns printable output for the set of opt-in visible circulations',
4148 { desc => 'Authentication token', type => 'string'},
4149 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4150 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4153 desc => q/An action_trigger.event object or error event./,
4159 __PACKAGE__->register_method(
4160 method => "user_visible_circs",
4161 api_name => "open-ils.actor.history.circ.visible.email",
4164 desc => 'Emails the set of opt-in visible circulations to the requestor',
4166 { desc => 'Authentication token', type => 'string'},
4167 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4168 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4171 desc => q/undef, or event on error/
4176 __PACKAGE__->register_method(
4177 method => "user_visible_circs",
4178 api_name => "open-ils.actor.history.hold.visible",
4181 desc => 'Returns the set of opt-in visible holds',
4183 { desc => 'Authentication token', type => 'string'},
4184 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4185 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4188 desc => q/An object with 1 field: "hold"/,
4194 __PACKAGE__->register_method(
4195 method => "user_visible_circs",
4196 api_name => "open-ils.actor.history.hold.visible.print",
4199 desc => 'Returns printable output for the set of opt-in visible holds',
4201 { desc => 'Authentication token', type => 'string'},
4202 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4203 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4206 desc => q/An action_trigger.event object or error event./,
4212 __PACKAGE__->register_method(
4213 method => "user_visible_circs",
4214 api_name => "open-ils.actor.history.hold.visible.email",
4217 desc => 'Emails the set of opt-in visible holds to the requestor',
4219 { desc => 'Authentication token', type => 'string'},
4220 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4221 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4224 desc => q/undef, or event on error/
4229 sub user_visible_circs {
4230 my($self, $conn, $auth, $user_id, $options) = @_;
4232 my $is_hold = ($self->api_name =~ /hold/);
4233 my $for_print = ($self->api_name =~ /print/);
4234 my $for_email = ($self->api_name =~ /email/);
4235 my $e = new_editor(authtoken => $auth);
4236 return $e->event unless $e->checkauth;
4238 $user_id ||= $e->requestor->id;
4240 $options->{limit} ||= 50;
4241 $options->{offset} ||= 0;
4243 if($user_id != $e->requestor->id) {
4244 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4245 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4246 return $e->event unless $e->allowed($perm, $user->home_ou);
4249 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4251 my $data = $e->json_query({
4252 from => [$db_func, $user_id],
4253 limit => $$options{limit},
4254 offset => $$options{offset}
4256 # TODO: I only want IDs. code below didn't get me there
4257 # {"select":{"au":[{"column":"id", "result_field":"id",
4258 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4263 return undef unless @$data;
4267 # collect the batch of objects
4271 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4272 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4276 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4277 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4280 } elsif ($for_email) {
4282 $conn->respond_complete(1) if $for_email; # no sense in waiting
4290 my $hold = $e->retrieve_action_hold_request($id);
4291 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4292 # events will be fired from action_trigger_runner
4296 my $circ = $e->retrieve_action_circulation($id);
4297 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4298 # events will be fired from action_trigger_runner
4302 } else { # just give me the data please
4310 my $hold = $e->retrieve_action_hold_request($id);
4311 $conn->respond({hold => $hold});
4315 my $circ = $e->retrieve_action_circulation($id);
4318 summary => $U->create_circ_chain_summary($e, $id)
4327 __PACKAGE__->register_method(
4328 method => "user_saved_search_cud",
4329 api_name => "open-ils.actor.user.saved_search.cud",
4332 desc => 'Create/Update/Delete Access to user saved searches',
4334 { desc => 'Authentication token', type => 'string' },
4335 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4338 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4344 __PACKAGE__->register_method(
4345 method => "user_saved_search_cud",
4346 api_name => "open-ils.actor.user.saved_search.retrieve",
4349 desc => 'Retrieve a saved search object',
4351 { desc => 'Authentication token', type => 'string' },
4352 { desc => 'Saved Search ID', type => 'number' }
4355 desc => q/The saved search object, Event on error/,
4361 sub user_saved_search_cud {
4362 my( $self, $client, $auth, $search ) = @_;
4363 my $e = new_editor( authtoken=>$auth );
4364 return $e->die_event unless $e->checkauth;
4366 my $o_search; # prior version of the object, if any
4367 my $res; # to be returned
4369 # branch on the operation type
4371 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4373 # Get the old version, to check ownership
4374 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4375 or return $e->die_event;
4377 # You can't read somebody else's search
4378 return OpenILS::Event->new('BAD_PARAMS')
4379 unless $o_search->owner == $e->requestor->id;
4385 $e->xact_begin; # start an editor transaction
4387 if( $search->isnew ) { # Create
4389 # You can't create a search for somebody else
4390 return OpenILS::Event->new('BAD_PARAMS')
4391 unless $search->owner == $e->requestor->id;
4393 $e->create_actor_usr_saved_search( $search )
4394 or return $e->die_event;
4398 } elsif( $search->ischanged ) { # Update
4400 # You can't change ownership of a search
4401 return OpenILS::Event->new('BAD_PARAMS')
4402 unless $search->owner == $e->requestor->id;
4404 # Get the old version, to check ownership
4405 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4406 or return $e->die_event;
4408 # You can't update somebody else's search
4409 return OpenILS::Event->new('BAD_PARAMS')
4410 unless $o_search->owner == $e->requestor->id;
4413 $e->update_actor_usr_saved_search( $search )
4414 or return $e->die_event;
4418 } elsif( $search->isdeleted ) { # Delete
4420 # Get the old version, to check ownership
4421 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4422 or return $e->die_event;
4424 # You can't delete somebody else's search
4425 return OpenILS::Event->new('BAD_PARAMS')
4426 unless $o_search->owner == $e->requestor->id;
4429 $e->delete_actor_usr_saved_search( $o_search )
4430 or return $e->die_event;
4441 __PACKAGE__->register_method(
4442 method => "get_barcodes",
4443 api_name => "open-ils.actor.get_barcodes"
4447 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4448 my $e = new_editor(authtoken => $auth);
4449 return $e->event unless $e->checkauth;
4450 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4452 my $db_result = $e->json_query(
4454 'evergreen.get_barcodes',
4455 $org_id, $context, $barcode,
4459 if($context =~ /actor/) {
4460 my $filter_result = ();
4462 foreach my $result (@$db_result) {
4463 if($result->{type} eq 'actor') {
4464 if($e->requestor->id != $result->{id}) {
4465 $patron = $e->retrieve_actor_user($result->{id});
4467 push(@$filter_result, $e->event);
4470 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4471 push(@$filter_result, $result);
4474 push(@$filter_result, $e->event);
4478 push(@$filter_result, $result);
4482 push(@$filter_result, $result);
4485 return $filter_result;
4491 __PACKAGE__->register_method(
4492 method => 'address_alert_test',
4493 api_name => 'open-ils.actor.address_alert.test',
4495 desc => "Tests a set of address fields to determine if they match with an address_alert",
4497 {desc => 'Authentication token', type => 'string'},
4498 {desc => 'Org Unit', type => 'number'},
4499 {desc => 'Fields', type => 'hash'},
4501 return => {desc => 'List of matching address_alerts'}
4505 sub address_alert_test {
4506 my ($self, $client, $auth, $org_unit, $fields) = @_;
4507 return [] unless $fields and grep {$_} values %$fields;
4509 my $e = new_editor(authtoken => $auth);
4510 return $e->event unless $e->checkauth;
4511 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4512 $org_unit ||= $e->requestor->ws_ou;
4514 my $alerts = $e->json_query({
4516 'actor.address_alert_matches',
4524 $$fields{post_code},
4525 $$fields{mailing_address},
4526 $$fields{billing_address}
4530 # map the json_query hashes to real objects
4532 map {$e->retrieve_actor_address_alert($_)}
4533 (map {$_->{id}} @$alerts)
4537 __PACKAGE__->register_method(
4538 method => "mark_users_contact_invalid",
4539 api_name => "open-ils.actor.invalidate.email",
4541 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",
4543 {desc => "Authentication token", type => "string"},
4544 {desc => "Patron ID", type => "number"},
4545 {desc => "Additional note text (optional)", type => "string"},
4546 {desc => "penalty org unit ID (optional)", type => "number"}
4548 return => {desc => "Event describing success or failure", type => "object"}
4552 __PACKAGE__->register_method(
4553 method => "mark_users_contact_invalid",
4554 api_name => "open-ils.actor.invalidate.day_phone",
4556 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",
4558 {desc => "Authentication token", type => "string"},
4559 {desc => "Patron ID", type => "number"},
4560 {desc => "Additional note text (optional)", type => "string"},
4561 {desc => "penalty org unit ID (optional)", type => "number"}
4563 return => {desc => "Event describing success or failure", type => "object"}
4567 __PACKAGE__->register_method(
4568 method => "mark_users_contact_invalid",
4569 api_name => "open-ils.actor.invalidate.evening_phone",
4571 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",
4573 {desc => "Authentication token", type => "string"},
4574 {desc => "Patron ID", type => "number"},
4575 {desc => "Additional note text (optional)", type => "string"},
4576 {desc => "penalty org unit ID (optional)", type => "number"}
4578 return => {desc => "Event describing success or failure", type => "object"}
4582 __PACKAGE__->register_method(
4583 method => "mark_users_contact_invalid",
4584 api_name => "open-ils.actor.invalidate.other_phone",
4586 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",
4588 {desc => "Authentication token", type => "string"},
4589 {desc => "Patron ID", type => "number"},
4590 {desc => "Additional note text (optional)", type => "string"},
4591 {desc => "penalty org unit ID (optional, default to top of org tree)",
4594 return => {desc => "Event describing success or failure", type => "object"}
4598 sub mark_users_contact_invalid {
4599 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4601 # This method invalidates an email address or a phone_number which
4602 # removes the bad email address or phone number, copying its contents
4603 # to a patron note, and institutes a standing penalty for "bad email"
4604 # or "bad phone number" which is cleared when the user is saved or
4605 # optionally only when the user is saved with an email address or
4606 # phone number (or staff manually delete the penalty).
4608 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4610 my $e = new_editor(authtoken => $auth, xact => 1);
4611 return $e->die_event unless $e->checkauth;
4613 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4614 $e, $contact_type, {usr => $patron_id},
4615 $addl_note, $penalty_ou, $e->requestor->id
4619 # Putting the following method in open-ils.actor is a bad fit, except in that
4620 # it serves an interface that lives under 'actor' in the templates directory,
4621 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4623 __PACKAGE__->register_method(
4624 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4625 method => "get_all_at_reactors_in_use",
4630 { name => 'authtoken', type => 'string' }
4633 desc => 'list of reactor names', type => 'array'
4638 sub get_all_at_reactors_in_use {
4639 my ($self, $conn, $auth) = @_;
4641 my $e = new_editor(authtoken => $auth);
4642 $e->checkauth or return $e->die_event;
4643 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4645 my $reactors = $e->json_query({
4647 atevdef => [{column => "reactor", transform => "distinct"}]
4649 from => {atevdef => {}}
4652 return $e->die_event unless ref $reactors eq "ARRAY";
4655 return [ map { $_->{reactor} } @$reactors ];
4658 __PACKAGE__->register_method(
4659 method => "filter_group_entry_crud",
4660 api_name => "open-ils.actor.filter_group_entry.crud",
4663 Provides CRUD access to filter group entry objects. These are not full accessible
4664 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4665 are not accessible via PCRUD (because they have no fields against which to link perms)
4668 {desc => "Authentication token", type => "string"},
4669 {desc => "Entry ID / Entry Object", type => "number"},
4670 {desc => "Additional note text (optional)", type => "string"},
4671 {desc => "penalty org unit ID (optional, default to top of org tree)",
4675 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4681 sub filter_group_entry_crud {
4682 my ($self, $conn, $auth, $arg) = @_;
4684 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4685 my $e = new_editor(authtoken => $auth, xact => 1);
4686 return $e->die_event unless $e->checkauth;
4692 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4693 or return $e->die_event;
4695 return $e->die_event unless $e->allowed(
4696 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4698 my $query = $arg->query;
4699 $query = $e->create_actor_search_query($query) or return $e->die_event;
4700 $arg->query($query->id);
4701 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4702 $entry->query($query);
4707 } elsif ($arg->ischanged) {
4709 my $entry = $e->retrieve_actor_search_filter_group_entry([
4712 flesh_fields => {asfge => ['grp']}
4714 ]) or return $e->die_event;
4716 return $e->die_event unless $e->allowed(
4717 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4719 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4720 $arg->query($arg->query->id);
4721 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4722 $arg->query($query);
4727 } elsif ($arg->isdeleted) {
4729 my $entry = $e->retrieve_actor_search_filter_group_entry([
4732 flesh_fields => {asfge => ['grp', 'query']}
4734 ]) or return $e->die_event;
4736 return $e->die_event unless $e->allowed(
4737 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4739 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4740 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4753 my $entry = $e->retrieve_actor_search_filter_group_entry([
4756 flesh_fields => {asfge => ['grp', 'query']}
4758 ]) or return $e->die_event;
4760 return $e->die_event unless $e->allowed(
4761 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4762 $entry->grp->owner);
4765 $entry->grp($entry->grp->id); # for consistency