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 __PACKAGE__->register_method(
1313 method => "patron_adv_search",
1314 api_name => "open-ils.actor.patron.search.advanced.opt_in_override"
1316 sub patron_adv_search {
1317 my( $self, $client, $auth, $search_hash,
1318 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1320 my $ignore_opt_in = 0;
1322 my $e = new_editor(authtoken=>$auth);
1323 return $e->event unless $e->checkauth;
1324 return $e->event unless $e->allowed('VIEW_USER');
1326 # depth boundary outside of which patrons must opt-in, default to 0
1327 my $opt_boundary = 0;
1328 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1330 # Override opt-in permissions
1331 if ($self->api_name =~ /opt_in_override/) {
1332 if ($e->allowed('OPT_IN_OVERRIDE')) {
1337 return $U->storagereq(
1338 "open-ils.storage.actor.user.crazy_search", $search_hash,
1339 $search_limit, $search_sort, $include_inactive,
1340 $e->requestor->ws_ou, $search_ou, $opt_boundary, $ignore_opt_in
1345 __PACKAGE__->register_method(
1346 method => "update_passwd",
1347 api_name => "open-ils.actor.user.password.update",
1349 desc => "Update the operator's password",
1351 { desc => 'Authentication token', type => 'string' },
1352 { desc => 'New password', type => 'string' },
1353 { desc => 'Current password', type => 'string' }
1355 return => {desc => '1 on success, Event on error or incorrect current password'}
1359 __PACKAGE__->register_method(
1360 method => "update_passwd",
1361 api_name => "open-ils.actor.user.username.update",
1363 desc => "Update the operator's username",
1365 { desc => 'Authentication token', type => 'string' },
1366 { desc => 'New username', type => 'string' },
1367 { desc => 'Current password', type => 'string' }
1369 return => {desc => '1 on success, Event on error or incorrect current password'}
1373 __PACKAGE__->register_method(
1374 method => "update_passwd",
1375 api_name => "open-ils.actor.user.email.update",
1377 desc => "Update the operator's email address",
1379 { desc => 'Authentication token', type => 'string' },
1380 { desc => 'New email address', type => 'string' },
1381 { desc => 'Current password', type => 'string' }
1383 return => {desc => '1 on success, Event on error or incorrect current password'}
1388 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1389 my $e = new_editor(xact=>1, authtoken=>$auth);
1390 return $e->die_event unless $e->checkauth;
1392 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1393 or return $e->die_event;
1394 my $api = $self->api_name;
1396 # make sure the original password matches the in-database password
1397 if (md5_hex($orig_pw) ne $db_user->passwd) {
1399 return new OpenILS::Event('INCORRECT_PASSWORD');
1402 if( $api =~ /password/o ) {
1404 $db_user->passwd($new_val);
1408 # if we don't clear the password, the user will be updated with
1409 # a hashed version of the hashed version of their password
1410 $db_user->clear_passwd;
1412 if( $api =~ /username/o ) {
1414 # make sure no one else has this username
1415 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1418 return new OpenILS::Event('USERNAME_EXISTS');
1420 $db_user->usrname($new_val);
1422 } elsif( $api =~ /email/o ) {
1423 $db_user->email($new_val);
1427 $e->update_actor_user($db_user) or return $e->die_event;
1430 # update the cached user to pick up these changes
1431 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1437 __PACKAGE__->register_method(
1438 method => "check_user_perms",
1439 api_name => "open-ils.actor.user.perm.check",
1440 notes => <<" NOTES");
1441 Takes a login session, user id, an org id, and an array of perm type strings. For each
1442 perm type, if the user does *not* have the given permission it is added
1443 to a list which is returned from the method. If all permissions
1444 are allowed, an empty list is returned
1445 if the logged in user does not match 'user_id', then the logged in user must
1446 have VIEW_PERMISSION priveleges.
1449 sub check_user_perms {
1450 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1452 my( $staff, $evt ) = $apputils->checkses($login_session);
1453 return $evt if $evt;
1455 if($staff->id ne $user_id) {
1456 if( $evt = $apputils->check_perms(
1457 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1463 for my $perm (@$perm_types) {
1464 if($apputils->check_perms($user_id, $org_id, $perm)) {
1465 push @not_allowed, $perm;
1469 return \@not_allowed
1472 __PACKAGE__->register_method(
1473 method => "check_user_perms2",
1474 api_name => "open-ils.actor.user.perm.check.multi_org",
1476 Checks the permissions on a list of perms and orgs for a user
1477 @param authtoken The login session key
1478 @param user_id The id of the user to check
1479 @param orgs The array of org ids
1480 @param perms The array of permission names
1481 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1482 if the logged in user does not match 'user_id', then the logged in user must
1483 have VIEW_PERMISSION priveleges.
1486 sub check_user_perms2 {
1487 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1489 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1490 $authtoken, $user_id, 'VIEW_PERMISSION' );
1491 return $evt if $evt;
1494 for my $org (@$orgs) {
1495 for my $perm (@$perms) {
1496 if($apputils->check_perms($user_id, $org, $perm)) {
1497 push @not_allowed, [ $org, $perm ];
1502 return \@not_allowed
1506 __PACKAGE__->register_method(
1507 method => 'check_user_perms3',
1508 api_name => 'open-ils.actor.user.perm.highest_org',
1510 Returns the highest org unit id at which a user has a given permission
1511 If the requestor does not match the target user, the requestor must have
1512 'VIEW_PERMISSION' rights at the home org unit of the target user
1513 @param authtoken The login session key
1514 @param userid The id of the user in question
1515 @param perm The permission to check
1516 @return The org unit highest in the org tree within which the user has
1517 the requested permission
1520 sub check_user_perms3 {
1521 my($self, $client, $authtoken, $user_id, $perm) = @_;
1522 my $e = new_editor(authtoken=>$authtoken);
1523 return $e->event unless $e->checkauth;
1525 my $tree = $U->get_org_tree();
1527 unless($e->requestor->id == $user_id) {
1528 my $user = $e->retrieve_actor_user($user_id)
1529 or return $e->event;
1530 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1531 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1534 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1537 __PACKAGE__->register_method(
1538 method => 'user_has_work_perm_at',
1539 api_name => 'open-ils.actor.user.has_work_perm_at',
1543 Returns a set of org unit IDs which represent the highest orgs in
1544 the org tree where the user has the requested permission. The
1545 purpose of this method is to return the smallest set of org units
1546 which represent the full expanse of the user's ability to perform
1547 the requested action. The user whose perms this method should
1548 check is implied by the authtoken. /,
1550 {desc => 'authtoken', type => 'string'},
1551 {desc => 'permission name', type => 'string'},
1552 {desc => q/user id, optional. If present, check perms for
1553 this user instead of the logged in user/, type => 'number'},
1555 return => {desc => 'An array of org IDs'}
1559 sub user_has_work_perm_at {
1560 my($self, $conn, $auth, $perm, $user_id) = @_;
1561 my $e = new_editor(authtoken=>$auth);
1562 return $e->event unless $e->checkauth;
1563 if(defined $user_id) {
1564 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1565 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1567 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1570 __PACKAGE__->register_method(
1571 method => 'user_has_work_perm_at_batch',
1572 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1576 sub user_has_work_perm_at_batch {
1577 my($self, $conn, $auth, $perms, $user_id) = @_;
1578 my $e = new_editor(authtoken=>$auth);
1579 return $e->event unless $e->checkauth;
1580 if(defined $user_id) {
1581 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1582 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1585 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1591 __PACKAGE__->register_method(
1592 method => 'check_user_perms4',
1593 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1595 Returns the highest org unit id at which a user has a given permission
1596 If the requestor does not match the target user, the requestor must have
1597 'VIEW_PERMISSION' rights at the home org unit of the target user
1598 @param authtoken The login session key
1599 @param userid The id of the user in question
1600 @param perms An array of perm names to check
1601 @return An array of orgId's representing the org unit
1602 highest in the org tree within which the user has the requested permission
1603 The arrah of orgId's has matches the order of the perms array
1606 sub check_user_perms4 {
1607 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1609 my( $staff, $target, $org, $evt );
1611 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1612 $authtoken, $userid, 'VIEW_PERMISSION' );
1613 return $evt if $evt;
1616 return [] unless ref($perms);
1617 my $tree = $U->get_org_tree();
1619 for my $p (@$perms) {
1620 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1626 __PACKAGE__->register_method(
1627 method => "user_fines_summary",
1628 api_name => "open-ils.actor.user.fines.summary",
1631 desc => 'Returns a short summary of the users total open fines, ' .
1632 'excluding voided fines Params are login_session, user_id' ,
1634 {desc => 'Authentication token', type => 'string'},
1635 {desc => 'User ID', type => 'string'} # number?
1638 desc => "a 'mous' object, event on error",
1643 sub user_fines_summary {
1644 my( $self, $client, $auth, $user_id ) = @_;
1646 my $e = new_editor(authtoken=>$auth);
1647 return $e->event unless $e->checkauth;
1649 if( $user_id ne $e->requestor->id ) {
1650 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1651 return $e->event unless
1652 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1655 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1659 __PACKAGE__->register_method(
1660 method => "user_opac_vitals",
1661 api_name => "open-ils.actor.user.opac.vital_stats",
1665 desc => 'Returns a short summary of the users vital stats, including ' .
1666 'identification information, accumulated balance, number of holds, ' .
1667 'and current open circulation stats' ,
1669 {desc => 'Authentication token', type => 'string'},
1670 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1673 desc => "An object with four properties: user, fines, checkouts and holds."
1678 sub user_opac_vitals {
1679 my( $self, $client, $auth, $user_id ) = @_;
1681 my $e = new_editor(authtoken=>$auth);
1682 return $e->event unless $e->checkauth;
1684 $user_id ||= $e->requestor->id;
1686 my $user = $e->retrieve_actor_user( $user_id );
1689 ->method_lookup('open-ils.actor.user.fines.summary')
1690 ->run($auth => $user_id);
1691 return $fines if (defined($U->event_code($fines)));
1694 $fines = new Fieldmapper::money::open_user_summary ();
1695 $fines->balance_owed(0.00);
1696 $fines->total_owed(0.00);
1697 $fines->total_paid(0.00);
1698 $fines->usr($user_id);
1702 ->method_lookup('open-ils.actor.user.hold_requests.count')
1703 ->run($auth => $user_id);
1704 return $holds if (defined($U->event_code($holds)));
1707 ->method_lookup('open-ils.actor.user.checked_out.count')
1708 ->run($auth => $user_id);
1709 return $out if (defined($U->event_code($out)));
1711 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1715 first_given_name => $user->first_given_name,
1716 second_given_name => $user->second_given_name,
1717 family_name => $user->family_name,
1718 alias => $user->alias,
1719 usrname => $user->usrname
1721 fines => $fines->to_bare_hash,
1728 ##### a small consolidation of related method registrations
1729 my $common_params = [
1730 { desc => 'Authentication token', type => 'string' },
1731 { desc => 'User ID', type => 'string' },
1732 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1733 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1736 'open-ils.actor.user.transactions' => '',
1737 'open-ils.actor.user.transactions.fleshed' => '',
1738 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1739 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1740 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1741 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1744 foreach (keys %methods) {
1746 method => "user_transactions",
1749 desc => 'For a given user, retrieve a list of '
1750 . (/\.fleshed/ ? 'fleshed ' : '')
1751 . 'transactions' . $methods{$_}
1752 . ' optionally limited to transactions of a given type.',
1753 params => $common_params,
1755 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1756 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1760 $args{authoritative} = 1;
1761 __PACKAGE__->register_method(%args);
1764 # Now for the counts
1766 'open-ils.actor.user.transactions.count' => '',
1767 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1768 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1771 foreach (keys %methods) {
1773 method => "user_transactions",
1776 desc => 'For a given user, retrieve a count of open '
1777 . 'transactions' . $methods{$_}
1778 . ' optionally limited to transactions of a given type.',
1779 params => $common_params,
1780 return => { desc => "Integer count of transactions, or event on error" }
1783 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1784 __PACKAGE__->register_method(%args);
1787 __PACKAGE__->register_method(
1788 method => "user_transactions",
1789 api_name => "open-ils.actor.user.transactions.have_balance.total",
1792 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1793 . ' optionally limited to transactions of a given type.',
1794 params => $common_params,
1795 return => { desc => "Decimal balance value, or event on error" }
1800 sub user_transactions {
1801 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1804 my $e = new_editor(authtoken => $auth);
1805 return $e->event unless $e->checkauth;
1807 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1809 return $e->event unless
1810 $e->requestor->id == $user_id or
1811 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1813 my $api = $self->api_name();
1815 my $filter = ($api =~ /have_balance/o) ?
1816 { 'balance_owed' => { '<>' => 0 } }:
1817 { 'total_owed' => { '>' => 0 } };
1819 my $method = 'open-ils.actor.user.transactions.history.still_open';
1820 $method = "$method.authoritative" if $api =~ /authoritative/;
1821 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1823 if($api =~ /total/o) {
1825 $total += $_->balance_owed for @$trans;
1829 ($api =~ /count/o ) and return scalar @$trans;
1830 ($api !~ /fleshed/o) and return $trans;
1833 for my $t (@$trans) {
1835 if( $t->xact_type ne 'circulation' ) {
1836 push @resp, {transaction => $t};
1840 my $circ_data = flesh_circ($e, $t->id);
1841 push @resp, {transaction => $t, %$circ_data};
1848 __PACKAGE__->register_method(
1849 method => "user_transaction_retrieve",
1850 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1853 notes => "Returns a fleshed transaction record"
1856 __PACKAGE__->register_method(
1857 method => "user_transaction_retrieve",
1858 api_name => "open-ils.actor.user.transaction.retrieve",
1861 notes => "Returns a transaction record"
1864 sub user_transaction_retrieve {
1865 my($self, $client, $auth, $bill_id) = @_;
1867 my $e = new_editor(authtoken => $auth);
1868 return $e->event unless $e->checkauth;
1870 my $trans = $e->retrieve_money_billable_transaction_summary(
1871 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1873 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1875 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1877 return $trans unless $self->api_name =~ /flesh/;
1878 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1880 my $circ_data = flesh_circ($e, $trans->id, 1);
1882 return {transaction => $trans, %$circ_data};
1887 my $circ_id = shift;
1888 my $flesh_copy = shift;
1890 my $circ = $e->retrieve_action_circulation([
1894 circ => ['target_copy'],
1895 acp => ['call_number'],
1902 my $copy = $circ->target_copy;
1904 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1905 $mods = new Fieldmapper::metabib::virtual_record;
1906 $mods->doc_id(OILS_PRECAT_RECORD);
1907 $mods->title($copy->dummy_title);
1908 $mods->author($copy->dummy_author);
1911 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1915 $circ->target_copy($circ->target_copy->id);
1916 $copy->call_number($copy->call_number->id);
1918 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1922 __PACKAGE__->register_method(
1923 method => "hold_request_count",
1924 api_name => "open-ils.actor.user.hold_requests.count",
1927 notes => 'Returns hold ready/total counts'
1930 sub hold_request_count {
1931 my( $self, $client, $authtoken, $user_id ) = @_;
1932 my $e = new_editor(authtoken => $authtoken);
1933 return $e->event unless $e->checkauth;
1935 $user_id = $e->requestor->id unless defined $user_id;
1937 if($e->requestor->id ne $user_id) {
1938 my $user = $e->retrieve_actor_user($user_id);
1939 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1942 my $holds = $e->json_query({
1943 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1947 fulfillment_time => {"=" => undef },
1948 cancel_time => undef,
1953 total => scalar(@$holds),
1956 $_->{current_shelf_lib} and # avoid undef warnings
1957 $_->{pickup_lib} eq $_->{current_shelf_lib}
1963 __PACKAGE__->register_method(
1964 method => "checked_out",
1965 api_name => "open-ils.actor.user.checked_out",
1969 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1970 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1971 . "(i.e., outstanding balance or some other pending action on the circ). "
1972 . "The .count method also includes a 'total' field which sums all open circs.",
1974 { desc => 'Authentication Token', type => 'string'},
1975 { desc => 'User ID', type => 'string'},
1978 desc => 'Returns event on error, or an object with ID lists, like: '
1979 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1984 __PACKAGE__->register_method(
1985 method => "checked_out",
1986 api_name => "open-ils.actor.user.checked_out.count",
1989 signature => q/@see open-ils.actor.user.checked_out/
1993 my( $self, $conn, $auth, $userid ) = @_;
1995 my $e = new_editor(authtoken=>$auth);
1996 return $e->event unless $e->checkauth;
1998 if( $userid ne $e->requestor->id ) {
1999 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2000 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2002 # see if there is a friend link allowing circ.view perms
2003 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2004 $e, $userid, $e->requestor->id, 'circ.view');
2005 return $e->event unless $allowed;
2009 my $count = $self->api_name =~ /count/;
2010 return _checked_out( $count, $e, $userid );
2014 my( $iscount, $e, $userid ) = @_;
2020 claims_returned => [],
2023 my $meth = 'retrieve_action_open_circ_';
2031 claims_returned => 0,
2038 my $data = $e->$meth($userid);
2042 $result{$_} += $data->$_() for (keys %result);
2043 $result{total} += $data->$_() for (keys %result);
2045 for my $k (keys %result) {
2046 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2056 __PACKAGE__->register_method(
2057 method => "checked_in_with_fines",
2058 api_name => "open-ils.actor.user.checked_in_with_fines",
2061 signature => q/@see open-ils.actor.user.checked_out/
2064 sub checked_in_with_fines {
2065 my( $self, $conn, $auth, $userid ) = @_;
2067 my $e = new_editor(authtoken=>$auth);
2068 return $e->event unless $e->checkauth;
2070 if( $userid ne $e->requestor->id ) {
2071 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2074 # money is owed on these items and they are checked in
2075 my $open = $e->search_action_circulation(
2078 xact_finish => undef,
2079 checkin_time => { "!=" => undef },
2084 my( @lost, @cr, @lo );
2085 for my $c (@$open) {
2086 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2087 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2088 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2093 claims_returned => \@cr,
2094 long_overdue => \@lo
2100 my ($api, $desc, $auth) = @_;
2101 $desc = $desc ? (" " . $desc) : '';
2102 my $ids = ($api =~ /ids$/) ? 1 : 0;
2105 method => "user_transaction_history",
2106 api_name => "open-ils.actor.user.transactions.$api",
2108 desc => "For a given User ID, returns a list of billable transaction" .
2109 ($ids ? " id" : '') .
2110 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2111 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2113 {desc => 'Authentication token', type => 'string'},
2114 {desc => 'User ID', type => 'number'},
2115 {desc => 'Transaction type (optional)', type => 'number'},
2116 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2119 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2123 $auth and push @sig, (authoritative => 1);
2127 my %auth_hist_methods = (
2129 'history.have_charge' => 'that have an initial charge',
2130 'history.still_open' => 'that are not finished',
2131 'history.have_balance' => 'that have a balance',
2132 'history.have_bill' => 'that have billings',
2133 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2134 'history.have_payment' => 'that have at least 1 payment',
2137 foreach (keys %auth_hist_methods) {
2138 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2139 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2140 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2143 sub user_transaction_history {
2144 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2148 my $e = new_editor(authtoken=>$auth);
2149 return $e->die_event unless $e->checkauth;
2151 if ($e->requestor->id ne $userid) {
2152 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2155 my $api = $self->api_name;
2156 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2158 if(defined($type)) {
2159 $filter->{'xact_type'} = $type;
2162 if($api =~ /have_bill_or_payment/o) {
2164 # transactions that have a non-zero sum across all billings or at least 1 payment
2165 $filter->{'-or'} = {
2166 'balance_owed' => { '<>' => 0 },
2167 'last_payment_ts' => { '<>' => undef }
2170 } elsif($api =~ /have_payment/) {
2172 $filter->{last_payment_ts} ||= {'<>' => undef};
2174 } elsif( $api =~ /have_balance/o) {
2176 # transactions that have a non-zero overall balance
2177 $filter->{'balance_owed'} = { '<>' => 0 };
2179 } elsif( $api =~ /have_charge/o) {
2181 # transactions that have at least 1 billing, regardless of whether it was voided
2182 $filter->{'last_billing_ts'} = { '<>' => undef };
2184 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2186 # transactions that have non-zero sum across all billings. This will exclude
2187 # xacts where all billings have been voided
2188 $filter->{'total_owed'} = { '<>' => 0 };
2191 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2192 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2193 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2195 my $mbts = $e->search_money_billable_transaction_summary(
2196 [ { usr => $userid, @xact_finish, %$filter },
2201 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2202 return $mbts unless $api =~ /fleshed/;
2205 for my $t (@$mbts) {
2207 if( $t->xact_type ne 'circulation' ) {
2208 push @resp, {transaction => $t};
2212 my $circ_data = flesh_circ($e, $t->id);
2213 push @resp, {transaction => $t, %$circ_data};
2221 __PACKAGE__->register_method(
2222 method => "user_perms",
2223 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2225 notes => "Returns a list of permissions"
2229 my( $self, $client, $authtoken, $user ) = @_;
2231 my( $staff, $evt ) = $apputils->checkses($authtoken);
2232 return $evt if $evt;
2234 $user ||= $staff->id;
2236 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2240 return $apputils->simple_scalar_request(
2242 "open-ils.storage.permission.user_perms.atomic",
2246 __PACKAGE__->register_method(
2247 method => "retrieve_perms",
2248 api_name => "open-ils.actor.permissions.retrieve",
2249 notes => "Returns a list of permissions"
2251 sub retrieve_perms {
2252 my( $self, $client ) = @_;
2253 return $apputils->simple_scalar_request(
2255 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2256 { id => { '!=' => undef } }
2260 __PACKAGE__->register_method(
2261 method => "retrieve_groups",
2262 api_name => "open-ils.actor.groups.retrieve",
2263 notes => "Returns a list of user groups"
2265 sub retrieve_groups {
2266 my( $self, $client ) = @_;
2267 return new_editor()->retrieve_all_permission_grp_tree();
2270 __PACKAGE__->register_method(
2271 method => "retrieve_org_address",
2272 api_name => "open-ils.actor.org_unit.address.retrieve",
2273 notes => <<' NOTES');
2274 Returns an org_unit address by ID
2275 @param An org_address ID
2277 sub retrieve_org_address {
2278 my( $self, $client, $id ) = @_;
2279 return $apputils->simple_scalar_request(
2281 "open-ils.cstore.direct.actor.org_address.retrieve",
2286 __PACKAGE__->register_method(
2287 method => "retrieve_groups_tree",
2288 api_name => "open-ils.actor.groups.tree.retrieve",
2289 notes => "Returns a list of user groups"
2292 sub retrieve_groups_tree {
2293 my( $self, $client ) = @_;
2294 return new_editor()->search_permission_grp_tree(
2299 flesh_fields => { pgt => ["children"] },
2300 order_by => { pgt => 'name'}
2307 __PACKAGE__->register_method(
2308 method => "add_user_to_groups",
2309 api_name => "open-ils.actor.user.set_groups",
2310 notes => "Adds a user to one or more permission groups"
2313 sub add_user_to_groups {
2314 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2316 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2317 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2318 return $evt if $evt;
2320 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2321 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2322 return $evt if $evt;
2324 $apputils->simplereq(
2326 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2328 for my $group (@$groups) {
2329 my $link = Fieldmapper::permission::usr_grp_map->new;
2331 $link->usr($userid);
2333 my $id = $apputils->simplereq(
2335 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2341 __PACKAGE__->register_method(
2342 method => "get_user_perm_groups",
2343 api_name => "open-ils.actor.user.get_groups",
2344 notes => "Retrieve a user's permission groups."
2348 sub get_user_perm_groups {
2349 my( $self, $client, $authtoken, $userid ) = @_;
2351 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2352 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2353 return $evt if $evt;
2355 return $apputils->simplereq(
2357 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2361 __PACKAGE__->register_method(
2362 method => "get_user_work_ous",
2363 api_name => "open-ils.actor.user.get_work_ous",
2364 notes => "Retrieve a user's work org units."
2367 __PACKAGE__->register_method(
2368 method => "get_user_work_ous",
2369 api_name => "open-ils.actor.user.get_work_ous.ids",
2370 notes => "Retrieve a user's work org units."
2373 sub get_user_work_ous {
2374 my( $self, $client, $auth, $userid ) = @_;
2375 my $e = new_editor(authtoken=>$auth);
2376 return $e->event unless $e->checkauth;
2377 $userid ||= $e->requestor->id;
2379 if($e->requestor->id != $userid) {
2380 my $user = $e->retrieve_actor_user($userid)
2381 or return $e->event;
2382 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2385 return $e->search_permission_usr_work_ou_map({usr => $userid})
2386 unless $self->api_name =~ /.ids$/;
2388 # client just wants a list of org IDs
2389 return $U->get_user_work_ou_ids($e, $userid);
2394 __PACKAGE__->register_method(
2395 method => 'register_workstation',
2396 api_name => 'open-ils.actor.workstation.register.override',
2397 signature => q/@see open-ils.actor.workstation.register/
2400 __PACKAGE__->register_method(
2401 method => 'register_workstation',
2402 api_name => 'open-ils.actor.workstation.register',
2404 Registers a new workstion in the system
2405 @param authtoken The login session key
2406 @param name The name of the workstation id
2407 @param owner The org unit that owns this workstation
2408 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2409 if the name is already in use.
2413 sub register_workstation {
2414 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2416 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2417 return $e->die_event unless $e->checkauth;
2418 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2419 my $existing = $e->search_actor_workstation({name => $name})->[0];
2420 $oargs = { all => 1 } unless defined $oargs;
2424 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2425 # workstation with the given name exists.
2427 if($owner ne $existing->owning_lib) {
2428 # if necessary, update the owning_lib of the workstation
2430 $logger->info("changing owning lib of workstation ".$existing->id.
2431 " from ".$existing->owning_lib." to $owner");
2432 return $e->die_event unless
2433 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2435 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2437 $existing->owning_lib($owner);
2438 return $e->die_event unless $e->update_actor_workstation($existing);
2444 "attempt to register an existing workstation. returning existing ID");
2447 return $existing->id;
2450 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2454 my $ws = Fieldmapper::actor::workstation->new;
2455 $ws->owning_lib($owner);
2457 $e->create_actor_workstation($ws) or return $e->die_event;
2459 return $ws->id; # note: editor sets the id on the new object for us
2462 __PACKAGE__->register_method(
2463 method => 'workstation_list',
2464 api_name => 'open-ils.actor.workstation.list',
2466 Returns a list of workstations registered at the given location
2467 @param authtoken The login session key
2468 @param ids A list of org_unit.id's for the workstation owners
2472 sub workstation_list {
2473 my( $self, $conn, $authtoken, @orgs ) = @_;
2475 my $e = new_editor(authtoken=>$authtoken);
2476 return $e->event unless $e->checkauth;
2481 unless $e->allowed('REGISTER_WORKSTATION', $o);
2482 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2488 __PACKAGE__->register_method(
2489 method => 'fetch_patron_note',
2490 api_name => 'open-ils.actor.note.retrieve.all',
2493 Returns a list of notes for a given user
2494 Requestor must have VIEW_USER permission if pub==false and
2495 @param authtoken The login session key
2496 @param args Hash of params including
2497 patronid : the patron's id
2498 pub : true if retrieving only public notes
2502 sub fetch_patron_note {
2503 my( $self, $conn, $authtoken, $args ) = @_;
2504 my $patronid = $$args{patronid};
2506 my($reqr, $evt) = $U->checkses($authtoken);
2507 return $evt if $evt;
2510 ($patron, $evt) = $U->fetch_user($patronid);
2511 return $evt if $evt;
2514 if( $patronid ne $reqr->id ) {
2515 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2516 return $evt if $evt;
2518 return $U->cstorereq(
2519 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2520 { usr => $patronid, pub => 't' } );
2523 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2524 return $evt if $evt;
2526 return $U->cstorereq(
2527 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2530 __PACKAGE__->register_method(
2531 method => 'create_user_note',
2532 api_name => 'open-ils.actor.note.create',
2534 Creates a new note for the given user
2535 @param authtoken The login session key
2536 @param note The note object
2539 sub create_user_note {
2540 my( $self, $conn, $authtoken, $note ) = @_;
2541 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2542 return $e->die_event unless $e->checkauth;
2544 my $user = $e->retrieve_actor_user($note->usr)
2545 or return $e->die_event;
2547 return $e->die_event unless
2548 $e->allowed('UPDATE_USER',$user->home_ou);
2550 $note->creator($e->requestor->id);
2551 $e->create_actor_usr_note($note) or return $e->die_event;
2557 __PACKAGE__->register_method(
2558 method => 'delete_user_note',
2559 api_name => 'open-ils.actor.note.delete',
2561 Deletes a note for the given user
2562 @param authtoken The login session key
2563 @param noteid The note id
2566 sub delete_user_note {
2567 my( $self, $conn, $authtoken, $noteid ) = @_;
2569 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2570 return $e->die_event unless $e->checkauth;
2571 my $note = $e->retrieve_actor_usr_note($noteid)
2572 or return $e->die_event;
2573 my $user = $e->retrieve_actor_user($note->usr)
2574 or return $e->die_event;
2575 return $e->die_event unless
2576 $e->allowed('UPDATE_USER', $user->home_ou);
2578 $e->delete_actor_usr_note($note) or return $e->die_event;
2584 __PACKAGE__->register_method(
2585 method => 'update_user_note',
2586 api_name => 'open-ils.actor.note.update',
2588 @param authtoken The login session key
2589 @param note The note
2593 sub update_user_note {
2594 my( $self, $conn, $auth, $note ) = @_;
2595 my $e = new_editor(authtoken=>$auth, xact=>1);
2596 return $e->die_event unless $e->checkauth;
2597 my $patron = $e->retrieve_actor_user($note->usr)
2598 or return $e->die_event;
2599 return $e->die_event unless
2600 $e->allowed('UPDATE_USER', $patron->home_ou);
2601 $e->update_actor_user_note($note)
2602 or return $e->die_event;
2609 __PACKAGE__->register_method(
2610 method => 'create_closed_date',
2611 api_name => 'open-ils.actor.org_unit.closed_date.create',
2613 Creates a new closing entry for the given org_unit
2614 @param authtoken The login session key
2615 @param note The closed_date object
2618 sub create_closed_date {
2619 my( $self, $conn, $authtoken, $cd ) = @_;
2621 my( $user, $evt ) = $U->checkses($authtoken);
2622 return $evt if $evt;
2624 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2625 return $evt if $evt;
2627 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2629 my $id = $U->storagereq(
2630 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2631 return $U->DB_UPDATE_FAILED($cd) unless $id;
2636 __PACKAGE__->register_method(
2637 method => 'delete_closed_date',
2638 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2640 Deletes a closing entry for the given org_unit
2641 @param authtoken The login session key
2642 @param noteid The close_date id
2645 sub delete_closed_date {
2646 my( $self, $conn, $authtoken, $cd ) = @_;
2648 my( $user, $evt ) = $U->checkses($authtoken);
2649 return $evt if $evt;
2652 ($cd_obj, $evt) = fetch_closed_date($cd);
2653 return $evt if $evt;
2655 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2656 return $evt if $evt;
2658 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2660 my $stat = $U->storagereq(
2661 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2662 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2667 __PACKAGE__->register_method(
2668 method => 'usrname_exists',
2669 api_name => 'open-ils.actor.username.exists',
2671 desc => 'Check if a username is already taken (by an undeleted patron)',
2673 {desc => 'Authentication token', type => 'string'},
2674 {desc => 'Username', type => 'string'}
2677 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2682 sub usrname_exists {
2683 my( $self, $conn, $auth, $usrname ) = @_;
2684 my $e = new_editor(authtoken=>$auth);
2685 return $e->event unless $e->checkauth;
2686 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2687 return $$a[0] if $a and @$a;
2691 __PACKAGE__->register_method(
2692 method => 'barcode_exists',
2693 api_name => 'open-ils.actor.barcode.exists',
2695 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2698 sub barcode_exists {
2699 my( $self, $conn, $auth, $barcode ) = @_;
2700 my $e = new_editor(authtoken=>$auth);
2701 return $e->event unless $e->checkauth;
2702 my $card = $e->search_actor_card({barcode => $barcode});
2708 #return undef unless @$card;
2709 #return $card->[0]->usr;
2713 __PACKAGE__->register_method(
2714 method => 'retrieve_net_levels',
2715 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2718 sub retrieve_net_levels {
2719 my( $self, $conn, $auth ) = @_;
2720 my $e = new_editor(authtoken=>$auth);
2721 return $e->event unless $e->checkauth;
2722 return $e->retrieve_all_config_net_access_level();
2725 # Retain the old typo API name just in case
2726 __PACKAGE__->register_method(
2727 method => 'fetch_org_by_shortname',
2728 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2730 __PACKAGE__->register_method(
2731 method => 'fetch_org_by_shortname',
2732 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2734 sub fetch_org_by_shortname {
2735 my( $self, $conn, $sname ) = @_;
2736 my $e = new_editor();
2737 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2738 return $e->event unless $org;
2743 __PACKAGE__->register_method(
2744 method => 'session_home_lib',
2745 api_name => 'open-ils.actor.session.home_lib',
2748 sub session_home_lib {
2749 my( $self, $conn, $auth ) = @_;
2750 my $e = new_editor(authtoken=>$auth);
2751 return undef unless $e->checkauth;
2752 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2753 return $org->shortname;
2756 __PACKAGE__->register_method(
2757 method => 'session_safe_token',
2758 api_name => 'open-ils.actor.session.safe_token',
2760 Returns a hashed session ID that is safe for export to the world.
2761 This safe token will expire after 1 hour of non-use.
2762 @param auth Active authentication token
2766 sub session_safe_token {
2767 my( $self, $conn, $auth ) = @_;
2768 my $e = new_editor(authtoken=>$auth);
2769 return undef unless $e->checkauth;
2771 my $safe_token = md5_hex($auth);
2773 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2775 # Add more like the following if needed...
2777 "safe-token-home_lib-shortname-$safe_token",
2778 $e->retrieve_actor_org_unit(
2779 $e->requestor->home_ou
2788 __PACKAGE__->register_method(
2789 method => 'safe_token_home_lib',
2790 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2792 Returns the home library shortname from the session
2793 asscociated with a safe token from generated by
2794 open-ils.actor.session.safe_token.
2795 @param safe_token Active safe token
2799 sub safe_token_home_lib {
2800 my( $self, $conn, $safe_token ) = @_;
2802 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2803 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2807 __PACKAGE__->register_method(
2808 method => "update_penalties",
2809 api_name => "open-ils.actor.user.penalties.update"
2812 sub update_penalties {
2813 my($self, $conn, $auth, $user_id) = @_;
2814 my $e = new_editor(authtoken=>$auth, xact => 1);
2815 return $e->die_event unless $e->checkauth;
2816 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2817 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2818 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2819 return $evt if $evt;
2825 __PACKAGE__->register_method(
2826 method => "apply_penalty",
2827 api_name => "open-ils.actor.user.penalty.apply"
2831 my($self, $conn, $auth, $penalty) = @_;
2833 my $e = new_editor(authtoken=>$auth, xact => 1);
2834 return $e->die_event unless $e->checkauth;
2836 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2837 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2839 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2842 (defined $ptype->org_depth) ?
2843 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2846 $penalty->org_unit($ctx_org);
2847 $penalty->staff($e->requestor->id);
2848 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2851 return $penalty->id;
2854 __PACKAGE__->register_method(
2855 method => "remove_penalty",
2856 api_name => "open-ils.actor.user.penalty.remove"
2859 sub remove_penalty {
2860 my($self, $conn, $auth, $penalty) = @_;
2861 my $e = new_editor(authtoken=>$auth, xact => 1);
2862 return $e->die_event unless $e->checkauth;
2863 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2864 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2866 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2871 __PACKAGE__->register_method(
2872 method => "update_penalty_note",
2873 api_name => "open-ils.actor.user.penalty.note.update"
2876 sub update_penalty_note {
2877 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2878 my $e = new_editor(authtoken=>$auth, xact => 1);
2879 return $e->die_event unless $e->checkauth;
2880 for my $penalty_id (@$penalty_ids) {
2881 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2882 if (! $penalty ) { return $e->die_event; }
2883 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2884 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2886 $penalty->note( $note ); $penalty->ischanged( 1 );
2888 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2894 __PACKAGE__->register_method(
2895 method => "ranged_penalty_thresholds",
2896 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2900 sub ranged_penalty_thresholds {
2901 my($self, $conn, $auth, $context_org) = @_;
2902 my $e = new_editor(authtoken=>$auth);
2903 return $e->event unless $e->checkauth;
2904 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2905 my $list = $e->search_permission_grp_penalty_threshold([
2906 {org_unit => $U->get_org_ancestors($context_org)},
2907 {order_by => {pgpt => 'id'}}
2909 $conn->respond($_) for @$list;
2915 __PACKAGE__->register_method(
2916 method => "user_retrieve_fleshed_by_id",
2918 api_name => "open-ils.actor.user.fleshed.retrieve",
2921 sub user_retrieve_fleshed_by_id {
2922 my( $self, $client, $auth, $user_id, $fields ) = @_;
2923 my $e = new_editor(authtoken => $auth);
2924 return $e->event unless $e->checkauth;
2926 if( $e->requestor->id != $user_id ) {
2927 return $e->event unless $e->allowed('VIEW_USER');
2933 "standing_penalties",
2939 return new_flesh_user($user_id, $fields, $e);
2943 sub new_flesh_user {
2946 my $fields = shift || [];
2949 my $fetch_penalties = 0;
2950 if(grep {$_ eq 'standing_penalties'} @$fields) {
2951 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2952 $fetch_penalties = 1;
2955 my $fetch_usr_act = 0;
2956 if(grep {$_ eq 'usr_activity'} @$fields) {
2957 $fields = [grep {$_ ne 'usr_activity'} @$fields];
2961 my $user = $e->retrieve_actor_user(
2966 "flesh_fields" => { "au" => $fields }
2969 ) or return $e->die_event;
2972 if( grep { $_ eq 'addresses' } @$fields ) {
2974 $user->addresses([]) unless @{$user->addresses};
2975 # don't expose "replaced" addresses by default
2976 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2978 if( ref $user->billing_address ) {
2979 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2980 push( @{$user->addresses}, $user->billing_address );
2984 if( ref $user->mailing_address ) {
2985 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2986 push( @{$user->addresses}, $user->mailing_address );
2991 if($fetch_penalties) {
2992 # grab the user penalties ranged for this location
2993 $user->standing_penalties(
2994 $e->search_actor_user_standing_penalty([
2997 {stop_date => undef},
2998 {stop_date => {'>' => 'now'}}
3000 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3003 flesh_fields => {ausp => ['standing_penalty']}
3009 # retrieve the most recent usr_activity entry
3010 if ($fetch_usr_act) {
3012 # max number to return for simple patron fleshing
3013 my $limit = $U->ou_ancestor_setting_value(
3014 $e->requestor->ws_ou,
3015 'circ.patron.usr_activity_retrieve.max');
3019 flesh_fields => {auact => ['etype']},
3020 order_by => {auact => 'event_time DESC'},
3023 # 0 == none, <0 == return all
3024 $limit = 1 unless defined $limit;
3025 $opts->{limit} = $limit if $limit > 0;
3027 $user->usr_activity(
3029 [] : # skip the DB call
3030 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3035 $user->clear_passwd();
3042 __PACKAGE__->register_method(
3043 method => "user_retrieve_parts",
3044 api_name => "open-ils.actor.user.retrieve.parts",
3047 sub user_retrieve_parts {
3048 my( $self, $client, $auth, $user_id, $fields ) = @_;
3049 my $e = new_editor(authtoken => $auth);
3050 return $e->event unless $e->checkauth;
3051 $user_id ||= $e->requestor->id;
3052 if( $e->requestor->id != $user_id ) {
3053 return $e->event unless $e->allowed('VIEW_USER');
3056 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3057 push(@resp, $user->$_()) for(@$fields);
3063 __PACKAGE__->register_method(
3064 method => 'user_opt_in_enabled',
3065 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3066 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3069 sub user_opt_in_enabled {
3070 my($self, $conn) = @_;
3071 my $sc = OpenSRF::Utils::SettingsClient->new;
3072 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3077 __PACKAGE__->register_method(
3078 method => 'user_opt_in_at_org',
3079 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3081 @param $auth The auth token
3082 @param user_id The ID of the user to test
3083 @return 1 if the user has opted in at the specified org,
3084 event on error, and 0 otherwise. /
3086 sub user_opt_in_at_org {
3087 my($self, $conn, $auth, $user_id) = @_;
3089 # see if we even need to enforce the opt-in value
3090 return 1 unless user_opt_in_enabled($self);
3092 my $e = new_editor(authtoken => $auth);
3093 return $e->event unless $e->checkauth;
3095 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3096 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3098 my $ws_org = $e->requestor->ws_ou;
3099 # user is automatically opted-in if they are from the local org
3100 return 1 if $user->home_ou eq $ws_org;
3102 # get the boundary setting
3103 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3105 # auto opt in if user falls within the opt boundary
3106 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3108 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3110 my $vals = $e->search_actor_usr_org_unit_opt_in(
3111 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3117 __PACKAGE__->register_method(
3118 method => 'create_user_opt_in_at_org',
3119 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3121 @param $auth The auth token
3122 @param user_id The ID of the user to test
3123 @return The ID of the newly created object, event on error./
3126 sub create_user_opt_in_at_org {
3127 my($self, $conn, $auth, $user_id, $org_id) = @_;
3129 my $e = new_editor(authtoken => $auth, xact=>1);
3130 return $e->die_event unless $e->checkauth;
3132 # if a specific org unit wasn't passed in, get one based on the defaults;
3134 my $wsou = $e->requestor->ws_ou;
3135 # get the default opt depth
3136 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3137 # get the org unit at that depth
3138 my $org = $e->json_query({
3139 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3141 $org_id = $org->{id};
3144 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3145 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3147 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3149 $opt_in->org_unit($org_id);
3150 $opt_in->usr($user_id);
3151 $opt_in->staff($e->requestor->id);
3152 $opt_in->opt_in_ts('now');
3153 $opt_in->opt_in_ws($e->requestor->wsid);
3155 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3156 or return $e->die_event;
3164 __PACKAGE__->register_method (
3165 method => 'retrieve_org_hours',
3166 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3168 Returns the hours of operation for a specified org unit
3169 @param authtoken The login session key
3170 @param org_id The org_unit ID
3174 sub retrieve_org_hours {
3175 my($self, $conn, $auth, $org_id) = @_;
3176 my $e = new_editor(authtoken => $auth);
3177 return $e->die_event unless $e->checkauth;
3178 $org_id ||= $e->requestor->ws_ou;
3179 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3183 __PACKAGE__->register_method (
3184 method => 'verify_user_password',
3185 api_name => 'open-ils.actor.verify_user_password',
3187 Given a barcode or username and the MD5 encoded password,
3188 returns 1 if the password is correct. Returns 0 otherwise.
3192 sub verify_user_password {
3193 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3194 my $e = new_editor(authtoken => $auth);
3195 return $e->die_event unless $e->checkauth;
3197 my $user_by_barcode;
3198 my $user_by_username;
3200 my $card = $e->search_actor_card([
3201 {barcode => $barcode},
3202 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3203 $user_by_barcode = $card->usr;
3204 $user = $user_by_barcode;
3207 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3208 $user = $user_by_username;
3210 return 0 if (!$user);
3211 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3212 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3213 return 1 if $user->passwd eq $password;
3217 __PACKAGE__->register_method (
3218 method => 'retrieve_usr_id_via_barcode_or_usrname',
3219 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3221 Given a barcode or username returns the id for the user or
3226 sub retrieve_usr_id_via_barcode_or_usrname {
3227 my($self, $conn, $auth, $barcode, $username) = @_;
3228 my $e = new_editor(authtoken => $auth);
3229 return $e->die_event unless $e->checkauth;
3230 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3232 my $user_by_barcode;
3233 my $user_by_username;
3234 $logger->info("$id_as_barcode is the ID as BARCODE");
3236 my $card = $e->search_actor_card([
3237 {barcode => $barcode},
3238 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3239 if ($id_as_barcode =~ /^t/i) {
3241 $user = $e->retrieve_actor_user($barcode);
3242 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3244 $user_by_barcode = $card->usr;
3245 $user = $user_by_barcode;
3248 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3249 $user_by_barcode = $card->usr;
3250 $user = $user_by_barcode;
3255 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3257 $user = $user_by_username;
3259 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3260 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3261 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3266 __PACKAGE__->register_method (
3267 method => 'merge_users',
3268 api_name => 'open-ils.actor.user.merge',
3271 Given a list of source users and destination user, transfer all data from the source
3272 to the dest user and delete the source user. All user related data is
3273 transferred, including circulations, holds, bookbags, etc.
3279 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3280 my $e = new_editor(xact => 1, authtoken => $auth);
3281 return $e->die_event unless $e->checkauth;
3283 # disallow the merge if any subordinate accounts are in collections
3284 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3285 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3287 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3288 my $del_addrs = ($U->ou_ancestor_setting_value(
3289 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3290 my $del_cards = ($U->ou_ancestor_setting_value(
3291 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3292 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3293 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3295 for my $src_id (@$user_ids) {
3296 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3298 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3299 if($src_user->home_ou ne $master_user->home_ou) {
3300 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3303 return $e->die_event unless
3304 $e->json_query({from => [
3319 __PACKAGE__->register_method (
3320 method => 'approve_user_address',
3321 api_name => 'open-ils.actor.user.pending_address.approve',
3328 sub approve_user_address {
3329 my($self, $conn, $auth, $addr) = @_;
3330 my $e = new_editor(xact => 1, authtoken => $auth);
3331 return $e->die_event unless $e->checkauth;
3333 # if the caller passes an address object, assume they want to
3334 # update it first before approving it
3335 $e->update_actor_user_address($addr) or return $e->die_event;
3337 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3339 my $user = $e->retrieve_actor_user($addr->usr);
3340 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3341 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3342 or return $e->die_event;
3344 return [values %$result]->[0];
3348 __PACKAGE__->register_method (
3349 method => 'retrieve_friends',
3350 api_name => 'open-ils.actor.friends.retrieve',
3353 returns { confirmed: [], pending_out: [], pending_in: []}
3354 pending_out are users I'm requesting friendship with
3355 pending_in are users requesting friendship with me
3360 sub retrieve_friends {
3361 my($self, $conn, $auth, $user_id, $options) = @_;
3362 my $e = new_editor(authtoken => $auth);
3363 return $e->event unless $e->checkauth;
3364 $user_id ||= $e->requestor->id;
3366 if($user_id != $e->requestor->id) {
3367 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3368 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3371 return OpenILS::Application::Actor::Friends->retrieve_friends(
3372 $e, $user_id, $options);
3377 __PACKAGE__->register_method (
3378 method => 'apply_friend_perms',
3379 api_name => 'open-ils.actor.friends.perms.apply',
3385 sub apply_friend_perms {
3386 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3387 my $e = new_editor(authtoken => $auth, xact => 1);
3388 return $e->die_event unless $e->checkauth;
3390 if($user_id != $e->requestor->id) {
3391 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3392 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3395 for my $perm (@perms) {
3397 OpenILS::Application::Actor::Friends->apply_friend_perm(
3398 $e, $user_id, $delegate_id, $perm);
3399 return $evt if $evt;
3407 __PACKAGE__->register_method (
3408 method => 'update_user_pending_address',
3409 api_name => 'open-ils.actor.user.address.pending.cud'
3412 sub update_user_pending_address {
3413 my($self, $conn, $auth, $addr) = @_;
3414 my $e = new_editor(authtoken => $auth, xact => 1);
3415 return $e->die_event unless $e->checkauth;
3417 if($addr->usr != $e->requestor->id) {
3418 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3419 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3423 $e->create_actor_user_address($addr) or return $e->die_event;
3424 } elsif($addr->isdeleted) {
3425 $e->delete_actor_user_address($addr) or return $e->die_event;
3427 $e->update_actor_user_address($addr) or return $e->die_event;
3435 __PACKAGE__->register_method (
3436 method => 'user_events',
3437 api_name => 'open-ils.actor.user.events.circ',
3440 __PACKAGE__->register_method (
3441 method => 'user_events',
3442 api_name => 'open-ils.actor.user.events.ahr',
3447 my($self, $conn, $auth, $user_id, $filters) = @_;
3448 my $e = new_editor(authtoken => $auth);
3449 return $e->event unless $e->checkauth;
3451 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3452 my $user_field = 'usr';
3455 $filters->{target} = {
3456 select => { $obj_type => ['id'] },
3458 where => {usr => $user_id}
3461 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3462 if($e->requestor->id != $user_id) {
3463 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3466 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3467 my $req = $ses->request('open-ils.trigger.events_by_target',
3468 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3470 while(my $resp = $req->recv) {
3471 my $val = $resp->content;
3472 my $tgt = $val->target;
3474 if($obj_type eq 'circ') {
3475 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3477 } elsif($obj_type eq 'ahr') {
3478 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3479 if $tgt->current_copy;
3482 $conn->respond($val) if $val;
3488 __PACKAGE__->register_method (
3489 method => 'copy_events',
3490 api_name => 'open-ils.actor.copy.events.circ',
3493 __PACKAGE__->register_method (
3494 method => 'copy_events',
3495 api_name => 'open-ils.actor.copy.events.ahr',
3500 my($self, $conn, $auth, $copy_id, $filters) = @_;
3501 my $e = new_editor(authtoken => $auth);
3502 return $e->event unless $e->checkauth;
3504 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3506 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3508 my $copy_field = 'target_copy';
3509 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3512 $filters->{target} = {
3513 select => { $obj_type => ['id'] },
3515 where => {$copy_field => $copy_id}
3519 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3520 my $req = $ses->request('open-ils.trigger.events_by_target',
3521 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3523 while(my $resp = $req->recv) {
3524 my $val = $resp->content;
3525 my $tgt = $val->target;
3527 my $user = $e->retrieve_actor_user($tgt->usr);
3528 if($e->requestor->id != $user->id) {
3529 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3532 $tgt->$copy_field($copy);
3535 $conn->respond($val) if $val;
3544 __PACKAGE__->register_method (
3545 method => 'update_events',
3546 api_name => 'open-ils.actor.user.event.cancel.batch',
3549 __PACKAGE__->register_method (
3550 method => 'update_events',
3551 api_name => 'open-ils.actor.user.event.reset.batch',
3556 my($self, $conn, $auth, $event_ids) = @_;
3557 my $e = new_editor(xact => 1, authtoken => $auth);
3558 return $e->die_event unless $e->checkauth;
3561 for my $id (@$event_ids) {
3563 # do a little dance to determine what user we are ultimately affecting
3564 my $event = $e->retrieve_action_trigger_event([
3567 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3569 ]) or return $e->die_event;
3572 if($event->event_def->hook->core_type eq 'circ') {
3573 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3574 } elsif($event->event_def->hook->core_type eq 'ahr') {
3575 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3580 my $user = $e->retrieve_actor_user($user_id);
3581 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3583 if($self->api_name =~ /cancel/) {
3584 $event->state('invalid');
3585 } elsif($self->api_name =~ /reset/) {
3586 $event->clear_start_time;
3587 $event->clear_update_time;
3588 $event->state('pending');
3591 $e->update_action_trigger_event($event) or return $e->die_event;
3592 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3596 return {complete => 1};
3600 __PACKAGE__->register_method (
3601 method => 'really_delete_user',
3602 api_name => 'open-ils.actor.user.delete.override',
3603 signature => q/@see open-ils.actor.user.delete/
3606 __PACKAGE__->register_method (
3607 method => 'really_delete_user',
3608 api_name => 'open-ils.actor.user.delete',
3610 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3611 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3612 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3613 dest_usr_id is only required when deleting a user that performs staff functions.
3617 sub really_delete_user {
3618 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3619 my $e = new_editor(authtoken => $auth, xact => 1);
3620 return $e->die_event unless $e->checkauth;
3621 $oargs = { all => 1 } unless defined $oargs;
3623 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3624 my $open_bills = $e->json_query({
3625 select => { mbts => ['id'] },
3628 xact_finish => { '=' => undef },
3629 usr => { '=' => $user_id },
3631 }) or return $e->die_event;
3633 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3635 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3637 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3638 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3639 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3641 # No deleting yourself - UI is supposed to stop you first, though.
3642 return $e->die_event unless $e->requestor->id != $user->id;
3643 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3644 # Check if you are allowed to mess with this patron permission group at all
3645 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3646 my $evt = group_perm_failed($session, $e->requestor, $user);
3647 return $e->die_event($evt) if $evt;
3648 my $stat = $e->json_query(
3649 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3650 or return $e->die_event;
3656 __PACKAGE__->register_method (
3657 method => 'user_payments',
3658 api_name => 'open-ils.actor.user.payments.retrieve',
3661 Returns all payments for a given user. Default order is newest payments first.
3662 @param auth Authentication token
3663 @param user_id The user ID
3664 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3669 my($self, $conn, $auth, $user_id, $filters) = @_;
3672 my $e = new_editor(authtoken => $auth);
3673 return $e->die_event unless $e->checkauth;
3675 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3676 return $e->event unless
3677 $e->requestor->id == $user_id or
3678 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3680 # Find all payments for all transactions for user $user_id
3682 select => {mp => ['id']},
3687 select => {mbt => ['id']},
3689 where => {usr => $user_id}
3694 { # by default, order newest payments first
3696 field => 'payment_ts',
3699 # secondary sort in ID as a tie-breaker, since payments created
3700 # within the same transaction will have identical payment_ts's
3707 for (qw/order_by limit offset/) {
3708 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3711 if(defined $filters->{where}) {
3712 foreach (keys %{$filters->{where}}) {
3713 # don't allow the caller to expand the result set to other users
3714 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3718 my $payment_ids = $e->json_query($query);
3719 for my $pid (@$payment_ids) {
3720 my $pay = $e->retrieve_money_payment([
3725 mbt => ['summary', 'circulation', 'grocery'],
3726 circ => ['target_copy'],
3727 acp => ['call_number'],
3735 xact_type => $pay->xact->summary->xact_type,
3736 last_billing_type => $pay->xact->summary->last_billing_type,
3739 if($pay->xact->summary->xact_type eq 'circulation') {
3740 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3741 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3744 $pay->xact($pay->xact->id); # de-flesh
3745 $conn->respond($resp);
3753 __PACKAGE__->register_method (
3754 method => 'negative_balance_users',
3755 api_name => 'open-ils.actor.users.negative_balance',
3758 Returns all users that have an overall negative balance
3759 @param auth Authentication token
3760 @param org_id The context org unit as an ID or list of IDs. This will be the home
3761 library of the user. If no org_unit is specified, no org unit filter is applied
3765 sub negative_balance_users {
3766 my($self, $conn, $auth, $org_id) = @_;
3768 my $e = new_editor(authtoken => $auth);
3769 return $e->die_event unless $e->checkauth;
3770 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3774 mous => ['usr', 'balance_owed'],
3777 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3778 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3795 where => {'+mous' => {balance_owed => {'<' => 0}}}
3798 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3800 my $list = $e->json_query($query, {timeout => 600});
3802 for my $data (@$list) {
3804 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3805 balance_owed => $data->{balance_owed},
3806 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3813 __PACKAGE__->register_method(
3814 method => "request_password_reset",
3815 api_name => "open-ils.actor.patron.password_reset.request",
3817 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3818 "method for changing a user's password. The UUID token is distributed via A/T " .
3819 "templates (i.e. email to the user).",
3821 { desc => 'user_id_type', type => 'string' },
3822 { desc => 'user_id', type => 'string' },
3823 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3825 return => {desc => '1 on success, Event on error'}
3828 sub request_password_reset {
3829 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3831 # Check to see if password reset requests are already being throttled:
3832 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3834 my $e = new_editor(xact => 1);
3837 # Get the user, if any, depending on the input value
3838 if ($user_id_type eq 'username') {
3839 $user = $e->search_actor_user({usrname => $user_id})->[0];
3842 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3844 } elsif ($user_id_type eq 'barcode') {
3845 my $card = $e->search_actor_card([
3846 {barcode => $user_id},
3847 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3850 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3855 # If the user doesn't have an email address, we can't help them
3856 if (!$user->email) {
3858 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3861 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3862 if ($email_must_match) {
3863 if ($user->email ne $email) {
3864 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3868 _reset_password_request($conn, $e, $user);
3871 # Once we have the user, we can issue the password reset request
3872 # XXX Add a wrapper method that accepts barcode + email input
3873 sub _reset_password_request {
3874 my ($conn, $e, $user) = @_;
3876 # 1. Get throttle threshold and time-to-live from OU_settings
3877 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3878 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3880 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3882 # 2. Get time of last request and number of active requests (num_active)
3883 my $active_requests = $e->json_query({
3889 transform => 'COUNT'
3892 column => 'request_time',
3898 has_been_reset => { '=' => 'f' },
3899 request_time => { '>' => $threshold_time }
3903 # Guard against no active requests
3904 if ($active_requests->[0]->{'request_time'}) {
3905 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3906 my $now = DateTime::Format::ISO8601->new();
3908 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3909 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3910 ($last_request->add_duration('1 minute') > $now)) {
3911 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3913 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3917 # TODO Check to see if the user is in a password-reset-restricted group
3919 # Otherwise, go ahead and try to get the user.
3921 # Check the number of active requests for this user
3922 $active_requests = $e->json_query({
3928 transform => 'COUNT'
3933 usr => { '=' => $user->id },
3934 has_been_reset => { '=' => 'f' },
3935 request_time => { '>' => $threshold_time }
3939 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3941 # if less than or equal to per-user threshold, proceed; otherwise, return event
3942 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3943 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3945 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3948 # Create the aupr object and insert into the database
3949 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3950 my $uuid = create_uuid_as_string(UUID_V4);
3951 $reset_request->uuid($uuid);
3952 $reset_request->usr($user->id);
3954 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3957 # Create an event to notify user of the URL to reset their password
3959 # Can we stuff this in the user_data param for trigger autocreate?
3960 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3962 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3963 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3966 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3971 __PACKAGE__->register_method(
3972 method => "commit_password_reset",
3973 api_name => "open-ils.actor.patron.password_reset.commit",
3975 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3976 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3977 "with the supplied password.",
3979 { desc => 'uuid', type => 'string' },
3980 { desc => 'password', type => 'string' },
3982 return => {desc => '1 on success, Event on error'}
3985 sub commit_password_reset {
3986 my($self, $conn, $uuid, $password) = @_;
3988 # Check to see if password reset requests are already being throttled:
3989 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3990 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3991 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3993 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3996 my $e = new_editor(xact => 1);
3998 my $aupr = $e->search_actor_usr_password_reset({
4005 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4007 my $user_id = $aupr->[0]->usr;
4008 my $user = $e->retrieve_actor_user($user_id);
4010 # Ensure we're still within the TTL for the request
4011 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4012 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4013 if ($threshold < DateTime->now(time_zone => 'local')) {
4015 $logger->info("Password reset request needed to be submitted before $threshold");
4016 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4019 # Check complexity of password against OU-defined regex
4020 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4024 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4025 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4026 $is_strong = check_password_strength_custom($password, $pw_regex);
4028 $is_strong = check_password_strength_default($password);
4033 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4036 # All is well; update the password
4037 $user->passwd($password);
4038 $e->update_actor_user($user);
4040 # And flag that this password reset request has been honoured
4041 $aupr->[0]->has_been_reset('t');
4042 $e->update_actor_usr_password_reset($aupr->[0]);
4048 sub check_password_strength_default {
4049 my $password = shift;
4050 # Use the default set of checks
4051 if ( (length($password) < 7) or
4052 ($password !~ m/.*\d+.*/) or
4053 ($password !~ m/.*[A-Za-z]+.*/)
4060 sub check_password_strength_custom {
4061 my ($password, $pw_regex) = @_;
4063 $pw_regex = qr/$pw_regex/;
4064 if ($password !~ /$pw_regex/) {
4072 __PACKAGE__->register_method(
4073 method => "event_def_opt_in_settings",
4074 api_name => "open-ils.actor.event_def.opt_in.settings",
4077 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4079 { desc => 'Authentication token', type => 'string'},
4081 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4086 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4093 sub event_def_opt_in_settings {
4094 my($self, $conn, $auth, $org_id) = @_;
4095 my $e = new_editor(authtoken => $auth);
4096 return $e->event unless $e->checkauth;
4098 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4099 return $e->event unless
4100 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4102 $org_id = $e->requestor->home_ou;
4105 # find all config.user_setting_type's related to event_defs for the requested org unit
4106 my $types = $e->json_query({
4107 select => {cust => ['name']},
4108 from => {atevdef => 'cust'},
4111 owner => $U->get_org_ancestors($org_id), # context org plus parents
4118 $conn->respond($_) for
4119 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4126 __PACKAGE__->register_method(
4127 method => "user_visible_circs",
4128 api_name => "open-ils.actor.history.circ.visible",
4131 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4133 { desc => 'Authentication token', type => 'string'},
4134 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4135 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4138 desc => q/An object with 2 fields: circulation and summary.
4139 circulation is the "circ" object. summary is the related "accs" object/,
4145 __PACKAGE__->register_method(
4146 method => "user_visible_circs",
4147 api_name => "open-ils.actor.history.circ.visible.print",
4150 desc => 'Returns printable output for the set of opt-in visible circulations',
4152 { desc => 'Authentication token', type => 'string'},
4153 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4154 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4157 desc => q/An action_trigger.event object or error event./,
4163 __PACKAGE__->register_method(
4164 method => "user_visible_circs",
4165 api_name => "open-ils.actor.history.circ.visible.email",
4168 desc => 'Emails the set of opt-in visible circulations to the requestor',
4170 { desc => 'Authentication token', type => 'string'},
4171 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4172 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4175 desc => q/undef, or event on error/
4180 __PACKAGE__->register_method(
4181 method => "user_visible_circs",
4182 api_name => "open-ils.actor.history.hold.visible",
4185 desc => 'Returns the set of opt-in visible holds',
4187 { desc => 'Authentication token', type => 'string'},
4188 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4189 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4192 desc => q/An object with 1 field: "hold"/,
4198 __PACKAGE__->register_method(
4199 method => "user_visible_circs",
4200 api_name => "open-ils.actor.history.hold.visible.print",
4203 desc => 'Returns printable output for the set of opt-in visible holds',
4205 { desc => 'Authentication token', type => 'string'},
4206 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4207 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4210 desc => q/An action_trigger.event object or error event./,
4216 __PACKAGE__->register_method(
4217 method => "user_visible_circs",
4218 api_name => "open-ils.actor.history.hold.visible.email",
4221 desc => 'Emails the set of opt-in visible holds to the requestor',
4223 { desc => 'Authentication token', type => 'string'},
4224 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4225 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4228 desc => q/undef, or event on error/
4233 sub user_visible_circs {
4234 my($self, $conn, $auth, $user_id, $options) = @_;
4236 my $is_hold = ($self->api_name =~ /hold/);
4237 my $for_print = ($self->api_name =~ /print/);
4238 my $for_email = ($self->api_name =~ /email/);
4239 my $e = new_editor(authtoken => $auth);
4240 return $e->event unless $e->checkauth;
4242 $user_id ||= $e->requestor->id;
4244 $options->{limit} ||= 50;
4245 $options->{offset} ||= 0;
4247 if($user_id != $e->requestor->id) {
4248 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4249 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4250 return $e->event unless $e->allowed($perm, $user->home_ou);
4253 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4255 my $data = $e->json_query({
4256 from => [$db_func, $user_id],
4257 limit => $$options{limit},
4258 offset => $$options{offset}
4260 # TODO: I only want IDs. code below didn't get me there
4261 # {"select":{"au":[{"column":"id", "result_field":"id",
4262 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4267 return undef unless @$data;
4271 # collect the batch of objects
4275 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4276 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4280 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4281 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4284 } elsif ($for_email) {
4286 $conn->respond_complete(1) if $for_email; # no sense in waiting
4294 my $hold = $e->retrieve_action_hold_request($id);
4295 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4296 # events will be fired from action_trigger_runner
4300 my $circ = $e->retrieve_action_circulation($id);
4301 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4302 # events will be fired from action_trigger_runner
4306 } else { # just give me the data please
4314 my $hold = $e->retrieve_action_hold_request($id);
4315 $conn->respond({hold => $hold});
4319 my $circ = $e->retrieve_action_circulation($id);
4322 summary => $U->create_circ_chain_summary($e, $id)
4331 __PACKAGE__->register_method(
4332 method => "user_saved_search_cud",
4333 api_name => "open-ils.actor.user.saved_search.cud",
4336 desc => 'Create/Update/Delete Access to user saved searches',
4338 { desc => 'Authentication token', type => 'string' },
4339 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4342 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4348 __PACKAGE__->register_method(
4349 method => "user_saved_search_cud",
4350 api_name => "open-ils.actor.user.saved_search.retrieve",
4353 desc => 'Retrieve a saved search object',
4355 { desc => 'Authentication token', type => 'string' },
4356 { desc => 'Saved Search ID', type => 'number' }
4359 desc => q/The saved search object, Event on error/,
4365 sub user_saved_search_cud {
4366 my( $self, $client, $auth, $search ) = @_;
4367 my $e = new_editor( authtoken=>$auth );
4368 return $e->die_event unless $e->checkauth;
4370 my $o_search; # prior version of the object, if any
4371 my $res; # to be returned
4373 # branch on the operation type
4375 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4377 # Get the old version, to check ownership
4378 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4379 or return $e->die_event;
4381 # You can't read somebody else's search
4382 return OpenILS::Event->new('BAD_PARAMS')
4383 unless $o_search->owner == $e->requestor->id;
4389 $e->xact_begin; # start an editor transaction
4391 if( $search->isnew ) { # Create
4393 # You can't create a search for somebody else
4394 return OpenILS::Event->new('BAD_PARAMS')
4395 unless $search->owner == $e->requestor->id;
4397 $e->create_actor_usr_saved_search( $search )
4398 or return $e->die_event;
4402 } elsif( $search->ischanged ) { # Update
4404 # You can't change ownership of a search
4405 return OpenILS::Event->new('BAD_PARAMS')
4406 unless $search->owner == $e->requestor->id;
4408 # Get the old version, to check ownership
4409 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4410 or return $e->die_event;
4412 # You can't update somebody else's search
4413 return OpenILS::Event->new('BAD_PARAMS')
4414 unless $o_search->owner == $e->requestor->id;
4417 $e->update_actor_usr_saved_search( $search )
4418 or return $e->die_event;
4422 } elsif( $search->isdeleted ) { # Delete
4424 # Get the old version, to check ownership
4425 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4426 or return $e->die_event;
4428 # You can't delete somebody else's search
4429 return OpenILS::Event->new('BAD_PARAMS')
4430 unless $o_search->owner == $e->requestor->id;
4433 $e->delete_actor_usr_saved_search( $o_search )
4434 or return $e->die_event;
4445 __PACKAGE__->register_method(
4446 method => "get_barcodes",
4447 api_name => "open-ils.actor.get_barcodes"
4451 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4452 my $e = new_editor(authtoken => $auth);
4453 return $e->event unless $e->checkauth;
4454 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4456 my $db_result = $e->json_query(
4458 'evergreen.get_barcodes',
4459 $org_id, $context, $barcode,
4463 if($context =~ /actor/) {
4464 my $filter_result = ();
4466 foreach my $result (@$db_result) {
4467 if($result->{type} eq 'actor') {
4468 if($e->requestor->id != $result->{id}) {
4469 $patron = $e->retrieve_actor_user($result->{id});
4471 push(@$filter_result, $e->event);
4474 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4475 push(@$filter_result, $result);
4478 push(@$filter_result, $e->event);
4482 push(@$filter_result, $result);
4486 push(@$filter_result, $result);
4489 return $filter_result;
4495 __PACKAGE__->register_method(
4496 method => 'address_alert_test',
4497 api_name => 'open-ils.actor.address_alert.test',
4499 desc => "Tests a set of address fields to determine if they match with an address_alert",
4501 {desc => 'Authentication token', type => 'string'},
4502 {desc => 'Org Unit', type => 'number'},
4503 {desc => 'Fields', type => 'hash'},
4505 return => {desc => 'List of matching address_alerts'}
4509 sub address_alert_test {
4510 my ($self, $client, $auth, $org_unit, $fields) = @_;
4511 return [] unless $fields and grep {$_} values %$fields;
4513 my $e = new_editor(authtoken => $auth);
4514 return $e->event unless $e->checkauth;
4515 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4516 $org_unit ||= $e->requestor->ws_ou;
4518 my $alerts = $e->json_query({
4520 'actor.address_alert_matches',
4528 $$fields{post_code},
4529 $$fields{mailing_address},
4530 $$fields{billing_address}
4534 # map the json_query hashes to real objects
4536 map {$e->retrieve_actor_address_alert($_)}
4537 (map {$_->{id}} @$alerts)
4541 __PACKAGE__->register_method(
4542 method => "mark_users_contact_invalid",
4543 api_name => "open-ils.actor.invalidate.email",
4545 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",
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.day_phone",
4560 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",
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.evening_phone",
4575 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",
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)", type => "number"}
4582 return => {desc => "Event describing success or failure", type => "object"}
4586 __PACKAGE__->register_method(
4587 method => "mark_users_contact_invalid",
4588 api_name => "open-ils.actor.invalidate.other_phone",
4590 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",
4592 {desc => "Authentication token", type => "string"},
4593 {desc => "Patron ID", type => "number"},
4594 {desc => "Additional note text (optional)", type => "string"},
4595 {desc => "penalty org unit ID (optional, default to top of org tree)",
4598 return => {desc => "Event describing success or failure", type => "object"}
4602 sub mark_users_contact_invalid {
4603 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4605 # This method invalidates an email address or a phone_number which
4606 # removes the bad email address or phone number, copying its contents
4607 # to a patron note, and institutes a standing penalty for "bad email"
4608 # or "bad phone number" which is cleared when the user is saved or
4609 # optionally only when the user is saved with an email address or
4610 # phone number (or staff manually delete the penalty).
4612 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4613 my $e = new_editor(authtoken => $auth, xact => 1);
4614 return $e->die_event unless $e->checkauth;
4616 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4617 $e, $contact_type, {usr => $patron_id},
4618 $addl_note, $penalty_ou, $e->requestor->id
4622 __PACKAGE__->register_method(
4623 method => "generate_patron_barcode",
4624 api_name => "open-ils.actor.generate_patron_barcode",
4626 desc => "Generates a new patron barcode. If a user ID is supplied," .
4627 "that user's card will be updated to point at the new barcode." ,
4629 {desc => 'Authentication token', type => 'string'},
4630 {desc => 'User ID', type => 'number'},
4631 {desc => 'prefix', type => 'string'},
4633 return => {desc => 'Generated barcode on success'}
4637 # evergreen.actor_update_barcode(user_id[, prefix]) generates a barcode, creates
4638 # an actor.card object, and points actor.usr.card to the new actor.card.id;
4639 # prefix is an optional prefix for the barcode
4641 # evergreen.actor_generate_barcode([prefix]) just generates a barcode, with
4642 # prefix as an optional prefix for the barcode
4643 sub generate_patron_barcode {
4645 my( $self, $client, $auth, $user_id, $prefix ) = @_;
4647 my $e = new_editor( authtoken=>$auth );
4648 return $e->die_event unless $e->checkauth;
4652 return $e->die_event unless $e->allowed('UPDATE_USER');
4653 my $args = ['evergreen.actor_update_barcode', $user_id];
4655 push @$args, $prefix;
4657 $barcode = $e->json_query(
4658 {from => $args})->[0]
4659 or return $e->die_event;
4661 my $args = ['evergreen.actor_generate_barcode'];
4663 push @$args, $prefix;
4665 $barcode = $e->json_query(
4666 {from => $args})->[0]
4667 or return $e->die_event;
4674 # Putting the following method in open-ils.actor is a bad fit, except in that
4675 # it serves an interface that lives under 'actor' in the templates directory,
4676 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4678 __PACKAGE__->register_method(
4679 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4680 method => "get_all_at_reactors_in_use",
4685 { name => 'authtoken', type => 'string' }
4688 desc => 'list of reactor names', type => 'array'
4693 sub get_all_at_reactors_in_use {
4694 my ($self, $conn, $auth) = @_;
4696 my $e = new_editor(authtoken => $auth);
4697 $e->checkauth or return $e->die_event;
4698 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4700 my $reactors = $e->json_query({
4702 atevdef => [{column => "reactor", transform => "distinct"}]
4704 from => {atevdef => {}}
4707 return $e->die_event unless ref $reactors eq "ARRAY";
4710 return [ map { $_->{reactor} } @$reactors ];
4713 __PACKAGE__->register_method(
4714 method => "filter_group_entry_crud",
4715 api_name => "open-ils.actor.filter_group_entry.crud",
4718 Provides CRUD access to filter group entry objects. These are not full accessible
4719 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4720 are not accessible via PCRUD (because they have no fields against which to link perms)
4723 {desc => "Authentication token", type => "string"},
4724 {desc => "Entry ID / Entry Object", type => "number"},
4725 {desc => "Additional note text (optional)", type => "string"},
4726 {desc => "penalty org unit ID (optional, default to top of org tree)",
4730 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4736 sub filter_group_entry_crud {
4737 my ($self, $conn, $auth, $arg) = @_;
4739 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4740 my $e = new_editor(authtoken => $auth, xact => 1);
4741 return $e->die_event unless $e->checkauth;
4747 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4748 or return $e->die_event;
4750 return $e->die_event unless $e->allowed(
4751 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4753 my $query = $arg->query;
4754 $query = $e->create_actor_search_query($query) or return $e->die_event;
4755 $arg->query($query->id);
4756 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4757 $entry->query($query);
4762 } elsif ($arg->ischanged) {
4764 my $entry = $e->retrieve_actor_search_filter_group_entry([
4767 flesh_fields => {asfge => ['grp']}
4769 ]) or return $e->die_event;
4771 return $e->die_event unless $e->allowed(
4772 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4774 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4775 $arg->query($arg->query->id);
4776 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4777 $arg->query($query);
4782 } elsif ($arg->isdeleted) {
4784 my $entry = $e->retrieve_actor_search_filter_group_entry([
4787 flesh_fields => {asfge => ['grp', 'query']}
4789 ]) or return $e->die_event;
4791 return $e->die_event unless $e->allowed(
4792 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4794 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4795 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4808 my $entry = $e->retrieve_actor_search_filter_group_entry([
4811 flesh_fields => {asfge => ['grp', 'query']}
4813 ]) or return $e->die_event;
4815 return $e->die_event unless $e->allowed(
4816 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4817 $entry->grp->owner);
4820 $entry->grp($entry->grp->id); # for consistency