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 'This method will make sure that the given user has permission to view that setting, if there is a ' .
273 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
274 'the user lacks the permisssion, undef will be returned.' ,
276 { desc => 'Org unit ID', type => 'number' },
277 { desc => 'setting name', type => 'string' },
278 { desc => 'authtoken (optional)', type => 'string' }
280 return => {desc => 'A value for the org unit setting, or undef'}
284 # ------------------------------------------------------------------
285 # Attempts to find the org setting value for a given org. if not
286 # found at the requested org, searches up the org tree until it
287 # finds a parent that has the requested setting.
288 # when found, returns { org => $id, value => $value }
289 # otherwise, returns NULL
290 # ------------------------------------------------------------------
291 sub ou_ancestor_setting {
292 my( $self, $client, $orgid, $name, $auth ) = @_;
293 # Make sure $auth is set to something if not given.
295 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
298 __PACKAGE__->register_method(
299 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
300 method => 'ou_ancestor_setting_batch',
302 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
303 'This method will make sure that the given user has permission to view that setting, if there is a ' .
304 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
305 'the user lacks the permisssion, undef will be returned.' ,
307 { desc => 'Org unit ID', type => 'number' },
308 { desc => 'setting name list', type => 'array' },
309 { desc => 'authtoken (optional)', type => 'string' }
311 return => {desc => 'A hash with name => value pairs for the org unit settings'}
314 sub ou_ancestor_setting_batch {
315 my( $self, $client, $orgid, $name_list, $auth ) = @_;
316 # Make sure $auth is set to something if not given.
319 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
325 __PACKAGE__->register_method(
326 method => "update_patron",
327 api_name => "open-ils.actor.patron.update",
330 Update an existing user, or create a new one. Related objects,
331 like cards, addresses, survey responses, and stat cats,
332 can be updated by attaching them to the user object in their
333 respective fields. For examples, the billing address object
334 may be inserted into the 'billing_address' field, etc. For each
335 attached object, indicate if the object should be created,
336 updated, or deleted using the built-in 'isnew', 'ischanged',
337 and 'isdeleted' fields on the object.
340 { desc => 'Authentication token', type => 'string' },
341 { desc => 'Patron data object', type => 'object' }
343 return => {desc => 'A fleshed user object, event on error'}
348 my( $self, $client, $user_session, $patron ) = @_;
350 my $session = $apputils->start_db_session();
352 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
354 my( $user_obj, $evt ) = $U->checkses($user_session);
357 $evt = check_group_perm($session, $user_obj, $patron);
360 $apputils->set_audit_info($session, $user_session, $user_obj->id, $user_obj->wsid);
362 # $new_patron is the patron in progress. $patron is the original patron
363 # passed in with the method. new_patron will change as the components
364 # of patron are added/updated.
368 # unflesh the real items on the patron
369 $patron->card( $patron->card->id ) if(ref($patron->card));
370 $patron->billing_address( $patron->billing_address->id )
371 if(ref($patron->billing_address));
372 $patron->mailing_address( $patron->mailing_address->id )
373 if(ref($patron->mailing_address));
375 # create/update the patron first so we can use his id
377 # $patron is the obj from the client (new data) and $new_patron is the
378 # patron object properly built for db insertion, so we need a third variable
379 # if we want to represent the old patron.
382 my $barred_hook = '';
384 if($patron->isnew()) {
385 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
387 if($U->is_true($patron->barred)) {
388 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'BAR_PATRON');
392 $new_patron = $patron;
394 # Did auth checking above already.
396 $old_patron = $e->retrieve_actor_user($patron->id) or
397 return $e->die_event;
399 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
400 $evt = $U->check_perms($user_obj->id, $patron->home_ou, $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON');
403 $barred_hook = $U->is_true($new_patron->barred) ?
404 'au.barred' : 'au.unbarred';
408 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
411 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
414 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
417 # re-update the patron if anything has happened to him during this process
418 if($new_patron->ischanged()) {
419 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
423 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
426 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
429 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
432 $apputils->commit_db_session($session);
434 $evt = apply_invalid_addr_penalty($patron);
437 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
439 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
441 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
443 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
444 $new_patron, $new_patron->home_ou) if $barred_hook;
447 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
450 sub apply_invalid_addr_penalty {
452 my $e = new_editor(xact => 1);
454 # grab the invalid address penalty if set
455 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
457 my ($addr_penalty) = grep
458 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
460 # do we enforce invalid address penalty
461 my $enforce = $U->ou_ancestor_setting_value(
462 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
464 my $addrs = $e->search_actor_user_address(
465 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
466 my $addr_count = scalar(@$addrs);
468 if($addr_count == 0 and $addr_penalty) {
470 # regardless of any settings, remove the penalty when the user has no invalid addresses
471 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
474 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
476 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
477 my $depth = $ptype->org_depth;
478 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
479 $ctx_org = $patron->home_ou unless defined $ctx_org;
481 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
482 $penalty->usr($patron->id);
483 $penalty->org_unit($ctx_org);
484 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
486 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
505 "standing_penalties",
513 push @$fields, "home_ou" if $home_ou;
514 return new_flesh_user($id, $fields, $e );
522 # clone and clear stuff that would break the database
526 my $new_patron = $patron->clone;
528 $new_patron->clear_billing_address();
529 $new_patron->clear_mailing_address();
530 $new_patron->clear_addresses();
531 $new_patron->clear_card();
532 $new_patron->clear_cards();
533 $new_patron->clear_id();
534 $new_patron->clear_isnew();
535 $new_patron->clear_ischanged();
536 $new_patron->clear_isdeleted();
537 $new_patron->clear_stat_cat_entries();
538 $new_patron->clear_permissions();
539 $new_patron->clear_standing_penalties();
549 my $user_obj = shift;
551 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
552 return (undef, $evt) if $evt;
554 my $ex = $session->request(
555 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
557 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
560 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
562 my $id = $session->request(
563 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
564 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
566 $logger->info("Successfully created new user [$id] in DB");
568 return ( $session->request(
569 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
573 sub check_group_perm {
574 my( $session, $requestor, $patron ) = @_;
577 # first let's see if the requestor has
578 # priveleges to update this user in any way
579 if( ! $patron->isnew ) {
580 my $p = $session->request(
581 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
583 # If we are the requestor (trying to update our own account)
584 # and we are not trying to change our profile, we're good
585 if( $p->id == $requestor->id and
586 $p->profile == $patron->profile ) {
591 $evt = group_perm_failed($session, $requestor, $p);
595 # They are allowed to edit this patron.. can they put the
596 # patron into the group requested?
597 $evt = group_perm_failed($session, $requestor, $patron);
603 sub group_perm_failed {
604 my( $session, $requestor, $patron ) = @_;
608 my $grpid = $patron->profile;
612 $logger->debug("user update looking for group perm for group $grpid");
613 $grp = $session->request(
614 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
615 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
617 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
619 $logger->info("user update checking perm $perm on user ".
620 $requestor->id." for update/create on user username=".$patron->usrname);
622 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
630 my( $session, $patron, $user_obj, $noperm) = @_;
632 $logger->info("Updating patron ".$patron->id." in DB");
637 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
638 return (undef, $evt) if $evt;
641 # update the password by itself to avoid the password protection magic
642 if( $patron->passwd ) {
643 my $s = $session->request(
644 'open-ils.storage.direct.actor.user.remote_update',
645 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
646 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
647 $patron->clear_passwd;
650 if(!$patron->ident_type) {
651 $patron->clear_ident_type;
652 $patron->clear_ident_value;
655 $evt = verify_last_xact($session, $patron);
656 return (undef, $evt) if $evt;
658 my $stat = $session->request(
659 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
660 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
665 sub verify_last_xact {
666 my( $session, $patron ) = @_;
667 return undef unless $patron->id and $patron->id > 0;
668 my $p = $session->request(
669 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
670 my $xact = $p->last_xact_id;
671 return undef unless $xact;
672 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
673 return OpenILS::Event->new('XACT_COLLISION')
674 if $xact ne $patron->last_xact_id;
679 sub _check_dup_ident {
680 my( $session, $patron ) = @_;
682 return undef unless $patron->ident_value;
685 ident_type => $patron->ident_type,
686 ident_value => $patron->ident_value,
689 $logger->debug("patron update searching for dup ident values: " .
690 $patron->ident_type . ':' . $patron->ident_value);
692 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
694 my $dups = $session->request(
695 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
698 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
705 sub _add_update_addresses {
709 my $new_patron = shift;
713 my $current_id; # id of the address before creation
715 my $addresses = $patron->addresses();
717 for my $address (@$addresses) {
719 next unless ref $address;
720 $current_id = $address->id();
722 if( $patron->billing_address() and
723 $patron->billing_address() == $current_id ) {
724 $logger->info("setting billing addr to $current_id");
725 $new_patron->billing_address($address->id());
726 $new_patron->ischanged(1);
729 if( $patron->mailing_address() and
730 $patron->mailing_address() == $current_id ) {
731 $new_patron->mailing_address($address->id());
732 $logger->info("setting mailing addr to $current_id");
733 $new_patron->ischanged(1);
737 if($address->isnew()) {
739 $address->usr($new_patron->id());
741 ($address, $evt) = _add_address($session,$address);
742 return (undef, $evt) if $evt;
744 # we need to get the new id
745 if( $patron->billing_address() and
746 $patron->billing_address() == $current_id ) {
747 $new_patron->billing_address($address->id());
748 $logger->info("setting billing addr to $current_id");
749 $new_patron->ischanged(1);
752 if( $patron->mailing_address() and
753 $patron->mailing_address() == $current_id ) {
754 $new_patron->mailing_address($address->id());
755 $logger->info("setting mailing addr to $current_id");
756 $new_patron->ischanged(1);
759 } elsif($address->ischanged() ) {
761 ($address, $evt) = _update_address($session, $address);
762 return (undef, $evt) if $evt;
764 } elsif($address->isdeleted() ) {
766 if( $address->id() == $new_patron->mailing_address() ) {
767 $new_patron->clear_mailing_address();
768 ($new_patron, $evt) = _update_patron($session, $new_patron);
769 return (undef, $evt) if $evt;
772 if( $address->id() == $new_patron->billing_address() ) {
773 $new_patron->clear_billing_address();
774 ($new_patron, $evt) = _update_patron($session, $new_patron);
775 return (undef, $evt) if $evt;
778 $evt = _delete_address($session, $address);
779 return (undef, $evt) if $evt;
783 return ( $new_patron, undef );
787 # adds an address to the db and returns the address with new id
789 my($session, $address) = @_;
790 $address->clear_id();
792 $logger->info("Creating new address at street ".$address->street1);
794 # put the address into the database
795 my $id = $session->request(
796 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
797 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
800 return ($address, undef);
804 sub _update_address {
805 my( $session, $address ) = @_;
807 $logger->info("Updating address ".$address->id." in the DB");
809 my $stat = $session->request(
810 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
812 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
813 return ($address, undef);
818 sub _add_update_cards {
822 my $new_patron = shift;
826 my $virtual_id; #id of the card before creation
828 my $cards = $patron->cards();
829 for my $card (@$cards) {
831 $card->usr($new_patron->id());
833 if(ref($card) and $card->isnew()) {
835 $virtual_id = $card->id();
836 ( $card, $evt ) = _add_card($session,$card);
837 return (undef, $evt) if $evt;
839 #if(ref($patron->card)) { $patron->card($patron->card->id); }
840 if($patron->card() == $virtual_id) {
841 $new_patron->card($card->id());
842 $new_patron->ischanged(1);
845 } elsif( ref($card) and $card->ischanged() ) {
846 $evt = _update_card($session, $card);
847 return (undef, $evt) if $evt;
851 return ( $new_patron, undef );
855 # adds an card to the db and returns the card with new id
857 my( $session, $card ) = @_;
860 $logger->info("Adding new patron card ".$card->barcode);
862 my $id = $session->request(
863 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
864 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
865 $logger->info("Successfully created patron card $id");
868 return ( $card, undef );
872 # returns event on error. returns undef otherwise
874 my( $session, $card ) = @_;
875 $logger->info("Updating patron card ".$card->id);
877 my $stat = $session->request(
878 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
879 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
886 # returns event on error. returns undef otherwise
887 sub _delete_address {
888 my( $session, $address ) = @_;
890 $logger->info("Deleting address ".$address->id." from DB");
892 my $stat = $session->request(
893 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
895 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
901 sub _add_survey_responses {
902 my ($session, $patron, $new_patron) = @_;
904 $logger->info( "Updating survey responses for patron ".$new_patron->id );
906 my $responses = $patron->survey_responses;
910 $_->usr($new_patron->id) for (@$responses);
912 my $evt = $U->simplereq( "open-ils.circ",
913 "open-ils.circ.survey.submit.user_id", $responses );
915 return (undef, $evt) if defined($U->event_code($evt));
919 return ( $new_patron, undef );
922 sub _clear_badcontact_penalties {
923 my ($session, $old_patron, $new_patron, $user_obj) = @_;
925 return ($new_patron, undef) unless $old_patron;
927 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
928 my $e = new_editor(xact => 1);
930 # This ignores whether the caller of update_patron has any permission
931 # to remove penalties, but these penalties no longer make sense
932 # if an email address field (for example) is changed (and the caller must
933 # have perms to do *that*) so there's no reason not to clear the penalties.
935 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
937 "+csp" => {"name" => [values(%$PNM)]},
938 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
940 "join" => {"csp" => {}},
942 "flesh_fields" => {"ausp" => ["standing_penalty"]}
944 ]) or return (undef, $e->die_event);
946 return ($new_patron, undef) unless @$bad_contact_penalties;
948 my @penalties_to_clear;
949 my ($field, $penalty_name);
951 # For each field that might have an associated bad contact penalty,
952 # check for such penalties and add them to the to-clear list if that
954 while (($field, $penalty_name) = each(%$PNM)) {
955 if ($old_patron->$field ne $new_patron->$field) {
956 push @penalties_to_clear, grep {
957 $_->standing_penalty->name eq $penalty_name
958 } @$bad_contact_penalties;
962 foreach (@penalties_to_clear) {
963 # Note that this "archives" penalties, in the terminology of the staff
964 # client, instead of just deleting them. This may assist reporting,
965 # or preserving old contact information when it is still potentially
967 $_->standing_penalty($_->standing_penalty->id); # deflesh
968 $_->stop_date('now');
969 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
973 return ($new_patron, undef);
977 sub _create_stat_maps {
979 my($session, $user_session, $patron, $new_patron) = @_;
981 my $maps = $patron->stat_cat_entries();
983 for my $map (@$maps) {
985 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
987 if ($map->isdeleted()) {
988 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
990 } elsif ($map->isnew()) {
991 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
996 $map->target_usr($new_patron->id);
999 $logger->info("Updating stat entry with method $method and map $map");
1001 my $stat = $session->request($method, $map)->gather(1);
1002 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1006 return ($new_patron, undef);
1009 sub _create_perm_maps {
1011 my($session, $user_session, $patron, $new_patron) = @_;
1013 my $maps = $patron->permissions;
1015 for my $map (@$maps) {
1017 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1018 if ($map->isdeleted()) {
1019 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1020 } elsif ($map->isnew()) {
1021 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1026 $map->usr($new_patron->id);
1028 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1029 $logger->info( "Updating permissions with method $method and map $map" );
1031 my $stat = $session->request($method, $map)->gather(1);
1032 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1036 return ($new_patron, undef);
1040 __PACKAGE__->register_method(
1041 method => "set_user_work_ous",
1042 api_name => "open-ils.actor.user.work_ous.update",
1045 sub set_user_work_ous {
1051 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1052 return $evt if $evt;
1054 my $session = $apputils->start_db_session();
1055 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1057 for my $map (@$maps) {
1059 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1060 if ($map->isdeleted()) {
1061 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1062 } elsif ($map->isnew()) {
1063 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1067 #warn( "Updating permissions with method $method and session $ses and map $map" );
1068 $logger->info( "Updating work_ou map with method $method and map $map" );
1070 my $stat = $session->request($method, $map)->gather(1);
1071 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1075 $apputils->commit_db_session($session);
1077 return scalar(@$maps);
1081 __PACKAGE__->register_method(
1082 method => "set_user_perms",
1083 api_name => "open-ils.actor.user.permissions.update",
1086 sub set_user_perms {
1092 my $session = $apputils->start_db_session();
1094 my( $user_obj, $evt ) = $U->checkses($ses);
1095 return $evt if $evt;
1096 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1098 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1101 $all = 1 if ($U->is_true($user_obj->super_user()));
1102 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1104 for my $map (@$maps) {
1106 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1107 if ($map->isdeleted()) {
1108 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1109 } elsif ($map->isnew()) {
1110 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1114 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1115 #warn( "Updating permissions with method $method and session $ses and map $map" );
1116 $logger->info( "Updating permissions with method $method and map $map" );
1118 my $stat = $session->request($method, $map)->gather(1);
1119 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1123 $apputils->commit_db_session($session);
1125 return scalar(@$maps);
1129 __PACKAGE__->register_method(
1130 method => "user_retrieve_by_barcode",
1132 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1134 sub user_retrieve_by_barcode {
1135 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1137 my $e = new_editor(authtoken => $auth);
1138 return $e->event unless $e->checkauth;
1140 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1141 or return $e->event;
1143 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1144 return $e->event unless $e->allowed(
1145 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1152 __PACKAGE__->register_method(
1153 method => "get_user_by_id",
1155 api_name => "open-ils.actor.user.retrieve",
1158 sub get_user_by_id {
1159 my ($self, $client, $auth, $id) = @_;
1160 my $e = new_editor(authtoken=>$auth);
1161 return $e->event unless $e->checkauth;
1162 my $user = $e->retrieve_actor_user($id) or return $e->event;
1163 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1168 __PACKAGE__->register_method(
1169 method => "get_org_types",
1170 api_name => "open-ils.actor.org_types.retrieve",
1173 return $U->get_org_types();
1177 __PACKAGE__->register_method(
1178 method => "get_user_ident_types",
1179 api_name => "open-ils.actor.user.ident_types.retrieve",
1182 sub get_user_ident_types {
1183 return $ident_types if $ident_types;
1184 return $ident_types =
1185 new_editor()->retrieve_all_config_identification_type();
1189 __PACKAGE__->register_method(
1190 method => "get_org_unit",
1191 api_name => "open-ils.actor.org_unit.retrieve",
1195 my( $self, $client, $user_session, $org_id ) = @_;
1196 my $e = new_editor(authtoken => $user_session);
1198 return $e->event unless $e->checkauth;
1199 $org_id = $e->requestor->ws_ou;
1201 my $o = $e->retrieve_actor_org_unit($org_id)
1202 or return $e->event;
1206 __PACKAGE__->register_method(
1207 method => "search_org_unit",
1208 api_name => "open-ils.actor.org_unit_list.search",
1211 sub search_org_unit {
1213 my( $self, $client, $field, $value ) = @_;
1215 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1217 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1218 { $field => $value } );
1224 # build the org tree
1226 __PACKAGE__->register_method(
1227 method => "get_org_tree",
1228 api_name => "open-ils.actor.org_tree.retrieve",
1230 note => "Returns the entire org tree structure",
1236 return $U->get_org_tree($client->session->session_locale);
1240 __PACKAGE__->register_method(
1241 method => "get_org_descendants",
1242 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1245 # depth is optional. org_unit is the id
1246 sub get_org_descendants {
1247 my( $self, $client, $org_unit, $depth ) = @_;
1249 if(ref $org_unit eq 'ARRAY') {
1252 for my $i (0..scalar(@$org_unit)-1) {
1253 my $list = $U->simple_scalar_request(
1255 "open-ils.storage.actor.org_unit.descendants.atomic",
1256 $org_unit->[$i], $depth->[$i] );
1257 push(@trees, $U->build_org_tree($list));
1262 my $orglist = $apputils->simple_scalar_request(
1264 "open-ils.storage.actor.org_unit.descendants.atomic",
1265 $org_unit, $depth );
1266 return $U->build_org_tree($orglist);
1271 __PACKAGE__->register_method(
1272 method => "get_org_ancestors",
1273 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1276 # depth is optional. org_unit is the id
1277 sub get_org_ancestors {
1278 my( $self, $client, $org_unit, $depth ) = @_;
1279 my $orglist = $apputils->simple_scalar_request(
1281 "open-ils.storage.actor.org_unit.ancestors.atomic",
1282 $org_unit, $depth );
1283 return $U->build_org_tree($orglist);
1287 __PACKAGE__->register_method(
1288 method => "get_standings",
1289 api_name => "open-ils.actor.standings.retrieve"
1294 return $user_standings if $user_standings;
1295 return $user_standings =
1296 $apputils->simple_scalar_request(
1298 "open-ils.cstore.direct.config.standing.search.atomic",
1299 { id => { "!=" => undef } }
1304 __PACKAGE__->register_method(
1305 method => "get_my_org_path",
1306 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1309 sub get_my_org_path {
1310 my( $self, $client, $auth, $org_id ) = @_;
1311 my $e = new_editor(authtoken=>$auth);
1312 return $e->event unless $e->checkauth;
1313 $org_id = $e->requestor->ws_ou unless defined $org_id;
1315 return $apputils->simple_scalar_request(
1317 "open-ils.storage.actor.org_unit.full_path.atomic",
1322 __PACKAGE__->register_method(
1323 method => "patron_adv_search",
1324 api_name => "open-ils.actor.patron.search.advanced"
1327 __PACKAGE__->register_method(
1328 method => "patron_adv_search",
1329 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1331 # TODO: change when opensrf 'bundling' is merged.
1332 # set a relatively small bundle size so the caller can start
1333 # seeing results fairly quickly
1334 max_chunk_size => 4096, # bundling
1337 # pending opensrf work -- also, not sure if needed since we're not
1338 # actaully creating an alternate vesrion, only offering to return a
1342 desc => q/Returns a stream of fleshed user objects instead of
1343 a pile of identifiers/
1347 sub patron_adv_search {
1348 my( $self, $client, $auth, $search_hash, $search_limit,
1349 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1351 # API params sanity checks.
1352 # Exit early with empty result if no filter exists.
1353 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1354 my $fleshed = ($self->api_name =~ /fleshed/);
1355 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1357 for my $key (keys %$search_hash) {
1358 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1362 return ($fleshed ? undef : []) unless $search_ok;
1364 my $e = new_editor(authtoken=>$auth);
1365 return $e->event unless $e->checkauth;
1366 return $e->event unless $e->allowed('VIEW_USER');
1368 # depth boundary outside of which patrons must opt-in, default to 0
1369 my $opt_boundary = 0;
1370 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1372 if (not defined $search_ou) {
1373 my $depth = $U->ou_ancestor_setting_value(
1374 $e->requestor->ws_ou,
1375 'circ.patron_edit.duplicate_patron_check_depth'
1378 if (defined $depth) {
1379 $search_ou = $U->org_unit_ancestor_at_depth(
1380 $e->requestor->ws_ou, $depth
1385 my $ids = $U->storagereq(
1386 "open-ils.storage.actor.user.crazy_search", $search_hash,
1387 $search_limit, $search_sort, $include_inactive,
1388 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1390 return $ids unless $self->api_name =~ /fleshed/;
1392 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1398 __PACKAGE__->register_method(
1399 method => "update_passwd",
1400 api_name => "open-ils.actor.user.password.update",
1402 desc => "Update the operator's password",
1404 { desc => 'Authentication token', type => 'string' },
1405 { desc => 'New password', type => 'string' },
1406 { desc => 'Current password', type => 'string' }
1408 return => {desc => '1 on success, Event on error or incorrect current password'}
1412 __PACKAGE__->register_method(
1413 method => "update_passwd",
1414 api_name => "open-ils.actor.user.username.update",
1416 desc => "Update the operator's username",
1418 { desc => 'Authentication token', type => 'string' },
1419 { desc => 'New username', type => 'string' },
1420 { desc => 'Current password', type => 'string' }
1422 return => {desc => '1 on success, Event on error or incorrect current password'}
1426 __PACKAGE__->register_method(
1427 method => "update_passwd",
1428 api_name => "open-ils.actor.user.email.update",
1430 desc => "Update the operator's email address",
1432 { desc => 'Authentication token', type => 'string' },
1433 { desc => 'New email address', type => 'string' },
1434 { desc => 'Current password', type => 'string' }
1436 return => {desc => '1 on success, Event on error or incorrect current password'}
1441 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1442 my $e = new_editor(xact=>1, authtoken=>$auth);
1443 return $e->die_event unless $e->checkauth;
1445 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1446 or return $e->die_event;
1447 my $api = $self->api_name;
1449 # make sure the original password matches the in-database password
1450 if (md5_hex($orig_pw) ne $db_user->passwd) {
1452 return new OpenILS::Event('INCORRECT_PASSWORD');
1455 if( $api =~ /password/o ) {
1457 $db_user->passwd($new_val);
1461 # if we don't clear the password, the user will be updated with
1462 # a hashed version of the hashed version of their password
1463 $db_user->clear_passwd;
1465 if( $api =~ /username/o ) {
1467 # make sure no one else has this username
1468 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1471 return new OpenILS::Event('USERNAME_EXISTS');
1473 $db_user->usrname($new_val);
1475 } elsif( $api =~ /email/o ) {
1476 $db_user->email($new_val);
1480 $e->update_actor_user($db_user) or return $e->die_event;
1483 # update the cached user to pick up these changes
1484 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1490 __PACKAGE__->register_method(
1491 method => "check_user_perms",
1492 api_name => "open-ils.actor.user.perm.check",
1493 notes => <<" NOTES");
1494 Takes a login session, user id, an org id, and an array of perm type strings. For each
1495 perm type, if the user does *not* have the given permission it is added
1496 to a list which is returned from the method. If all permissions
1497 are allowed, an empty list is returned
1498 if the logged in user does not match 'user_id', then the logged in user must
1499 have VIEW_PERMISSION priveleges.
1502 sub check_user_perms {
1503 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1505 my( $staff, $evt ) = $apputils->checkses($login_session);
1506 return $evt if $evt;
1508 if($staff->id ne $user_id) {
1509 if( $evt = $apputils->check_perms(
1510 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1516 for my $perm (@$perm_types) {
1517 if($apputils->check_perms($user_id, $org_id, $perm)) {
1518 push @not_allowed, $perm;
1522 return \@not_allowed
1525 __PACKAGE__->register_method(
1526 method => "check_user_perms2",
1527 api_name => "open-ils.actor.user.perm.check.multi_org",
1529 Checks the permissions on a list of perms and orgs for a user
1530 @param authtoken The login session key
1531 @param user_id The id of the user to check
1532 @param orgs The array of org ids
1533 @param perms The array of permission names
1534 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1535 if the logged in user does not match 'user_id', then the logged in user must
1536 have VIEW_PERMISSION priveleges.
1539 sub check_user_perms2 {
1540 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1542 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1543 $authtoken, $user_id, 'VIEW_PERMISSION' );
1544 return $evt if $evt;
1547 for my $org (@$orgs) {
1548 for my $perm (@$perms) {
1549 if($apputils->check_perms($user_id, $org, $perm)) {
1550 push @not_allowed, [ $org, $perm ];
1555 return \@not_allowed
1559 __PACKAGE__->register_method(
1560 method => 'check_user_perms3',
1561 api_name => 'open-ils.actor.user.perm.highest_org',
1563 Returns the highest org unit id at which a user has a given permission
1564 If the requestor does not match the target user, the requestor must have
1565 'VIEW_PERMISSION' rights at the home org unit of the target user
1566 @param authtoken The login session key
1567 @param userid The id of the user in question
1568 @param perm The permission to check
1569 @return The org unit highest in the org tree within which the user has
1570 the requested permission
1573 sub check_user_perms3 {
1574 my($self, $client, $authtoken, $user_id, $perm) = @_;
1575 my $e = new_editor(authtoken=>$authtoken);
1576 return $e->event unless $e->checkauth;
1578 my $tree = $U->get_org_tree();
1580 unless($e->requestor->id == $user_id) {
1581 my $user = $e->retrieve_actor_user($user_id)
1582 or return $e->event;
1583 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1584 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1587 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1590 __PACKAGE__->register_method(
1591 method => 'user_has_work_perm_at',
1592 api_name => 'open-ils.actor.user.has_work_perm_at',
1596 Returns a set of org unit IDs which represent the highest orgs in
1597 the org tree where the user has the requested permission. The
1598 purpose of this method is to return the smallest set of org units
1599 which represent the full expanse of the user's ability to perform
1600 the requested action. The user whose perms this method should
1601 check is implied by the authtoken. /,
1603 {desc => 'authtoken', type => 'string'},
1604 {desc => 'permission name', type => 'string'},
1605 {desc => q/user id, optional. If present, check perms for
1606 this user instead of the logged in user/, type => 'number'},
1608 return => {desc => 'An array of org IDs'}
1612 sub user_has_work_perm_at {
1613 my($self, $conn, $auth, $perm, $user_id) = @_;
1614 my $e = new_editor(authtoken=>$auth);
1615 return $e->event unless $e->checkauth;
1616 if(defined $user_id) {
1617 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1618 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1620 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1623 __PACKAGE__->register_method(
1624 method => 'user_has_work_perm_at_batch',
1625 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1629 sub user_has_work_perm_at_batch {
1630 my($self, $conn, $auth, $perms, $user_id) = @_;
1631 my $e = new_editor(authtoken=>$auth);
1632 return $e->event unless $e->checkauth;
1633 if(defined $user_id) {
1634 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1635 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1638 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1644 __PACKAGE__->register_method(
1645 method => 'check_user_perms4',
1646 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1648 Returns the highest org unit id at which a user has a given permission
1649 If the requestor does not match the target user, the requestor must have
1650 'VIEW_PERMISSION' rights at the home org unit of the target user
1651 @param authtoken The login session key
1652 @param userid The id of the user in question
1653 @param perms An array of perm names to check
1654 @return An array of orgId's representing the org unit
1655 highest in the org tree within which the user has the requested permission
1656 The arrah of orgId's has matches the order of the perms array
1659 sub check_user_perms4 {
1660 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1662 my( $staff, $target, $org, $evt );
1664 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1665 $authtoken, $userid, 'VIEW_PERMISSION' );
1666 return $evt if $evt;
1669 return [] unless ref($perms);
1670 my $tree = $U->get_org_tree();
1672 for my $p (@$perms) {
1673 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1679 __PACKAGE__->register_method(
1680 method => "user_fines_summary",
1681 api_name => "open-ils.actor.user.fines.summary",
1684 desc => 'Returns a short summary of the users total open fines, ' .
1685 'excluding voided fines Params are login_session, user_id' ,
1687 {desc => 'Authentication token', type => 'string'},
1688 {desc => 'User ID', type => 'string'} # number?
1691 desc => "a 'mous' object, event on error",
1696 sub user_fines_summary {
1697 my( $self, $client, $auth, $user_id ) = @_;
1699 my $e = new_editor(authtoken=>$auth);
1700 return $e->event unless $e->checkauth;
1702 if( $user_id ne $e->requestor->id ) {
1703 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1704 return $e->event unless
1705 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1708 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1712 __PACKAGE__->register_method(
1713 method => "user_opac_vitals",
1714 api_name => "open-ils.actor.user.opac.vital_stats",
1718 desc => 'Returns a short summary of the users vital stats, including ' .
1719 'identification information, accumulated balance, number of holds, ' .
1720 'and current open circulation stats' ,
1722 {desc => 'Authentication token', type => 'string'},
1723 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1726 desc => "An object with four properties: user, fines, checkouts and holds."
1731 sub user_opac_vitals {
1732 my( $self, $client, $auth, $user_id ) = @_;
1734 my $e = new_editor(authtoken=>$auth);
1735 return $e->event unless $e->checkauth;
1737 $user_id ||= $e->requestor->id;
1739 my $user = $e->retrieve_actor_user( $user_id );
1742 ->method_lookup('open-ils.actor.user.fines.summary')
1743 ->run($auth => $user_id);
1744 return $fines if (defined($U->event_code($fines)));
1747 $fines = new Fieldmapper::money::open_user_summary ();
1748 $fines->balance_owed(0.00);
1749 $fines->total_owed(0.00);
1750 $fines->total_paid(0.00);
1751 $fines->usr($user_id);
1755 ->method_lookup('open-ils.actor.user.hold_requests.count')
1756 ->run($auth => $user_id);
1757 return $holds if (defined($U->event_code($holds)));
1760 ->method_lookup('open-ils.actor.user.checked_out.count')
1761 ->run($auth => $user_id);
1762 return $out if (defined($U->event_code($out)));
1764 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1766 my $unread_msgs = $e->search_actor_usr_message([
1767 {usr => $user_id, read_date => undef, deleted => 'f'},
1773 first_given_name => $user->first_given_name,
1774 second_given_name => $user->second_given_name,
1775 family_name => $user->family_name,
1776 alias => $user->alias,
1777 usrname => $user->usrname
1779 fines => $fines->to_bare_hash,
1782 messages => { unread => scalar(@$unread_msgs) }
1787 ##### a small consolidation of related method registrations
1788 my $common_params = [
1789 { desc => 'Authentication token', type => 'string' },
1790 { desc => 'User ID', type => 'string' },
1791 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1792 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1795 'open-ils.actor.user.transactions' => '',
1796 'open-ils.actor.user.transactions.fleshed' => '',
1797 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1798 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1799 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1800 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1803 foreach (keys %methods) {
1805 method => "user_transactions",
1808 desc => 'For a given user, retrieve a list of '
1809 . (/\.fleshed/ ? 'fleshed ' : '')
1810 . 'transactions' . $methods{$_}
1811 . ' optionally limited to transactions of a given type.',
1812 params => $common_params,
1814 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1815 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1819 $args{authoritative} = 1;
1820 __PACKAGE__->register_method(%args);
1823 # Now for the counts
1825 'open-ils.actor.user.transactions.count' => '',
1826 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1827 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1830 foreach (keys %methods) {
1832 method => "user_transactions",
1835 desc => 'For a given user, retrieve a count of open '
1836 . 'transactions' . $methods{$_}
1837 . ' optionally limited to transactions of a given type.',
1838 params => $common_params,
1839 return => { desc => "Integer count of transactions, or event on error" }
1842 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1843 __PACKAGE__->register_method(%args);
1846 __PACKAGE__->register_method(
1847 method => "user_transactions",
1848 api_name => "open-ils.actor.user.transactions.have_balance.total",
1851 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1852 . ' optionally limited to transactions of a given type.',
1853 params => $common_params,
1854 return => { desc => "Decimal balance value, or event on error" }
1859 sub user_transactions {
1860 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1863 my $e = new_editor(authtoken => $auth);
1864 return $e->event unless $e->checkauth;
1866 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1868 return $e->event unless
1869 $e->requestor->id == $user_id or
1870 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1872 my $api = $self->api_name();
1874 my $filter = ($api =~ /have_balance/o) ?
1875 { 'balance_owed' => { '<>' => 0 } }:
1876 { 'total_owed' => { '>' => 0 } };
1878 my $method = 'open-ils.actor.user.transactions.history.still_open';
1879 $method = "$method.authoritative" if $api =~ /authoritative/;
1880 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1882 if($api =~ /total/o) {
1884 $total += $_->balance_owed for @$trans;
1888 ($api =~ /count/o ) and return scalar @$trans;
1889 ($api !~ /fleshed/o) and return $trans;
1892 for my $t (@$trans) {
1894 if( $t->xact_type ne 'circulation' ) {
1895 push @resp, {transaction => $t};
1899 my $circ_data = flesh_circ($e, $t->id);
1900 push @resp, {transaction => $t, %$circ_data};
1907 __PACKAGE__->register_method(
1908 method => "user_transaction_retrieve",
1909 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1912 notes => "Returns a fleshed transaction record"
1915 __PACKAGE__->register_method(
1916 method => "user_transaction_retrieve",
1917 api_name => "open-ils.actor.user.transaction.retrieve",
1920 notes => "Returns a transaction record"
1923 sub user_transaction_retrieve {
1924 my($self, $client, $auth, $bill_id) = @_;
1926 my $e = new_editor(authtoken => $auth);
1927 return $e->event unless $e->checkauth;
1929 my $trans = $e->retrieve_money_billable_transaction_summary(
1930 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1932 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1934 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1936 return $trans unless $self->api_name =~ /flesh/;
1937 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1939 my $circ_data = flesh_circ($e, $trans->id, 1);
1941 return {transaction => $trans, %$circ_data};
1946 my $circ_id = shift;
1947 my $flesh_copy = shift;
1949 my $circ = $e->retrieve_action_circulation([
1953 circ => ['target_copy'],
1954 acp => ['call_number'],
1961 my $copy = $circ->target_copy;
1963 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1964 $mods = new Fieldmapper::metabib::virtual_record;
1965 $mods->doc_id(OILS_PRECAT_RECORD);
1966 $mods->title($copy->dummy_title);
1967 $mods->author($copy->dummy_author);
1970 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1974 $circ->target_copy($circ->target_copy->id);
1975 $copy->call_number($copy->call_number->id);
1977 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1981 __PACKAGE__->register_method(
1982 method => "hold_request_count",
1983 api_name => "open-ils.actor.user.hold_requests.count",
1987 Returns hold ready vs. total counts.
1988 If a context org unit is provided, a third value
1989 is returned with key 'behind_desk', which reports
1990 how many holds are ready at the pickup library
1991 with the behind_desk flag set to true.
1995 sub hold_request_count {
1996 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
1997 my $e = new_editor(authtoken => $authtoken);
1998 return $e->event unless $e->checkauth;
2000 $user_id = $e->requestor->id unless defined $user_id;
2002 if($e->requestor->id ne $user_id) {
2003 my $user = $e->retrieve_actor_user($user_id);
2004 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2007 my $holds = $e->json_query({
2008 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2012 fulfillment_time => {"=" => undef },
2013 cancel_time => undef,
2018 $_->{current_shelf_lib} and # avoid undef warnings
2019 $_->{pickup_lib} eq $_->{current_shelf_lib}
2023 total => scalar(@$holds),
2024 ready => scalar(@ready)
2028 # count of holds ready at pickup lib with behind_desk true.
2029 $resp->{behind_desk} = scalar(
2031 $_->{pickup_lib} == $ctx_org and
2032 $U->is_true($_->{behind_desk})
2040 __PACKAGE__->register_method(
2041 method => "checked_out",
2042 api_name => "open-ils.actor.user.checked_out",
2046 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2047 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2048 . "(i.e., outstanding balance or some other pending action on the circ). "
2049 . "The .count method also includes a 'total' field which sums all open circs.",
2051 { desc => 'Authentication Token', type => 'string'},
2052 { desc => 'User ID', type => 'string'},
2055 desc => 'Returns event on error, or an object with ID lists, like: '
2056 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2061 __PACKAGE__->register_method(
2062 method => "checked_out",
2063 api_name => "open-ils.actor.user.checked_out.count",
2066 signature => q/@see open-ils.actor.user.checked_out/
2070 my( $self, $conn, $auth, $userid ) = @_;
2072 my $e = new_editor(authtoken=>$auth);
2073 return $e->event unless $e->checkauth;
2075 if( $userid ne $e->requestor->id ) {
2076 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2077 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2079 # see if there is a friend link allowing circ.view perms
2080 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2081 $e, $userid, $e->requestor->id, 'circ.view');
2082 return $e->event unless $allowed;
2086 my $count = $self->api_name =~ /count/;
2087 return _checked_out( $count, $e, $userid );
2091 my( $iscount, $e, $userid ) = @_;
2097 claims_returned => [],
2100 my $meth = 'retrieve_action_open_circ_';
2108 claims_returned => 0,
2115 my $data = $e->$meth($userid);
2119 $result{$_} += $data->$_() for (keys %result);
2120 $result{total} += $data->$_() for (keys %result);
2122 for my $k (keys %result) {
2123 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2133 __PACKAGE__->register_method(
2134 method => "checked_in_with_fines",
2135 api_name => "open-ils.actor.user.checked_in_with_fines",
2138 signature => q/@see open-ils.actor.user.checked_out/
2141 sub checked_in_with_fines {
2142 my( $self, $conn, $auth, $userid ) = @_;
2144 my $e = new_editor(authtoken=>$auth);
2145 return $e->event unless $e->checkauth;
2147 if( $userid ne $e->requestor->id ) {
2148 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2151 # money is owed on these items and they are checked in
2152 my $open = $e->search_action_circulation(
2155 xact_finish => undef,
2156 checkin_time => { "!=" => undef },
2161 my( @lost, @cr, @lo );
2162 for my $c (@$open) {
2163 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2164 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2165 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2170 claims_returned => \@cr,
2171 long_overdue => \@lo
2177 my ($api, $desc, $auth) = @_;
2178 $desc = $desc ? (" " . $desc) : '';
2179 my $ids = ($api =~ /ids$/) ? 1 : 0;
2182 method => "user_transaction_history",
2183 api_name => "open-ils.actor.user.transactions.$api",
2185 desc => "For a given User ID, returns a list of billable transaction" .
2186 ($ids ? " id" : '') .
2187 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2188 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2190 {desc => 'Authentication token', type => 'string'},
2191 {desc => 'User ID', type => 'number'},
2192 {desc => 'Transaction type (optional)', type => 'number'},
2193 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2196 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2200 $auth and push @sig, (authoritative => 1);
2204 my %auth_hist_methods = (
2206 'history.have_charge' => 'that have an initial charge',
2207 'history.still_open' => 'that are not finished',
2208 'history.have_balance' => 'that have a balance',
2209 'history.have_bill' => 'that have billings',
2210 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2211 'history.have_payment' => 'that have at least 1 payment',
2214 foreach (keys %auth_hist_methods) {
2215 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2216 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2217 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2220 sub user_transaction_history {
2221 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2225 my $e = new_editor(authtoken=>$auth);
2226 return $e->die_event unless $e->checkauth;
2228 if ($e->requestor->id ne $userid) {
2229 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2232 my $api = $self->api_name;
2233 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2235 if(defined($type)) {
2236 $filter->{'xact_type'} = $type;
2239 if($api =~ /have_bill_or_payment/o) {
2241 # transactions that have a non-zero sum across all billings or at least 1 payment
2242 $filter->{'-or'} = {
2243 'balance_owed' => { '<>' => 0 },
2244 'last_payment_ts' => { '<>' => undef }
2247 } elsif($api =~ /have_payment/) {
2249 $filter->{last_payment_ts} ||= {'<>' => undef};
2251 } elsif( $api =~ /have_balance/o) {
2253 # transactions that have a non-zero overall balance
2254 $filter->{'balance_owed'} = { '<>' => 0 };
2256 } elsif( $api =~ /have_charge/o) {
2258 # transactions that have at least 1 billing, regardless of whether it was voided
2259 $filter->{'last_billing_ts'} = { '<>' => undef };
2261 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2263 # transactions that have non-zero sum across all billings. This will exclude
2264 # xacts where all billings have been voided
2265 $filter->{'total_owed'} = { '<>' => 0 };
2268 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2269 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2270 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2272 my $mbts = $e->search_money_billable_transaction_summary(
2273 [ { usr => $userid, @xact_finish, %$filter },
2278 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2279 return $mbts unless $api =~ /fleshed/;
2282 for my $t (@$mbts) {
2284 if( $t->xact_type ne 'circulation' ) {
2285 push @resp, {transaction => $t};
2289 my $circ_data = flesh_circ($e, $t->id);
2290 push @resp, {transaction => $t, %$circ_data};
2298 __PACKAGE__->register_method(
2299 method => "user_perms",
2300 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2302 notes => "Returns a list of permissions"
2306 my( $self, $client, $authtoken, $user ) = @_;
2308 my( $staff, $evt ) = $apputils->checkses($authtoken);
2309 return $evt if $evt;
2311 $user ||= $staff->id;
2313 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2317 return $apputils->simple_scalar_request(
2319 "open-ils.storage.permission.user_perms.atomic",
2323 __PACKAGE__->register_method(
2324 method => "retrieve_perms",
2325 api_name => "open-ils.actor.permissions.retrieve",
2326 notes => "Returns a list of permissions"
2328 sub retrieve_perms {
2329 my( $self, $client ) = @_;
2330 return $apputils->simple_scalar_request(
2332 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2333 { id => { '!=' => undef } }
2337 __PACKAGE__->register_method(
2338 method => "retrieve_groups",
2339 api_name => "open-ils.actor.groups.retrieve",
2340 notes => "Returns a list of user groups"
2342 sub retrieve_groups {
2343 my( $self, $client ) = @_;
2344 return new_editor()->retrieve_all_permission_grp_tree();
2347 __PACKAGE__->register_method(
2348 method => "retrieve_org_address",
2349 api_name => "open-ils.actor.org_unit.address.retrieve",
2350 notes => <<' NOTES');
2351 Returns an org_unit address by ID
2352 @param An org_address ID
2354 sub retrieve_org_address {
2355 my( $self, $client, $id ) = @_;
2356 return $apputils->simple_scalar_request(
2358 "open-ils.cstore.direct.actor.org_address.retrieve",
2363 __PACKAGE__->register_method(
2364 method => "retrieve_groups_tree",
2365 api_name => "open-ils.actor.groups.tree.retrieve",
2366 notes => "Returns a list of user groups"
2369 sub retrieve_groups_tree {
2370 my( $self, $client ) = @_;
2371 return new_editor()->search_permission_grp_tree(
2376 flesh_fields => { pgt => ["children"] },
2377 order_by => { pgt => 'name'}
2384 __PACKAGE__->register_method(
2385 method => "add_user_to_groups",
2386 api_name => "open-ils.actor.user.set_groups",
2387 notes => "Adds a user to one or more permission groups"
2390 sub add_user_to_groups {
2391 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2393 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2394 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2395 return $evt if $evt;
2397 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2398 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2399 return $evt if $evt;
2401 $apputils->simplereq(
2403 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2405 for my $group (@$groups) {
2406 my $link = Fieldmapper::permission::usr_grp_map->new;
2408 $link->usr($userid);
2410 my $id = $apputils->simplereq(
2412 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2418 __PACKAGE__->register_method(
2419 method => "get_user_perm_groups",
2420 api_name => "open-ils.actor.user.get_groups",
2421 notes => "Retrieve a user's permission groups."
2425 sub get_user_perm_groups {
2426 my( $self, $client, $authtoken, $userid ) = @_;
2428 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2429 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2430 return $evt if $evt;
2432 return $apputils->simplereq(
2434 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2438 __PACKAGE__->register_method(
2439 method => "get_user_work_ous",
2440 api_name => "open-ils.actor.user.get_work_ous",
2441 notes => "Retrieve a user's work org units."
2444 __PACKAGE__->register_method(
2445 method => "get_user_work_ous",
2446 api_name => "open-ils.actor.user.get_work_ous.ids",
2447 notes => "Retrieve a user's work org units."
2450 sub get_user_work_ous {
2451 my( $self, $client, $auth, $userid ) = @_;
2452 my $e = new_editor(authtoken=>$auth);
2453 return $e->event unless $e->checkauth;
2454 $userid ||= $e->requestor->id;
2456 if($e->requestor->id != $userid) {
2457 my $user = $e->retrieve_actor_user($userid)
2458 or return $e->event;
2459 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2462 return $e->search_permission_usr_work_ou_map({usr => $userid})
2463 unless $self->api_name =~ /.ids$/;
2465 # client just wants a list of org IDs
2466 return $U->get_user_work_ou_ids($e, $userid);
2471 __PACKAGE__->register_method(
2472 method => 'register_workstation',
2473 api_name => 'open-ils.actor.workstation.register.override',
2474 signature => q/@see open-ils.actor.workstation.register/
2477 __PACKAGE__->register_method(
2478 method => 'register_workstation',
2479 api_name => 'open-ils.actor.workstation.register',
2481 Registers a new workstion in the system
2482 @param authtoken The login session key
2483 @param name The name of the workstation id
2484 @param owner The org unit that owns this workstation
2485 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2486 if the name is already in use.
2490 sub register_workstation {
2491 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2493 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2494 return $e->die_event unless $e->checkauth;
2495 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2496 my $existing = $e->search_actor_workstation({name => $name})->[0];
2497 $oargs = { all => 1 } unless defined $oargs;
2501 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2502 # workstation with the given name exists.
2504 if($owner ne $existing->owning_lib) {
2505 # if necessary, update the owning_lib of the workstation
2507 $logger->info("changing owning lib of workstation ".$existing->id.
2508 " from ".$existing->owning_lib." to $owner");
2509 return $e->die_event unless
2510 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2512 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2514 $existing->owning_lib($owner);
2515 return $e->die_event unless $e->update_actor_workstation($existing);
2521 "attempt to register an existing workstation. returning existing ID");
2524 return $existing->id;
2527 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2531 my $ws = Fieldmapper::actor::workstation->new;
2532 $ws->owning_lib($owner);
2534 $e->create_actor_workstation($ws) or return $e->die_event;
2536 return $ws->id; # note: editor sets the id on the new object for us
2539 __PACKAGE__->register_method(
2540 method => 'workstation_list',
2541 api_name => 'open-ils.actor.workstation.list',
2543 Returns a list of workstations registered at the given location
2544 @param authtoken The login session key
2545 @param ids A list of org_unit.id's for the workstation owners
2549 sub workstation_list {
2550 my( $self, $conn, $authtoken, @orgs ) = @_;
2552 my $e = new_editor(authtoken=>$authtoken);
2553 return $e->event unless $e->checkauth;
2558 unless $e->allowed('REGISTER_WORKSTATION', $o);
2559 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2565 __PACKAGE__->register_method(
2566 method => 'fetch_patron_note',
2567 api_name => 'open-ils.actor.note.retrieve.all',
2570 Returns a list of notes for a given user
2571 Requestor must have VIEW_USER permission if pub==false and
2572 @param authtoken The login session key
2573 @param args Hash of params including
2574 patronid : the patron's id
2575 pub : true if retrieving only public notes
2579 sub fetch_patron_note {
2580 my( $self, $conn, $authtoken, $args ) = @_;
2581 my $patronid = $$args{patronid};
2583 my($reqr, $evt) = $U->checkses($authtoken);
2584 return $evt if $evt;
2587 ($patron, $evt) = $U->fetch_user($patronid);
2588 return $evt if $evt;
2591 if( $patronid ne $reqr->id ) {
2592 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2593 return $evt if $evt;
2595 return $U->cstorereq(
2596 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2597 { usr => $patronid, pub => 't' } );
2600 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2601 return $evt if $evt;
2603 return $U->cstorereq(
2604 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2607 __PACKAGE__->register_method(
2608 method => 'create_user_note',
2609 api_name => 'open-ils.actor.note.create',
2611 Creates a new note for the given user
2612 @param authtoken The login session key
2613 @param note The note object
2616 sub create_user_note {
2617 my( $self, $conn, $authtoken, $note ) = @_;
2618 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2619 return $e->die_event unless $e->checkauth;
2621 my $user = $e->retrieve_actor_user($note->usr)
2622 or return $e->die_event;
2624 return $e->die_event unless
2625 $e->allowed('UPDATE_USER',$user->home_ou);
2627 $note->creator($e->requestor->id);
2628 $e->create_actor_usr_note($note) or return $e->die_event;
2634 __PACKAGE__->register_method(
2635 method => 'delete_user_note',
2636 api_name => 'open-ils.actor.note.delete',
2638 Deletes a note for the given user
2639 @param authtoken The login session key
2640 @param noteid The note id
2643 sub delete_user_note {
2644 my( $self, $conn, $authtoken, $noteid ) = @_;
2646 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2647 return $e->die_event unless $e->checkauth;
2648 my $note = $e->retrieve_actor_usr_note($noteid)
2649 or return $e->die_event;
2650 my $user = $e->retrieve_actor_user($note->usr)
2651 or return $e->die_event;
2652 return $e->die_event unless
2653 $e->allowed('UPDATE_USER', $user->home_ou);
2655 $e->delete_actor_usr_note($note) or return $e->die_event;
2661 __PACKAGE__->register_method(
2662 method => 'update_user_note',
2663 api_name => 'open-ils.actor.note.update',
2665 @param authtoken The login session key
2666 @param note The note
2670 sub update_user_note {
2671 my( $self, $conn, $auth, $note ) = @_;
2672 my $e = new_editor(authtoken=>$auth, xact=>1);
2673 return $e->die_event unless $e->checkauth;
2674 my $patron = $e->retrieve_actor_user($note->usr)
2675 or return $e->die_event;
2676 return $e->die_event unless
2677 $e->allowed('UPDATE_USER', $patron->home_ou);
2678 $e->update_actor_user_note($note)
2679 or return $e->die_event;
2684 __PACKAGE__->register_method(
2685 method => 'fetch_patron_messages',
2686 api_name => 'open-ils.actor.message.retrieve',
2689 Returns a list of notes for a given user, not
2690 including ones marked deleted
2691 @param authtoken The login session key
2692 @param patronid patron ID
2693 @param options hash containing optional limit and offset
2697 sub fetch_patron_messages {
2698 my( $self, $conn, $auth, $patronid, $options ) = @_;
2702 my $e = new_editor(authtoken => $auth);
2703 return $e->die_event unless $e->checkauth;
2705 if ($e->requestor->id ne $patronid) {
2706 return $e->die_event unless $e->allowed('VIEW_USER');
2709 my $select_clause = { usr => $patronid };
2710 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2711 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2712 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2714 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2719 __PACKAGE__->register_method(
2720 method => 'create_closed_date',
2721 api_name => 'open-ils.actor.org_unit.closed_date.create',
2723 Creates a new closing entry for the given org_unit
2724 @param authtoken The login session key
2725 @param note The closed_date object
2728 sub create_closed_date {
2729 my( $self, $conn, $authtoken, $cd ) = @_;
2731 my( $user, $evt ) = $U->checkses($authtoken);
2732 return $evt if $evt;
2734 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2735 return $evt if $evt;
2737 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2739 my $id = $U->storagereq(
2740 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2741 return $U->DB_UPDATE_FAILED($cd) unless $id;
2746 __PACKAGE__->register_method(
2747 method => 'delete_closed_date',
2748 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2750 Deletes a closing entry for the given org_unit
2751 @param authtoken The login session key
2752 @param noteid The close_date id
2755 sub delete_closed_date {
2756 my( $self, $conn, $authtoken, $cd ) = @_;
2758 my( $user, $evt ) = $U->checkses($authtoken);
2759 return $evt if $evt;
2762 ($cd_obj, $evt) = fetch_closed_date($cd);
2763 return $evt if $evt;
2765 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2766 return $evt if $evt;
2768 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2770 my $stat = $U->storagereq(
2771 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2772 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2777 __PACKAGE__->register_method(
2778 method => 'usrname_exists',
2779 api_name => 'open-ils.actor.username.exists',
2781 desc => 'Check if a username is already taken (by an undeleted patron)',
2783 {desc => 'Authentication token', type => 'string'},
2784 {desc => 'Username', type => 'string'}
2787 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2792 sub usrname_exists {
2793 my( $self, $conn, $auth, $usrname ) = @_;
2794 my $e = new_editor(authtoken=>$auth);
2795 return $e->event unless $e->checkauth;
2796 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2797 return $$a[0] if $a and @$a;
2801 __PACKAGE__->register_method(
2802 method => 'barcode_exists',
2803 api_name => 'open-ils.actor.barcode.exists',
2805 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2808 sub barcode_exists {
2809 my( $self, $conn, $auth, $barcode ) = @_;
2810 my $e = new_editor(authtoken=>$auth);
2811 return $e->event unless $e->checkauth;
2812 my $card = $e->search_actor_card({barcode => $barcode});
2818 #return undef unless @$card;
2819 #return $card->[0]->usr;
2823 __PACKAGE__->register_method(
2824 method => 'retrieve_net_levels',
2825 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2828 sub retrieve_net_levels {
2829 my( $self, $conn, $auth ) = @_;
2830 my $e = new_editor(authtoken=>$auth);
2831 return $e->event unless $e->checkauth;
2832 return $e->retrieve_all_config_net_access_level();
2835 # Retain the old typo API name just in case
2836 __PACKAGE__->register_method(
2837 method => 'fetch_org_by_shortname',
2838 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2840 __PACKAGE__->register_method(
2841 method => 'fetch_org_by_shortname',
2842 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2844 sub fetch_org_by_shortname {
2845 my( $self, $conn, $sname ) = @_;
2846 my $e = new_editor();
2847 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2848 return $e->event unless $org;
2853 __PACKAGE__->register_method(
2854 method => 'session_home_lib',
2855 api_name => 'open-ils.actor.session.home_lib',
2858 sub session_home_lib {
2859 my( $self, $conn, $auth ) = @_;
2860 my $e = new_editor(authtoken=>$auth);
2861 return undef unless $e->checkauth;
2862 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2863 return $org->shortname;
2866 __PACKAGE__->register_method(
2867 method => 'session_safe_token',
2868 api_name => 'open-ils.actor.session.safe_token',
2870 Returns a hashed session ID that is safe for export to the world.
2871 This safe token will expire after 1 hour of non-use.
2872 @param auth Active authentication token
2876 sub session_safe_token {
2877 my( $self, $conn, $auth ) = @_;
2878 my $e = new_editor(authtoken=>$auth);
2879 return undef unless $e->checkauth;
2881 my $safe_token = md5_hex($auth);
2883 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2885 # add more user fields as needed
2887 "safe-token-user-$safe_token", {
2888 id => $e->requestor->id,
2889 home_ou_shortname => $e->retrieve_actor_org_unit(
2890 $e->requestor->home_ou)->shortname,
2899 __PACKAGE__->register_method(
2900 method => 'safe_token_home_lib',
2901 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2903 Returns the home library shortname from the session
2904 asscociated with a safe token from generated by
2905 open-ils.actor.session.safe_token.
2906 @param safe_token Active safe token
2907 @param who Optional user activity "ewho" value
2911 sub safe_token_home_lib {
2912 my( $self, $conn, $safe_token, $who ) = @_;
2913 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2915 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2916 return unless $blob;
2918 $U->log_user_activity($blob->{id}, $who, 'verify');
2919 return $blob->{home_ou_shortname};
2923 __PACKAGE__->register_method(
2924 method => "update_penalties",
2925 api_name => "open-ils.actor.user.penalties.update"
2928 sub update_penalties {
2929 my($self, $conn, $auth, $user_id) = @_;
2930 my $e = new_editor(authtoken=>$auth, xact => 1);
2931 return $e->die_event unless $e->checkauth;
2932 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2933 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2934 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2935 return $evt if $evt;
2941 __PACKAGE__->register_method(
2942 method => "apply_penalty",
2943 api_name => "open-ils.actor.user.penalty.apply"
2947 my($self, $conn, $auth, $penalty) = @_;
2949 my $e = new_editor(authtoken=>$auth, xact => 1);
2950 return $e->die_event unless $e->checkauth;
2952 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2953 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2955 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2958 (defined $ptype->org_depth) ?
2959 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2962 $penalty->org_unit($ctx_org);
2963 $penalty->staff($e->requestor->id);
2964 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2967 return $penalty->id;
2970 __PACKAGE__->register_method(
2971 method => "remove_penalty",
2972 api_name => "open-ils.actor.user.penalty.remove"
2975 sub remove_penalty {
2976 my($self, $conn, $auth, $penalty) = @_;
2977 my $e = new_editor(authtoken=>$auth, xact => 1);
2978 return $e->die_event unless $e->checkauth;
2979 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2980 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2982 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2987 __PACKAGE__->register_method(
2988 method => "update_penalty_note",
2989 api_name => "open-ils.actor.user.penalty.note.update"
2992 sub update_penalty_note {
2993 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2994 my $e = new_editor(authtoken=>$auth, xact => 1);
2995 return $e->die_event unless $e->checkauth;
2996 for my $penalty_id (@$penalty_ids) {
2997 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2998 if (! $penalty ) { return $e->die_event; }
2999 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
3000 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3002 $penalty->note( $note ); $penalty->ischanged( 1 );
3004 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
3010 __PACKAGE__->register_method(
3011 method => "ranged_penalty_thresholds",
3012 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
3016 sub ranged_penalty_thresholds {
3017 my($self, $conn, $auth, $context_org) = @_;
3018 my $e = new_editor(authtoken=>$auth);
3019 return $e->event unless $e->checkauth;
3020 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3021 my $list = $e->search_permission_grp_penalty_threshold([
3022 {org_unit => $U->get_org_ancestors($context_org)},
3023 {order_by => {pgpt => 'id'}}
3025 $conn->respond($_) for @$list;
3031 __PACKAGE__->register_method(
3032 method => "user_retrieve_fleshed_by_id",
3034 api_name => "open-ils.actor.user.fleshed.retrieve",
3037 sub user_retrieve_fleshed_by_id {
3038 my( $self, $client, $auth, $user_id, $fields ) = @_;
3039 my $e = new_editor(authtoken => $auth);
3040 return $e->event unless $e->checkauth;
3042 if( $e->requestor->id != $user_id ) {
3043 return $e->event unless $e->allowed('VIEW_USER');
3050 "standing_penalties",
3056 return new_flesh_user($user_id, $fields, $e);
3060 sub new_flesh_user {
3063 my $fields = shift || [];
3066 my $fetch_penalties = 0;
3067 if(grep {$_ eq 'standing_penalties'} @$fields) {
3068 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3069 $fetch_penalties = 1;
3072 my $fetch_usr_act = 0;
3073 if(grep {$_ eq 'usr_activity'} @$fields) {
3074 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3078 my $user = $e->retrieve_actor_user(
3083 "flesh_fields" => { "au" => $fields }
3086 ) or return $e->die_event;
3089 if( grep { $_ eq 'addresses' } @$fields ) {
3091 $user->addresses([]) unless @{$user->addresses};
3092 # don't expose "replaced" addresses by default
3093 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3095 if( ref $user->billing_address ) {
3096 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3097 push( @{$user->addresses}, $user->billing_address );
3101 if( ref $user->mailing_address ) {
3102 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3103 push( @{$user->addresses}, $user->mailing_address );
3108 if($fetch_penalties) {
3109 # grab the user penalties ranged for this location
3110 $user->standing_penalties(
3111 $e->search_actor_user_standing_penalty([
3114 {stop_date => undef},
3115 {stop_date => {'>' => 'now'}}
3117 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3120 flesh_fields => {ausp => ['standing_penalty']}
3126 # retrieve the most recent usr_activity entry
3127 if ($fetch_usr_act) {
3129 # max number to return for simple patron fleshing
3130 my $limit = $U->ou_ancestor_setting_value(
3131 $e->requestor->ws_ou,
3132 'circ.patron.usr_activity_retrieve.max');
3136 flesh_fields => {auact => ['etype']},
3137 order_by => {auact => 'event_time DESC'},
3140 # 0 == none, <0 == return all
3141 $limit = 1 unless defined $limit;
3142 $opts->{limit} = $limit if $limit > 0;
3144 $user->usr_activity(
3146 [] : # skip the DB call
3147 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3152 $user->clear_passwd();
3159 __PACKAGE__->register_method(
3160 method => "user_retrieve_parts",
3161 api_name => "open-ils.actor.user.retrieve.parts",
3164 sub user_retrieve_parts {
3165 my( $self, $client, $auth, $user_id, $fields ) = @_;
3166 my $e = new_editor(authtoken => $auth);
3167 return $e->event unless $e->checkauth;
3168 $user_id ||= $e->requestor->id;
3169 if( $e->requestor->id != $user_id ) {
3170 return $e->event unless $e->allowed('VIEW_USER');
3173 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3174 push(@resp, $user->$_()) for(@$fields);
3180 __PACKAGE__->register_method(
3181 method => 'user_opt_in_enabled',
3182 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3183 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3186 sub user_opt_in_enabled {
3187 my($self, $conn) = @_;
3188 my $sc = OpenSRF::Utils::SettingsClient->new;
3189 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3194 __PACKAGE__->register_method(
3195 method => 'user_opt_in_at_org',
3196 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3198 @param $auth The auth token
3199 @param user_id The ID of the user to test
3200 @return 1 if the user has opted in at the specified org,
3201 event on error, and 0 otherwise. /
3203 sub user_opt_in_at_org {
3204 my($self, $conn, $auth, $user_id) = @_;
3206 # see if we even need to enforce the opt-in value
3207 return 1 unless user_opt_in_enabled($self);
3209 my $e = new_editor(authtoken => $auth);
3210 return $e->event unless $e->checkauth;
3212 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3213 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3215 my $ws_org = $e->requestor->ws_ou;
3216 # user is automatically opted-in if they are from the local org
3217 return 1 if $user->home_ou eq $ws_org;
3219 # get the boundary setting
3220 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3222 # auto opt in if user falls within the opt boundary
3223 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3225 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3227 my $vals = $e->search_actor_usr_org_unit_opt_in(
3228 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3234 __PACKAGE__->register_method(
3235 method => 'create_user_opt_in_at_org',
3236 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3238 @param $auth The auth token
3239 @param user_id The ID of the user to test
3240 @return The ID of the newly created object, event on error./
3243 sub create_user_opt_in_at_org {
3244 my($self, $conn, $auth, $user_id, $org_id) = @_;
3246 my $e = new_editor(authtoken => $auth, xact=>1);
3247 return $e->die_event unless $e->checkauth;
3249 # if a specific org unit wasn't passed in, get one based on the defaults;
3251 my $wsou = $e->requestor->ws_ou;
3252 # get the default opt depth
3253 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3254 # get the org unit at that depth
3255 my $org = $e->json_query({
3256 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3257 $org_id = $org->{id};
3260 # fall back to the workstation OU, the pre-opt-in-boundary way
3261 $org_id = $e->requestor->ws_ou;
3264 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3265 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3267 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3269 $opt_in->org_unit($org_id);
3270 $opt_in->usr($user_id);
3271 $opt_in->staff($e->requestor->id);
3272 $opt_in->opt_in_ts('now');
3273 $opt_in->opt_in_ws($e->requestor->wsid);
3275 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3276 or return $e->die_event;
3284 __PACKAGE__->register_method (
3285 method => 'retrieve_org_hours',
3286 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3288 Returns the hours of operation for a specified org unit
3289 @param authtoken The login session key
3290 @param org_id The org_unit ID
3294 sub retrieve_org_hours {
3295 my($self, $conn, $auth, $org_id) = @_;
3296 my $e = new_editor(authtoken => $auth);
3297 return $e->die_event unless $e->checkauth;
3298 $org_id ||= $e->requestor->ws_ou;
3299 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3303 __PACKAGE__->register_method (
3304 method => 'verify_user_password',
3305 api_name => 'open-ils.actor.verify_user_password',
3307 Given a barcode or username and the MD5 encoded password,
3308 returns 1 if the password is correct. Returns 0 otherwise.
3312 sub verify_user_password {
3313 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3314 my $e = new_editor(authtoken => $auth);
3315 return $e->die_event unless $e->checkauth;
3317 my $user_by_barcode;
3318 my $user_by_username;
3320 my $card = $e->search_actor_card([
3321 {barcode => $barcode},
3322 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3323 $user_by_barcode = $card->usr;
3324 $user = $user_by_barcode;
3327 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3328 $user = $user_by_username;
3330 return 0 if (!$user);
3331 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3332 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3333 return 1 if $user->passwd eq $password;
3337 __PACKAGE__->register_method (
3338 method => 'retrieve_usr_id_via_barcode_or_usrname',
3339 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3341 Given a barcode or username returns the id for the user or
3346 sub retrieve_usr_id_via_barcode_or_usrname {
3347 my($self, $conn, $auth, $barcode, $username) = @_;
3348 my $e = new_editor(authtoken => $auth);
3349 return $e->die_event unless $e->checkauth;
3350 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3352 my $user_by_barcode;
3353 my $user_by_username;
3354 $logger->info("$id_as_barcode is the ID as BARCODE");
3356 my $card = $e->search_actor_card([
3357 {barcode => $barcode},
3358 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3359 if ($id_as_barcode =~ /^t/i) {
3361 $user = $e->retrieve_actor_user($barcode);
3362 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3364 $user_by_barcode = $card->usr;
3365 $user = $user_by_barcode;
3368 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3369 $user_by_barcode = $card->usr;
3370 $user = $user_by_barcode;
3375 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3377 $user = $user_by_username;
3379 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3380 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3381 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3386 __PACKAGE__->register_method (
3387 method => 'merge_users',
3388 api_name => 'open-ils.actor.user.merge',
3391 Given a list of source users and destination user, transfer all data from the source
3392 to the dest user and delete the source user. All user related data is
3393 transferred, including circulations, holds, bookbags, etc.
3399 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3400 my $e = new_editor(xact => 1, authtoken => $auth);
3401 return $e->die_event unless $e->checkauth;
3403 # disallow the merge if any subordinate accounts are in collections
3404 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3405 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3407 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3408 my $del_addrs = ($U->ou_ancestor_setting_value(
3409 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3410 my $del_cards = ($U->ou_ancestor_setting_value(
3411 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3412 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3413 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3415 for my $src_id (@$user_ids) {
3416 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3418 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3419 if($src_user->home_ou ne $master_user->home_ou) {
3420 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3423 return $e->die_event unless
3424 $e->json_query({from => [
3439 __PACKAGE__->register_method (
3440 method => 'approve_user_address',
3441 api_name => 'open-ils.actor.user.pending_address.approve',
3448 sub approve_user_address {
3449 my($self, $conn, $auth, $addr) = @_;
3450 my $e = new_editor(xact => 1, authtoken => $auth);
3451 return $e->die_event unless $e->checkauth;
3453 # if the caller passes an address object, assume they want to
3454 # update it first before approving it
3455 $e->update_actor_user_address($addr) or return $e->die_event;
3457 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3459 my $user = $e->retrieve_actor_user($addr->usr);
3460 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3461 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3462 or return $e->die_event;
3464 return [values %$result]->[0];
3468 __PACKAGE__->register_method (
3469 method => 'retrieve_friends',
3470 api_name => 'open-ils.actor.friends.retrieve',
3473 returns { confirmed: [], pending_out: [], pending_in: []}
3474 pending_out are users I'm requesting friendship with
3475 pending_in are users requesting friendship with me
3480 sub retrieve_friends {
3481 my($self, $conn, $auth, $user_id, $options) = @_;
3482 my $e = new_editor(authtoken => $auth);
3483 return $e->event unless $e->checkauth;
3484 $user_id ||= $e->requestor->id;
3486 if($user_id != $e->requestor->id) {
3487 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3488 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3491 return OpenILS::Application::Actor::Friends->retrieve_friends(
3492 $e, $user_id, $options);
3497 __PACKAGE__->register_method (
3498 method => 'apply_friend_perms',
3499 api_name => 'open-ils.actor.friends.perms.apply',
3505 sub apply_friend_perms {
3506 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3507 my $e = new_editor(authtoken => $auth, xact => 1);
3508 return $e->die_event unless $e->checkauth;
3510 if($user_id != $e->requestor->id) {
3511 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3512 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3515 for my $perm (@perms) {
3517 OpenILS::Application::Actor::Friends->apply_friend_perm(
3518 $e, $user_id, $delegate_id, $perm);
3519 return $evt if $evt;
3527 __PACKAGE__->register_method (
3528 method => 'update_user_pending_address',
3529 api_name => 'open-ils.actor.user.address.pending.cud'
3532 sub update_user_pending_address {
3533 my($self, $conn, $auth, $addr) = @_;
3534 my $e = new_editor(authtoken => $auth, xact => 1);
3535 return $e->die_event unless $e->checkauth;
3537 if($addr->usr != $e->requestor->id) {
3538 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3539 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3543 $e->create_actor_user_address($addr) or return $e->die_event;
3544 } elsif($addr->isdeleted) {
3545 $e->delete_actor_user_address($addr) or return $e->die_event;
3547 $e->update_actor_user_address($addr) or return $e->die_event;
3555 __PACKAGE__->register_method (
3556 method => 'user_events',
3557 api_name => 'open-ils.actor.user.events.circ',
3560 __PACKAGE__->register_method (
3561 method => 'user_events',
3562 api_name => 'open-ils.actor.user.events.ahr',
3567 my($self, $conn, $auth, $user_id, $filters) = @_;
3568 my $e = new_editor(authtoken => $auth);
3569 return $e->event unless $e->checkauth;
3571 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3572 my $user_field = 'usr';
3575 $filters->{target} = {
3576 select => { $obj_type => ['id'] },
3578 where => {usr => $user_id}
3581 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3582 if($e->requestor->id != $user_id) {
3583 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3586 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3587 my $req = $ses->request('open-ils.trigger.events_by_target',
3588 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3590 while(my $resp = $req->recv) {
3591 my $val = $resp->content;
3592 my $tgt = $val->target;
3594 if($obj_type eq 'circ') {
3595 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3597 } elsif($obj_type eq 'ahr') {
3598 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3599 if $tgt->current_copy;
3602 $conn->respond($val) if $val;
3608 __PACKAGE__->register_method (
3609 method => 'copy_events',
3610 api_name => 'open-ils.actor.copy.events.circ',
3613 __PACKAGE__->register_method (
3614 method => 'copy_events',
3615 api_name => 'open-ils.actor.copy.events.ahr',
3620 my($self, $conn, $auth, $copy_id, $filters) = @_;
3621 my $e = new_editor(authtoken => $auth);
3622 return $e->event unless $e->checkauth;
3624 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3626 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3628 my $copy_field = 'target_copy';
3629 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3632 $filters->{target} = {
3633 select => { $obj_type => ['id'] },
3635 where => {$copy_field => $copy_id}
3639 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3640 my $req = $ses->request('open-ils.trigger.events_by_target',
3641 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3643 while(my $resp = $req->recv) {
3644 my $val = $resp->content;
3645 my $tgt = $val->target;
3647 my $user = $e->retrieve_actor_user($tgt->usr);
3648 if($e->requestor->id != $user->id) {
3649 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3652 $tgt->$copy_field($copy);
3655 $conn->respond($val) if $val;
3664 __PACKAGE__->register_method (
3665 method => 'update_events',
3666 api_name => 'open-ils.actor.user.event.cancel.batch',
3669 __PACKAGE__->register_method (
3670 method => 'update_events',
3671 api_name => 'open-ils.actor.user.event.reset.batch',
3676 my($self, $conn, $auth, $event_ids) = @_;
3677 my $e = new_editor(xact => 1, authtoken => $auth);
3678 return $e->die_event unless $e->checkauth;
3681 for my $id (@$event_ids) {
3683 # do a little dance to determine what user we are ultimately affecting
3684 my $event = $e->retrieve_action_trigger_event([
3687 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3689 ]) or return $e->die_event;
3692 if($event->event_def->hook->core_type eq 'circ') {
3693 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3694 } elsif($event->event_def->hook->core_type eq 'ahr') {
3695 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3700 my $user = $e->retrieve_actor_user($user_id);
3701 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3703 if($self->api_name =~ /cancel/) {
3704 $event->state('invalid');
3705 } elsif($self->api_name =~ /reset/) {
3706 $event->clear_start_time;
3707 $event->clear_update_time;
3708 $event->state('pending');
3711 $e->update_action_trigger_event($event) or return $e->die_event;
3712 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3716 return {complete => 1};
3720 __PACKAGE__->register_method (
3721 method => 'really_delete_user',
3722 api_name => 'open-ils.actor.user.delete.override',
3723 signature => q/@see open-ils.actor.user.delete/
3726 __PACKAGE__->register_method (
3727 method => 'really_delete_user',
3728 api_name => 'open-ils.actor.user.delete',
3730 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3731 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3732 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3733 dest_usr_id is only required when deleting a user that performs staff functions.
3737 sub really_delete_user {
3738 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3739 my $e = new_editor(authtoken => $auth, xact => 1);
3740 return $e->die_event unless $e->checkauth;
3741 $oargs = { all => 1 } unless defined $oargs;
3743 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3744 my $open_bills = $e->json_query({
3745 select => { mbts => ['id'] },
3748 xact_finish => { '=' => undef },
3749 usr => { '=' => $user_id },
3751 }) or return $e->die_event;
3753 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3755 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3757 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3758 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3759 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3761 # No deleting yourself - UI is supposed to stop you first, though.
3762 return $e->die_event unless $e->requestor->id != $user->id;
3763 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3764 # Check if you are allowed to mess with this patron permission group at all
3765 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3766 my $evt = group_perm_failed($session, $e->requestor, $user);
3767 return $e->die_event($evt) if $evt;
3768 my $stat = $e->json_query(
3769 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3770 or return $e->die_event;
3776 __PACKAGE__->register_method (
3777 method => 'user_payments',
3778 api_name => 'open-ils.actor.user.payments.retrieve',
3781 Returns all payments for a given user. Default order is newest payments first.
3782 @param auth Authentication token
3783 @param user_id The user ID
3784 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3789 my($self, $conn, $auth, $user_id, $filters) = @_;
3792 my $e = new_editor(authtoken => $auth);
3793 return $e->die_event unless $e->checkauth;
3795 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3796 return $e->event unless
3797 $e->requestor->id == $user_id or
3798 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3800 # Find all payments for all transactions for user $user_id
3802 select => {mp => ['id']},
3807 select => {mbt => ['id']},
3809 where => {usr => $user_id}
3814 { # by default, order newest payments first
3816 field => 'payment_ts',
3819 # secondary sort in ID as a tie-breaker, since payments created
3820 # within the same transaction will have identical payment_ts's
3827 for (qw/order_by limit offset/) {
3828 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3831 if(defined $filters->{where}) {
3832 foreach (keys %{$filters->{where}}) {
3833 # don't allow the caller to expand the result set to other users
3834 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3838 my $payment_ids = $e->json_query($query);
3839 for my $pid (@$payment_ids) {
3840 my $pay = $e->retrieve_money_payment([
3845 mbt => ['summary', 'circulation', 'grocery'],
3846 circ => ['target_copy'],
3847 acp => ['call_number'],
3855 xact_type => $pay->xact->summary->xact_type,
3856 last_billing_type => $pay->xact->summary->last_billing_type,
3859 if($pay->xact->summary->xact_type eq 'circulation') {
3860 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3861 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3864 $pay->xact($pay->xact->id); # de-flesh
3865 $conn->respond($resp);
3873 __PACKAGE__->register_method (
3874 method => 'negative_balance_users',
3875 api_name => 'open-ils.actor.users.negative_balance',
3878 Returns all users that have an overall negative balance
3879 @param auth Authentication token
3880 @param org_id The context org unit as an ID or list of IDs. This will be the home
3881 library of the user. If no org_unit is specified, no org unit filter is applied
3885 sub negative_balance_users {
3886 my($self, $conn, $auth, $org_id) = @_;
3888 my $e = new_editor(authtoken => $auth);
3889 return $e->die_event unless $e->checkauth;
3890 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3894 mous => ['usr', 'balance_owed'],
3897 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3898 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3915 where => {'+mous' => {balance_owed => {'<' => 0}}}
3918 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3920 my $list = $e->json_query($query, {timeout => 600});
3922 for my $data (@$list) {
3924 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3925 balance_owed => $data->{balance_owed},
3926 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3933 __PACKAGE__->register_method(
3934 method => "request_password_reset",
3935 api_name => "open-ils.actor.patron.password_reset.request",
3937 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3938 "method for changing a user's password. The UUID token is distributed via A/T " .
3939 "templates (i.e. email to the user).",
3941 { desc => 'user_id_type', type => 'string' },
3942 { desc => 'user_id', type => 'string' },
3943 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3945 return => {desc => '1 on success, Event on error'}
3948 sub request_password_reset {
3949 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3951 # Check to see if password reset requests are already being throttled:
3952 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3954 my $e = new_editor(xact => 1);
3957 # Get the user, if any, depending on the input value
3958 if ($user_id_type eq 'username') {
3959 $user = $e->search_actor_user({usrname => $user_id})->[0];
3962 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3964 } elsif ($user_id_type eq 'barcode') {
3965 my $card = $e->search_actor_card([
3966 {barcode => $user_id},
3967 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3970 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3975 # If the user doesn't have an email address, we can't help them
3976 if (!$user->email) {
3978 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3981 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3982 if ($email_must_match) {
3983 if ($user->email ne $email) {
3984 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3988 _reset_password_request($conn, $e, $user);
3991 # Once we have the user, we can issue the password reset request
3992 # XXX Add a wrapper method that accepts barcode + email input
3993 sub _reset_password_request {
3994 my ($conn, $e, $user) = @_;
3996 # 1. Get throttle threshold and time-to-live from OU_settings
3997 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3998 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4000 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4002 # 2. Get time of last request and number of active requests (num_active)
4003 my $active_requests = $e->json_query({
4009 transform => 'COUNT'
4012 column => 'request_time',
4018 has_been_reset => { '=' => 'f' },
4019 request_time => { '>' => $threshold_time }
4023 # Guard against no active requests
4024 if ($active_requests->[0]->{'request_time'}) {
4025 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
4026 my $now = DateTime::Format::ISO8601->new();
4028 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4029 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4030 ($last_request->add_duration('1 minute') > $now)) {
4031 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4033 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4037 # TODO Check to see if the user is in a password-reset-restricted group
4039 # Otherwise, go ahead and try to get the user.
4041 # Check the number of active requests for this user
4042 $active_requests = $e->json_query({
4048 transform => 'COUNT'
4053 usr => { '=' => $user->id },
4054 has_been_reset => { '=' => 'f' },
4055 request_time => { '>' => $threshold_time }
4059 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4061 # if less than or equal to per-user threshold, proceed; otherwise, return event
4062 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4063 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4065 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4068 # Create the aupr object and insert into the database
4069 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4070 my $uuid = create_uuid_as_string(UUID_V4);
4071 $reset_request->uuid($uuid);
4072 $reset_request->usr($user->id);
4074 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4077 # Create an event to notify user of the URL to reset their password
4079 # Can we stuff this in the user_data param for trigger autocreate?
4080 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4082 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4083 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4086 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4091 __PACKAGE__->register_method(
4092 method => "commit_password_reset",
4093 api_name => "open-ils.actor.patron.password_reset.commit",
4095 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4096 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4097 "with the supplied password.",
4099 { desc => 'uuid', type => 'string' },
4100 { desc => 'password', type => 'string' },
4102 return => {desc => '1 on success, Event on error'}
4105 sub commit_password_reset {
4106 my($self, $conn, $uuid, $password) = @_;
4108 # Check to see if password reset requests are already being throttled:
4109 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4110 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4111 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4113 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4116 my $e = new_editor(xact => 1);
4118 my $aupr = $e->search_actor_usr_password_reset({
4125 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4127 my $user_id = $aupr->[0]->usr;
4128 my $user = $e->retrieve_actor_user($user_id);
4130 # Ensure we're still within the TTL for the request
4131 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4132 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4133 if ($threshold < DateTime->now(time_zone => 'local')) {
4135 $logger->info("Password reset request needed to be submitted before $threshold");
4136 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4139 # Check complexity of password against OU-defined regex
4140 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4144 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4145 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4146 $is_strong = check_password_strength_custom($password, $pw_regex);
4148 $is_strong = check_password_strength_default($password);
4153 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4156 # All is well; update the password
4157 $user->passwd($password);
4158 $e->update_actor_user($user);
4160 # And flag that this password reset request has been honoured
4161 $aupr->[0]->has_been_reset('t');
4162 $e->update_actor_usr_password_reset($aupr->[0]);
4168 sub check_password_strength_default {
4169 my $password = shift;
4170 # Use the default set of checks
4171 if ( (length($password) < 7) or
4172 ($password !~ m/.*\d+.*/) or
4173 ($password !~ m/.*[A-Za-z]+.*/)
4180 sub check_password_strength_custom {
4181 my ($password, $pw_regex) = @_;
4183 $pw_regex = qr/$pw_regex/;
4184 if ($password !~ /$pw_regex/) {
4192 __PACKAGE__->register_method(
4193 method => "event_def_opt_in_settings",
4194 api_name => "open-ils.actor.event_def.opt_in.settings",
4197 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4199 { desc => 'Authentication token', type => 'string'},
4201 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4206 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4213 sub event_def_opt_in_settings {
4214 my($self, $conn, $auth, $org_id) = @_;
4215 my $e = new_editor(authtoken => $auth);
4216 return $e->event unless $e->checkauth;
4218 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4219 return $e->event unless
4220 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4222 $org_id = $e->requestor->home_ou;
4225 # find all config.user_setting_type's related to event_defs for the requested org unit
4226 my $types = $e->json_query({
4227 select => {cust => ['name']},
4228 from => {atevdef => 'cust'},
4231 owner => $U->get_org_ancestors($org_id), # context org plus parents
4238 $conn->respond($_) for
4239 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4246 __PACKAGE__->register_method(
4247 method => "user_visible_circs",
4248 api_name => "open-ils.actor.history.circ.visible",
4251 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4253 { desc => 'Authentication token', type => 'string'},
4254 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4255 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4258 desc => q/An object with 2 fields: circulation and summary.
4259 circulation is the "circ" object. summary is the related "accs" object/,
4265 __PACKAGE__->register_method(
4266 method => "user_visible_circs",
4267 api_name => "open-ils.actor.history.circ.visible.print",
4270 desc => 'Returns printable output for the set of opt-in visible circulations',
4272 { desc => 'Authentication token', type => 'string'},
4273 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4274 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4277 desc => q/An action_trigger.event object or error event./,
4283 __PACKAGE__->register_method(
4284 method => "user_visible_circs",
4285 api_name => "open-ils.actor.history.circ.visible.email",
4288 desc => 'Emails the set of opt-in visible circulations to the requestor',
4290 { desc => 'Authentication token', type => 'string'},
4291 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4292 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4295 desc => q/undef, or event on error/
4300 __PACKAGE__->register_method(
4301 method => "user_visible_circs",
4302 api_name => "open-ils.actor.history.hold.visible",
4305 desc => 'Returns the set of opt-in visible holds',
4307 { desc => 'Authentication token', type => 'string'},
4308 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4309 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4312 desc => q/An object with 1 field: "hold"/,
4318 __PACKAGE__->register_method(
4319 method => "user_visible_circs",
4320 api_name => "open-ils.actor.history.hold.visible.print",
4323 desc => 'Returns printable output for the set of opt-in visible holds',
4325 { desc => 'Authentication token', type => 'string'},
4326 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4327 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4330 desc => q/An action_trigger.event object or error event./,
4336 __PACKAGE__->register_method(
4337 method => "user_visible_circs",
4338 api_name => "open-ils.actor.history.hold.visible.email",
4341 desc => 'Emails the set of opt-in visible holds to the requestor',
4343 { desc => 'Authentication token', type => 'string'},
4344 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4345 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4348 desc => q/undef, or event on error/
4353 sub user_visible_circs {
4354 my($self, $conn, $auth, $user_id, $options) = @_;
4356 my $is_hold = ($self->api_name =~ /hold/);
4357 my $for_print = ($self->api_name =~ /print/);
4358 my $for_email = ($self->api_name =~ /email/);
4359 my $e = new_editor(authtoken => $auth);
4360 return $e->event unless $e->checkauth;
4362 $user_id ||= $e->requestor->id;
4364 $options->{limit} ||= 50;
4365 $options->{offset} ||= 0;
4367 if($user_id != $e->requestor->id) {
4368 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4369 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4370 return $e->event unless $e->allowed($perm, $user->home_ou);
4373 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4375 my $data = $e->json_query({
4376 from => [$db_func, $user_id],
4377 limit => $$options{limit},
4378 offset => $$options{offset}
4380 # TODO: I only want IDs. code below didn't get me there
4381 # {"select":{"au":[{"column":"id", "result_field":"id",
4382 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4387 return undef unless @$data;
4391 # collect the batch of objects
4395 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4396 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4400 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4401 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4404 } elsif ($for_email) {
4406 $conn->respond_complete(1) if $for_email; # no sense in waiting
4414 my $hold = $e->retrieve_action_hold_request($id);
4415 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4416 # events will be fired from action_trigger_runner
4420 my $circ = $e->retrieve_action_circulation($id);
4421 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4422 # events will be fired from action_trigger_runner
4426 } else { # just give me the data please
4434 my $hold = $e->retrieve_action_hold_request($id);
4435 $conn->respond({hold => $hold});
4439 my $circ = $e->retrieve_action_circulation($id);
4442 summary => $U->create_circ_chain_summary($e, $id)
4451 __PACKAGE__->register_method(
4452 method => "user_saved_search_cud",
4453 api_name => "open-ils.actor.user.saved_search.cud",
4456 desc => 'Create/Update/Delete Access to user saved searches',
4458 { desc => 'Authentication token', type => 'string' },
4459 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4462 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4468 __PACKAGE__->register_method(
4469 method => "user_saved_search_cud",
4470 api_name => "open-ils.actor.user.saved_search.retrieve",
4473 desc => 'Retrieve a saved search object',
4475 { desc => 'Authentication token', type => 'string' },
4476 { desc => 'Saved Search ID', type => 'number' }
4479 desc => q/The saved search object, Event on error/,
4485 sub user_saved_search_cud {
4486 my( $self, $client, $auth, $search ) = @_;
4487 my $e = new_editor( authtoken=>$auth );
4488 return $e->die_event unless $e->checkauth;
4490 my $o_search; # prior version of the object, if any
4491 my $res; # to be returned
4493 # branch on the operation type
4495 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4497 # Get the old version, to check ownership
4498 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4499 or return $e->die_event;
4501 # You can't read somebody else's search
4502 return OpenILS::Event->new('BAD_PARAMS')
4503 unless $o_search->owner == $e->requestor->id;
4509 $e->xact_begin; # start an editor transaction
4511 if( $search->isnew ) { # Create
4513 # You can't create a search for somebody else
4514 return OpenILS::Event->new('BAD_PARAMS')
4515 unless $search->owner == $e->requestor->id;
4517 $e->create_actor_usr_saved_search( $search )
4518 or return $e->die_event;
4522 } elsif( $search->ischanged ) { # Update
4524 # You can't change ownership of a search
4525 return OpenILS::Event->new('BAD_PARAMS')
4526 unless $search->owner == $e->requestor->id;
4528 # Get the old version, to check ownership
4529 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4530 or return $e->die_event;
4532 # You can't update somebody else's search
4533 return OpenILS::Event->new('BAD_PARAMS')
4534 unless $o_search->owner == $e->requestor->id;
4537 $e->update_actor_usr_saved_search( $search )
4538 or return $e->die_event;
4542 } elsif( $search->isdeleted ) { # Delete
4544 # Get the old version, to check ownership
4545 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4546 or return $e->die_event;
4548 # You can't delete somebody else's search
4549 return OpenILS::Event->new('BAD_PARAMS')
4550 unless $o_search->owner == $e->requestor->id;
4553 $e->delete_actor_usr_saved_search( $o_search )
4554 or return $e->die_event;
4565 __PACKAGE__->register_method(
4566 method => "get_barcodes",
4567 api_name => "open-ils.actor.get_barcodes"
4571 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4572 my $e = new_editor(authtoken => $auth);
4573 return $e->event unless $e->checkauth;
4574 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4576 my $db_result = $e->json_query(
4578 'evergreen.get_barcodes',
4579 $org_id, $context, $barcode,
4583 if($context =~ /actor/) {
4584 my $filter_result = ();
4586 foreach my $result (@$db_result) {
4587 if($result->{type} eq 'actor') {
4588 if($e->requestor->id != $result->{id}) {
4589 $patron = $e->retrieve_actor_user($result->{id});
4591 push(@$filter_result, $e->event);
4594 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4595 push(@$filter_result, $result);
4598 push(@$filter_result, $e->event);
4602 push(@$filter_result, $result);
4606 push(@$filter_result, $result);
4609 return $filter_result;
4615 __PACKAGE__->register_method(
4616 method => 'address_alert_test',
4617 api_name => 'open-ils.actor.address_alert.test',
4619 desc => "Tests a set of address fields to determine if they match with an address_alert",
4621 {desc => 'Authentication token', type => 'string'},
4622 {desc => 'Org Unit', type => 'number'},
4623 {desc => 'Fields', type => 'hash'},
4625 return => {desc => 'List of matching address_alerts'}
4629 sub address_alert_test {
4630 my ($self, $client, $auth, $org_unit, $fields) = @_;
4631 return [] unless $fields and grep {$_} values %$fields;
4633 my $e = new_editor(authtoken => $auth);
4634 return $e->event unless $e->checkauth;
4635 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4636 $org_unit ||= $e->requestor->ws_ou;
4638 my $alerts = $e->json_query({
4640 'actor.address_alert_matches',
4648 $$fields{post_code},
4649 $$fields{mailing_address},
4650 $$fields{billing_address}
4654 # map the json_query hashes to real objects
4656 map {$e->retrieve_actor_address_alert($_)}
4657 (map {$_->{id}} @$alerts)
4661 __PACKAGE__->register_method(
4662 method => "mark_users_contact_invalid",
4663 api_name => "open-ils.actor.invalidate.email",
4665 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",
4667 {desc => "Authentication token", type => "string"},
4668 {desc => "Patron ID", type => "number"},
4669 {desc => "Additional note text (optional)", type => "string"},
4670 {desc => "penalty org unit ID (optional)", type => "number"}
4672 return => {desc => "Event describing success or failure", type => "object"}
4676 __PACKAGE__->register_method(
4677 method => "mark_users_contact_invalid",
4678 api_name => "open-ils.actor.invalidate.day_phone",
4680 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",
4682 {desc => "Authentication token", type => "string"},
4683 {desc => "Patron ID", type => "number"},
4684 {desc => "Additional note text (optional)", type => "string"},
4685 {desc => "penalty org unit ID (optional)", type => "number"}
4687 return => {desc => "Event describing success or failure", type => "object"}
4691 __PACKAGE__->register_method(
4692 method => "mark_users_contact_invalid",
4693 api_name => "open-ils.actor.invalidate.evening_phone",
4695 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",
4697 {desc => "Authentication token", type => "string"},
4698 {desc => "Patron ID", type => "number"},
4699 {desc => "Additional note text (optional)", type => "string"},
4700 {desc => "penalty org unit ID (optional)", type => "number"}
4702 return => {desc => "Event describing success or failure", type => "object"}
4706 __PACKAGE__->register_method(
4707 method => "mark_users_contact_invalid",
4708 api_name => "open-ils.actor.invalidate.other_phone",
4710 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",
4712 {desc => "Authentication token", type => "string"},
4713 {desc => "Patron ID", type => "number"},
4714 {desc => "Additional note text (optional)", type => "string"},
4715 {desc => "penalty org unit ID (optional, default to top of org tree)",
4718 return => {desc => "Event describing success or failure", type => "object"}
4722 sub mark_users_contact_invalid {
4723 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4725 # This method invalidates an email address or a phone_number which
4726 # removes the bad email address or phone number, copying its contents
4727 # to a patron note, and institutes a standing penalty for "bad email"
4728 # or "bad phone number" which is cleared when the user is saved or
4729 # optionally only when the user is saved with an email address or
4730 # phone number (or staff manually delete the penalty).
4732 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4734 my $e = new_editor(authtoken => $auth, xact => 1);
4735 return $e->die_event unless $e->checkauth;
4737 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4738 $e, $contact_type, {usr => $patron_id},
4739 $addl_note, $penalty_ou, $e->requestor->id
4743 # Putting the following method in open-ils.actor is a bad fit, except in that
4744 # it serves an interface that lives under 'actor' in the templates directory,
4745 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4747 __PACKAGE__->register_method(
4748 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4749 method => "get_all_at_reactors_in_use",
4754 { name => 'authtoken', type => 'string' }
4757 desc => 'list of reactor names', type => 'array'
4762 sub get_all_at_reactors_in_use {
4763 my ($self, $conn, $auth) = @_;
4765 my $e = new_editor(authtoken => $auth);
4766 $e->checkauth or return $e->die_event;
4767 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4769 my $reactors = $e->json_query({
4771 atevdef => [{column => "reactor", transform => "distinct"}]
4773 from => {atevdef => {}}
4776 return $e->die_event unless ref $reactors eq "ARRAY";
4779 return [ map { $_->{reactor} } @$reactors ];
4782 __PACKAGE__->register_method(
4783 method => "filter_group_entry_crud",
4784 api_name => "open-ils.actor.filter_group_entry.crud",
4787 Provides CRUD access to filter group entry objects. These are not full accessible
4788 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4789 are not accessible via PCRUD (because they have no fields against which to link perms)
4792 {desc => "Authentication token", type => "string"},
4793 {desc => "Entry ID / Entry Object", type => "number"},
4794 {desc => "Additional note text (optional)", type => "string"},
4795 {desc => "penalty org unit ID (optional, default to top of org tree)",
4799 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4805 sub filter_group_entry_crud {
4806 my ($self, $conn, $auth, $arg) = @_;
4808 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4809 my $e = new_editor(authtoken => $auth, xact => 1);
4810 return $e->die_event unless $e->checkauth;
4816 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4817 or return $e->die_event;
4819 return $e->die_event unless $e->allowed(
4820 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4822 my $query = $arg->query;
4823 $query = $e->create_actor_search_query($query) or return $e->die_event;
4824 $arg->query($query->id);
4825 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4826 $entry->query($query);
4831 } elsif ($arg->ischanged) {
4833 my $entry = $e->retrieve_actor_search_filter_group_entry([
4836 flesh_fields => {asfge => ['grp']}
4838 ]) or return $e->die_event;
4840 return $e->die_event unless $e->allowed(
4841 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4843 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4844 $arg->query($arg->query->id);
4845 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4846 $arg->query($query);
4851 } elsif ($arg->isdeleted) {
4853 my $entry = $e->retrieve_actor_search_filter_group_entry([
4856 flesh_fields => {asfge => ['grp', 'query']}
4858 ]) or return $e->die_event;
4860 return $e->die_event unless $e->allowed(
4861 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4863 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4864 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4877 my $entry = $e->retrieve_actor_search_filter_group_entry([
4880 flesh_fields => {asfge => ['grp', 'query']}
4882 ]) or return $e->die_event;
4884 return $e->die_event unless $e->allowed(
4885 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4886 $entry->grp->owner);
4889 $entry->grp($entry->grp->id); # for consistency