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.
377 if($patron->isnew()) {
378 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
380 if($U->is_true($patron->barred)) {
381 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
385 $new_patron = $patron;
387 # Did auth checking above already.
389 $old_patron = $e->retrieve_actor_user($patron->id) or
390 return $e->die_event;
392 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
393 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
398 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
401 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
404 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
407 # re-update the patron if anything has happened to him during this process
408 if($new_patron->ischanged()) {
409 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
413 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
416 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
419 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
422 $apputils->commit_db_session($session);
424 $evt = apply_invalid_addr_penalty($patron);
427 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
429 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
431 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
434 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
437 sub apply_invalid_addr_penalty {
439 my $e = new_editor(xact => 1);
441 # grab the invalid address penalty if set
442 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
444 my ($addr_penalty) = grep
445 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
447 # do we enforce invalid address penalty
448 my $enforce = $U->ou_ancestor_setting_value(
449 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
451 my $addrs = $e->search_actor_user_address(
452 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
453 my $addr_count = scalar(@$addrs);
455 if($addr_count == 0 and $addr_penalty) {
457 # regardless of any settings, remove the penalty when the user has no invalid addresses
458 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
461 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
463 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
464 my $depth = $ptype->org_depth;
465 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
466 $ctx_org = $patron->home_ou unless defined $ctx_org;
468 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
469 $penalty->usr($patron->id);
470 $penalty->org_unit($ctx_org);
471 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
473 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
492 "standing_penalties",
500 push @$fields, "home_ou" if $home_ou;
501 return new_flesh_user($id, $fields, $e );
509 # clone and clear stuff that would break the database
513 my $new_patron = $patron->clone;
515 $new_patron->clear_billing_address();
516 $new_patron->clear_mailing_address();
517 $new_patron->clear_addresses();
518 $new_patron->clear_card();
519 $new_patron->clear_cards();
520 $new_patron->clear_id();
521 $new_patron->clear_isnew();
522 $new_patron->clear_ischanged();
523 $new_patron->clear_isdeleted();
524 $new_patron->clear_stat_cat_entries();
525 $new_patron->clear_permissions();
526 $new_patron->clear_standing_penalties();
536 my $user_obj = shift;
538 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
539 return (undef, $evt) if $evt;
541 my $ex = $session->request(
542 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
544 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
547 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
549 my $id = $session->request(
550 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
551 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
553 $logger->info("Successfully created new user [$id] in DB");
555 return ( $session->request(
556 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
560 sub check_group_perm {
561 my( $session, $requestor, $patron ) = @_;
564 # first let's see if the requestor has
565 # priveleges to update this user in any way
566 if( ! $patron->isnew ) {
567 my $p = $session->request(
568 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
570 # If we are the requestor (trying to update our own account)
571 # and we are not trying to change our profile, we're good
572 if( $p->id == $requestor->id and
573 $p->profile == $patron->profile ) {
578 $evt = group_perm_failed($session, $requestor, $p);
582 # They are allowed to edit this patron.. can they put the
583 # patron into the group requested?
584 $evt = group_perm_failed($session, $requestor, $patron);
590 sub group_perm_failed {
591 my( $session, $requestor, $patron ) = @_;
595 my $grpid = $patron->profile;
599 $logger->debug("user update looking for group perm for group $grpid");
600 $grp = $session->request(
601 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
602 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
604 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
606 $logger->info("user update checking perm $perm on user ".
607 $requestor->id." for update/create on user username=".$patron->usrname);
609 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
617 my( $session, $patron, $user_obj, $noperm) = @_;
619 $logger->info("Updating patron ".$patron->id." in DB");
624 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
625 return (undef, $evt) if $evt;
628 # update the password by itself to avoid the password protection magic
629 if( $patron->passwd ) {
630 my $s = $session->request(
631 'open-ils.storage.direct.actor.user.remote_update',
632 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
633 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
634 $patron->clear_passwd;
637 if(!$patron->ident_type) {
638 $patron->clear_ident_type;
639 $patron->clear_ident_value;
642 $evt = verify_last_xact($session, $patron);
643 return (undef, $evt) if $evt;
645 my $stat = $session->request(
646 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
647 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
652 sub verify_last_xact {
653 my( $session, $patron ) = @_;
654 return undef unless $patron->id and $patron->id > 0;
655 my $p = $session->request(
656 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
657 my $xact = $p->last_xact_id;
658 return undef unless $xact;
659 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
660 return OpenILS::Event->new('XACT_COLLISION')
661 if $xact ne $patron->last_xact_id;
666 sub _check_dup_ident {
667 my( $session, $patron ) = @_;
669 return undef unless $patron->ident_value;
672 ident_type => $patron->ident_type,
673 ident_value => $patron->ident_value,
676 $logger->debug("patron update searching for dup ident values: " .
677 $patron->ident_type . ':' . $patron->ident_value);
679 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
681 my $dups = $session->request(
682 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
685 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
692 sub _add_update_addresses {
696 my $new_patron = shift;
700 my $current_id; # id of the address before creation
702 my $addresses = $patron->addresses();
704 for my $address (@$addresses) {
706 next unless ref $address;
707 $current_id = $address->id();
709 if( $patron->billing_address() and
710 $patron->billing_address() == $current_id ) {
711 $logger->info("setting billing addr to $current_id");
712 $new_patron->billing_address($address->id());
713 $new_patron->ischanged(1);
716 if( $patron->mailing_address() and
717 $patron->mailing_address() == $current_id ) {
718 $new_patron->mailing_address($address->id());
719 $logger->info("setting mailing addr to $current_id");
720 $new_patron->ischanged(1);
724 if($address->isnew()) {
726 $address->usr($new_patron->id());
728 ($address, $evt) = _add_address($session,$address);
729 return (undef, $evt) if $evt;
731 # we need to get the new id
732 if( $patron->billing_address() and
733 $patron->billing_address() == $current_id ) {
734 $new_patron->billing_address($address->id());
735 $logger->info("setting billing addr to $current_id");
736 $new_patron->ischanged(1);
739 if( $patron->mailing_address() and
740 $patron->mailing_address() == $current_id ) {
741 $new_patron->mailing_address($address->id());
742 $logger->info("setting mailing addr to $current_id");
743 $new_patron->ischanged(1);
746 } elsif($address->ischanged() ) {
748 ($address, $evt) = _update_address($session, $address);
749 return (undef, $evt) if $evt;
751 } elsif($address->isdeleted() ) {
753 if( $address->id() == $new_patron->mailing_address() ) {
754 $new_patron->clear_mailing_address();
755 ($new_patron, $evt) = _update_patron($session, $new_patron);
756 return (undef, $evt) if $evt;
759 if( $address->id() == $new_patron->billing_address() ) {
760 $new_patron->clear_billing_address();
761 ($new_patron, $evt) = _update_patron($session, $new_patron);
762 return (undef, $evt) if $evt;
765 $evt = _delete_address($session, $address);
766 return (undef, $evt) if $evt;
770 return ( $new_patron, undef );
774 # adds an address to the db and returns the address with new id
776 my($session, $address) = @_;
777 $address->clear_id();
779 $logger->info("Creating new address at street ".$address->street1);
781 # put the address into the database
782 my $id = $session->request(
783 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
784 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
787 return ($address, undef);
791 sub _update_address {
792 my( $session, $address ) = @_;
794 $logger->info("Updating address ".$address->id." in the DB");
796 my $stat = $session->request(
797 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
799 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
800 return ($address, undef);
805 sub _add_update_cards {
809 my $new_patron = shift;
813 my $virtual_id; #id of the card before creation
815 my $cards = $patron->cards();
816 for my $card (@$cards) {
818 $card->usr($new_patron->id());
820 if(ref($card) and $card->isnew()) {
822 $virtual_id = $card->id();
823 ( $card, $evt ) = _add_card($session,$card);
824 return (undef, $evt) if $evt;
826 #if(ref($patron->card)) { $patron->card($patron->card->id); }
827 if($patron->card() == $virtual_id) {
828 $new_patron->card($card->id());
829 $new_patron->ischanged(1);
832 } elsif( ref($card) and $card->ischanged() ) {
833 $evt = _update_card($session, $card);
834 return (undef, $evt) if $evt;
838 return ( $new_patron, undef );
842 # adds an card to the db and returns the card with new id
844 my( $session, $card ) = @_;
847 $logger->info("Adding new patron card ".$card->barcode);
849 my $id = $session->request(
850 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
851 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
852 $logger->info("Successfully created patron card $id");
855 return ( $card, undef );
859 # returns event on error. returns undef otherwise
861 my( $session, $card ) = @_;
862 $logger->info("Updating patron card ".$card->id);
864 my $stat = $session->request(
865 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
866 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
873 # returns event on error. returns undef otherwise
874 sub _delete_address {
875 my( $session, $address ) = @_;
877 $logger->info("Deleting address ".$address->id." from DB");
879 my $stat = $session->request(
880 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
882 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
888 sub _add_survey_responses {
889 my ($session, $patron, $new_patron) = @_;
891 $logger->info( "Updating survey responses for patron ".$new_patron->id );
893 my $responses = $patron->survey_responses;
897 $_->usr($new_patron->id) for (@$responses);
899 my $evt = $U->simplereq( "open-ils.circ",
900 "open-ils.circ.survey.submit.user_id", $responses );
902 return (undef, $evt) if defined($U->event_code($evt));
906 return ( $new_patron, undef );
909 sub _clear_badcontact_penalties {
910 my ($session, $old_patron, $new_patron, $user_obj) = @_;
912 return ($new_patron, undef) unless $old_patron;
914 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
915 my $e = new_editor(xact => 1);
917 # This ignores whether the caller of update_patron has any permission
918 # to remove penalties, but these penalties no longer make sense
919 # if an email address field (for example) is changed (and the caller must
920 # have perms to do *that*) so there's no reason not to clear the penalties.
922 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
924 "+csp" => {"name" => [values(%$PNM)]},
925 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
927 "join" => {"csp" => {}},
929 "flesh_fields" => {"ausp" => ["standing_penalty"]}
931 ]) or return (undef, $e->die_event);
933 return ($new_patron, undef) unless @$bad_contact_penalties;
935 my @penalties_to_clear;
936 my ($field, $penalty_name);
938 # For each field that might have an associated bad contact penalty,
939 # check for such penalties and add them to the to-clear list if that
941 while (($field, $penalty_name) = each(%$PNM)) {
942 if ($old_patron->$field ne $new_patron->$field) {
943 push @penalties_to_clear, grep {
944 $_->standing_penalty->name eq $penalty_name
945 } @$bad_contact_penalties;
949 foreach (@penalties_to_clear) {
950 # Note that this "archives" penalties, in the terminology of the staff
951 # client, instead of just deleting them. This may assist reporting,
952 # or preserving old contact information when it is still potentially
954 $_->standing_penalty($_->standing_penalty->id); # deflesh
955 $_->stop_date('now');
956 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
960 return ($new_patron, undef);
964 sub _create_stat_maps {
966 my($session, $user_session, $patron, $new_patron) = @_;
968 my $maps = $patron->stat_cat_entries();
970 for my $map (@$maps) {
972 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
974 if ($map->isdeleted()) {
975 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
977 } elsif ($map->isnew()) {
978 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
983 $map->target_usr($new_patron->id);
986 $logger->info("Updating stat entry with method $method and map $map");
988 my $stat = $session->request($method, $map)->gather(1);
989 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
993 return ($new_patron, undef);
996 sub _create_perm_maps {
998 my($session, $user_session, $patron, $new_patron) = @_;
1000 my $maps = $patron->permissions;
1002 for my $map (@$maps) {
1004 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1005 if ($map->isdeleted()) {
1006 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1007 } elsif ($map->isnew()) {
1008 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1013 $map->usr($new_patron->id);
1015 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1016 $logger->info( "Updating permissions with method $method and map $map" );
1018 my $stat = $session->request($method, $map)->gather(1);
1019 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1023 return ($new_patron, undef);
1027 __PACKAGE__->register_method(
1028 method => "set_user_work_ous",
1029 api_name => "open-ils.actor.user.work_ous.update",
1032 sub set_user_work_ous {
1038 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1039 return $evt if $evt;
1041 my $session = $apputils->start_db_session();
1042 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1044 for my $map (@$maps) {
1046 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1047 if ($map->isdeleted()) {
1048 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1049 } elsif ($map->isnew()) {
1050 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1054 #warn( "Updating permissions with method $method and session $ses and map $map" );
1055 $logger->info( "Updating work_ou map with method $method and map $map" );
1057 my $stat = $session->request($method, $map)->gather(1);
1058 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1062 $apputils->commit_db_session($session);
1064 return scalar(@$maps);
1068 __PACKAGE__->register_method(
1069 method => "set_user_perms",
1070 api_name => "open-ils.actor.user.permissions.update",
1073 sub set_user_perms {
1079 my $session = $apputils->start_db_session();
1081 my( $user_obj, $evt ) = $U->checkses($ses);
1082 return $evt if $evt;
1083 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1085 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1088 $all = 1 if ($U->is_true($user_obj->super_user()));
1089 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1091 for my $map (@$maps) {
1093 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1094 if ($map->isdeleted()) {
1095 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1096 } elsif ($map->isnew()) {
1097 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1101 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1102 #warn( "Updating permissions with method $method and session $ses and map $map" );
1103 $logger->info( "Updating permissions with method $method and map $map" );
1105 my $stat = $session->request($method, $map)->gather(1);
1106 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1110 $apputils->commit_db_session($session);
1112 return scalar(@$maps);
1116 __PACKAGE__->register_method(
1117 method => "user_retrieve_by_barcode",
1119 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1121 sub user_retrieve_by_barcode {
1122 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1124 my $e = new_editor(authtoken => $auth);
1125 return $e->event unless $e->checkauth;
1127 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1128 or return $e->event;
1130 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1131 return $e->event unless $e->allowed(
1132 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1139 __PACKAGE__->register_method(
1140 method => "get_user_by_id",
1142 api_name => "open-ils.actor.user.retrieve",
1145 sub get_user_by_id {
1146 my ($self, $client, $auth, $id) = @_;
1147 my $e = new_editor(authtoken=>$auth);
1148 return $e->event unless $e->checkauth;
1149 my $user = $e->retrieve_actor_user($id) or return $e->event;
1150 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1155 __PACKAGE__->register_method(
1156 method => "get_org_types",
1157 api_name => "open-ils.actor.org_types.retrieve",
1160 return $U->get_org_types();
1164 __PACKAGE__->register_method(
1165 method => "get_user_ident_types",
1166 api_name => "open-ils.actor.user.ident_types.retrieve",
1169 sub get_user_ident_types {
1170 return $ident_types if $ident_types;
1171 return $ident_types =
1172 new_editor()->retrieve_all_config_identification_type();
1176 __PACKAGE__->register_method(
1177 method => "get_org_unit",
1178 api_name => "open-ils.actor.org_unit.retrieve",
1182 my( $self, $client, $user_session, $org_id ) = @_;
1183 my $e = new_editor(authtoken => $user_session);
1185 return $e->event unless $e->checkauth;
1186 $org_id = $e->requestor->ws_ou;
1188 my $o = $e->retrieve_actor_org_unit($org_id)
1189 or return $e->event;
1193 __PACKAGE__->register_method(
1194 method => "search_org_unit",
1195 api_name => "open-ils.actor.org_unit_list.search",
1198 sub search_org_unit {
1200 my( $self, $client, $field, $value ) = @_;
1202 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1204 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1205 { $field => $value } );
1211 # build the org tree
1213 __PACKAGE__->register_method(
1214 method => "get_org_tree",
1215 api_name => "open-ils.actor.org_tree.retrieve",
1217 note => "Returns the entire org tree structure",
1223 return $U->get_org_tree($client->session->session_locale);
1227 __PACKAGE__->register_method(
1228 method => "get_org_descendants",
1229 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1232 # depth is optional. org_unit is the id
1233 sub get_org_descendants {
1234 my( $self, $client, $org_unit, $depth ) = @_;
1236 if(ref $org_unit eq 'ARRAY') {
1239 for my $i (0..scalar(@$org_unit)-1) {
1240 my $list = $U->simple_scalar_request(
1242 "open-ils.storage.actor.org_unit.descendants.atomic",
1243 $org_unit->[$i], $depth->[$i] );
1244 push(@trees, $U->build_org_tree($list));
1249 my $orglist = $apputils->simple_scalar_request(
1251 "open-ils.storage.actor.org_unit.descendants.atomic",
1252 $org_unit, $depth );
1253 return $U->build_org_tree($orglist);
1258 __PACKAGE__->register_method(
1259 method => "get_org_ancestors",
1260 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1263 # depth is optional. org_unit is the id
1264 sub get_org_ancestors {
1265 my( $self, $client, $org_unit, $depth ) = @_;
1266 my $orglist = $apputils->simple_scalar_request(
1268 "open-ils.storage.actor.org_unit.ancestors.atomic",
1269 $org_unit, $depth );
1270 return $U->build_org_tree($orglist);
1274 __PACKAGE__->register_method(
1275 method => "get_standings",
1276 api_name => "open-ils.actor.standings.retrieve"
1281 return $user_standings if $user_standings;
1282 return $user_standings =
1283 $apputils->simple_scalar_request(
1285 "open-ils.cstore.direct.config.standing.search.atomic",
1286 { id => { "!=" => undef } }
1291 __PACKAGE__->register_method(
1292 method => "get_my_org_path",
1293 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1296 sub get_my_org_path {
1297 my( $self, $client, $auth, $org_id ) = @_;
1298 my $e = new_editor(authtoken=>$auth);
1299 return $e->event unless $e->checkauth;
1300 $org_id = $e->requestor->ws_ou unless defined $org_id;
1302 return $apputils->simple_scalar_request(
1304 "open-ils.storage.actor.org_unit.full_path.atomic",
1309 __PACKAGE__->register_method(
1310 method => "patron_adv_search",
1311 api_name => "open-ils.actor.patron.search.advanced"
1313 sub patron_adv_search {
1314 my( $self, $client, $auth, $search_hash,
1315 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1317 my $e = new_editor(authtoken=>$auth);
1318 return $e->event unless $e->checkauth;
1319 return $e->event unless $e->allowed('VIEW_USER');
1321 # depth boundary outside of which patrons must opt-in, default to 0
1322 my $opt_boundary = 0;
1323 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1325 return $U->storagereq(
1326 "open-ils.storage.actor.user.crazy_search", $search_hash,
1327 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1331 __PACKAGE__->register_method(
1332 method => "update_passwd",
1333 api_name => "open-ils.actor.user.password.update",
1335 desc => "Update the operator's password",
1337 { desc => 'Authentication token', type => 'string' },
1338 { desc => 'New password', type => 'string' },
1339 { desc => 'Current password', type => 'string' }
1341 return => {desc => '1 on success, Event on error or incorrect current password'}
1345 __PACKAGE__->register_method(
1346 method => "update_passwd",
1347 api_name => "open-ils.actor.user.username.update",
1349 desc => "Update the operator's username",
1351 { desc => 'Authentication token', type => 'string' },
1352 { desc => 'New username', type => 'string' },
1353 { desc => 'Current password', type => 'string' }
1355 return => {desc => '1 on success, Event on error or incorrect current password'}
1359 __PACKAGE__->register_method(
1360 method => "update_passwd",
1361 api_name => "open-ils.actor.user.email.update",
1363 desc => "Update the operator's email address",
1365 { desc => 'Authentication token', type => 'string' },
1366 { desc => 'New email address', type => 'string' },
1367 { desc => 'Current password', type => 'string' }
1369 return => {desc => '1 on success, Event on error or incorrect current password'}
1374 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1375 my $e = new_editor(xact=>1, authtoken=>$auth);
1376 return $e->die_event unless $e->checkauth;
1378 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1379 or return $e->die_event;
1380 my $api = $self->api_name;
1382 # make sure the original password matches the in-database password
1383 if (md5_hex($orig_pw) ne $db_user->passwd) {
1385 return new OpenILS::Event('INCORRECT_PASSWORD');
1388 if( $api =~ /password/o ) {
1390 $db_user->passwd($new_val);
1394 # if we don't clear the password, the user will be updated with
1395 # a hashed version of the hashed version of their password
1396 $db_user->clear_passwd;
1398 if( $api =~ /username/o ) {
1400 # make sure no one else has this username
1401 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1404 return new OpenILS::Event('USERNAME_EXISTS');
1406 $db_user->usrname($new_val);
1408 } elsif( $api =~ /email/o ) {
1409 $db_user->email($new_val);
1413 $e->update_actor_user($db_user) or return $e->die_event;
1416 # update the cached user to pick up these changes
1417 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1423 __PACKAGE__->register_method(
1424 method => "check_user_perms",
1425 api_name => "open-ils.actor.user.perm.check",
1426 notes => <<" NOTES");
1427 Takes a login session, user id, an org id, and an array of perm type strings. For each
1428 perm type, if the user does *not* have the given permission it is added
1429 to a list which is returned from the method. If all permissions
1430 are allowed, an empty list is returned
1431 if the logged in user does not match 'user_id', then the logged in user must
1432 have VIEW_PERMISSION priveleges.
1435 sub check_user_perms {
1436 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1438 my( $staff, $evt ) = $apputils->checkses($login_session);
1439 return $evt if $evt;
1441 if($staff->id ne $user_id) {
1442 if( $evt = $apputils->check_perms(
1443 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1449 for my $perm (@$perm_types) {
1450 if($apputils->check_perms($user_id, $org_id, $perm)) {
1451 push @not_allowed, $perm;
1455 return \@not_allowed
1458 __PACKAGE__->register_method(
1459 method => "check_user_perms2",
1460 api_name => "open-ils.actor.user.perm.check.multi_org",
1462 Checks the permissions on a list of perms and orgs for a user
1463 @param authtoken The login session key
1464 @param user_id The id of the user to check
1465 @param orgs The array of org ids
1466 @param perms The array of permission names
1467 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1468 if the logged in user does not match 'user_id', then the logged in user must
1469 have VIEW_PERMISSION priveleges.
1472 sub check_user_perms2 {
1473 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1475 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1476 $authtoken, $user_id, 'VIEW_PERMISSION' );
1477 return $evt if $evt;
1480 for my $org (@$orgs) {
1481 for my $perm (@$perms) {
1482 if($apputils->check_perms($user_id, $org, $perm)) {
1483 push @not_allowed, [ $org, $perm ];
1488 return \@not_allowed
1492 __PACKAGE__->register_method(
1493 method => 'check_user_perms3',
1494 api_name => 'open-ils.actor.user.perm.highest_org',
1496 Returns the highest org unit id at which a user has a given permission
1497 If the requestor does not match the target user, the requestor must have
1498 'VIEW_PERMISSION' rights at the home org unit of the target user
1499 @param authtoken The login session key
1500 @param userid The id of the user in question
1501 @param perm The permission to check
1502 @return The org unit highest in the org tree within which the user has
1503 the requested permission
1506 sub check_user_perms3 {
1507 my($self, $client, $authtoken, $user_id, $perm) = @_;
1508 my $e = new_editor(authtoken=>$authtoken);
1509 return $e->event unless $e->checkauth;
1511 my $tree = $U->get_org_tree();
1513 unless($e->requestor->id == $user_id) {
1514 my $user = $e->retrieve_actor_user($user_id)
1515 or return $e->event;
1516 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1517 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1520 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1523 __PACKAGE__->register_method(
1524 method => 'user_has_work_perm_at',
1525 api_name => 'open-ils.actor.user.has_work_perm_at',
1529 Returns a set of org unit IDs which represent the highest orgs in
1530 the org tree where the user has the requested permission. The
1531 purpose of this method is to return the smallest set of org units
1532 which represent the full expanse of the user's ability to perform
1533 the requested action. The user whose perms this method should
1534 check is implied by the authtoken. /,
1536 {desc => 'authtoken', type => 'string'},
1537 {desc => 'permission name', type => 'string'},
1538 {desc => q/user id, optional. If present, check perms for
1539 this user instead of the logged in user/, type => 'number'},
1541 return => {desc => 'An array of org IDs'}
1545 sub user_has_work_perm_at {
1546 my($self, $conn, $auth, $perm, $user_id) = @_;
1547 my $e = new_editor(authtoken=>$auth);
1548 return $e->event unless $e->checkauth;
1549 if(defined $user_id) {
1550 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1551 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1553 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1556 __PACKAGE__->register_method(
1557 method => 'user_has_work_perm_at_batch',
1558 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1562 sub user_has_work_perm_at_batch {
1563 my($self, $conn, $auth, $perms, $user_id) = @_;
1564 my $e = new_editor(authtoken=>$auth);
1565 return $e->event unless $e->checkauth;
1566 if(defined $user_id) {
1567 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1568 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1571 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1577 __PACKAGE__->register_method(
1578 method => 'check_user_perms4',
1579 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1581 Returns the highest org unit id at which a user has a given permission
1582 If the requestor does not match the target user, the requestor must have
1583 'VIEW_PERMISSION' rights at the home org unit of the target user
1584 @param authtoken The login session key
1585 @param userid The id of the user in question
1586 @param perms An array of perm names to check
1587 @return An array of orgId's representing the org unit
1588 highest in the org tree within which the user has the requested permission
1589 The arrah of orgId's has matches the order of the perms array
1592 sub check_user_perms4 {
1593 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1595 my( $staff, $target, $org, $evt );
1597 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1598 $authtoken, $userid, 'VIEW_PERMISSION' );
1599 return $evt if $evt;
1602 return [] unless ref($perms);
1603 my $tree = $U->get_org_tree();
1605 for my $p (@$perms) {
1606 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1612 __PACKAGE__->register_method(
1613 method => "user_fines_summary",
1614 api_name => "open-ils.actor.user.fines.summary",
1617 desc => 'Returns a short summary of the users total open fines, ' .
1618 'excluding voided fines Params are login_session, user_id' ,
1620 {desc => 'Authentication token', type => 'string'},
1621 {desc => 'User ID', type => 'string'} # number?
1624 desc => "a 'mous' object, event on error",
1629 sub user_fines_summary {
1630 my( $self, $client, $auth, $user_id ) = @_;
1632 my $e = new_editor(authtoken=>$auth);
1633 return $e->event unless $e->checkauth;
1635 if( $user_id ne $e->requestor->id ) {
1636 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1637 return $e->event unless
1638 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1641 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1645 __PACKAGE__->register_method(
1646 method => "user_opac_vitals",
1647 api_name => "open-ils.actor.user.opac.vital_stats",
1651 desc => 'Returns a short summary of the users vital stats, including ' .
1652 'identification information, accumulated balance, number of holds, ' .
1653 'and current open circulation stats' ,
1655 {desc => 'Authentication token', type => 'string'},
1656 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1659 desc => "An object with four properties: user, fines, checkouts and holds."
1664 sub user_opac_vitals {
1665 my( $self, $client, $auth, $user_id ) = @_;
1667 my $e = new_editor(authtoken=>$auth);
1668 return $e->event unless $e->checkauth;
1670 $user_id ||= $e->requestor->id;
1672 my $user = $e->retrieve_actor_user( $user_id );
1675 ->method_lookup('open-ils.actor.user.fines.summary')
1676 ->run($auth => $user_id);
1677 return $fines if (defined($U->event_code($fines)));
1680 $fines = new Fieldmapper::money::open_user_summary ();
1681 $fines->balance_owed(0.00);
1682 $fines->total_owed(0.00);
1683 $fines->total_paid(0.00);
1684 $fines->usr($user_id);
1688 ->method_lookup('open-ils.actor.user.hold_requests.count')
1689 ->run($auth => $user_id);
1690 return $holds if (defined($U->event_code($holds)));
1693 ->method_lookup('open-ils.actor.user.checked_out.count')
1694 ->run($auth => $user_id);
1695 return $out if (defined($U->event_code($out)));
1697 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1701 first_given_name => $user->first_given_name,
1702 second_given_name => $user->second_given_name,
1703 family_name => $user->family_name,
1704 alias => $user->alias,
1705 usrname => $user->usrname
1707 fines => $fines->to_bare_hash,
1714 ##### a small consolidation of related method registrations
1715 my $common_params = [
1716 { desc => 'Authentication token', type => 'string' },
1717 { desc => 'User ID', type => 'string' },
1718 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1719 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1722 'open-ils.actor.user.transactions' => '',
1723 'open-ils.actor.user.transactions.fleshed' => '',
1724 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1725 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1726 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1727 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1730 foreach (keys %methods) {
1732 method => "user_transactions",
1735 desc => 'For a given user, retrieve a list of '
1736 . (/\.fleshed/ ? 'fleshed ' : '')
1737 . 'transactions' . $methods{$_}
1738 . ' optionally limited to transactions of a given type.',
1739 params => $common_params,
1741 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1742 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1746 $args{authoritative} = 1;
1747 __PACKAGE__->register_method(%args);
1750 # Now for the counts
1752 'open-ils.actor.user.transactions.count' => '',
1753 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1754 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1757 foreach (keys %methods) {
1759 method => "user_transactions",
1762 desc => 'For a given user, retrieve a count of open '
1763 . 'transactions' . $methods{$_}
1764 . ' optionally limited to transactions of a given type.',
1765 params => $common_params,
1766 return => { desc => "Integer count of transactions, or event on error" }
1769 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1770 __PACKAGE__->register_method(%args);
1773 __PACKAGE__->register_method(
1774 method => "user_transactions",
1775 api_name => "open-ils.actor.user.transactions.have_balance.total",
1778 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1779 . ' optionally limited to transactions of a given type.',
1780 params => $common_params,
1781 return => { desc => "Decimal balance value, or event on error" }
1786 sub user_transactions {
1787 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1790 my $e = new_editor(authtoken => $auth);
1791 return $e->event unless $e->checkauth;
1793 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1795 return $e->event unless
1796 $e->requestor->id == $user_id or
1797 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1799 my $api = $self->api_name();
1801 my $filter = ($api =~ /have_balance/o) ?
1802 { 'balance_owed' => { '<>' => 0 } }:
1803 { 'total_owed' => { '>' => 0 } };
1805 my $method = 'open-ils.actor.user.transactions.history.still_open';
1806 $method = "$method.authoritative" if $api =~ /authoritative/;
1807 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1809 if($api =~ /total/o) {
1811 $total += $_->balance_owed for @$trans;
1815 ($api =~ /count/o ) and return scalar @$trans;
1816 ($api !~ /fleshed/o) and return $trans;
1819 for my $t (@$trans) {
1821 if( $t->xact_type ne 'circulation' ) {
1822 push @resp, {transaction => $t};
1826 my $circ_data = flesh_circ($e, $t->id);
1827 push @resp, {transaction => $t, %$circ_data};
1834 __PACKAGE__->register_method(
1835 method => "user_transaction_retrieve",
1836 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1839 notes => "Returns a fleshed transaction record"
1842 __PACKAGE__->register_method(
1843 method => "user_transaction_retrieve",
1844 api_name => "open-ils.actor.user.transaction.retrieve",
1847 notes => "Returns a transaction record"
1850 sub user_transaction_retrieve {
1851 my($self, $client, $auth, $bill_id) = @_;
1853 my $e = new_editor(authtoken => $auth);
1854 return $e->event unless $e->checkauth;
1856 my $trans = $e->retrieve_money_billable_transaction_summary(
1857 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1859 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1861 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1863 return $trans unless $self->api_name =~ /flesh/;
1864 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1866 my $circ_data = flesh_circ($e, $trans->id, 1);
1868 return {transaction => $trans, %$circ_data};
1873 my $circ_id = shift;
1874 my $flesh_copy = shift;
1876 my $circ = $e->retrieve_action_circulation([
1880 circ => ['target_copy'],
1881 acp => ['call_number'],
1888 my $copy = $circ->target_copy;
1890 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1891 $mods = new Fieldmapper::metabib::virtual_record;
1892 $mods->doc_id(OILS_PRECAT_RECORD);
1893 $mods->title($copy->dummy_title);
1894 $mods->author($copy->dummy_author);
1897 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1901 $circ->target_copy($circ->target_copy->id);
1902 $copy->call_number($copy->call_number->id);
1904 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1908 __PACKAGE__->register_method(
1909 method => "hold_request_count",
1910 api_name => "open-ils.actor.user.hold_requests.count",
1913 notes => 'Returns hold ready/total counts'
1916 sub hold_request_count {
1917 my( $self, $client, $authtoken, $user_id ) = @_;
1918 my $e = new_editor(authtoken => $authtoken);
1919 return $e->event unless $e->checkauth;
1921 $user_id = $e->requestor->id unless defined $user_id;
1923 if($e->requestor->id ne $user_id) {
1924 my $user = $e->retrieve_actor_user($user_id);
1925 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1928 my $holds = $e->json_query({
1929 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1933 fulfillment_time => {"=" => undef },
1934 cancel_time => undef,
1939 total => scalar(@$holds),
1942 $_->{current_shelf_lib} and # avoid undef warnings
1943 $_->{pickup_lib} eq $_->{current_shelf_lib}
1949 __PACKAGE__->register_method(
1950 method => "checked_out",
1951 api_name => "open-ils.actor.user.checked_out",
1955 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1956 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1957 . "(i.e., outstanding balance or some other pending action on the circ). "
1958 . "The .count method also includes a 'total' field which sums all open circs.",
1960 { desc => 'Authentication Token', type => 'string'},
1961 { desc => 'User ID', type => 'string'},
1964 desc => 'Returns event on error, or an object with ID lists, like: '
1965 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1970 __PACKAGE__->register_method(
1971 method => "checked_out",
1972 api_name => "open-ils.actor.user.checked_out.count",
1975 signature => q/@see open-ils.actor.user.checked_out/
1979 my( $self, $conn, $auth, $userid ) = @_;
1981 my $e = new_editor(authtoken=>$auth);
1982 return $e->event unless $e->checkauth;
1984 if( $userid ne $e->requestor->id ) {
1985 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1986 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1988 # see if there is a friend link allowing circ.view perms
1989 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1990 $e, $userid, $e->requestor->id, 'circ.view');
1991 return $e->event unless $allowed;
1995 my $count = $self->api_name =~ /count/;
1996 return _checked_out( $count, $e, $userid );
2000 my( $iscount, $e, $userid ) = @_;
2006 claims_returned => [],
2009 my $meth = 'retrieve_action_open_circ_';
2017 claims_returned => 0,
2024 my $data = $e->$meth($userid);
2028 $result{$_} += $data->$_() for (keys %result);
2029 $result{total} += $data->$_() for (keys %result);
2031 for my $k (keys %result) {
2032 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2042 __PACKAGE__->register_method(
2043 method => "checked_in_with_fines",
2044 api_name => "open-ils.actor.user.checked_in_with_fines",
2047 signature => q/@see open-ils.actor.user.checked_out/
2050 sub checked_in_with_fines {
2051 my( $self, $conn, $auth, $userid ) = @_;
2053 my $e = new_editor(authtoken=>$auth);
2054 return $e->event unless $e->checkauth;
2056 if( $userid ne $e->requestor->id ) {
2057 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2060 # money is owed on these items and they are checked in
2061 my $open = $e->search_action_circulation(
2064 xact_finish => undef,
2065 checkin_time => { "!=" => undef },
2070 my( @lost, @cr, @lo );
2071 for my $c (@$open) {
2072 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2073 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2074 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2079 claims_returned => \@cr,
2080 long_overdue => \@lo
2086 my ($api, $desc, $auth) = @_;
2087 $desc = $desc ? (" " . $desc) : '';
2088 my $ids = ($api =~ /ids$/) ? 1 : 0;
2091 method => "user_transaction_history",
2092 api_name => "open-ils.actor.user.transactions.$api",
2094 desc => "For a given User ID, returns a list of billable transaction" .
2095 ($ids ? " id" : '') .
2096 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2097 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2099 {desc => 'Authentication token', type => 'string'},
2100 {desc => 'User ID', type => 'number'},
2101 {desc => 'Transaction type (optional)', type => 'number'},
2102 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2105 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2109 $auth and push @sig, (authoritative => 1);
2113 my %auth_hist_methods = (
2115 'history.have_charge' => 'that have an initial charge',
2116 'history.still_open' => 'that are not finished',
2117 'history.have_balance' => 'that have a balance',
2118 'history.have_bill' => 'that have billings',
2119 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2120 'history.have_payment' => 'that have at least 1 payment',
2123 foreach (keys %auth_hist_methods) {
2124 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2125 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2126 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2129 sub user_transaction_history {
2130 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2134 my $e = new_editor(authtoken=>$auth);
2135 return $e->die_event unless $e->checkauth;
2137 if ($e->requestor->id ne $userid) {
2138 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2141 my $api = $self->api_name;
2142 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2144 if(defined($type)) {
2145 $filter->{'xact_type'} = $type;
2148 if($api =~ /have_bill_or_payment/o) {
2150 # transactions that have a non-zero sum across all billings or at least 1 payment
2151 $filter->{'-or'} = {
2152 'balance_owed' => { '<>' => 0 },
2153 'last_payment_ts' => { '<>' => undef }
2156 } elsif($api =~ /have_payment/) {
2158 $filter->{last_payment_ts} ||= {'<>' => undef};
2160 } elsif( $api =~ /have_balance/o) {
2162 # transactions that have a non-zero overall balance
2163 $filter->{'balance_owed'} = { '<>' => 0 };
2165 } elsif( $api =~ /have_charge/o) {
2167 # transactions that have at least 1 billing, regardless of whether it was voided
2168 $filter->{'last_billing_ts'} = { '<>' => undef };
2170 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2172 # transactions that have non-zero sum across all billings. This will exclude
2173 # xacts where all billings have been voided
2174 $filter->{'total_owed'} = { '<>' => 0 };
2177 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2178 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2179 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2181 my $mbts = $e->search_money_billable_transaction_summary(
2182 [ { usr => $userid, @xact_finish, %$filter },
2187 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2188 return $mbts unless $api =~ /fleshed/;
2191 for my $t (@$mbts) {
2193 if( $t->xact_type ne 'circulation' ) {
2194 push @resp, {transaction => $t};
2198 my $circ_data = flesh_circ($e, $t->id);
2199 push @resp, {transaction => $t, %$circ_data};
2207 __PACKAGE__->register_method(
2208 method => "user_perms",
2209 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2211 notes => "Returns a list of permissions"
2215 my( $self, $client, $authtoken, $user ) = @_;
2217 my( $staff, $evt ) = $apputils->checkses($authtoken);
2218 return $evt if $evt;
2220 $user ||= $staff->id;
2222 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2226 return $apputils->simple_scalar_request(
2228 "open-ils.storage.permission.user_perms.atomic",
2232 __PACKAGE__->register_method(
2233 method => "retrieve_perms",
2234 api_name => "open-ils.actor.permissions.retrieve",
2235 notes => "Returns a list of permissions"
2237 sub retrieve_perms {
2238 my( $self, $client ) = @_;
2239 return $apputils->simple_scalar_request(
2241 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2242 { id => { '!=' => undef } }
2246 __PACKAGE__->register_method(
2247 method => "retrieve_groups",
2248 api_name => "open-ils.actor.groups.retrieve",
2249 notes => "Returns a list of user groups"
2251 sub retrieve_groups {
2252 my( $self, $client ) = @_;
2253 return new_editor()->retrieve_all_permission_grp_tree();
2256 __PACKAGE__->register_method(
2257 method => "retrieve_org_address",
2258 api_name => "open-ils.actor.org_unit.address.retrieve",
2259 notes => <<' NOTES');
2260 Returns an org_unit address by ID
2261 @param An org_address ID
2263 sub retrieve_org_address {
2264 my( $self, $client, $id ) = @_;
2265 return $apputils->simple_scalar_request(
2267 "open-ils.cstore.direct.actor.org_address.retrieve",
2272 __PACKAGE__->register_method(
2273 method => "retrieve_groups_tree",
2274 api_name => "open-ils.actor.groups.tree.retrieve",
2275 notes => "Returns a list of user groups"
2278 sub retrieve_groups_tree {
2279 my( $self, $client ) = @_;
2280 return new_editor()->search_permission_grp_tree(
2285 flesh_fields => { pgt => ["children"] },
2286 order_by => { pgt => 'name'}
2293 __PACKAGE__->register_method(
2294 method => "add_user_to_groups",
2295 api_name => "open-ils.actor.user.set_groups",
2296 notes => "Adds a user to one or more permission groups"
2299 sub add_user_to_groups {
2300 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2302 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2303 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2304 return $evt if $evt;
2306 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2307 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2308 return $evt if $evt;
2310 $apputils->simplereq(
2312 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2314 for my $group (@$groups) {
2315 my $link = Fieldmapper::permission::usr_grp_map->new;
2317 $link->usr($userid);
2319 my $id = $apputils->simplereq(
2321 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2327 __PACKAGE__->register_method(
2328 method => "get_user_perm_groups",
2329 api_name => "open-ils.actor.user.get_groups",
2330 notes => "Retrieve a user's permission groups."
2334 sub get_user_perm_groups {
2335 my( $self, $client, $authtoken, $userid ) = @_;
2337 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2338 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2339 return $evt if $evt;
2341 return $apputils->simplereq(
2343 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2347 __PACKAGE__->register_method(
2348 method => "get_user_work_ous",
2349 api_name => "open-ils.actor.user.get_work_ous",
2350 notes => "Retrieve a user's work org units."
2353 __PACKAGE__->register_method(
2354 method => "get_user_work_ous",
2355 api_name => "open-ils.actor.user.get_work_ous.ids",
2356 notes => "Retrieve a user's work org units."
2359 sub get_user_work_ous {
2360 my( $self, $client, $auth, $userid ) = @_;
2361 my $e = new_editor(authtoken=>$auth);
2362 return $e->event unless $e->checkauth;
2363 $userid ||= $e->requestor->id;
2365 if($e->requestor->id != $userid) {
2366 my $user = $e->retrieve_actor_user($userid)
2367 or return $e->event;
2368 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2371 return $e->search_permission_usr_work_ou_map({usr => $userid})
2372 unless $self->api_name =~ /.ids$/;
2374 # client just wants a list of org IDs
2375 return $U->get_user_work_ou_ids($e, $userid);
2380 __PACKAGE__->register_method(
2381 method => 'register_workstation',
2382 api_name => 'open-ils.actor.workstation.register.override',
2383 signature => q/@see open-ils.actor.workstation.register/
2386 __PACKAGE__->register_method(
2387 method => 'register_workstation',
2388 api_name => 'open-ils.actor.workstation.register',
2390 Registers a new workstion in the system
2391 @param authtoken The login session key
2392 @param name The name of the workstation id
2393 @param owner The org unit that owns this workstation
2394 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2395 if the name is already in use.
2399 sub register_workstation {
2400 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2402 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2403 return $e->die_event unless $e->checkauth;
2404 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2405 my $existing = $e->search_actor_workstation({name => $name})->[0];
2406 $oargs = { all => 1 } unless defined $oargs;
2410 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2411 # workstation with the given name exists.
2413 if($owner ne $existing->owning_lib) {
2414 # if necessary, update the owning_lib of the workstation
2416 $logger->info("changing owning lib of workstation ".$existing->id.
2417 " from ".$existing->owning_lib." to $owner");
2418 return $e->die_event unless
2419 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2421 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2423 $existing->owning_lib($owner);
2424 return $e->die_event unless $e->update_actor_workstation($existing);
2430 "attempt to register an existing workstation. returning existing ID");
2433 return $existing->id;
2436 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2440 my $ws = Fieldmapper::actor::workstation->new;
2441 $ws->owning_lib($owner);
2443 $e->create_actor_workstation($ws) or return $e->die_event;
2445 return $ws->id; # note: editor sets the id on the new object for us
2448 __PACKAGE__->register_method(
2449 method => 'workstation_list',
2450 api_name => 'open-ils.actor.workstation.list',
2452 Returns a list of workstations registered at the given location
2453 @param authtoken The login session key
2454 @param ids A list of org_unit.id's for the workstation owners
2458 sub workstation_list {
2459 my( $self, $conn, $authtoken, @orgs ) = @_;
2461 my $e = new_editor(authtoken=>$authtoken);
2462 return $e->event unless $e->checkauth;
2467 unless $e->allowed('REGISTER_WORKSTATION', $o);
2468 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2474 __PACKAGE__->register_method(
2475 method => 'fetch_patron_note',
2476 api_name => 'open-ils.actor.note.retrieve.all',
2479 Returns a list of notes for a given user
2480 Requestor must have VIEW_USER permission if pub==false and
2481 @param authtoken The login session key
2482 @param args Hash of params including
2483 patronid : the patron's id
2484 pub : true if retrieving only public notes
2488 sub fetch_patron_note {
2489 my( $self, $conn, $authtoken, $args ) = @_;
2490 my $patronid = $$args{patronid};
2492 my($reqr, $evt) = $U->checkses($authtoken);
2493 return $evt if $evt;
2496 ($patron, $evt) = $U->fetch_user($patronid);
2497 return $evt if $evt;
2500 if( $patronid ne $reqr->id ) {
2501 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2502 return $evt if $evt;
2504 return $U->cstorereq(
2505 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2506 { usr => $patronid, pub => 't' } );
2509 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2510 return $evt if $evt;
2512 return $U->cstorereq(
2513 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2516 __PACKAGE__->register_method(
2517 method => 'create_user_note',
2518 api_name => 'open-ils.actor.note.create',
2520 Creates a new note for the given user
2521 @param authtoken The login session key
2522 @param note The note object
2525 sub create_user_note {
2526 my( $self, $conn, $authtoken, $note ) = @_;
2527 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2528 return $e->die_event unless $e->checkauth;
2530 my $user = $e->retrieve_actor_user($note->usr)
2531 or return $e->die_event;
2533 return $e->die_event unless
2534 $e->allowed('UPDATE_USER',$user->home_ou);
2536 $note->creator($e->requestor->id);
2537 $e->create_actor_usr_note($note) or return $e->die_event;
2543 __PACKAGE__->register_method(
2544 method => 'delete_user_note',
2545 api_name => 'open-ils.actor.note.delete',
2547 Deletes a note for the given user
2548 @param authtoken The login session key
2549 @param noteid The note id
2552 sub delete_user_note {
2553 my( $self, $conn, $authtoken, $noteid ) = @_;
2555 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2556 return $e->die_event unless $e->checkauth;
2557 my $note = $e->retrieve_actor_usr_note($noteid)
2558 or return $e->die_event;
2559 my $user = $e->retrieve_actor_user($note->usr)
2560 or return $e->die_event;
2561 return $e->die_event unless
2562 $e->allowed('UPDATE_USER', $user->home_ou);
2564 $e->delete_actor_usr_note($note) or return $e->die_event;
2570 __PACKAGE__->register_method(
2571 method => 'update_user_note',
2572 api_name => 'open-ils.actor.note.update',
2574 @param authtoken The login session key
2575 @param note The note
2579 sub update_user_note {
2580 my( $self, $conn, $auth, $note ) = @_;
2581 my $e = new_editor(authtoken=>$auth, xact=>1);
2582 return $e->die_event unless $e->checkauth;
2583 my $patron = $e->retrieve_actor_user($note->usr)
2584 or return $e->die_event;
2585 return $e->die_event unless
2586 $e->allowed('UPDATE_USER', $patron->home_ou);
2587 $e->update_actor_user_note($note)
2588 or return $e->die_event;
2595 __PACKAGE__->register_method(
2596 method => 'create_closed_date',
2597 api_name => 'open-ils.actor.org_unit.closed_date.create',
2599 Creates a new closing entry for the given org_unit
2600 @param authtoken The login session key
2601 @param note The closed_date object
2604 sub create_closed_date {
2605 my( $self, $conn, $authtoken, $cd ) = @_;
2607 my( $user, $evt ) = $U->checkses($authtoken);
2608 return $evt if $evt;
2610 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2611 return $evt if $evt;
2613 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2615 my $id = $U->storagereq(
2616 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2617 return $U->DB_UPDATE_FAILED($cd) unless $id;
2622 __PACKAGE__->register_method(
2623 method => 'delete_closed_date',
2624 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2626 Deletes a closing entry for the given org_unit
2627 @param authtoken The login session key
2628 @param noteid The close_date id
2631 sub delete_closed_date {
2632 my( $self, $conn, $authtoken, $cd ) = @_;
2634 my( $user, $evt ) = $U->checkses($authtoken);
2635 return $evt if $evt;
2638 ($cd_obj, $evt) = fetch_closed_date($cd);
2639 return $evt if $evt;
2641 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2642 return $evt if $evt;
2644 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2646 my $stat = $U->storagereq(
2647 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2648 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2653 __PACKAGE__->register_method(
2654 method => 'usrname_exists',
2655 api_name => 'open-ils.actor.username.exists',
2657 desc => 'Check if a username is already taken (by an undeleted patron)',
2659 {desc => 'Authentication token', type => 'string'},
2660 {desc => 'Username', type => 'string'}
2663 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2668 sub usrname_exists {
2669 my( $self, $conn, $auth, $usrname ) = @_;
2670 my $e = new_editor(authtoken=>$auth);
2671 return $e->event unless $e->checkauth;
2672 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2673 return $$a[0] if $a and @$a;
2677 __PACKAGE__->register_method(
2678 method => 'barcode_exists',
2679 api_name => 'open-ils.actor.barcode.exists',
2681 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2684 sub barcode_exists {
2685 my( $self, $conn, $auth, $barcode ) = @_;
2686 my $e = new_editor(authtoken=>$auth);
2687 return $e->event unless $e->checkauth;
2688 my $card = $e->search_actor_card({barcode => $barcode});
2694 #return undef unless @$card;
2695 #return $card->[0]->usr;
2699 __PACKAGE__->register_method(
2700 method => 'retrieve_net_levels',
2701 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2704 sub retrieve_net_levels {
2705 my( $self, $conn, $auth ) = @_;
2706 my $e = new_editor(authtoken=>$auth);
2707 return $e->event unless $e->checkauth;
2708 return $e->retrieve_all_config_net_access_level();
2711 # Retain the old typo API name just in case
2712 __PACKAGE__->register_method(
2713 method => 'fetch_org_by_shortname',
2714 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2716 __PACKAGE__->register_method(
2717 method => 'fetch_org_by_shortname',
2718 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2720 sub fetch_org_by_shortname {
2721 my( $self, $conn, $sname ) = @_;
2722 my $e = new_editor();
2723 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2724 return $e->event unless $org;
2729 __PACKAGE__->register_method(
2730 method => 'session_home_lib',
2731 api_name => 'open-ils.actor.session.home_lib',
2734 sub session_home_lib {
2735 my( $self, $conn, $auth ) = @_;
2736 my $e = new_editor(authtoken=>$auth);
2737 return undef unless $e->checkauth;
2738 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2739 return $org->shortname;
2742 __PACKAGE__->register_method(
2743 method => 'session_safe_token',
2744 api_name => 'open-ils.actor.session.safe_token',
2746 Returns a hashed session ID that is safe for export to the world.
2747 This safe token will expire after 1 hour of non-use.
2748 @param auth Active authentication token
2752 sub session_safe_token {
2753 my( $self, $conn, $auth ) = @_;
2754 my $e = new_editor(authtoken=>$auth);
2755 return undef unless $e->checkauth;
2757 my $safe_token = md5_hex($auth);
2759 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2761 # Add more like the following if needed...
2763 "safe-token-home_lib-shortname-$safe_token",
2764 $e->retrieve_actor_org_unit(
2765 $e->requestor->home_ou
2774 __PACKAGE__->register_method(
2775 method => 'safe_token_home_lib',
2776 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2778 Returns the home library shortname from the session
2779 asscociated with a safe token from generated by
2780 open-ils.actor.session.safe_token.
2781 @param safe_token Active safe token
2785 sub safe_token_home_lib {
2786 my( $self, $conn, $safe_token ) = @_;
2788 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2789 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2793 __PACKAGE__->register_method(
2794 method => "update_penalties",
2795 api_name => "open-ils.actor.user.penalties.update"
2798 sub update_penalties {
2799 my($self, $conn, $auth, $user_id) = @_;
2800 my $e = new_editor(authtoken=>$auth, xact => 1);
2801 return $e->die_event unless $e->checkauth;
2802 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2803 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2804 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2805 return $evt if $evt;
2811 __PACKAGE__->register_method(
2812 method => "apply_penalty",
2813 api_name => "open-ils.actor.user.penalty.apply"
2817 my($self, $conn, $auth, $penalty) = @_;
2819 my $e = new_editor(authtoken=>$auth, xact => 1);
2820 return $e->die_event unless $e->checkauth;
2822 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2823 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2825 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2828 (defined $ptype->org_depth) ?
2829 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2832 $penalty->org_unit($ctx_org);
2833 $penalty->staff($e->requestor->id);
2834 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2837 return $penalty->id;
2840 __PACKAGE__->register_method(
2841 method => "remove_penalty",
2842 api_name => "open-ils.actor.user.penalty.remove"
2845 sub remove_penalty {
2846 my($self, $conn, $auth, $penalty) = @_;
2847 my $e = new_editor(authtoken=>$auth, xact => 1);
2848 return $e->die_event unless $e->checkauth;
2849 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2850 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2852 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2857 __PACKAGE__->register_method(
2858 method => "update_penalty_note",
2859 api_name => "open-ils.actor.user.penalty.note.update"
2862 sub update_penalty_note {
2863 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2864 my $e = new_editor(authtoken=>$auth, xact => 1);
2865 return $e->die_event unless $e->checkauth;
2866 for my $penalty_id (@$penalty_ids) {
2867 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2868 if (! $penalty ) { return $e->die_event; }
2869 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2870 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2872 $penalty->note( $note ); $penalty->ischanged( 1 );
2874 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2880 __PACKAGE__->register_method(
2881 method => "ranged_penalty_thresholds",
2882 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2886 sub ranged_penalty_thresholds {
2887 my($self, $conn, $auth, $context_org) = @_;
2888 my $e = new_editor(authtoken=>$auth);
2889 return $e->event unless $e->checkauth;
2890 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2891 my $list = $e->search_permission_grp_penalty_threshold([
2892 {org_unit => $U->get_org_ancestors($context_org)},
2893 {order_by => {pgpt => 'id'}}
2895 $conn->respond($_) for @$list;
2901 __PACKAGE__->register_method(
2902 method => "user_retrieve_fleshed_by_id",
2904 api_name => "open-ils.actor.user.fleshed.retrieve",
2907 sub user_retrieve_fleshed_by_id {
2908 my( $self, $client, $auth, $user_id, $fields ) = @_;
2909 my $e = new_editor(authtoken => $auth);
2910 return $e->event unless $e->checkauth;
2912 if( $e->requestor->id != $user_id ) {
2913 return $e->event unless $e->allowed('VIEW_USER');
2919 "standing_penalties",
2925 return new_flesh_user($user_id, $fields, $e);
2929 sub new_flesh_user {
2932 my $fields = shift || [];
2935 my $fetch_penalties = 0;
2936 if(grep {$_ eq 'standing_penalties'} @$fields) {
2937 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2938 $fetch_penalties = 1;
2941 my $fetch_usr_act = 0;
2942 if(grep {$_ eq 'usr_activity'} @$fields) {
2943 $fields = [grep {$_ ne 'usr_activity'} @$fields];
2947 my $user = $e->retrieve_actor_user(
2952 "flesh_fields" => { "au" => $fields }
2955 ) or return $e->die_event;
2958 if( grep { $_ eq 'addresses' } @$fields ) {
2960 $user->addresses([]) unless @{$user->addresses};
2961 # don't expose "replaced" addresses by default
2962 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2964 if( ref $user->billing_address ) {
2965 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2966 push( @{$user->addresses}, $user->billing_address );
2970 if( ref $user->mailing_address ) {
2971 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2972 push( @{$user->addresses}, $user->mailing_address );
2977 if($fetch_penalties) {
2978 # grab the user penalties ranged for this location
2979 $user->standing_penalties(
2980 $e->search_actor_user_standing_penalty([
2983 {stop_date => undef},
2984 {stop_date => {'>' => 'now'}}
2986 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
2989 flesh_fields => {ausp => ['standing_penalty']}
2995 # retrieve the most recent usr_activity entry
2996 if ($fetch_usr_act) {
2998 # max number to return for simple patron fleshing
2999 my $limit = $U->ou_ancestor_setting_value(
3000 $e->requestor->ws_ou,
3001 'circ.patron.usr_activity_retrieve.max');
3005 flesh_fields => {auact => ['etype']},
3006 order_by => {auact => 'event_time DESC'},
3009 # 0 == none, <0 == return all
3010 $limit = 1 unless defined $limit;
3011 $opts->{limit} = $limit if $limit > 0;
3013 $user->usr_activity(
3015 [] : # skip the DB call
3016 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3021 $user->clear_passwd();
3028 __PACKAGE__->register_method(
3029 method => "user_retrieve_parts",
3030 api_name => "open-ils.actor.user.retrieve.parts",
3033 sub user_retrieve_parts {
3034 my( $self, $client, $auth, $user_id, $fields ) = @_;
3035 my $e = new_editor(authtoken => $auth);
3036 return $e->event unless $e->checkauth;
3037 $user_id ||= $e->requestor->id;
3038 if( $e->requestor->id != $user_id ) {
3039 return $e->event unless $e->allowed('VIEW_USER');
3042 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3043 push(@resp, $user->$_()) for(@$fields);
3049 __PACKAGE__->register_method(
3050 method => 'user_opt_in_enabled',
3051 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3052 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3055 sub user_opt_in_enabled {
3056 my($self, $conn) = @_;
3057 my $sc = OpenSRF::Utils::SettingsClient->new;
3058 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3063 __PACKAGE__->register_method(
3064 method => 'user_opt_in_at_org',
3065 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3067 @param $auth The auth token
3068 @param user_id The ID of the user to test
3069 @return 1 if the user has opted in at the specified org,
3070 event on error, and 0 otherwise. /
3072 sub user_opt_in_at_org {
3073 my($self, $conn, $auth, $user_id) = @_;
3075 # see if we even need to enforce the opt-in value
3076 return 1 unless user_opt_in_enabled($self);
3078 my $e = new_editor(authtoken => $auth);
3079 return $e->event unless $e->checkauth;
3081 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3082 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3084 my $ws_org = $e->requestor->ws_ou;
3085 # user is automatically opted-in if they are from the local org
3086 return 1 if $user->home_ou eq $ws_org;
3088 # get the boundary setting
3089 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3091 # auto opt in if user falls within the opt boundary
3092 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3094 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3096 my $vals = $e->search_actor_usr_org_unit_opt_in(
3097 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3103 __PACKAGE__->register_method(
3104 method => 'create_user_opt_in_at_org',
3105 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3107 @param $auth The auth token
3108 @param user_id The ID of the user to test
3109 @return The ID of the newly created object, event on error./
3112 sub create_user_opt_in_at_org {
3113 my($self, $conn, $auth, $user_id, $org_id) = @_;
3115 my $e = new_editor(authtoken => $auth, xact=>1);
3116 return $e->die_event unless $e->checkauth;
3118 # if a specific org unit wasn't passed in, get one based on the defaults;
3120 my $wsou = $e->requestor->ws_ou;
3121 # get the default opt depth
3122 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3123 # get the org unit at that depth
3124 my $org = $e->json_query({
3125 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3127 $org_id = $org->{id};
3130 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3131 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3133 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3135 $opt_in->org_unit($org_id);
3136 $opt_in->usr($user_id);
3137 $opt_in->staff($e->requestor->id);
3138 $opt_in->opt_in_ts('now');
3139 $opt_in->opt_in_ws($e->requestor->wsid);
3141 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3142 or return $e->die_event;
3150 __PACKAGE__->register_method (
3151 method => 'retrieve_org_hours',
3152 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3154 Returns the hours of operation for a specified org unit
3155 @param authtoken The login session key
3156 @param org_id The org_unit ID
3160 sub retrieve_org_hours {
3161 my($self, $conn, $auth, $org_id) = @_;
3162 my $e = new_editor(authtoken => $auth);
3163 return $e->die_event unless $e->checkauth;
3164 $org_id ||= $e->requestor->ws_ou;
3165 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3169 __PACKAGE__->register_method (
3170 method => 'verify_user_password',
3171 api_name => 'open-ils.actor.verify_user_password',
3173 Given a barcode or username and the MD5 encoded password,
3174 returns 1 if the password is correct. Returns 0 otherwise.
3178 sub verify_user_password {
3179 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3180 my $e = new_editor(authtoken => $auth);
3181 return $e->die_event unless $e->checkauth;
3183 my $user_by_barcode;
3184 my $user_by_username;
3186 my $card = $e->search_actor_card([
3187 {barcode => $barcode},
3188 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3189 $user_by_barcode = $card->usr;
3190 $user = $user_by_barcode;
3193 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3194 $user = $user_by_username;
3196 return 0 if (!$user);
3197 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3198 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3199 return 1 if $user->passwd eq $password;
3203 __PACKAGE__->register_method (
3204 method => 'retrieve_usr_id_via_barcode_or_usrname',
3205 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3207 Given a barcode or username returns the id for the user or
3212 sub retrieve_usr_id_via_barcode_or_usrname {
3213 my($self, $conn, $auth, $barcode, $username) = @_;
3214 my $e = new_editor(authtoken => $auth);
3215 return $e->die_event unless $e->checkauth;
3216 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3218 my $user_by_barcode;
3219 my $user_by_username;
3220 $logger->info("$id_as_barcode is the ID as BARCODE");
3222 my $card = $e->search_actor_card([
3223 {barcode => $barcode},
3224 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3225 if ($id_as_barcode =~ /^t/i) {
3227 $user = $e->retrieve_actor_user($barcode);
3228 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3230 $user_by_barcode = $card->usr;
3231 $user = $user_by_barcode;
3234 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3235 $user_by_barcode = $card->usr;
3236 $user = $user_by_barcode;
3241 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3243 $user = $user_by_username;
3245 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3246 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3247 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3252 __PACKAGE__->register_method (
3253 method => 'merge_users',
3254 api_name => 'open-ils.actor.user.merge',
3257 Given a list of source users and destination user, transfer all data from the source
3258 to the dest user and delete the source user. All user related data is
3259 transferred, including circulations, holds, bookbags, etc.
3265 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3266 my $e = new_editor(xact => 1, authtoken => $auth);
3267 return $e->die_event unless $e->checkauth;
3269 # disallow the merge if any subordinate accounts are in collections
3270 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3271 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3273 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3274 my $del_addrs = ($U->ou_ancestor_setting_value(
3275 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3276 my $del_cards = ($U->ou_ancestor_setting_value(
3277 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3278 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3279 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3281 for my $src_id (@$user_ids) {
3282 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3284 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3285 if($src_user->home_ou ne $master_user->home_ou) {
3286 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3289 return $e->die_event unless
3290 $e->json_query({from => [
3305 __PACKAGE__->register_method (
3306 method => 'approve_user_address',
3307 api_name => 'open-ils.actor.user.pending_address.approve',
3314 sub approve_user_address {
3315 my($self, $conn, $auth, $addr) = @_;
3316 my $e = new_editor(xact => 1, authtoken => $auth);
3317 return $e->die_event unless $e->checkauth;
3319 # if the caller passes an address object, assume they want to
3320 # update it first before approving it
3321 $e->update_actor_user_address($addr) or return $e->die_event;
3323 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3325 my $user = $e->retrieve_actor_user($addr->usr);
3326 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3327 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3328 or return $e->die_event;
3330 return [values %$result]->[0];
3334 __PACKAGE__->register_method (
3335 method => 'retrieve_friends',
3336 api_name => 'open-ils.actor.friends.retrieve',
3339 returns { confirmed: [], pending_out: [], pending_in: []}
3340 pending_out are users I'm requesting friendship with
3341 pending_in are users requesting friendship with me
3346 sub retrieve_friends {
3347 my($self, $conn, $auth, $user_id, $options) = @_;
3348 my $e = new_editor(authtoken => $auth);
3349 return $e->event unless $e->checkauth;
3350 $user_id ||= $e->requestor->id;
3352 if($user_id != $e->requestor->id) {
3353 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3354 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3357 return OpenILS::Application::Actor::Friends->retrieve_friends(
3358 $e, $user_id, $options);
3363 __PACKAGE__->register_method (
3364 method => 'apply_friend_perms',
3365 api_name => 'open-ils.actor.friends.perms.apply',
3371 sub apply_friend_perms {
3372 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3373 my $e = new_editor(authtoken => $auth, xact => 1);
3374 return $e->die_event unless $e->checkauth;
3376 if($user_id != $e->requestor->id) {
3377 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3378 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3381 for my $perm (@perms) {
3383 OpenILS::Application::Actor::Friends->apply_friend_perm(
3384 $e, $user_id, $delegate_id, $perm);
3385 return $evt if $evt;
3393 __PACKAGE__->register_method (
3394 method => 'update_user_pending_address',
3395 api_name => 'open-ils.actor.user.address.pending.cud'
3398 sub update_user_pending_address {
3399 my($self, $conn, $auth, $addr) = @_;
3400 my $e = new_editor(authtoken => $auth, xact => 1);
3401 return $e->die_event unless $e->checkauth;
3403 if($addr->usr != $e->requestor->id) {
3404 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3405 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3409 $e->create_actor_user_address($addr) or return $e->die_event;
3410 } elsif($addr->isdeleted) {
3411 $e->delete_actor_user_address($addr) or return $e->die_event;
3413 $e->update_actor_user_address($addr) or return $e->die_event;
3421 __PACKAGE__->register_method (
3422 method => 'user_events',
3423 api_name => 'open-ils.actor.user.events.circ',
3426 __PACKAGE__->register_method (
3427 method => 'user_events',
3428 api_name => 'open-ils.actor.user.events.ahr',
3433 my($self, $conn, $auth, $user_id, $filters) = @_;
3434 my $e = new_editor(authtoken => $auth);
3435 return $e->event unless $e->checkauth;
3437 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3438 my $user_field = 'usr';
3441 $filters->{target} = {
3442 select => { $obj_type => ['id'] },
3444 where => {usr => $user_id}
3447 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3448 if($e->requestor->id != $user_id) {
3449 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3452 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3453 my $req = $ses->request('open-ils.trigger.events_by_target',
3454 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3456 while(my $resp = $req->recv) {
3457 my $val = $resp->content;
3458 my $tgt = $val->target;
3460 if($obj_type eq 'circ') {
3461 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3463 } elsif($obj_type eq 'ahr') {
3464 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3465 if $tgt->current_copy;
3468 $conn->respond($val) if $val;
3474 __PACKAGE__->register_method (
3475 method => 'copy_events',
3476 api_name => 'open-ils.actor.copy.events.circ',
3479 __PACKAGE__->register_method (
3480 method => 'copy_events',
3481 api_name => 'open-ils.actor.copy.events.ahr',
3486 my($self, $conn, $auth, $copy_id, $filters) = @_;
3487 my $e = new_editor(authtoken => $auth);
3488 return $e->event unless $e->checkauth;
3490 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3492 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3494 my $copy_field = 'target_copy';
3495 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3498 $filters->{target} = {
3499 select => { $obj_type => ['id'] },
3501 where => {$copy_field => $copy_id}
3505 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3506 my $req = $ses->request('open-ils.trigger.events_by_target',
3507 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3509 while(my $resp = $req->recv) {
3510 my $val = $resp->content;
3511 my $tgt = $val->target;
3513 my $user = $e->retrieve_actor_user($tgt->usr);
3514 if($e->requestor->id != $user->id) {
3515 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3518 $tgt->$copy_field($copy);
3521 $conn->respond($val) if $val;
3530 __PACKAGE__->register_method (
3531 method => 'update_events',
3532 api_name => 'open-ils.actor.user.event.cancel.batch',
3535 __PACKAGE__->register_method (
3536 method => 'update_events',
3537 api_name => 'open-ils.actor.user.event.reset.batch',
3542 my($self, $conn, $auth, $event_ids) = @_;
3543 my $e = new_editor(xact => 1, authtoken => $auth);
3544 return $e->die_event unless $e->checkauth;
3547 for my $id (@$event_ids) {
3549 # do a little dance to determine what user we are ultimately affecting
3550 my $event = $e->retrieve_action_trigger_event([
3553 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3555 ]) or return $e->die_event;
3558 if($event->event_def->hook->core_type eq 'circ') {
3559 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3560 } elsif($event->event_def->hook->core_type eq 'ahr') {
3561 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3566 my $user = $e->retrieve_actor_user($user_id);
3567 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3569 if($self->api_name =~ /cancel/) {
3570 $event->state('invalid');
3571 } elsif($self->api_name =~ /reset/) {
3572 $event->clear_start_time;
3573 $event->clear_update_time;
3574 $event->state('pending');
3577 $e->update_action_trigger_event($event) or return $e->die_event;
3578 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3582 return {complete => 1};
3586 __PACKAGE__->register_method (
3587 method => 'really_delete_user',
3588 api_name => 'open-ils.actor.user.delete.override',
3589 signature => q/@see open-ils.actor.user.delete/
3592 __PACKAGE__->register_method (
3593 method => 'really_delete_user',
3594 api_name => 'open-ils.actor.user.delete',
3596 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3597 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3598 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3599 dest_usr_id is only required when deleting a user that performs staff functions.
3603 sub really_delete_user {
3604 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3605 my $e = new_editor(authtoken => $auth, xact => 1);
3606 return $e->die_event unless $e->checkauth;
3607 $oargs = { all => 1 } unless defined $oargs;
3609 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3610 my $open_bills = $e->json_query({
3611 select => { mbts => ['id'] },
3614 xact_finish => { '=' => undef },
3615 usr => { '=' => $user_id },
3617 }) or return $e->die_event;
3619 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3621 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3623 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3624 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3625 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3627 # No deleting yourself - UI is supposed to stop you first, though.
3628 return $e->die_event unless $e->requestor->id != $user->id;
3629 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3630 # Check if you are allowed to mess with this patron permission group at all
3631 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3632 my $evt = group_perm_failed($session, $e->requestor, $user);
3633 return $e->die_event($evt) if $evt;
3634 my $stat = $e->json_query(
3635 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3636 or return $e->die_event;
3642 __PACKAGE__->register_method (
3643 method => 'user_payments',
3644 api_name => 'open-ils.actor.user.payments.retrieve',
3647 Returns all payments for a given user. Default order is newest payments first.
3648 @param auth Authentication token
3649 @param user_id The user ID
3650 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3655 my($self, $conn, $auth, $user_id, $filters) = @_;
3658 my $e = new_editor(authtoken => $auth);
3659 return $e->die_event unless $e->checkauth;
3661 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3662 return $e->event unless
3663 $e->requestor->id == $user_id or
3664 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3666 # Find all payments for all transactions for user $user_id
3668 select => {mp => ['id']},
3673 select => {mbt => ['id']},
3675 where => {usr => $user_id}
3680 { # by default, order newest payments first
3682 field => 'payment_ts',
3685 # secondary sort in ID as a tie-breaker, since payments created
3686 # within the same transaction will have identical payment_ts's
3693 for (qw/order_by limit offset/) {
3694 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3697 if(defined $filters->{where}) {
3698 foreach (keys %{$filters->{where}}) {
3699 # don't allow the caller to expand the result set to other users
3700 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3704 my $payment_ids = $e->json_query($query);
3705 for my $pid (@$payment_ids) {
3706 my $pay = $e->retrieve_money_payment([
3711 mbt => ['summary', 'circulation', 'grocery'],
3712 circ => ['target_copy'],
3713 acp => ['call_number'],
3721 xact_type => $pay->xact->summary->xact_type,
3722 last_billing_type => $pay->xact->summary->last_billing_type,
3725 if($pay->xact->summary->xact_type eq 'circulation') {
3726 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3727 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3730 $pay->xact($pay->xact->id); # de-flesh
3731 $conn->respond($resp);
3739 __PACKAGE__->register_method (
3740 method => 'negative_balance_users',
3741 api_name => 'open-ils.actor.users.negative_balance',
3744 Returns all users that have an overall negative balance
3745 @param auth Authentication token
3746 @param org_id The context org unit as an ID or list of IDs. This will be the home
3747 library of the user. If no org_unit is specified, no org unit filter is applied
3751 sub negative_balance_users {
3752 my($self, $conn, $auth, $org_id) = @_;
3754 my $e = new_editor(authtoken => $auth);
3755 return $e->die_event unless $e->checkauth;
3756 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3760 mous => ['usr', 'balance_owed'],
3763 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3764 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3781 where => {'+mous' => {balance_owed => {'<' => 0}}}
3784 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3786 my $list = $e->json_query($query, {timeout => 600});
3788 for my $data (@$list) {
3790 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3791 balance_owed => $data->{balance_owed},
3792 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3799 __PACKAGE__->register_method(
3800 method => "request_password_reset",
3801 api_name => "open-ils.actor.patron.password_reset.request",
3803 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3804 "method for changing a user's password. The UUID token is distributed via A/T " .
3805 "templates (i.e. email to the user).",
3807 { desc => 'user_id_type', type => 'string' },
3808 { desc => 'user_id', type => 'string' },
3809 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3811 return => {desc => '1 on success, Event on error'}
3814 sub request_password_reset {
3815 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3817 # Check to see if password reset requests are already being throttled:
3818 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3820 my $e = new_editor(xact => 1);
3823 # Get the user, if any, depending on the input value
3824 if ($user_id_type eq 'username') {
3825 $user = $e->search_actor_user({usrname => $user_id})->[0];
3828 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3830 } elsif ($user_id_type eq 'barcode') {
3831 my $card = $e->search_actor_card([
3832 {barcode => $user_id},
3833 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3836 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3841 # If the user doesn't have an email address, we can't help them
3842 if (!$user->email) {
3844 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3847 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3848 if ($email_must_match) {
3849 if ($user->email ne $email) {
3850 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3854 _reset_password_request($conn, $e, $user);
3857 # Once we have the user, we can issue the password reset request
3858 # XXX Add a wrapper method that accepts barcode + email input
3859 sub _reset_password_request {
3860 my ($conn, $e, $user) = @_;
3862 # 1. Get throttle threshold and time-to-live from OU_settings
3863 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3864 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3866 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3868 # 2. Get time of last request and number of active requests (num_active)
3869 my $active_requests = $e->json_query({
3875 transform => 'COUNT'
3878 column => 'request_time',
3884 has_been_reset => { '=' => 'f' },
3885 request_time => { '>' => $threshold_time }
3889 # Guard against no active requests
3890 if ($active_requests->[0]->{'request_time'}) {
3891 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3892 my $now = DateTime::Format::ISO8601->new();
3894 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3895 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3896 ($last_request->add_duration('1 minute') > $now)) {
3897 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3899 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3903 # TODO Check to see if the user is in a password-reset-restricted group
3905 # Otherwise, go ahead and try to get the user.
3907 # Check the number of active requests for this user
3908 $active_requests = $e->json_query({
3914 transform => 'COUNT'
3919 usr => { '=' => $user->id },
3920 has_been_reset => { '=' => 'f' },
3921 request_time => { '>' => $threshold_time }
3925 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3927 # if less than or equal to per-user threshold, proceed; otherwise, return event
3928 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3929 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3931 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3934 # Create the aupr object and insert into the database
3935 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3936 my $uuid = create_uuid_as_string(UUID_V4);
3937 $reset_request->uuid($uuid);
3938 $reset_request->usr($user->id);
3940 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3943 # Create an event to notify user of the URL to reset their password
3945 # Can we stuff this in the user_data param for trigger autocreate?
3946 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3948 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3949 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3952 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3957 __PACKAGE__->register_method(
3958 method => "commit_password_reset",
3959 api_name => "open-ils.actor.patron.password_reset.commit",
3961 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3962 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3963 "with the supplied password.",
3965 { desc => 'uuid', type => 'string' },
3966 { desc => 'password', type => 'string' },
3968 return => {desc => '1 on success, Event on error'}
3971 sub commit_password_reset {
3972 my($self, $conn, $uuid, $password) = @_;
3974 # Check to see if password reset requests are already being throttled:
3975 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3976 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3977 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3979 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3982 my $e = new_editor(xact => 1);
3984 my $aupr = $e->search_actor_usr_password_reset({
3991 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3993 my $user_id = $aupr->[0]->usr;
3994 my $user = $e->retrieve_actor_user($user_id);
3996 # Ensure we're still within the TTL for the request
3997 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3998 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3999 if ($threshold < DateTime->now(time_zone => 'local')) {
4001 $logger->info("Password reset request needed to be submitted before $threshold");
4002 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4005 # Check complexity of password against OU-defined regex
4006 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4010 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4011 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4012 $is_strong = check_password_strength_custom($password, $pw_regex);
4014 $is_strong = check_password_strength_default($password);
4019 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4022 # All is well; update the password
4023 $user->passwd($password);
4024 $e->update_actor_user($user);
4026 # And flag that this password reset request has been honoured
4027 $aupr->[0]->has_been_reset('t');
4028 $e->update_actor_usr_password_reset($aupr->[0]);
4034 sub check_password_strength_default {
4035 my $password = shift;
4036 # Use the default set of checks
4037 if ( (length($password) < 7) or
4038 ($password !~ m/.*\d+.*/) or
4039 ($password !~ m/.*[A-Za-z]+.*/)
4046 sub check_password_strength_custom {
4047 my ($password, $pw_regex) = @_;
4049 $pw_regex = qr/$pw_regex/;
4050 if ($password !~ /$pw_regex/) {
4058 __PACKAGE__->register_method(
4059 method => "event_def_opt_in_settings",
4060 api_name => "open-ils.actor.event_def.opt_in.settings",
4063 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4065 { desc => 'Authentication token', type => 'string'},
4067 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4072 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4079 sub event_def_opt_in_settings {
4080 my($self, $conn, $auth, $org_id) = @_;
4081 my $e = new_editor(authtoken => $auth);
4082 return $e->event unless $e->checkauth;
4084 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4085 return $e->event unless
4086 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4088 $org_id = $e->requestor->home_ou;
4091 # find all config.user_setting_type's related to event_defs for the requested org unit
4092 my $types = $e->json_query({
4093 select => {cust => ['name']},
4094 from => {atevdef => 'cust'},
4097 owner => $U->get_org_ancestors($org_id), # context org plus parents
4104 $conn->respond($_) for
4105 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4112 __PACKAGE__->register_method(
4113 method => "user_visible_circs",
4114 api_name => "open-ils.actor.history.circ.visible",
4117 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4119 { desc => 'Authentication token', type => 'string'},
4120 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4121 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4124 desc => q/An object with 2 fields: circulation and summary.
4125 circulation is the "circ" object. summary is the related "accs" object/,
4131 __PACKAGE__->register_method(
4132 method => "user_visible_circs",
4133 api_name => "open-ils.actor.history.circ.visible.print",
4136 desc => 'Returns printable output for the set of opt-in visible circulations',
4138 { desc => 'Authentication token', type => 'string'},
4139 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4140 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4143 desc => q/An action_trigger.event object or error event./,
4149 __PACKAGE__->register_method(
4150 method => "user_visible_circs",
4151 api_name => "open-ils.actor.history.circ.visible.email",
4154 desc => 'Emails the set of opt-in visible circulations to the requestor',
4156 { desc => 'Authentication token', type => 'string'},
4157 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4158 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4161 desc => q/undef, or event on error/
4166 __PACKAGE__->register_method(
4167 method => "user_visible_circs",
4168 api_name => "open-ils.actor.history.hold.visible",
4171 desc => 'Returns the set of opt-in visible holds',
4173 { desc => 'Authentication token', type => 'string'},
4174 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4175 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4178 desc => q/An object with 1 field: "hold"/,
4184 __PACKAGE__->register_method(
4185 method => "user_visible_circs",
4186 api_name => "open-ils.actor.history.hold.visible.print",
4189 desc => 'Returns printable output for the set of opt-in visible holds',
4191 { desc => 'Authentication token', type => 'string'},
4192 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4193 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4196 desc => q/An action_trigger.event object or error event./,
4202 __PACKAGE__->register_method(
4203 method => "user_visible_circs",
4204 api_name => "open-ils.actor.history.hold.visible.email",
4207 desc => 'Emails the set of opt-in visible holds to the requestor',
4209 { desc => 'Authentication token', type => 'string'},
4210 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4211 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4214 desc => q/undef, or event on error/
4219 sub user_visible_circs {
4220 my($self, $conn, $auth, $user_id, $options) = @_;
4222 my $is_hold = ($self->api_name =~ /hold/);
4223 my $for_print = ($self->api_name =~ /print/);
4224 my $for_email = ($self->api_name =~ /email/);
4225 my $e = new_editor(authtoken => $auth);
4226 return $e->event unless $e->checkauth;
4228 $user_id ||= $e->requestor->id;
4230 $options->{limit} ||= 50;
4231 $options->{offset} ||= 0;
4233 if($user_id != $e->requestor->id) {
4234 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4235 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4236 return $e->event unless $e->allowed($perm, $user->home_ou);
4239 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4241 my $data = $e->json_query({
4242 from => [$db_func, $user_id],
4243 limit => $$options{limit},
4244 offset => $$options{offset}
4246 # TODO: I only want IDs. code below didn't get me there
4247 # {"select":{"au":[{"column":"id", "result_field":"id",
4248 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4253 return undef unless @$data;
4257 # collect the batch of objects
4261 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4262 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4266 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4267 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4270 } elsif ($for_email) {
4272 $conn->respond_complete(1) if $for_email; # no sense in waiting
4280 my $hold = $e->retrieve_action_hold_request($id);
4281 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4282 # events will be fired from action_trigger_runner
4286 my $circ = $e->retrieve_action_circulation($id);
4287 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4288 # events will be fired from action_trigger_runner
4292 } else { # just give me the data please
4300 my $hold = $e->retrieve_action_hold_request($id);
4301 $conn->respond({hold => $hold});
4305 my $circ = $e->retrieve_action_circulation($id);
4308 summary => $U->create_circ_chain_summary($e, $id)
4317 __PACKAGE__->register_method(
4318 method => "user_saved_search_cud",
4319 api_name => "open-ils.actor.user.saved_search.cud",
4322 desc => 'Create/Update/Delete Access to user saved searches',
4324 { desc => 'Authentication token', type => 'string' },
4325 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4328 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4334 __PACKAGE__->register_method(
4335 method => "user_saved_search_cud",
4336 api_name => "open-ils.actor.user.saved_search.retrieve",
4339 desc => 'Retrieve a saved search object',
4341 { desc => 'Authentication token', type => 'string' },
4342 { desc => 'Saved Search ID', type => 'number' }
4345 desc => q/The saved search object, Event on error/,
4351 sub user_saved_search_cud {
4352 my( $self, $client, $auth, $search ) = @_;
4353 my $e = new_editor( authtoken=>$auth );
4354 return $e->die_event unless $e->checkauth;
4356 my $o_search; # prior version of the object, if any
4357 my $res; # to be returned
4359 # branch on the operation type
4361 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4363 # Get the old version, to check ownership
4364 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4365 or return $e->die_event;
4367 # You can't read somebody else's search
4368 return OpenILS::Event->new('BAD_PARAMS')
4369 unless $o_search->owner == $e->requestor->id;
4375 $e->xact_begin; # start an editor transaction
4377 if( $search->isnew ) { # Create
4379 # You can't create a search for somebody else
4380 return OpenILS::Event->new('BAD_PARAMS')
4381 unless $search->owner == $e->requestor->id;
4383 $e->create_actor_usr_saved_search( $search )
4384 or return $e->die_event;
4388 } elsif( $search->ischanged ) { # Update
4390 # You can't change ownership of a search
4391 return OpenILS::Event->new('BAD_PARAMS')
4392 unless $search->owner == $e->requestor->id;
4394 # Get the old version, to check ownership
4395 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4396 or return $e->die_event;
4398 # You can't update somebody else's search
4399 return OpenILS::Event->new('BAD_PARAMS')
4400 unless $o_search->owner == $e->requestor->id;
4403 $e->update_actor_usr_saved_search( $search )
4404 or return $e->die_event;
4408 } elsif( $search->isdeleted ) { # Delete
4410 # Get the old version, to check ownership
4411 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4412 or return $e->die_event;
4414 # You can't delete somebody else's search
4415 return OpenILS::Event->new('BAD_PARAMS')
4416 unless $o_search->owner == $e->requestor->id;
4419 $e->delete_actor_usr_saved_search( $o_search )
4420 or return $e->die_event;
4431 __PACKAGE__->register_method(
4432 method => "get_barcodes",
4433 api_name => "open-ils.actor.get_barcodes"
4437 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4438 my $e = new_editor(authtoken => $auth);
4439 return $e->event unless $e->checkauth;
4440 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4442 my $db_result = $e->json_query(
4444 'evergreen.get_barcodes',
4445 $org_id, $context, $barcode,
4449 if($context =~ /actor/) {
4450 my $filter_result = ();
4452 foreach my $result (@$db_result) {
4453 if($result->{type} eq 'actor') {
4454 if($e->requestor->id != $result->{id}) {
4455 $patron = $e->retrieve_actor_user($result->{id});
4457 push(@$filter_result, $e->event);
4460 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4461 push(@$filter_result, $result);
4464 push(@$filter_result, $e->event);
4468 push(@$filter_result, $result);
4472 push(@$filter_result, $result);
4475 return $filter_result;
4481 __PACKAGE__->register_method(
4482 method => 'address_alert_test',
4483 api_name => 'open-ils.actor.address_alert.test',
4485 desc => "Tests a set of address fields to determine if they match with an address_alert",
4487 {desc => 'Authentication token', type => 'string'},
4488 {desc => 'Org Unit', type => 'number'},
4489 {desc => 'Fields', type => 'hash'},
4491 return => {desc => 'List of matching address_alerts'}
4495 sub address_alert_test {
4496 my ($self, $client, $auth, $org_unit, $fields) = @_;
4497 return [] unless $fields and grep {$_} values %$fields;
4499 my $e = new_editor(authtoken => $auth);
4500 return $e->event unless $e->checkauth;
4501 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4502 $org_unit ||= $e->requestor->ws_ou;
4504 my $alerts = $e->json_query({
4506 'actor.address_alert_matches',
4514 $$fields{post_code},
4515 $$fields{mailing_address},
4516 $$fields{billing_address}
4520 # map the json_query hashes to real objects
4522 map {$e->retrieve_actor_address_alert($_)}
4523 (map {$_->{id}} @$alerts)
4527 __PACKAGE__->register_method(
4528 method => "mark_users_contact_invalid",
4529 api_name => "open-ils.actor.invalidate.email",
4531 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",
4533 {desc => "Authentication token", type => "string"},
4534 {desc => "Patron ID", type => "number"},
4535 {desc => "Additional note text (optional)", type => "string"},
4536 {desc => "penalty org unit ID (optional)", type => "number"}
4538 return => {desc => "Event describing success or failure", type => "object"}
4542 __PACKAGE__->register_method(
4543 method => "mark_users_contact_invalid",
4544 api_name => "open-ils.actor.invalidate.day_phone",
4546 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",
4548 {desc => "Authentication token", type => "string"},
4549 {desc => "Patron ID", type => "number"},
4550 {desc => "Additional note text (optional)", type => "string"},
4551 {desc => "penalty org unit ID (optional)", type => "number"}
4553 return => {desc => "Event describing success or failure", type => "object"}
4557 __PACKAGE__->register_method(
4558 method => "mark_users_contact_invalid",
4559 api_name => "open-ils.actor.invalidate.evening_phone",
4561 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",
4563 {desc => "Authentication token", type => "string"},
4564 {desc => "Patron ID", type => "number"},
4565 {desc => "Additional note text (optional)", type => "string"},
4566 {desc => "penalty org unit ID (optional)", type => "number"}
4568 return => {desc => "Event describing success or failure", type => "object"}
4572 __PACKAGE__->register_method(
4573 method => "mark_users_contact_invalid",
4574 api_name => "open-ils.actor.invalidate.other_phone",
4576 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",
4578 {desc => "Authentication token", type => "string"},
4579 {desc => "Patron ID", type => "number"},
4580 {desc => "Additional note text (optional)", type => "string"},
4581 {desc => "penalty org unit ID (optional, default to top of org tree)",
4584 return => {desc => "Event describing success or failure", type => "object"}
4588 sub mark_users_contact_invalid {
4589 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4591 # This method invalidates an email address or a phone_number which
4592 # removes the bad email address or phone number, copying its contents
4593 # to a patron note, and institutes a standing penalty for "bad email"
4594 # or "bad phone number" which is cleared when the user is saved or
4595 # optionally only when the user is saved with an email address or
4596 # phone number (or staff manually delete the penalty).
4598 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4600 my $e = new_editor(authtoken => $auth, xact => 1);
4601 return $e->die_event unless $e->checkauth;
4603 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4604 $e, $contact_type, {usr => $patron_id},
4605 $addl_note, $penalty_ou, $e->requestor->id
4609 # Putting the following method in open-ils.actor is a bad fit, except in that
4610 # it serves an interface that lives under 'actor' in the templates directory,
4611 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4613 __PACKAGE__->register_method(
4614 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4615 method => "get_all_at_reactors_in_use",
4620 { name => 'authtoken', type => 'string' }
4623 desc => 'list of reactor names', type => 'array'
4628 sub get_all_at_reactors_in_use {
4629 my ($self, $conn, $auth) = @_;
4631 my $e = new_editor(authtoken => $auth);
4632 $e->checkauth or return $e->die_event;
4633 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4635 my $reactors = $e->json_query({
4637 atevdef => [{column => "reactor", transform => "distinct"}]
4639 from => {atevdef => {}}
4642 return $e->die_event unless ref $reactors eq "ARRAY";
4645 return [ map { $_->{reactor} } @$reactors ];
4648 __PACKAGE__->register_method(
4649 method => "filter_group_entry_crud",
4650 api_name => "open-ils.actor.filter_group_entry.crud",
4653 Provides CRUD access to filter group entry objects. These are not full accessible
4654 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4655 are not accessible via PCRUD (because they have no fields against which to link perms)
4658 {desc => "Authentication token", type => "string"},
4659 {desc => "Entry ID / Entry Object", type => "number"},
4660 {desc => "Additional note text (optional)", type => "string"},
4661 {desc => "penalty org unit ID (optional, default to top of org tree)",
4665 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4671 sub filter_group_entry_crud {
4672 my ($self, $conn, $auth, $arg) = @_;
4674 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4675 my $e = new_editor(authtoken => $auth, xact => 1);
4676 return $e->die_event unless $e->checkauth;
4682 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4683 or return $e->die_event;
4685 return $e->die_event unless $e->allowed(
4686 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4688 my $query = $arg->query;
4689 $query = $e->create_actor_search_query($query) or return $e->die_event;
4690 $arg->query($query->id);
4691 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4692 $entry->query($query);
4697 } elsif ($arg->ischanged) {
4699 my $entry = $e->retrieve_actor_search_filter_group_entry([
4702 flesh_fields => {asfge => ['grp']}
4704 ]) or return $e->die_event;
4706 return $e->die_event unless $e->allowed(
4707 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4709 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4710 $arg->query($arg->query->id);
4711 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4712 $arg->query($query);
4717 } elsif ($arg->isdeleted) {
4719 my $entry = $e->retrieve_actor_search_filter_group_entry([
4722 flesh_fields => {asfge => ['grp', 'query']}
4724 ]) or return $e->die_event;
4726 return $e->die_event unless $e->allowed(
4727 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4729 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4730 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4743 my $entry = $e->retrieve_actor_search_filter_group_entry([
4746 flesh_fields => {asfge => ['grp', 'query']}
4748 ]) or return $e->die_event;
4750 return $e->die_event unless $e->allowed(
4751 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4752 $entry->grp->owner);
4755 $entry->grp($entry->grp->id); # for consistency