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",
499 push @$fields, "home_ou" if $home_ou;
500 return new_flesh_user($id, $fields, $e );
508 # clone and clear stuff that would break the database
512 my $new_patron = $patron->clone;
514 $new_patron->clear_billing_address();
515 $new_patron->clear_mailing_address();
516 $new_patron->clear_addresses();
517 $new_patron->clear_card();
518 $new_patron->clear_cards();
519 $new_patron->clear_id();
520 $new_patron->clear_isnew();
521 $new_patron->clear_ischanged();
522 $new_patron->clear_isdeleted();
523 $new_patron->clear_stat_cat_entries();
524 $new_patron->clear_permissions();
525 $new_patron->clear_standing_penalties();
535 my $user_obj = shift;
537 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
538 return (undef, $evt) if $evt;
540 my $ex = $session->request(
541 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
543 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
546 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
548 my $id = $session->request(
549 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
550 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
552 $logger->info("Successfully created new user [$id] in DB");
554 return ( $session->request(
555 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
559 sub check_group_perm {
560 my( $session, $requestor, $patron ) = @_;
563 # first let's see if the requestor has
564 # priveleges to update this user in any way
565 if( ! $patron->isnew ) {
566 my $p = $session->request(
567 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
569 # If we are the requestor (trying to update our own account)
570 # and we are not trying to change our profile, we're good
571 if( $p->id == $requestor->id and
572 $p->profile == $patron->profile ) {
577 $evt = group_perm_failed($session, $requestor, $p);
581 # They are allowed to edit this patron.. can they put the
582 # patron into the group requested?
583 $evt = group_perm_failed($session, $requestor, $patron);
589 sub group_perm_failed {
590 my( $session, $requestor, $patron ) = @_;
594 my $grpid = $patron->profile;
598 $logger->debug("user update looking for group perm for group $grpid");
599 $grp = $session->request(
600 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
601 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
603 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
605 $logger->info("user update checking perm $perm on user ".
606 $requestor->id." for update/create on user username=".$patron->usrname);
608 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
616 my( $session, $patron, $user_obj, $noperm) = @_;
618 $logger->info("Updating patron ".$patron->id." in DB");
623 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
624 return (undef, $evt) if $evt;
627 # update the password by itself to avoid the password protection magic
628 if( $patron->passwd ) {
629 my $s = $session->request(
630 'open-ils.storage.direct.actor.user.remote_update',
631 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
632 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
633 $patron->clear_passwd;
636 if(!$patron->ident_type) {
637 $patron->clear_ident_type;
638 $patron->clear_ident_value;
641 $evt = verify_last_xact($session, $patron);
642 return (undef, $evt) if $evt;
644 my $stat = $session->request(
645 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
646 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
651 sub verify_last_xact {
652 my( $session, $patron ) = @_;
653 return undef unless $patron->id and $patron->id > 0;
654 my $p = $session->request(
655 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
656 my $xact = $p->last_xact_id;
657 return undef unless $xact;
658 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
659 return OpenILS::Event->new('XACT_COLLISION')
660 if $xact ne $patron->last_xact_id;
665 sub _check_dup_ident {
666 my( $session, $patron ) = @_;
668 return undef unless $patron->ident_value;
671 ident_type => $patron->ident_type,
672 ident_value => $patron->ident_value,
675 $logger->debug("patron update searching for dup ident values: " .
676 $patron->ident_type . ':' . $patron->ident_value);
678 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
680 my $dups = $session->request(
681 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
684 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
691 sub _add_update_addresses {
695 my $new_patron = shift;
699 my $current_id; # id of the address before creation
701 my $addresses = $patron->addresses();
703 for my $address (@$addresses) {
705 next unless ref $address;
706 $current_id = $address->id();
708 if( $patron->billing_address() and
709 $patron->billing_address() == $current_id ) {
710 $logger->info("setting billing addr to $current_id");
711 $new_patron->billing_address($address->id());
712 $new_patron->ischanged(1);
715 if( $patron->mailing_address() and
716 $patron->mailing_address() == $current_id ) {
717 $new_patron->mailing_address($address->id());
718 $logger->info("setting mailing addr to $current_id");
719 $new_patron->ischanged(1);
723 if($address->isnew()) {
725 $address->usr($new_patron->id());
727 ($address, $evt) = _add_address($session,$address);
728 return (undef, $evt) if $evt;
730 # we need to get the new id
731 if( $patron->billing_address() and
732 $patron->billing_address() == $current_id ) {
733 $new_patron->billing_address($address->id());
734 $logger->info("setting billing addr to $current_id");
735 $new_patron->ischanged(1);
738 if( $patron->mailing_address() and
739 $patron->mailing_address() == $current_id ) {
740 $new_patron->mailing_address($address->id());
741 $logger->info("setting mailing addr to $current_id");
742 $new_patron->ischanged(1);
745 } elsif($address->ischanged() ) {
747 ($address, $evt) = _update_address($session, $address);
748 return (undef, $evt) if $evt;
750 } elsif($address->isdeleted() ) {
752 if( $address->id() == $new_patron->mailing_address() ) {
753 $new_patron->clear_mailing_address();
754 ($new_patron, $evt) = _update_patron($session, $new_patron);
755 return (undef, $evt) if $evt;
758 if( $address->id() == $new_patron->billing_address() ) {
759 $new_patron->clear_billing_address();
760 ($new_patron, $evt) = _update_patron($session, $new_patron);
761 return (undef, $evt) if $evt;
764 $evt = _delete_address($session, $address);
765 return (undef, $evt) if $evt;
769 return ( $new_patron, undef );
773 # adds an address to the db and returns the address with new id
775 my($session, $address) = @_;
776 $address->clear_id();
778 $logger->info("Creating new address at street ".$address->street1);
780 # put the address into the database
781 my $id = $session->request(
782 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
783 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
786 return ($address, undef);
790 sub _update_address {
791 my( $session, $address ) = @_;
793 $logger->info("Updating address ".$address->id." in the DB");
795 my $stat = $session->request(
796 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
798 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
799 return ($address, undef);
804 sub _add_update_cards {
808 my $new_patron = shift;
812 my $virtual_id; #id of the card before creation
814 my $cards = $patron->cards();
815 for my $card (@$cards) {
817 $card->usr($new_patron->id());
819 if(ref($card) and $card->isnew()) {
821 $virtual_id = $card->id();
822 ( $card, $evt ) = _add_card($session,$card);
823 return (undef, $evt) if $evt;
825 #if(ref($patron->card)) { $patron->card($patron->card->id); }
826 if($patron->card() == $virtual_id) {
827 $new_patron->card($card->id());
828 $new_patron->ischanged(1);
831 } elsif( ref($card) and $card->ischanged() ) {
832 $evt = _update_card($session, $card);
833 return (undef, $evt) if $evt;
837 return ( $new_patron, undef );
841 # adds an card to the db and returns the card with new id
843 my( $session, $card ) = @_;
846 $logger->info("Adding new patron card ".$card->barcode);
848 my $id = $session->request(
849 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
850 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
851 $logger->info("Successfully created patron card $id");
854 return ( $card, undef );
858 # returns event on error. returns undef otherwise
860 my( $session, $card ) = @_;
861 $logger->info("Updating patron card ".$card->id);
863 my $stat = $session->request(
864 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
865 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
872 # returns event on error. returns undef otherwise
873 sub _delete_address {
874 my( $session, $address ) = @_;
876 $logger->info("Deleting address ".$address->id." from DB");
878 my $stat = $session->request(
879 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
881 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
887 sub _add_survey_responses {
888 my ($session, $patron, $new_patron) = @_;
890 $logger->info( "Updating survey responses for patron ".$new_patron->id );
892 my $responses = $patron->survey_responses;
896 $_->usr($new_patron->id) for (@$responses);
898 my $evt = $U->simplereq( "open-ils.circ",
899 "open-ils.circ.survey.submit.user_id", $responses );
901 return (undef, $evt) if defined($U->event_code($evt));
905 return ( $new_patron, undef );
908 sub _clear_badcontact_penalties {
909 my ($session, $old_patron, $new_patron, $user_obj) = @_;
911 return ($new_patron, undef) unless $old_patron;
913 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
914 my $e = new_editor(xact => 1);
916 # This ignores whether the caller of update_patron has any permission
917 # to remove penalties, but these penalties no longer make sense
918 # if an email address field (for example) is changed (and the caller must
919 # have perms to do *that*) so there's no reason not to clear the penalties.
921 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
923 "+csp" => {"name" => [values(%$PNM)]},
924 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
926 "join" => {"csp" => {}},
928 "flesh_fields" => {"ausp" => ["standing_penalty"]}
930 ]) or return (undef, $e->die_event);
932 return ($new_patron, undef) unless @$bad_contact_penalties;
934 my @penalties_to_clear;
935 my ($field, $penalty_name);
937 # For each field that might have an associated bad contact penalty,
938 # check for such penalties and add them to the to-clear list if that
940 while (($field, $penalty_name) = each(%$PNM)) {
941 if ($old_patron->$field ne $new_patron->$field) {
942 push @penalties_to_clear, grep {
943 $_->standing_penalty->name eq $penalty_name
944 } @$bad_contact_penalties;
948 foreach (@penalties_to_clear) {
949 # Note that this "archives" penalties, in the terminology of the staff
950 # client, instead of just deleting them. This may assist reporting,
951 # or preserving old contact information when it is still potentially
953 $_->standing_penalty($_->standing_penalty->id); # deflesh
954 $_->stop_date('now');
955 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
959 return ($new_patron, undef);
963 sub _create_stat_maps {
965 my($session, $user_session, $patron, $new_patron) = @_;
967 my $maps = $patron->stat_cat_entries();
969 for my $map (@$maps) {
971 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
973 if ($map->isdeleted()) {
974 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
976 } elsif ($map->isnew()) {
977 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
982 $map->target_usr($new_patron->id);
985 $logger->info("Updating stat entry with method $method and map $map");
987 my $stat = $session->request($method, $map)->gather(1);
988 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
992 return ($new_patron, undef);
995 sub _create_perm_maps {
997 my($session, $user_session, $patron, $new_patron) = @_;
999 my $maps = $patron->permissions;
1001 for my $map (@$maps) {
1003 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1004 if ($map->isdeleted()) {
1005 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1006 } elsif ($map->isnew()) {
1007 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1012 $map->usr($new_patron->id);
1014 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1015 $logger->info( "Updating permissions with method $method and map $map" );
1017 my $stat = $session->request($method, $map)->gather(1);
1018 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1022 return ($new_patron, undef);
1026 __PACKAGE__->register_method(
1027 method => "set_user_work_ous",
1028 api_name => "open-ils.actor.user.work_ous.update",
1031 sub set_user_work_ous {
1037 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1038 return $evt if $evt;
1040 my $session = $apputils->start_db_session();
1041 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1043 for my $map (@$maps) {
1045 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1046 if ($map->isdeleted()) {
1047 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1048 } elsif ($map->isnew()) {
1049 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1053 #warn( "Updating permissions with method $method and session $ses and map $map" );
1054 $logger->info( "Updating work_ou map with method $method and map $map" );
1056 my $stat = $session->request($method, $map)->gather(1);
1057 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1061 $apputils->commit_db_session($session);
1063 return scalar(@$maps);
1067 __PACKAGE__->register_method(
1068 method => "set_user_perms",
1069 api_name => "open-ils.actor.user.permissions.update",
1072 sub set_user_perms {
1078 my $session = $apputils->start_db_session();
1080 my( $user_obj, $evt ) = $U->checkses($ses);
1081 return $evt if $evt;
1082 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1084 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1087 $all = 1 if ($U->is_true($user_obj->super_user()));
1088 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1090 for my $map (@$maps) {
1092 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1093 if ($map->isdeleted()) {
1094 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1095 } elsif ($map->isnew()) {
1096 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1100 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1101 #warn( "Updating permissions with method $method and session $ses and map $map" );
1102 $logger->info( "Updating permissions with method $method and map $map" );
1104 my $stat = $session->request($method, $map)->gather(1);
1105 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1109 $apputils->commit_db_session($session);
1111 return scalar(@$maps);
1115 __PACKAGE__->register_method(
1116 method => "user_retrieve_by_barcode",
1118 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1120 sub user_retrieve_by_barcode {
1121 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1123 my $e = new_editor(authtoken => $auth);
1124 return $e->event unless $e->checkauth;
1126 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1127 or return $e->event;
1129 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1130 return $e->event unless $e->allowed(
1131 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1138 __PACKAGE__->register_method(
1139 method => "get_user_by_id",
1141 api_name => "open-ils.actor.user.retrieve",
1144 sub get_user_by_id {
1145 my ($self, $client, $auth, $id) = @_;
1146 my $e = new_editor(authtoken=>$auth);
1147 return $e->event unless $e->checkauth;
1148 my $user = $e->retrieve_actor_user($id) or return $e->event;
1149 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1154 __PACKAGE__->register_method(
1155 method => "get_org_types",
1156 api_name => "open-ils.actor.org_types.retrieve",
1159 return $U->get_org_types();
1163 __PACKAGE__->register_method(
1164 method => "get_user_ident_types",
1165 api_name => "open-ils.actor.user.ident_types.retrieve",
1168 sub get_user_ident_types {
1169 return $ident_types if $ident_types;
1170 return $ident_types =
1171 new_editor()->retrieve_all_config_identification_type();
1175 __PACKAGE__->register_method(
1176 method => "get_org_unit",
1177 api_name => "open-ils.actor.org_unit.retrieve",
1181 my( $self, $client, $user_session, $org_id ) = @_;
1182 my $e = new_editor(authtoken => $user_session);
1184 return $e->event unless $e->checkauth;
1185 $org_id = $e->requestor->ws_ou;
1187 my $o = $e->retrieve_actor_org_unit($org_id)
1188 or return $e->event;
1192 __PACKAGE__->register_method(
1193 method => "search_org_unit",
1194 api_name => "open-ils.actor.org_unit_list.search",
1197 sub search_org_unit {
1199 my( $self, $client, $field, $value ) = @_;
1201 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1203 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1204 { $field => $value } );
1210 # build the org tree
1212 __PACKAGE__->register_method(
1213 method => "get_org_tree",
1214 api_name => "open-ils.actor.org_tree.retrieve",
1216 note => "Returns the entire org tree structure",
1222 return $U->get_org_tree($client->session->session_locale);
1226 __PACKAGE__->register_method(
1227 method => "get_org_descendants",
1228 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1231 # depth is optional. org_unit is the id
1232 sub get_org_descendants {
1233 my( $self, $client, $org_unit, $depth ) = @_;
1235 if(ref $org_unit eq 'ARRAY') {
1238 for my $i (0..scalar(@$org_unit)-1) {
1239 my $list = $U->simple_scalar_request(
1241 "open-ils.storage.actor.org_unit.descendants.atomic",
1242 $org_unit->[$i], $depth->[$i] );
1243 push(@trees, $U->build_org_tree($list));
1248 my $orglist = $apputils->simple_scalar_request(
1250 "open-ils.storage.actor.org_unit.descendants.atomic",
1251 $org_unit, $depth );
1252 return $U->build_org_tree($orglist);
1257 __PACKAGE__->register_method(
1258 method => "get_org_ancestors",
1259 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1262 # depth is optional. org_unit is the id
1263 sub get_org_ancestors {
1264 my( $self, $client, $org_unit, $depth ) = @_;
1265 my $orglist = $apputils->simple_scalar_request(
1267 "open-ils.storage.actor.org_unit.ancestors.atomic",
1268 $org_unit, $depth );
1269 return $U->build_org_tree($orglist);
1273 __PACKAGE__->register_method(
1274 method => "get_standings",
1275 api_name => "open-ils.actor.standings.retrieve"
1280 return $user_standings if $user_standings;
1281 return $user_standings =
1282 $apputils->simple_scalar_request(
1284 "open-ils.cstore.direct.config.standing.search.atomic",
1285 { id => { "!=" => undef } }
1290 __PACKAGE__->register_method(
1291 method => "get_my_org_path",
1292 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1295 sub get_my_org_path {
1296 my( $self, $client, $auth, $org_id ) = @_;
1297 my $e = new_editor(authtoken=>$auth);
1298 return $e->event unless $e->checkauth;
1299 $org_id = $e->requestor->ws_ou unless defined $org_id;
1301 return $apputils->simple_scalar_request(
1303 "open-ils.storage.actor.org_unit.full_path.atomic",
1308 __PACKAGE__->register_method(
1309 method => "patron_adv_search",
1310 api_name => "open-ils.actor.patron.search.advanced"
1312 sub patron_adv_search {
1313 my( $self, $client, $auth, $search_hash,
1314 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1316 my $e = new_editor(authtoken=>$auth);
1317 return $e->event unless $e->checkauth;
1318 return $e->event unless $e->allowed('VIEW_USER');
1320 # depth boundary outside of which patrons must opt-in, default to 0
1321 my $opt_boundary = 0;
1322 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1324 return $U->storagereq(
1325 "open-ils.storage.actor.user.crazy_search", $search_hash,
1326 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1330 __PACKAGE__->register_method(
1331 method => "update_passwd",
1332 api_name => "open-ils.actor.user.password.update",
1334 desc => "Update the operator's password",
1336 { desc => 'Authentication token', type => 'string' },
1337 { desc => 'New password', type => 'string' },
1338 { desc => 'Current password', type => 'string' }
1340 return => {desc => '1 on success, Event on error or incorrect current password'}
1344 __PACKAGE__->register_method(
1345 method => "update_passwd",
1346 api_name => "open-ils.actor.user.username.update",
1348 desc => "Update the operator's username",
1350 { desc => 'Authentication token', type => 'string' },
1351 { desc => 'New username', type => 'string' },
1352 { desc => 'Current password', type => 'string' }
1354 return => {desc => '1 on success, Event on error or incorrect current password'}
1358 __PACKAGE__->register_method(
1359 method => "update_passwd",
1360 api_name => "open-ils.actor.user.email.update",
1362 desc => "Update the operator's email address",
1364 { desc => 'Authentication token', type => 'string' },
1365 { desc => 'New email address', type => 'string' },
1366 { desc => 'Current password', type => 'string' }
1368 return => {desc => '1 on success, Event on error or incorrect current password'}
1373 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1374 my $e = new_editor(xact=>1, authtoken=>$auth);
1375 return $e->die_event unless $e->checkauth;
1377 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1378 or return $e->die_event;
1379 my $api = $self->api_name;
1381 # make sure the original password matches the in-database password
1382 if (md5_hex($orig_pw) ne $db_user->passwd) {
1384 return new OpenILS::Event('INCORRECT_PASSWORD');
1387 if( $api =~ /password/o ) {
1389 $db_user->passwd($new_val);
1393 # if we don't clear the password, the user will be updated with
1394 # a hashed version of the hashed version of their password
1395 $db_user->clear_passwd;
1397 if( $api =~ /username/o ) {
1399 # make sure no one else has this username
1400 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1403 return new OpenILS::Event('USERNAME_EXISTS');
1405 $db_user->usrname($new_val);
1407 } elsif( $api =~ /email/o ) {
1408 $db_user->email($new_val);
1412 $e->update_actor_user($db_user) or return $e->die_event;
1415 # update the cached user to pick up these changes
1416 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1422 __PACKAGE__->register_method(
1423 method => "check_user_perms",
1424 api_name => "open-ils.actor.user.perm.check",
1425 notes => <<" NOTES");
1426 Takes a login session, user id, an org id, and an array of perm type strings. For each
1427 perm type, if the user does *not* have the given permission it is added
1428 to a list which is returned from the method. If all permissions
1429 are allowed, an empty list is returned
1430 if the logged in user does not match 'user_id', then the logged in user must
1431 have VIEW_PERMISSION priveleges.
1434 sub check_user_perms {
1435 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1437 my( $staff, $evt ) = $apputils->checkses($login_session);
1438 return $evt if $evt;
1440 if($staff->id ne $user_id) {
1441 if( $evt = $apputils->check_perms(
1442 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1448 for my $perm (@$perm_types) {
1449 if($apputils->check_perms($user_id, $org_id, $perm)) {
1450 push @not_allowed, $perm;
1454 return \@not_allowed
1457 __PACKAGE__->register_method(
1458 method => "check_user_perms2",
1459 api_name => "open-ils.actor.user.perm.check.multi_org",
1461 Checks the permissions on a list of perms and orgs for a user
1462 @param authtoken The login session key
1463 @param user_id The id of the user to check
1464 @param orgs The array of org ids
1465 @param perms The array of permission names
1466 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1467 if the logged in user does not match 'user_id', then the logged in user must
1468 have VIEW_PERMISSION priveleges.
1471 sub check_user_perms2 {
1472 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1474 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1475 $authtoken, $user_id, 'VIEW_PERMISSION' );
1476 return $evt if $evt;
1479 for my $org (@$orgs) {
1480 for my $perm (@$perms) {
1481 if($apputils->check_perms($user_id, $org, $perm)) {
1482 push @not_allowed, [ $org, $perm ];
1487 return \@not_allowed
1491 __PACKAGE__->register_method(
1492 method => 'check_user_perms3',
1493 api_name => 'open-ils.actor.user.perm.highest_org',
1495 Returns the highest org unit id at which a user has a given permission
1496 If the requestor does not match the target user, the requestor must have
1497 'VIEW_PERMISSION' rights at the home org unit of the target user
1498 @param authtoken The login session key
1499 @param userid The id of the user in question
1500 @param perm The permission to check
1501 @return The org unit highest in the org tree within which the user has
1502 the requested permission
1505 sub check_user_perms3 {
1506 my($self, $client, $authtoken, $user_id, $perm) = @_;
1507 my $e = new_editor(authtoken=>$authtoken);
1508 return $e->event unless $e->checkauth;
1510 my $tree = $U->get_org_tree();
1512 unless($e->requestor->id == $user_id) {
1513 my $user = $e->retrieve_actor_user($user_id)
1514 or return $e->event;
1515 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1516 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1519 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1522 __PACKAGE__->register_method(
1523 method => 'user_has_work_perm_at',
1524 api_name => 'open-ils.actor.user.has_work_perm_at',
1528 Returns a set of org unit IDs which represent the highest orgs in
1529 the org tree where the user has the requested permission. The
1530 purpose of this method is to return the smallest set of org units
1531 which represent the full expanse of the user's ability to perform
1532 the requested action. The user whose perms this method should
1533 check is implied by the authtoken. /,
1535 {desc => 'authtoken', type => 'string'},
1536 {desc => 'permission name', type => 'string'},
1537 {desc => q/user id, optional. If present, check perms for
1538 this user instead of the logged in user/, type => 'number'},
1540 return => {desc => 'An array of org IDs'}
1544 sub user_has_work_perm_at {
1545 my($self, $conn, $auth, $perm, $user_id) = @_;
1546 my $e = new_editor(authtoken=>$auth);
1547 return $e->event unless $e->checkauth;
1548 if(defined $user_id) {
1549 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1550 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1552 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1555 __PACKAGE__->register_method(
1556 method => 'user_has_work_perm_at_batch',
1557 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1561 sub user_has_work_perm_at_batch {
1562 my($self, $conn, $auth, $perms, $user_id) = @_;
1563 my $e = new_editor(authtoken=>$auth);
1564 return $e->event unless $e->checkauth;
1565 if(defined $user_id) {
1566 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1567 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1570 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1576 __PACKAGE__->register_method(
1577 method => 'check_user_perms4',
1578 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1580 Returns the highest org unit id at which a user has a given permission
1581 If the requestor does not match the target user, the requestor must have
1582 'VIEW_PERMISSION' rights at the home org unit of the target user
1583 @param authtoken The login session key
1584 @param userid The id of the user in question
1585 @param perms An array of perm names to check
1586 @return An array of orgId's representing the org unit
1587 highest in the org tree within which the user has the requested permission
1588 The arrah of orgId's has matches the order of the perms array
1591 sub check_user_perms4 {
1592 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1594 my( $staff, $target, $org, $evt );
1596 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1597 $authtoken, $userid, 'VIEW_PERMISSION' );
1598 return $evt if $evt;
1601 return [] unless ref($perms);
1602 my $tree = $U->get_org_tree();
1604 for my $p (@$perms) {
1605 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1611 __PACKAGE__->register_method(
1612 method => "user_fines_summary",
1613 api_name => "open-ils.actor.user.fines.summary",
1616 desc => 'Returns a short summary of the users total open fines, ' .
1617 'excluding voided fines Params are login_session, user_id' ,
1619 {desc => 'Authentication token', type => 'string'},
1620 {desc => 'User ID', type => 'string'} # number?
1623 desc => "a 'mous' object, event on error",
1628 sub user_fines_summary {
1629 my( $self, $client, $auth, $user_id ) = @_;
1631 my $e = new_editor(authtoken=>$auth);
1632 return $e->event unless $e->checkauth;
1634 if( $user_id ne $e->requestor->id ) {
1635 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1636 return $e->event unless
1637 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1640 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1644 __PACKAGE__->register_method(
1645 method => "user_opac_vitals",
1646 api_name => "open-ils.actor.user.opac.vital_stats",
1650 desc => 'Returns a short summary of the users vital stats, including ' .
1651 'identification information, accumulated balance, number of holds, ' .
1652 'and current open circulation stats' ,
1654 {desc => 'Authentication token', type => 'string'},
1655 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1658 desc => "An object with four properties: user, fines, checkouts and holds."
1663 sub user_opac_vitals {
1664 my( $self, $client, $auth, $user_id ) = @_;
1666 my $e = new_editor(authtoken=>$auth);
1667 return $e->event unless $e->checkauth;
1669 $user_id ||= $e->requestor->id;
1671 my $user = $e->retrieve_actor_user( $user_id );
1674 ->method_lookup('open-ils.actor.user.fines.summary')
1675 ->run($auth => $user_id);
1676 return $fines if (defined($U->event_code($fines)));
1679 $fines = new Fieldmapper::money::open_user_summary ();
1680 $fines->balance_owed(0.00);
1681 $fines->total_owed(0.00);
1682 $fines->total_paid(0.00);
1683 $fines->usr($user_id);
1687 ->method_lookup('open-ils.actor.user.hold_requests.count')
1688 ->run($auth => $user_id);
1689 return $holds if (defined($U->event_code($holds)));
1692 ->method_lookup('open-ils.actor.user.checked_out.count')
1693 ->run($auth => $user_id);
1694 return $out if (defined($U->event_code($out)));
1696 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1700 first_given_name => $user->first_given_name,
1701 second_given_name => $user->second_given_name,
1702 family_name => $user->family_name,
1703 alias => $user->alias,
1704 usrname => $user->usrname
1706 fines => $fines->to_bare_hash,
1713 ##### a small consolidation of related method registrations
1714 my $common_params = [
1715 { desc => 'Authentication token', type => 'string' },
1716 { desc => 'User ID', type => 'string' },
1717 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1718 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1721 'open-ils.actor.user.transactions' => '',
1722 'open-ils.actor.user.transactions.fleshed' => '',
1723 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1724 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1725 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1726 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1729 foreach (keys %methods) {
1731 method => "user_transactions",
1734 desc => 'For a given user, retrieve a list of '
1735 . (/\.fleshed/ ? 'fleshed ' : '')
1736 . 'transactions' . $methods{$_}
1737 . ' optionally limited to transactions of a given type.',
1738 params => $common_params,
1740 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1741 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1745 $args{authoritative} = 1;
1746 __PACKAGE__->register_method(%args);
1749 # Now for the counts
1751 'open-ils.actor.user.transactions.count' => '',
1752 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1753 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1756 foreach (keys %methods) {
1758 method => "user_transactions",
1761 desc => 'For a given user, retrieve a count of open '
1762 . 'transactions' . $methods{$_}
1763 . ' optionally limited to transactions of a given type.',
1764 params => $common_params,
1765 return => { desc => "Integer count of transactions, or event on error" }
1768 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1769 __PACKAGE__->register_method(%args);
1772 __PACKAGE__->register_method(
1773 method => "user_transactions",
1774 api_name => "open-ils.actor.user.transactions.have_balance.total",
1777 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1778 . ' optionally limited to transactions of a given type.',
1779 params => $common_params,
1780 return => { desc => "Decimal balance value, or event on error" }
1785 sub user_transactions {
1786 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1789 my $e = new_editor(authtoken => $auth);
1790 return $e->event unless $e->checkauth;
1792 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1794 return $e->event unless
1795 $e->requestor->id == $user_id or
1796 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1798 my $api = $self->api_name();
1800 my $filter = ($api =~ /have_balance/o) ?
1801 { 'balance_owed' => { '<>' => 0 } }:
1802 { 'total_owed' => { '>' => 0 } };
1804 my $method = 'open-ils.actor.user.transactions.history.still_open';
1805 $method = "$method.authoritative" if $api =~ /authoritative/;
1806 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1808 if($api =~ /total/o) {
1810 $total += $_->balance_owed for @$trans;
1814 ($api =~ /count/o ) and return scalar @$trans;
1815 ($api !~ /fleshed/o) and return $trans;
1818 for my $t (@$trans) {
1820 if( $t->xact_type ne 'circulation' ) {
1821 push @resp, {transaction => $t};
1825 my $circ_data = flesh_circ($e, $t->id);
1826 push @resp, {transaction => $t, %$circ_data};
1833 __PACKAGE__->register_method(
1834 method => "user_transaction_retrieve",
1835 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1838 notes => "Returns a fleshed transaction record"
1841 __PACKAGE__->register_method(
1842 method => "user_transaction_retrieve",
1843 api_name => "open-ils.actor.user.transaction.retrieve",
1846 notes => "Returns a transaction record"
1849 sub user_transaction_retrieve {
1850 my($self, $client, $auth, $bill_id) = @_;
1852 my $e = new_editor(authtoken => $auth);
1853 return $e->event unless $e->checkauth;
1855 my $trans = $e->retrieve_money_billable_transaction_summary(
1856 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1858 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1860 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1862 return $trans unless $self->api_name =~ /flesh/;
1863 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1865 my $circ_data = flesh_circ($e, $trans->id, 1);
1867 return {transaction => $trans, %$circ_data};
1872 my $circ_id = shift;
1873 my $flesh_copy = shift;
1875 my $circ = $e->retrieve_action_circulation([
1879 circ => ['target_copy'],
1880 acp => ['call_number'],
1887 my $copy = $circ->target_copy;
1889 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1890 $mods = new Fieldmapper::metabib::virtual_record;
1891 $mods->doc_id(OILS_PRECAT_RECORD);
1892 $mods->title($copy->dummy_title);
1893 $mods->author($copy->dummy_author);
1896 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1900 $circ->target_copy($circ->target_copy->id);
1901 $copy->call_number($copy->call_number->id);
1903 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1907 __PACKAGE__->register_method(
1908 method => "hold_request_count",
1909 api_name => "open-ils.actor.user.hold_requests.count",
1912 notes => 'Returns hold ready/total counts'
1915 sub hold_request_count {
1916 my( $self, $client, $authtoken, $user_id ) = @_;
1917 my $e = new_editor(authtoken => $authtoken);
1918 return $e->event unless $e->checkauth;
1920 $user_id = $e->requestor->id unless defined $user_id;
1922 if($e->requestor->id ne $user_id) {
1923 my $user = $e->retrieve_actor_user($user_id);
1924 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1927 my $holds = $e->json_query({
1928 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1932 fulfillment_time => {"=" => undef },
1933 cancel_time => undef,
1938 total => scalar(@$holds),
1941 $_->{current_shelf_lib} and # avoid undef warnings
1942 $_->{pickup_lib} eq $_->{current_shelf_lib}
1948 __PACKAGE__->register_method(
1949 method => "checked_out",
1950 api_name => "open-ils.actor.user.checked_out",
1954 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1955 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1956 . "(i.e., outstanding balance or some other pending action on the circ). "
1957 . "The .count method also includes a 'total' field which sums all open circs.",
1959 { desc => 'Authentication Token', type => 'string'},
1960 { desc => 'User ID', type => 'string'},
1963 desc => 'Returns event on error, or an object with ID lists, like: '
1964 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1969 __PACKAGE__->register_method(
1970 method => "checked_out",
1971 api_name => "open-ils.actor.user.checked_out.count",
1974 signature => q/@see open-ils.actor.user.checked_out/
1978 my( $self, $conn, $auth, $userid ) = @_;
1980 my $e = new_editor(authtoken=>$auth);
1981 return $e->event unless $e->checkauth;
1983 if( $userid ne $e->requestor->id ) {
1984 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1985 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1987 # see if there is a friend link allowing circ.view perms
1988 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1989 $e, $userid, $e->requestor->id, 'circ.view');
1990 return $e->event unless $allowed;
1994 my $count = $self->api_name =~ /count/;
1995 return _checked_out( $count, $e, $userid );
1999 my( $iscount, $e, $userid ) = @_;
2005 claims_returned => [],
2008 my $meth = 'retrieve_action_open_circ_';
2016 claims_returned => 0,
2023 my $data = $e->$meth($userid);
2027 $result{$_} += $data->$_() for (keys %result);
2028 $result{total} += $data->$_() for (keys %result);
2030 for my $k (keys %result) {
2031 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2041 __PACKAGE__->register_method(
2042 method => "checked_in_with_fines",
2043 api_name => "open-ils.actor.user.checked_in_with_fines",
2046 signature => q/@see open-ils.actor.user.checked_out/
2049 sub checked_in_with_fines {
2050 my( $self, $conn, $auth, $userid ) = @_;
2052 my $e = new_editor(authtoken=>$auth);
2053 return $e->event unless $e->checkauth;
2055 if( $userid ne $e->requestor->id ) {
2056 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2059 # money is owed on these items and they are checked in
2060 my $open = $e->search_action_circulation(
2063 xact_finish => undef,
2064 checkin_time => { "!=" => undef },
2069 my( @lost, @cr, @lo );
2070 for my $c (@$open) {
2071 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2072 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2073 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2078 claims_returned => \@cr,
2079 long_overdue => \@lo
2085 my ($api, $desc, $auth) = @_;
2086 $desc = $desc ? (" " . $desc) : '';
2087 my $ids = ($api =~ /ids$/) ? 1 : 0;
2090 method => "user_transaction_history",
2091 api_name => "open-ils.actor.user.transactions.$api",
2093 desc => "For a given User ID, returns a list of billable transaction" .
2094 ($ids ? " id" : '') .
2095 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2096 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2098 {desc => 'Authentication token', type => 'string'},
2099 {desc => 'User ID', type => 'number'},
2100 {desc => 'Transaction type (optional)', type => 'number'},
2101 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2104 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2108 $auth and push @sig, (authoritative => 1);
2112 my %auth_hist_methods = (
2114 'history.have_charge' => 'that have an initial charge',
2115 'history.still_open' => 'that are not finished',
2116 'history.have_balance' => 'that have a balance',
2117 'history.have_bill' => 'that have billings',
2118 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2119 'history.have_payment' => 'that have at least 1 payment',
2122 foreach (keys %auth_hist_methods) {
2123 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2124 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2125 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2128 sub user_transaction_history {
2129 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2133 my $e = new_editor(authtoken=>$auth);
2134 return $e->die_event unless $e->checkauth;
2136 if ($e->requestor->id ne $userid) {
2137 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2140 my $api = $self->api_name;
2141 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2143 if(defined($type)) {
2144 $filter->{'xact_type'} = $type;
2147 if($api =~ /have_bill_or_payment/o) {
2149 # transactions that have a non-zero sum across all billings or at least 1 payment
2150 $filter->{'-or'} = {
2151 'balance_owed' => { '<>' => 0 },
2152 'last_payment_ts' => { '<>' => undef }
2155 } elsif($api =~ /have_payment/) {
2157 $filter->{last_payment_ts} ||= {'<>' => undef};
2159 } elsif( $api =~ /have_balance/o) {
2161 # transactions that have a non-zero overall balance
2162 $filter->{'balance_owed'} = { '<>' => 0 };
2164 } elsif( $api =~ /have_charge/o) {
2166 # transactions that have at least 1 billing, regardless of whether it was voided
2167 $filter->{'last_billing_ts'} = { '<>' => undef };
2169 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2171 # transactions that have non-zero sum across all billings. This will exclude
2172 # xacts where all billings have been voided
2173 $filter->{'total_owed'} = { '<>' => 0 };
2176 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2177 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2178 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2180 my $mbts = $e->search_money_billable_transaction_summary(
2181 [ { usr => $userid, @xact_finish, %$filter },
2186 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2187 return $mbts unless $api =~ /fleshed/;
2190 for my $t (@$mbts) {
2192 if( $t->xact_type ne 'circulation' ) {
2193 push @resp, {transaction => $t};
2197 my $circ_data = flesh_circ($e, $t->id);
2198 push @resp, {transaction => $t, %$circ_data};
2206 __PACKAGE__->register_method(
2207 method => "user_perms",
2208 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2210 notes => "Returns a list of permissions"
2214 my( $self, $client, $authtoken, $user ) = @_;
2216 my( $staff, $evt ) = $apputils->checkses($authtoken);
2217 return $evt if $evt;
2219 $user ||= $staff->id;
2221 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2225 return $apputils->simple_scalar_request(
2227 "open-ils.storage.permission.user_perms.atomic",
2231 __PACKAGE__->register_method(
2232 method => "retrieve_perms",
2233 api_name => "open-ils.actor.permissions.retrieve",
2234 notes => "Returns a list of permissions"
2236 sub retrieve_perms {
2237 my( $self, $client ) = @_;
2238 return $apputils->simple_scalar_request(
2240 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2241 { id => { '!=' => undef } }
2245 __PACKAGE__->register_method(
2246 method => "retrieve_groups",
2247 api_name => "open-ils.actor.groups.retrieve",
2248 notes => "Returns a list of user groups"
2250 sub retrieve_groups {
2251 my( $self, $client ) = @_;
2252 return new_editor()->retrieve_all_permission_grp_tree();
2255 __PACKAGE__->register_method(
2256 method => "retrieve_org_address",
2257 api_name => "open-ils.actor.org_unit.address.retrieve",
2258 notes => <<' NOTES');
2259 Returns an org_unit address by ID
2260 @param An org_address ID
2262 sub retrieve_org_address {
2263 my( $self, $client, $id ) = @_;
2264 return $apputils->simple_scalar_request(
2266 "open-ils.cstore.direct.actor.org_address.retrieve",
2271 __PACKAGE__->register_method(
2272 method => "retrieve_groups_tree",
2273 api_name => "open-ils.actor.groups.tree.retrieve",
2274 notes => "Returns a list of user groups"
2277 sub retrieve_groups_tree {
2278 my( $self, $client ) = @_;
2279 return new_editor()->search_permission_grp_tree(
2284 flesh_fields => { pgt => ["children"] },
2285 order_by => { pgt => 'name'}
2292 __PACKAGE__->register_method(
2293 method => "add_user_to_groups",
2294 api_name => "open-ils.actor.user.set_groups",
2295 notes => "Adds a user to one or more permission groups"
2298 sub add_user_to_groups {
2299 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2301 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2302 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2303 return $evt if $evt;
2305 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2306 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2307 return $evt if $evt;
2309 $apputils->simplereq(
2311 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2313 for my $group (@$groups) {
2314 my $link = Fieldmapper::permission::usr_grp_map->new;
2316 $link->usr($userid);
2318 my $id = $apputils->simplereq(
2320 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2326 __PACKAGE__->register_method(
2327 method => "get_user_perm_groups",
2328 api_name => "open-ils.actor.user.get_groups",
2329 notes => "Retrieve a user's permission groups."
2333 sub get_user_perm_groups {
2334 my( $self, $client, $authtoken, $userid ) = @_;
2336 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2337 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2338 return $evt if $evt;
2340 return $apputils->simplereq(
2342 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2346 __PACKAGE__->register_method(
2347 method => "get_user_work_ous",
2348 api_name => "open-ils.actor.user.get_work_ous",
2349 notes => "Retrieve a user's work org units."
2352 __PACKAGE__->register_method(
2353 method => "get_user_work_ous",
2354 api_name => "open-ils.actor.user.get_work_ous.ids",
2355 notes => "Retrieve a user's work org units."
2358 sub get_user_work_ous {
2359 my( $self, $client, $auth, $userid ) = @_;
2360 my $e = new_editor(authtoken=>$auth);
2361 return $e->event unless $e->checkauth;
2362 $userid ||= $e->requestor->id;
2364 if($e->requestor->id != $userid) {
2365 my $user = $e->retrieve_actor_user($userid)
2366 or return $e->event;
2367 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2370 return $e->search_permission_usr_work_ou_map({usr => $userid})
2371 unless $self->api_name =~ /.ids$/;
2373 # client just wants a list of org IDs
2374 return $U->get_user_work_ou_ids($e, $userid);
2379 __PACKAGE__->register_method(
2380 method => 'register_workstation',
2381 api_name => 'open-ils.actor.workstation.register.override',
2382 signature => q/@see open-ils.actor.workstation.register/
2385 __PACKAGE__->register_method(
2386 method => 'register_workstation',
2387 api_name => 'open-ils.actor.workstation.register',
2389 Registers a new workstion in the system
2390 @param authtoken The login session key
2391 @param name The name of the workstation id
2392 @param owner The org unit that owns this workstation
2393 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2394 if the name is already in use.
2398 sub register_workstation {
2399 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2401 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2402 return $e->die_event unless $e->checkauth;
2403 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2404 my $existing = $e->search_actor_workstation({name => $name})->[0];
2405 $oargs = { all => 1 } unless defined $oargs;
2409 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2410 # workstation with the given name exists.
2412 if($owner ne $existing->owning_lib) {
2413 # if necessary, update the owning_lib of the workstation
2415 $logger->info("changing owning lib of workstation ".$existing->id.
2416 " from ".$existing->owning_lib." to $owner");
2417 return $e->die_event unless
2418 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2420 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2422 $existing->owning_lib($owner);
2423 return $e->die_event unless $e->update_actor_workstation($existing);
2429 "attempt to register an existing workstation. returning existing ID");
2432 return $existing->id;
2435 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2439 my $ws = Fieldmapper::actor::workstation->new;
2440 $ws->owning_lib($owner);
2442 $e->create_actor_workstation($ws) or return $e->die_event;
2444 return $ws->id; # note: editor sets the id on the new object for us
2447 __PACKAGE__->register_method(
2448 method => 'workstation_list',
2449 api_name => 'open-ils.actor.workstation.list',
2451 Returns a list of workstations registered at the given location
2452 @param authtoken The login session key
2453 @param ids A list of org_unit.id's for the workstation owners
2457 sub workstation_list {
2458 my( $self, $conn, $authtoken, @orgs ) = @_;
2460 my $e = new_editor(authtoken=>$authtoken);
2461 return $e->event unless $e->checkauth;
2466 unless $e->allowed('REGISTER_WORKSTATION', $o);
2467 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2473 __PACKAGE__->register_method(
2474 method => 'fetch_patron_note',
2475 api_name => 'open-ils.actor.note.retrieve.all',
2478 Returns a list of notes for a given user
2479 Requestor must have VIEW_USER permission if pub==false and
2480 @param authtoken The login session key
2481 @param args Hash of params including
2482 patronid : the patron's id
2483 pub : true if retrieving only public notes
2487 sub fetch_patron_note {
2488 my( $self, $conn, $authtoken, $args ) = @_;
2489 my $patronid = $$args{patronid};
2491 my($reqr, $evt) = $U->checkses($authtoken);
2492 return $evt if $evt;
2495 ($patron, $evt) = $U->fetch_user($patronid);
2496 return $evt if $evt;
2499 if( $patronid ne $reqr->id ) {
2500 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2501 return $evt if $evt;
2503 return $U->cstorereq(
2504 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2505 { usr => $patronid, pub => 't' } );
2508 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2509 return $evt if $evt;
2511 return $U->cstorereq(
2512 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2515 __PACKAGE__->register_method(
2516 method => 'create_user_note',
2517 api_name => 'open-ils.actor.note.create',
2519 Creates a new note for the given user
2520 @param authtoken The login session key
2521 @param note The note object
2524 sub create_user_note {
2525 my( $self, $conn, $authtoken, $note ) = @_;
2526 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2527 return $e->die_event unless $e->checkauth;
2529 my $user = $e->retrieve_actor_user($note->usr)
2530 or return $e->die_event;
2532 return $e->die_event unless
2533 $e->allowed('UPDATE_USER',$user->home_ou);
2535 $note->creator($e->requestor->id);
2536 $e->create_actor_usr_note($note) or return $e->die_event;
2542 __PACKAGE__->register_method(
2543 method => 'delete_user_note',
2544 api_name => 'open-ils.actor.note.delete',
2546 Deletes a note for the given user
2547 @param authtoken The login session key
2548 @param noteid The note id
2551 sub delete_user_note {
2552 my( $self, $conn, $authtoken, $noteid ) = @_;
2554 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2555 return $e->die_event unless $e->checkauth;
2556 my $note = $e->retrieve_actor_usr_note($noteid)
2557 or return $e->die_event;
2558 my $user = $e->retrieve_actor_user($note->usr)
2559 or return $e->die_event;
2560 return $e->die_event unless
2561 $e->allowed('UPDATE_USER', $user->home_ou);
2563 $e->delete_actor_usr_note($note) or return $e->die_event;
2569 __PACKAGE__->register_method(
2570 method => 'update_user_note',
2571 api_name => 'open-ils.actor.note.update',
2573 @param authtoken The login session key
2574 @param note The note
2578 sub update_user_note {
2579 my( $self, $conn, $auth, $note ) = @_;
2580 my $e = new_editor(authtoken=>$auth, xact=>1);
2581 return $e->die_event unless $e->checkauth;
2582 my $patron = $e->retrieve_actor_user($note->usr)
2583 or return $e->die_event;
2584 return $e->die_event unless
2585 $e->allowed('UPDATE_USER', $patron->home_ou);
2586 $e->update_actor_user_note($note)
2587 or return $e->die_event;
2594 __PACKAGE__->register_method(
2595 method => 'create_closed_date',
2596 api_name => 'open-ils.actor.org_unit.closed_date.create',
2598 Creates a new closing entry for the given org_unit
2599 @param authtoken The login session key
2600 @param note The closed_date object
2603 sub create_closed_date {
2604 my( $self, $conn, $authtoken, $cd ) = @_;
2606 my( $user, $evt ) = $U->checkses($authtoken);
2607 return $evt if $evt;
2609 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2610 return $evt if $evt;
2612 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2614 my $id = $U->storagereq(
2615 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2616 return $U->DB_UPDATE_FAILED($cd) unless $id;
2621 __PACKAGE__->register_method(
2622 method => 'delete_closed_date',
2623 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2625 Deletes a closing entry for the given org_unit
2626 @param authtoken The login session key
2627 @param noteid The close_date id
2630 sub delete_closed_date {
2631 my( $self, $conn, $authtoken, $cd ) = @_;
2633 my( $user, $evt ) = $U->checkses($authtoken);
2634 return $evt if $evt;
2637 ($cd_obj, $evt) = fetch_closed_date($cd);
2638 return $evt if $evt;
2640 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2641 return $evt if $evt;
2643 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2645 my $stat = $U->storagereq(
2646 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2647 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2652 __PACKAGE__->register_method(
2653 method => 'usrname_exists',
2654 api_name => 'open-ils.actor.username.exists',
2656 desc => 'Check if a username is already taken (by an undeleted patron)',
2658 {desc => 'Authentication token', type => 'string'},
2659 {desc => 'Username', type => 'string'}
2662 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2667 sub usrname_exists {
2668 my( $self, $conn, $auth, $usrname ) = @_;
2669 my $e = new_editor(authtoken=>$auth);
2670 return $e->event unless $e->checkauth;
2671 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2672 return $$a[0] if $a and @$a;
2676 __PACKAGE__->register_method(
2677 method => 'barcode_exists',
2678 api_name => 'open-ils.actor.barcode.exists',
2680 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2683 sub barcode_exists {
2684 my( $self, $conn, $auth, $barcode ) = @_;
2685 my $e = new_editor(authtoken=>$auth);
2686 return $e->event unless $e->checkauth;
2687 my $card = $e->search_actor_card({barcode => $barcode});
2693 #return undef unless @$card;
2694 #return $card->[0]->usr;
2698 __PACKAGE__->register_method(
2699 method => 'retrieve_net_levels',
2700 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2703 sub retrieve_net_levels {
2704 my( $self, $conn, $auth ) = @_;
2705 my $e = new_editor(authtoken=>$auth);
2706 return $e->event unless $e->checkauth;
2707 return $e->retrieve_all_config_net_access_level();
2710 # Retain the old typo API name just in case
2711 __PACKAGE__->register_method(
2712 method => 'fetch_org_by_shortname',
2713 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2715 __PACKAGE__->register_method(
2716 method => 'fetch_org_by_shortname',
2717 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2719 sub fetch_org_by_shortname {
2720 my( $self, $conn, $sname ) = @_;
2721 my $e = new_editor();
2722 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2723 return $e->event unless $org;
2728 __PACKAGE__->register_method(
2729 method => 'session_home_lib',
2730 api_name => 'open-ils.actor.session.home_lib',
2733 sub session_home_lib {
2734 my( $self, $conn, $auth ) = @_;
2735 my $e = new_editor(authtoken=>$auth);
2736 return undef unless $e->checkauth;
2737 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2738 return $org->shortname;
2741 __PACKAGE__->register_method(
2742 method => 'session_safe_token',
2743 api_name => 'open-ils.actor.session.safe_token',
2745 Returns a hashed session ID that is safe for export to the world.
2746 This safe token will expire after 1 hour of non-use.
2747 @param auth Active authentication token
2751 sub session_safe_token {
2752 my( $self, $conn, $auth ) = @_;
2753 my $e = new_editor(authtoken=>$auth);
2754 return undef unless $e->checkauth;
2756 my $safe_token = md5_hex($auth);
2758 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2760 # Add more like the following if needed...
2762 "safe-token-home_lib-shortname-$safe_token",
2763 $e->retrieve_actor_org_unit(
2764 $e->requestor->home_ou
2773 __PACKAGE__->register_method(
2774 method => 'safe_token_home_lib',
2775 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2777 Returns the home library shortname from the session
2778 asscociated with a safe token from generated by
2779 open-ils.actor.session.safe_token.
2780 @param safe_token Active safe token
2784 sub safe_token_home_lib {
2785 my( $self, $conn, $safe_token ) = @_;
2787 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2788 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2792 __PACKAGE__->register_method(
2793 method => "update_penalties",
2794 api_name => "open-ils.actor.user.penalties.update"
2797 sub update_penalties {
2798 my($self, $conn, $auth, $user_id) = @_;
2799 my $e = new_editor(authtoken=>$auth, xact => 1);
2800 return $e->die_event unless $e->checkauth;
2801 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2802 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2803 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2804 return $evt if $evt;
2810 __PACKAGE__->register_method(
2811 method => "apply_penalty",
2812 api_name => "open-ils.actor.user.penalty.apply"
2816 my($self, $conn, $auth, $penalty) = @_;
2818 my $e = new_editor(authtoken=>$auth, xact => 1);
2819 return $e->die_event unless $e->checkauth;
2821 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2822 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2824 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2827 (defined $ptype->org_depth) ?
2828 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2831 $penalty->org_unit($ctx_org);
2832 $penalty->staff($e->requestor->id);
2833 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2836 return $penalty->id;
2839 __PACKAGE__->register_method(
2840 method => "remove_penalty",
2841 api_name => "open-ils.actor.user.penalty.remove"
2844 sub remove_penalty {
2845 my($self, $conn, $auth, $penalty) = @_;
2846 my $e = new_editor(authtoken=>$auth, xact => 1);
2847 return $e->die_event unless $e->checkauth;
2848 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2849 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2851 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2856 __PACKAGE__->register_method(
2857 method => "update_penalty_note",
2858 api_name => "open-ils.actor.user.penalty.note.update"
2861 sub update_penalty_note {
2862 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2863 my $e = new_editor(authtoken=>$auth, xact => 1);
2864 return $e->die_event unless $e->checkauth;
2865 for my $penalty_id (@$penalty_ids) {
2866 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2867 if (! $penalty ) { return $e->die_event; }
2868 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2869 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2871 $penalty->note( $note ); $penalty->ischanged( 1 );
2873 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2879 __PACKAGE__->register_method(
2880 method => "ranged_penalty_thresholds",
2881 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2885 sub ranged_penalty_thresholds {
2886 my($self, $conn, $auth, $context_org) = @_;
2887 my $e = new_editor(authtoken=>$auth);
2888 return $e->event unless $e->checkauth;
2889 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2890 my $list = $e->search_permission_grp_penalty_threshold([
2891 {org_unit => $U->get_org_ancestors($context_org)},
2892 {order_by => {pgpt => 'id'}}
2894 $conn->respond($_) for @$list;
2900 __PACKAGE__->register_method(
2901 method => "user_retrieve_fleshed_by_id",
2903 api_name => "open-ils.actor.user.fleshed.retrieve",
2906 sub user_retrieve_fleshed_by_id {
2907 my( $self, $client, $auth, $user_id, $fields ) = @_;
2908 my $e = new_editor(authtoken => $auth);
2909 return $e->event unless $e->checkauth;
2911 if( $e->requestor->id != $user_id ) {
2912 return $e->event unless $e->allowed('VIEW_USER');
2918 "standing_penalties",
2924 return new_flesh_user($user_id, $fields, $e);
2928 sub new_flesh_user {
2931 my $fields = shift || [];
2934 my $fetch_penalties = 0;
2935 if(grep {$_ eq 'standing_penalties'} @$fields) {
2936 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2937 $fetch_penalties = 1;
2940 my $fetch_usr_act = 0;
2941 if(grep {$_ eq 'usr_activity'} @$fields) {
2942 $fields = [grep {$_ ne 'usr_activity'} @$fields];
2946 my $user = $e->retrieve_actor_user(
2951 "flesh_fields" => { "au" => $fields }
2954 ) or return $e->die_event;
2957 if( grep { $_ eq 'addresses' } @$fields ) {
2959 $user->addresses([]) unless @{$user->addresses};
2960 # don't expose "replaced" addresses by default
2961 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2963 if( ref $user->billing_address ) {
2964 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2965 push( @{$user->addresses}, $user->billing_address );
2969 if( ref $user->mailing_address ) {
2970 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2971 push( @{$user->addresses}, $user->mailing_address );
2976 if($fetch_penalties) {
2977 # grab the user penalties ranged for this location
2978 $user->standing_penalties(
2979 $e->search_actor_user_standing_penalty([
2982 {stop_date => undef},
2983 {stop_date => {'>' => 'now'}}
2985 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
2988 flesh_fields => {ausp => ['standing_penalty']}
2994 # retrieve the most recent usr_activity entry
2995 if ($fetch_usr_act) {
2997 # max number to return for simple patron fleshing
2998 my $limit = $U->ou_ancestor_setting_value(
2999 $e->requestor->ws_ou,
3000 'circ.patron.usr_activity_retrieve.max');
3004 flesh_fields => {auact => ['etype']},
3005 order_by => {auact => 'event_time DESC'},
3008 # 0 == none, <0 == return all
3009 $limit = 1 unless defined $limit;
3010 $opts->{limit} = $limit if $limit > 0;
3012 $user->usr_activity(
3014 [] : # skip the DB call
3015 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3020 $user->clear_passwd();
3027 __PACKAGE__->register_method(
3028 method => "user_retrieve_parts",
3029 api_name => "open-ils.actor.user.retrieve.parts",
3032 sub user_retrieve_parts {
3033 my( $self, $client, $auth, $user_id, $fields ) = @_;
3034 my $e = new_editor(authtoken => $auth);
3035 return $e->event unless $e->checkauth;
3036 $user_id ||= $e->requestor->id;
3037 if( $e->requestor->id != $user_id ) {
3038 return $e->event unless $e->allowed('VIEW_USER');
3041 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3042 push(@resp, $user->$_()) for(@$fields);
3048 __PACKAGE__->register_method(
3049 method => 'user_opt_in_enabled',
3050 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3051 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3054 sub user_opt_in_enabled {
3055 my($self, $conn) = @_;
3056 my $sc = OpenSRF::Utils::SettingsClient->new;
3057 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3062 __PACKAGE__->register_method(
3063 method => 'user_opt_in_at_org',
3064 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3066 @param $auth The auth token
3067 @param user_id The ID of the user to test
3068 @return 1 if the user has opted in at the specified org,
3069 event on error, and 0 otherwise. /
3071 sub user_opt_in_at_org {
3072 my($self, $conn, $auth, $user_id) = @_;
3074 # see if we even need to enforce the opt-in value
3075 return 1 unless user_opt_in_enabled($self);
3077 my $e = new_editor(authtoken => $auth);
3078 return $e->event unless $e->checkauth;
3080 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3081 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3083 my $ws_org = $e->requestor->ws_ou;
3084 # user is automatically opted-in if they are from the local org
3085 return 1 if $user->home_ou eq $ws_org;
3087 # get the boundary setting
3088 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3090 # auto opt in if user falls within the opt boundary
3091 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3093 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3095 my $vals = $e->search_actor_usr_org_unit_opt_in(
3096 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3102 __PACKAGE__->register_method(
3103 method => 'create_user_opt_in_at_org',
3104 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3106 @param $auth The auth token
3107 @param user_id The ID of the user to test
3108 @return The ID of the newly created object, event on error./
3111 sub create_user_opt_in_at_org {
3112 my($self, $conn, $auth, $user_id, $org_id) = @_;
3114 my $e = new_editor(authtoken => $auth, xact=>1);
3115 return $e->die_event unless $e->checkauth;
3117 # if a specific org unit wasn't passed in, get one based on the defaults;
3119 my $wsou = $e->requestor->ws_ou;
3120 # get the default opt depth
3121 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3122 # get the org unit at that depth
3123 my $org = $e->json_query({
3124 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3126 $org_id = $org->{id};
3129 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3130 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3132 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3134 $opt_in->org_unit($org_id);
3135 $opt_in->usr($user_id);
3136 $opt_in->staff($e->requestor->id);
3137 $opt_in->opt_in_ts('now');
3138 $opt_in->opt_in_ws($e->requestor->wsid);
3140 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3141 or return $e->die_event;
3149 __PACKAGE__->register_method (
3150 method => 'retrieve_org_hours',
3151 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3153 Returns the hours of operation for a specified org unit
3154 @param authtoken The login session key
3155 @param org_id The org_unit ID
3159 sub retrieve_org_hours {
3160 my($self, $conn, $auth, $org_id) = @_;
3161 my $e = new_editor(authtoken => $auth);
3162 return $e->die_event unless $e->checkauth;
3163 $org_id ||= $e->requestor->ws_ou;
3164 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3168 __PACKAGE__->register_method (
3169 method => 'verify_user_password',
3170 api_name => 'open-ils.actor.verify_user_password',
3172 Given a barcode or username and the MD5 encoded password,
3173 returns 1 if the password is correct. Returns 0 otherwise.
3177 sub verify_user_password {
3178 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3179 my $e = new_editor(authtoken => $auth);
3180 return $e->die_event unless $e->checkauth;
3182 my $user_by_barcode;
3183 my $user_by_username;
3185 my $card = $e->search_actor_card([
3186 {barcode => $barcode},
3187 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3188 $user_by_barcode = $card->usr;
3189 $user = $user_by_barcode;
3192 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3193 $user = $user_by_username;
3195 return 0 if (!$user);
3196 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3197 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3198 return 1 if $user->passwd eq $password;
3202 __PACKAGE__->register_method (
3203 method => 'retrieve_usr_id_via_barcode_or_usrname',
3204 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3206 Given a barcode or username returns the id for the user or
3211 sub retrieve_usr_id_via_barcode_or_usrname {
3212 my($self, $conn, $auth, $barcode, $username) = @_;
3213 my $e = new_editor(authtoken => $auth);
3214 return $e->die_event unless $e->checkauth;
3215 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3217 my $user_by_barcode;
3218 my $user_by_username;
3219 $logger->info("$id_as_barcode is the ID as BARCODE");
3221 my $card = $e->search_actor_card([
3222 {barcode => $barcode},
3223 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3224 if ($id_as_barcode =~ /^t/i) {
3226 $user = $e->retrieve_actor_user($barcode);
3227 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3229 $user_by_barcode = $card->usr;
3230 $user = $user_by_barcode;
3233 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3234 $user_by_barcode = $card->usr;
3235 $user = $user_by_barcode;
3240 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3242 $user = $user_by_username;
3244 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3245 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3246 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3251 __PACKAGE__->register_method (
3252 method => 'merge_users',
3253 api_name => 'open-ils.actor.user.merge',
3256 Given a list of source users and destination user, transfer all data from the source
3257 to the dest user and delete the source user. All user related data is
3258 transferred, including circulations, holds, bookbags, etc.
3264 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3265 my $e = new_editor(xact => 1, authtoken => $auth);
3266 return $e->die_event unless $e->checkauth;
3268 # disallow the merge if any subordinate accounts are in collections
3269 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3270 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3272 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3273 my $del_addrs = ($U->ou_ancestor_setting_value(
3274 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3275 my $del_cards = ($U->ou_ancestor_setting_value(
3276 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3277 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3278 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3280 for my $src_id (@$user_ids) {
3281 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3283 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3284 if($src_user->home_ou ne $master_user->home_ou) {
3285 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3288 return $e->die_event unless
3289 $e->json_query({from => [
3304 __PACKAGE__->register_method (
3305 method => 'approve_user_address',
3306 api_name => 'open-ils.actor.user.pending_address.approve',
3313 sub approve_user_address {
3314 my($self, $conn, $auth, $addr) = @_;
3315 my $e = new_editor(xact => 1, authtoken => $auth);
3316 return $e->die_event unless $e->checkauth;
3318 # if the caller passes an address object, assume they want to
3319 # update it first before approving it
3320 $e->update_actor_user_address($addr) or return $e->die_event;
3322 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3324 my $user = $e->retrieve_actor_user($addr->usr);
3325 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3326 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3327 or return $e->die_event;
3329 return [values %$result]->[0];
3333 __PACKAGE__->register_method (
3334 method => 'retrieve_friends',
3335 api_name => 'open-ils.actor.friends.retrieve',
3338 returns { confirmed: [], pending_out: [], pending_in: []}
3339 pending_out are users I'm requesting friendship with
3340 pending_in are users requesting friendship with me
3345 sub retrieve_friends {
3346 my($self, $conn, $auth, $user_id, $options) = @_;
3347 my $e = new_editor(authtoken => $auth);
3348 return $e->event unless $e->checkauth;
3349 $user_id ||= $e->requestor->id;
3351 if($user_id != $e->requestor->id) {
3352 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3353 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3356 return OpenILS::Application::Actor::Friends->retrieve_friends(
3357 $e, $user_id, $options);
3362 __PACKAGE__->register_method (
3363 method => 'apply_friend_perms',
3364 api_name => 'open-ils.actor.friends.perms.apply',
3370 sub apply_friend_perms {
3371 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3372 my $e = new_editor(authtoken => $auth, xact => 1);
3373 return $e->die_event unless $e->checkauth;
3375 if($user_id != $e->requestor->id) {
3376 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3377 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3380 for my $perm (@perms) {
3382 OpenILS::Application::Actor::Friends->apply_friend_perm(
3383 $e, $user_id, $delegate_id, $perm);
3384 return $evt if $evt;
3392 __PACKAGE__->register_method (
3393 method => 'update_user_pending_address',
3394 api_name => 'open-ils.actor.user.address.pending.cud'
3397 sub update_user_pending_address {
3398 my($self, $conn, $auth, $addr) = @_;
3399 my $e = new_editor(authtoken => $auth, xact => 1);
3400 return $e->die_event unless $e->checkauth;
3402 if($addr->usr != $e->requestor->id) {
3403 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3404 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3408 $e->create_actor_user_address($addr) or return $e->die_event;
3409 } elsif($addr->isdeleted) {
3410 $e->delete_actor_user_address($addr) or return $e->die_event;
3412 $e->update_actor_user_address($addr) or return $e->die_event;
3420 __PACKAGE__->register_method (
3421 method => 'user_events',
3422 api_name => 'open-ils.actor.user.events.circ',
3425 __PACKAGE__->register_method (
3426 method => 'user_events',
3427 api_name => 'open-ils.actor.user.events.ahr',
3432 my($self, $conn, $auth, $user_id, $filters) = @_;
3433 my $e = new_editor(authtoken => $auth);
3434 return $e->event unless $e->checkauth;
3436 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3437 my $user_field = 'usr';
3440 $filters->{target} = {
3441 select => { $obj_type => ['id'] },
3443 where => {usr => $user_id}
3446 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3447 if($e->requestor->id != $user_id) {
3448 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3451 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3452 my $req = $ses->request('open-ils.trigger.events_by_target',
3453 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3455 while(my $resp = $req->recv) {
3456 my $val = $resp->content;
3457 my $tgt = $val->target;
3459 if($obj_type eq 'circ') {
3460 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3462 } elsif($obj_type eq 'ahr') {
3463 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3464 if $tgt->current_copy;
3467 $conn->respond($val) if $val;
3473 __PACKAGE__->register_method (
3474 method => 'copy_events',
3475 api_name => 'open-ils.actor.copy.events.circ',
3478 __PACKAGE__->register_method (
3479 method => 'copy_events',
3480 api_name => 'open-ils.actor.copy.events.ahr',
3485 my($self, $conn, $auth, $copy_id, $filters) = @_;
3486 my $e = new_editor(authtoken => $auth);
3487 return $e->event unless $e->checkauth;
3489 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3491 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3493 my $copy_field = 'target_copy';
3494 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3497 $filters->{target} = {
3498 select => { $obj_type => ['id'] },
3500 where => {$copy_field => $copy_id}
3504 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3505 my $req = $ses->request('open-ils.trigger.events_by_target',
3506 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3508 while(my $resp = $req->recv) {
3509 my $val = $resp->content;
3510 my $tgt = $val->target;
3512 my $user = $e->retrieve_actor_user($tgt->usr);
3513 if($e->requestor->id != $user->id) {
3514 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3517 $tgt->$copy_field($copy);
3520 $conn->respond($val) if $val;
3529 __PACKAGE__->register_method (
3530 method => 'update_events',
3531 api_name => 'open-ils.actor.user.event.cancel.batch',
3534 __PACKAGE__->register_method (
3535 method => 'update_events',
3536 api_name => 'open-ils.actor.user.event.reset.batch',
3541 my($self, $conn, $auth, $event_ids) = @_;
3542 my $e = new_editor(xact => 1, authtoken => $auth);
3543 return $e->die_event unless $e->checkauth;
3546 for my $id (@$event_ids) {
3548 # do a little dance to determine what user we are ultimately affecting
3549 my $event = $e->retrieve_action_trigger_event([
3552 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3554 ]) or return $e->die_event;
3557 if($event->event_def->hook->core_type eq 'circ') {
3558 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3559 } elsif($event->event_def->hook->core_type eq 'ahr') {
3560 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3565 my $user = $e->retrieve_actor_user($user_id);
3566 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3568 if($self->api_name =~ /cancel/) {
3569 $event->state('invalid');
3570 } elsif($self->api_name =~ /reset/) {
3571 $event->clear_start_time;
3572 $event->clear_update_time;
3573 $event->state('pending');
3576 $e->update_action_trigger_event($event) or return $e->die_event;
3577 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3581 return {complete => 1};
3585 __PACKAGE__->register_method (
3586 method => 'really_delete_user',
3587 api_name => 'open-ils.actor.user.delete.override',
3588 signature => q/@see open-ils.actor.user.delete/
3591 __PACKAGE__->register_method (
3592 method => 'really_delete_user',
3593 api_name => 'open-ils.actor.user.delete',
3595 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3596 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3597 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3598 dest_usr_id is only required when deleting a user that performs staff functions.
3602 sub really_delete_user {
3603 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3604 my $e = new_editor(authtoken => $auth, xact => 1);
3605 return $e->die_event unless $e->checkauth;
3606 $oargs = { all => 1 } unless defined $oargs;
3608 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3609 my $open_bills = $e->json_query({
3610 select => { mbts => ['id'] },
3613 xact_finish => { '=' => undef },
3614 usr => { '=' => $user_id },
3616 }) or return $e->die_event;
3618 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3620 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3622 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3623 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3624 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3626 # No deleting yourself - UI is supposed to stop you first, though.
3627 return $e->die_event unless $e->requestor->id != $user->id;
3628 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3629 # Check if you are allowed to mess with this patron permission group at all
3630 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3631 my $evt = group_perm_failed($session, $e->requestor, $user);
3632 return $e->die_event($evt) if $evt;
3633 my $stat = $e->json_query(
3634 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3635 or return $e->die_event;
3641 __PACKAGE__->register_method (
3642 method => 'user_payments',
3643 api_name => 'open-ils.actor.user.payments.retrieve',
3646 Returns all payments for a given user. Default order is newest payments first.
3647 @param auth Authentication token
3648 @param user_id The user ID
3649 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3654 my($self, $conn, $auth, $user_id, $filters) = @_;
3657 my $e = new_editor(authtoken => $auth);
3658 return $e->die_event unless $e->checkauth;
3660 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3661 return $e->event unless
3662 $e->requestor->id == $user_id or
3663 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3665 # Find all payments for all transactions for user $user_id
3667 select => {mp => ['id']},
3672 select => {mbt => ['id']},
3674 where => {usr => $user_id}
3679 { # by default, order newest payments first
3681 field => 'payment_ts',
3684 # secondary sort in ID as a tie-breaker, since payments created
3685 # within the same transaction will have identical payment_ts's
3692 for (qw/order_by limit offset/) {
3693 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3696 if(defined $filters->{where}) {
3697 foreach (keys %{$filters->{where}}) {
3698 # don't allow the caller to expand the result set to other users
3699 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3703 my $payment_ids = $e->json_query($query);
3704 for my $pid (@$payment_ids) {
3705 my $pay = $e->retrieve_money_payment([
3710 mbt => ['summary', 'circulation', 'grocery'],
3711 circ => ['target_copy'],
3712 acp => ['call_number'],
3720 xact_type => $pay->xact->summary->xact_type,
3721 last_billing_type => $pay->xact->summary->last_billing_type,
3724 if($pay->xact->summary->xact_type eq 'circulation') {
3725 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3726 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3729 $pay->xact($pay->xact->id); # de-flesh
3730 $conn->respond($resp);
3738 __PACKAGE__->register_method (
3739 method => 'negative_balance_users',
3740 api_name => 'open-ils.actor.users.negative_balance',
3743 Returns all users that have an overall negative balance
3744 @param auth Authentication token
3745 @param org_id The context org unit as an ID or list of IDs. This will be the home
3746 library of the user. If no org_unit is specified, no org unit filter is applied
3750 sub negative_balance_users {
3751 my($self, $conn, $auth, $org_id) = @_;
3753 my $e = new_editor(authtoken => $auth);
3754 return $e->die_event unless $e->checkauth;
3755 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3759 mous => ['usr', 'balance_owed'],
3762 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3763 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3780 where => {'+mous' => {balance_owed => {'<' => 0}}}
3783 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3785 my $list = $e->json_query($query, {timeout => 600});
3787 for my $data (@$list) {
3789 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3790 balance_owed => $data->{balance_owed},
3791 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3798 __PACKAGE__->register_method(
3799 method => "request_password_reset",
3800 api_name => "open-ils.actor.patron.password_reset.request",
3802 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3803 "method for changing a user's password. The UUID token is distributed via A/T " .
3804 "templates (i.e. email to the user).",
3806 { desc => 'user_id_type', type => 'string' },
3807 { desc => 'user_id', type => 'string' },
3808 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3810 return => {desc => '1 on success, Event on error'}
3813 sub request_password_reset {
3814 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3816 # Check to see if password reset requests are already being throttled:
3817 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3819 my $e = new_editor(xact => 1);
3822 # Get the user, if any, depending on the input value
3823 if ($user_id_type eq 'username') {
3824 $user = $e->search_actor_user({usrname => $user_id})->[0];
3827 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3829 } elsif ($user_id_type eq 'barcode') {
3830 my $card = $e->search_actor_card([
3831 {barcode => $user_id},
3832 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3835 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3840 # If the user doesn't have an email address, we can't help them
3841 if (!$user->email) {
3843 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3846 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3847 if ($email_must_match) {
3848 if ($user->email ne $email) {
3849 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3853 _reset_password_request($conn, $e, $user);
3856 # Once we have the user, we can issue the password reset request
3857 # XXX Add a wrapper method that accepts barcode + email input
3858 sub _reset_password_request {
3859 my ($conn, $e, $user) = @_;
3861 # 1. Get throttle threshold and time-to-live from OU_settings
3862 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3863 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3865 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3867 # 2. Get time of last request and number of active requests (num_active)
3868 my $active_requests = $e->json_query({
3874 transform => 'COUNT'
3877 column => 'request_time',
3883 has_been_reset => { '=' => 'f' },
3884 request_time => { '>' => $threshold_time }
3888 # Guard against no active requests
3889 if ($active_requests->[0]->{'request_time'}) {
3890 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3891 my $now = DateTime::Format::ISO8601->new();
3893 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3894 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3895 ($last_request->add_duration('1 minute') > $now)) {
3896 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3898 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3902 # TODO Check to see if the user is in a password-reset-restricted group
3904 # Otherwise, go ahead and try to get the user.
3906 # Check the number of active requests for this user
3907 $active_requests = $e->json_query({
3913 transform => 'COUNT'
3918 usr => { '=' => $user->id },
3919 has_been_reset => { '=' => 'f' },
3920 request_time => { '>' => $threshold_time }
3924 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3926 # if less than or equal to per-user threshold, proceed; otherwise, return event
3927 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3928 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3930 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3933 # Create the aupr object and insert into the database
3934 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3935 my $uuid = create_uuid_as_string(UUID_V4);
3936 $reset_request->uuid($uuid);
3937 $reset_request->usr($user->id);
3939 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3942 # Create an event to notify user of the URL to reset their password
3944 # Can we stuff this in the user_data param for trigger autocreate?
3945 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3947 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3948 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3951 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3956 __PACKAGE__->register_method(
3957 method => "commit_password_reset",
3958 api_name => "open-ils.actor.patron.password_reset.commit",
3960 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3961 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3962 "with the supplied password.",
3964 { desc => 'uuid', type => 'string' },
3965 { desc => 'password', type => 'string' },
3967 return => {desc => '1 on success, Event on error'}
3970 sub commit_password_reset {
3971 my($self, $conn, $uuid, $password) = @_;
3973 # Check to see if password reset requests are already being throttled:
3974 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3975 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3976 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3978 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3981 my $e = new_editor(xact => 1);
3983 my $aupr = $e->search_actor_usr_password_reset({
3990 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3992 my $user_id = $aupr->[0]->usr;
3993 my $user = $e->retrieve_actor_user($user_id);
3995 # Ensure we're still within the TTL for the request
3996 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3997 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3998 if ($threshold < DateTime->now(time_zone => 'local')) {
4000 $logger->info("Password reset request needed to be submitted before $threshold");
4001 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4004 # Check complexity of password against OU-defined regex
4005 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4009 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4010 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4011 $is_strong = check_password_strength_custom($password, $pw_regex);
4013 $is_strong = check_password_strength_default($password);
4018 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4021 # All is well; update the password
4022 $user->passwd($password);
4023 $e->update_actor_user($user);
4025 # And flag that this password reset request has been honoured
4026 $aupr->[0]->has_been_reset('t');
4027 $e->update_actor_usr_password_reset($aupr->[0]);
4033 sub check_password_strength_default {
4034 my $password = shift;
4035 # Use the default set of checks
4036 if ( (length($password) < 7) or
4037 ($password !~ m/.*\d+.*/) or
4038 ($password !~ m/.*[A-Za-z]+.*/)
4045 sub check_password_strength_custom {
4046 my ($password, $pw_regex) = @_;
4048 $pw_regex = qr/$pw_regex/;
4049 if ($password !~ /$pw_regex/) {
4057 __PACKAGE__->register_method(
4058 method => "event_def_opt_in_settings",
4059 api_name => "open-ils.actor.event_def.opt_in.settings",
4062 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4064 { desc => 'Authentication token', type => 'string'},
4066 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4071 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4078 sub event_def_opt_in_settings {
4079 my($self, $conn, $auth, $org_id) = @_;
4080 my $e = new_editor(authtoken => $auth);
4081 return $e->event unless $e->checkauth;
4083 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4084 return $e->event unless
4085 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4087 $org_id = $e->requestor->home_ou;
4090 # find all config.user_setting_type's related to event_defs for the requested org unit
4091 my $types = $e->json_query({
4092 select => {cust => ['name']},
4093 from => {atevdef => 'cust'},
4096 owner => $U->get_org_ancestors($org_id), # context org plus parents
4103 $conn->respond($_) for
4104 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4111 __PACKAGE__->register_method(
4112 method => "user_visible_circs",
4113 api_name => "open-ils.actor.history.circ.visible",
4116 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4118 { desc => 'Authentication token', type => 'string'},
4119 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4120 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4123 desc => q/An object with 2 fields: circulation and summary.
4124 circulation is the "circ" object. summary is the related "accs" object/,
4130 __PACKAGE__->register_method(
4131 method => "user_visible_circs",
4132 api_name => "open-ils.actor.history.circ.visible.print",
4135 desc => 'Returns printable output for the set of opt-in visible circulations',
4137 { desc => 'Authentication token', type => 'string'},
4138 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4139 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4142 desc => q/An action_trigger.event object or error event./,
4148 __PACKAGE__->register_method(
4149 method => "user_visible_circs",
4150 api_name => "open-ils.actor.history.circ.visible.email",
4153 desc => 'Emails the set of opt-in visible circulations to the requestor',
4155 { desc => 'Authentication token', type => 'string'},
4156 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4157 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4160 desc => q/undef, or event on error/
4165 __PACKAGE__->register_method(
4166 method => "user_visible_circs",
4167 api_name => "open-ils.actor.history.hold.visible",
4170 desc => 'Returns the set of opt-in visible holds',
4172 { desc => 'Authentication token', type => 'string'},
4173 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4174 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4177 desc => q/An object with 1 field: "hold"/,
4183 __PACKAGE__->register_method(
4184 method => "user_visible_circs",
4185 api_name => "open-ils.actor.history.hold.visible.print",
4188 desc => 'Returns printable output for the set of opt-in visible holds',
4190 { desc => 'Authentication token', type => 'string'},
4191 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4192 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4195 desc => q/An action_trigger.event object or error event./,
4201 __PACKAGE__->register_method(
4202 method => "user_visible_circs",
4203 api_name => "open-ils.actor.history.hold.visible.email",
4206 desc => 'Emails the set of opt-in visible holds to the requestor',
4208 { desc => 'Authentication token', type => 'string'},
4209 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4210 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4213 desc => q/undef, or event on error/
4218 sub user_visible_circs {
4219 my($self, $conn, $auth, $user_id, $options) = @_;
4221 my $is_hold = ($self->api_name =~ /hold/);
4222 my $for_print = ($self->api_name =~ /print/);
4223 my $for_email = ($self->api_name =~ /email/);
4224 my $e = new_editor(authtoken => $auth);
4225 return $e->event unless $e->checkauth;
4227 $user_id ||= $e->requestor->id;
4229 $options->{limit} ||= 50;
4230 $options->{offset} ||= 0;
4232 if($user_id != $e->requestor->id) {
4233 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4234 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4235 return $e->event unless $e->allowed($perm, $user->home_ou);
4238 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4240 my $data = $e->json_query({
4241 from => [$db_func, $user_id],
4242 limit => $$options{limit},
4243 offset => $$options{offset}
4245 # TODO: I only want IDs. code below didn't get me there
4246 # {"select":{"au":[{"column":"id", "result_field":"id",
4247 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4252 return undef unless @$data;
4256 # collect the batch of objects
4260 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4261 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4265 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4266 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4269 } elsif ($for_email) {
4271 $conn->respond_complete(1) if $for_email; # no sense in waiting
4279 my $hold = $e->retrieve_action_hold_request($id);
4280 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4281 # events will be fired from action_trigger_runner
4285 my $circ = $e->retrieve_action_circulation($id);
4286 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4287 # events will be fired from action_trigger_runner
4291 } else { # just give me the data please
4299 my $hold = $e->retrieve_action_hold_request($id);
4300 $conn->respond({hold => $hold});
4304 my $circ = $e->retrieve_action_circulation($id);
4307 summary => $U->create_circ_chain_summary($e, $id)
4316 __PACKAGE__->register_method(
4317 method => "user_saved_search_cud",
4318 api_name => "open-ils.actor.user.saved_search.cud",
4321 desc => 'Create/Update/Delete Access to user saved searches',
4323 { desc => 'Authentication token', type => 'string' },
4324 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4327 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4333 __PACKAGE__->register_method(
4334 method => "user_saved_search_cud",
4335 api_name => "open-ils.actor.user.saved_search.retrieve",
4338 desc => 'Retrieve a saved search object',
4340 { desc => 'Authentication token', type => 'string' },
4341 { desc => 'Saved Search ID', type => 'number' }
4344 desc => q/The saved search object, Event on error/,
4350 sub user_saved_search_cud {
4351 my( $self, $client, $auth, $search ) = @_;
4352 my $e = new_editor( authtoken=>$auth );
4353 return $e->die_event unless $e->checkauth;
4355 my $o_search; # prior version of the object, if any
4356 my $res; # to be returned
4358 # branch on the operation type
4360 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4362 # Get the old version, to check ownership
4363 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4364 or return $e->die_event;
4366 # You can't read somebody else's search
4367 return OpenILS::Event->new('BAD_PARAMS')
4368 unless $o_search->owner == $e->requestor->id;
4374 $e->xact_begin; # start an editor transaction
4376 if( $search->isnew ) { # Create
4378 # You can't create a search for somebody else
4379 return OpenILS::Event->new('BAD_PARAMS')
4380 unless $search->owner == $e->requestor->id;
4382 $e->create_actor_usr_saved_search( $search )
4383 or return $e->die_event;
4387 } elsif( $search->ischanged ) { # Update
4389 # You can't change ownership of a search
4390 return OpenILS::Event->new('BAD_PARAMS')
4391 unless $search->owner == $e->requestor->id;
4393 # Get the old version, to check ownership
4394 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4395 or return $e->die_event;
4397 # You can't update somebody else's search
4398 return OpenILS::Event->new('BAD_PARAMS')
4399 unless $o_search->owner == $e->requestor->id;
4402 $e->update_actor_usr_saved_search( $search )
4403 or return $e->die_event;
4407 } elsif( $search->isdeleted ) { # Delete
4409 # Get the old version, to check ownership
4410 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4411 or return $e->die_event;
4413 # You can't delete somebody else's search
4414 return OpenILS::Event->new('BAD_PARAMS')
4415 unless $o_search->owner == $e->requestor->id;
4418 $e->delete_actor_usr_saved_search( $o_search )
4419 or return $e->die_event;
4430 __PACKAGE__->register_method(
4431 method => "get_barcodes",
4432 api_name => "open-ils.actor.get_barcodes"
4436 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4437 my $e = new_editor(authtoken => $auth);
4438 return $e->event unless $e->checkauth;
4439 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4441 my $db_result = $e->json_query(
4443 'evergreen.get_barcodes',
4444 $org_id, $context, $barcode,
4448 if($context =~ /actor/) {
4449 my $filter_result = ();
4451 foreach my $result (@$db_result) {
4452 if($result->{type} eq 'actor') {
4453 if($e->requestor->id != $result->{id}) {
4454 $patron = $e->retrieve_actor_user($result->{id});
4456 push(@$filter_result, $e->event);
4459 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4460 push(@$filter_result, $result);
4463 push(@$filter_result, $e->event);
4467 push(@$filter_result, $result);
4471 push(@$filter_result, $result);
4474 return $filter_result;
4480 __PACKAGE__->register_method(
4481 method => 'address_alert_test',
4482 api_name => 'open-ils.actor.address_alert.test',
4484 desc => "Tests a set of address fields to determine if they match with an address_alert",
4486 {desc => 'Authentication token', type => 'string'},
4487 {desc => 'Org Unit', type => 'number'},
4488 {desc => 'Fields', type => 'hash'},
4490 return => {desc => 'List of matching address_alerts'}
4494 sub address_alert_test {
4495 my ($self, $client, $auth, $org_unit, $fields) = @_;
4496 return [] unless $fields and grep {$_} values %$fields;
4498 my $e = new_editor(authtoken => $auth);
4499 return $e->event unless $e->checkauth;
4500 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4501 $org_unit ||= $e->requestor->ws_ou;
4503 my $alerts = $e->json_query({
4505 'actor.address_alert_matches',
4513 $$fields{post_code},
4514 $$fields{mailing_address},
4515 $$fields{billing_address}
4519 # map the json_query hashes to real objects
4521 map {$e->retrieve_actor_address_alert($_)}
4522 (map {$_->{id}} @$alerts)
4526 __PACKAGE__->register_method(
4527 method => "mark_users_contact_invalid",
4528 api_name => "open-ils.actor.invalidate.email",
4530 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",
4532 {desc => "Authentication token", type => "string"},
4533 {desc => "Patron ID", type => "number"},
4534 {desc => "Additional note text (optional)", type => "string"},
4535 {desc => "penalty org unit ID (optional)", type => "number"}
4537 return => {desc => "Event describing success or failure", type => "object"}
4541 __PACKAGE__->register_method(
4542 method => "mark_users_contact_invalid",
4543 api_name => "open-ils.actor.invalidate.day_phone",
4545 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",
4547 {desc => "Authentication token", type => "string"},
4548 {desc => "Patron ID", type => "number"},
4549 {desc => "Additional note text (optional)", type => "string"},
4550 {desc => "penalty org unit ID (optional)", type => "number"}
4552 return => {desc => "Event describing success or failure", type => "object"}
4556 __PACKAGE__->register_method(
4557 method => "mark_users_contact_invalid",
4558 api_name => "open-ils.actor.invalidate.evening_phone",
4560 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",
4562 {desc => "Authentication token", type => "string"},
4563 {desc => "Patron ID", type => "number"},
4564 {desc => "Additional note text (optional)", type => "string"},
4565 {desc => "penalty org unit ID (optional)", type => "number"}
4567 return => {desc => "Event describing success or failure", type => "object"}
4571 __PACKAGE__->register_method(
4572 method => "mark_users_contact_invalid",
4573 api_name => "open-ils.actor.invalidate.other_phone",
4575 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",
4577 {desc => "Authentication token", type => "string"},
4578 {desc => "Patron ID", type => "number"},
4579 {desc => "Additional note text (optional)", type => "string"},
4580 {desc => "penalty org unit ID (optional, default to top of org tree)",
4583 return => {desc => "Event describing success or failure", type => "object"}
4587 sub mark_users_contact_invalid {
4588 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4590 # This method invalidates an email address or a phone_number which
4591 # removes the bad email address or phone number, copying its contents
4592 # to a patron note, and institutes a standing penalty for "bad email"
4593 # or "bad phone number" which is cleared when the user is saved or
4594 # optionally only when the user is saved with an email address or
4595 # phone number (or staff manually delete the penalty).
4597 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4599 my $e = new_editor(authtoken => $auth, xact => 1);
4600 return $e->die_event unless $e->checkauth;
4602 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4603 $e, $contact_type, {usr => $patron_id},
4604 $addl_note, $penalty_ou, $e->requestor->id
4608 # Putting the following method in open-ils.actor is a bad fit, except in that
4609 # it serves an interface that lives under 'actor' in the templates directory,
4610 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4612 __PACKAGE__->register_method(
4613 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4614 method => "get_all_at_reactors_in_use",
4619 { name => 'authtoken', type => 'string' }
4622 desc => 'list of reactor names', type => 'array'
4627 sub get_all_at_reactors_in_use {
4628 my ($self, $conn, $auth) = @_;
4630 my $e = new_editor(authtoken => $auth);
4631 $e->checkauth or return $e->die_event;
4632 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4634 my $reactors = $e->json_query({
4636 atevdef => [{column => "reactor", transform => "distinct"}]
4638 from => {atevdef => {}}
4641 return $e->die_event unless ref $reactors eq "ARRAY";
4644 return [ map { $_->{reactor} } @$reactors ];
4647 __PACKAGE__->register_method(
4648 method => "filter_group_entry_crud",
4649 api_name => "open-ils.actor.filter_group_entry.crud",
4652 Provides CRUD access to filter group entry objects. These are not full accessible
4653 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4654 are not accessible via PCRUD (because they have no fields against which to link perms)
4657 {desc => "Authentication token", type => "string"},
4658 {desc => "Entry ID / Entry Object", type => "number"},
4659 {desc => "Additional note text (optional)", type => "string"},
4660 {desc => "penalty org unit ID (optional, default to top of org tree)",
4664 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4670 sub filter_group_entry_crud {
4671 my ($self, $conn, $auth, $arg) = @_;
4673 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4674 my $e = new_editor(authtoken => $auth, xact => 1);
4675 return $e->die_event unless $e->checkauth;
4681 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4682 or return $e->die_event;
4684 return $e->die_event unless $e->allowed(
4685 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4687 my $query = $arg->query;
4688 $query = $e->create_actor_search_query($query) or return $e->die_event;
4689 $arg->query($query->id);
4690 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4691 $entry->query($query);
4696 } elsif ($arg->ischanged) {
4698 my $entry = $e->retrieve_actor_search_filter_group_entry([
4701 flesh_fields => {asfge => ['grp']}
4703 ]) or return $e->die_event;
4705 return $e->die_event unless $e->allowed(
4706 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4708 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4709 $arg->query($arg->query->id);
4710 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4711 $arg->query($query);
4716 } elsif ($arg->isdeleted) {
4718 my $entry = $e->retrieve_actor_search_filter_group_entry([
4721 flesh_fields => {asfge => ['grp', 'query']}
4723 ]) or return $e->die_event;
4725 return $e->die_event unless $e->allowed(
4726 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4728 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4729 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4742 my $entry = $e->retrieve_actor_search_filter_group_entry([
4745 flesh_fields => {asfge => ['grp', 'query']}
4747 ]) or return $e->die_event;
4749 return $e->die_event unless $e->allowed(
4750 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4751 $entry->grp->owner);
4754 $entry->grp($entry->grp->id); # for consistency