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 my $e = new_editor(authtoken=>$auth);
1352 return $e->event unless $e->checkauth;
1353 return $e->event unless $e->allowed('VIEW_USER');
1355 # depth boundary outside of which patrons must opt-in, default to 0
1356 my $opt_boundary = 0;
1357 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1359 if (not defined $search_ou) {
1360 my $depth = $U->ou_ancestor_setting_value(
1361 $e->requestor->ws_ou,
1362 'circ.patron_edit.duplicate_patron_check_depth'
1365 if (defined $depth) {
1366 $search_ou = $U->org_unit_ancestor_at_depth(
1367 $e->requestor->ws_ou, $depth
1372 my $ids = $U->storagereq(
1373 "open-ils.storage.actor.user.crazy_search", $search_hash,
1374 $search_limit, $search_sort, $include_inactive,
1375 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1377 return $ids unless $self->api_name =~ /fleshed/;
1379 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1385 __PACKAGE__->register_method(
1386 method => "update_passwd",
1387 api_name => "open-ils.actor.user.password.update",
1389 desc => "Update the operator's password",
1391 { desc => 'Authentication token', type => 'string' },
1392 { desc => 'New password', type => 'string' },
1393 { desc => 'Current password', type => 'string' }
1395 return => {desc => '1 on success, Event on error or incorrect current password'}
1399 __PACKAGE__->register_method(
1400 method => "update_passwd",
1401 api_name => "open-ils.actor.user.username.update",
1403 desc => "Update the operator's username",
1405 { desc => 'Authentication token', type => 'string' },
1406 { desc => 'New username', type => 'string' },
1407 { desc => 'Current password', type => 'string' }
1409 return => {desc => '1 on success, Event on error or incorrect current password'}
1413 __PACKAGE__->register_method(
1414 method => "update_passwd",
1415 api_name => "open-ils.actor.user.email.update",
1417 desc => "Update the operator's email address",
1419 { desc => 'Authentication token', type => 'string' },
1420 { desc => 'New email address', type => 'string' },
1421 { desc => 'Current password', type => 'string' }
1423 return => {desc => '1 on success, Event on error or incorrect current password'}
1428 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1429 my $e = new_editor(xact=>1, authtoken=>$auth);
1430 return $e->die_event unless $e->checkauth;
1432 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1433 or return $e->die_event;
1434 my $api = $self->api_name;
1436 # make sure the original password matches the in-database password
1437 if (md5_hex($orig_pw) ne $db_user->passwd) {
1439 return new OpenILS::Event('INCORRECT_PASSWORD');
1442 if( $api =~ /password/o ) {
1444 $db_user->passwd($new_val);
1448 # if we don't clear the password, the user will be updated with
1449 # a hashed version of the hashed version of their password
1450 $db_user->clear_passwd;
1452 if( $api =~ /username/o ) {
1454 # make sure no one else has this username
1455 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1458 return new OpenILS::Event('USERNAME_EXISTS');
1460 $db_user->usrname($new_val);
1462 } elsif( $api =~ /email/o ) {
1463 $db_user->email($new_val);
1467 $e->update_actor_user($db_user) or return $e->die_event;
1470 # update the cached user to pick up these changes
1471 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1477 __PACKAGE__->register_method(
1478 method => "check_user_perms",
1479 api_name => "open-ils.actor.user.perm.check",
1480 notes => <<" NOTES");
1481 Takes a login session, user id, an org id, and an array of perm type strings. For each
1482 perm type, if the user does *not* have the given permission it is added
1483 to a list which is returned from the method. If all permissions
1484 are allowed, an empty list is returned
1485 if the logged in user does not match 'user_id', then the logged in user must
1486 have VIEW_PERMISSION priveleges.
1489 sub check_user_perms {
1490 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1492 my( $staff, $evt ) = $apputils->checkses($login_session);
1493 return $evt if $evt;
1495 if($staff->id ne $user_id) {
1496 if( $evt = $apputils->check_perms(
1497 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1503 for my $perm (@$perm_types) {
1504 if($apputils->check_perms($user_id, $org_id, $perm)) {
1505 push @not_allowed, $perm;
1509 return \@not_allowed
1512 __PACKAGE__->register_method(
1513 method => "check_user_perms2",
1514 api_name => "open-ils.actor.user.perm.check.multi_org",
1516 Checks the permissions on a list of perms and orgs for a user
1517 @param authtoken The login session key
1518 @param user_id The id of the user to check
1519 @param orgs The array of org ids
1520 @param perms The array of permission names
1521 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1522 if the logged in user does not match 'user_id', then the logged in user must
1523 have VIEW_PERMISSION priveleges.
1526 sub check_user_perms2 {
1527 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1529 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1530 $authtoken, $user_id, 'VIEW_PERMISSION' );
1531 return $evt if $evt;
1534 for my $org (@$orgs) {
1535 for my $perm (@$perms) {
1536 if($apputils->check_perms($user_id, $org, $perm)) {
1537 push @not_allowed, [ $org, $perm ];
1542 return \@not_allowed
1546 __PACKAGE__->register_method(
1547 method => 'check_user_perms3',
1548 api_name => 'open-ils.actor.user.perm.highest_org',
1550 Returns the highest org unit id at which a user has a given permission
1551 If the requestor does not match the target user, the requestor must have
1552 'VIEW_PERMISSION' rights at the home org unit of the target user
1553 @param authtoken The login session key
1554 @param userid The id of the user in question
1555 @param perm The permission to check
1556 @return The org unit highest in the org tree within which the user has
1557 the requested permission
1560 sub check_user_perms3 {
1561 my($self, $client, $authtoken, $user_id, $perm) = @_;
1562 my $e = new_editor(authtoken=>$authtoken);
1563 return $e->event unless $e->checkauth;
1565 my $tree = $U->get_org_tree();
1567 unless($e->requestor->id == $user_id) {
1568 my $user = $e->retrieve_actor_user($user_id)
1569 or return $e->event;
1570 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1571 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1574 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1577 __PACKAGE__->register_method(
1578 method => 'user_has_work_perm_at',
1579 api_name => 'open-ils.actor.user.has_work_perm_at',
1583 Returns a set of org unit IDs which represent the highest orgs in
1584 the org tree where the user has the requested permission. The
1585 purpose of this method is to return the smallest set of org units
1586 which represent the full expanse of the user's ability to perform
1587 the requested action. The user whose perms this method should
1588 check is implied by the authtoken. /,
1590 {desc => 'authtoken', type => 'string'},
1591 {desc => 'permission name', type => 'string'},
1592 {desc => q/user id, optional. If present, check perms for
1593 this user instead of the logged in user/, type => 'number'},
1595 return => {desc => 'An array of org IDs'}
1599 sub user_has_work_perm_at {
1600 my($self, $conn, $auth, $perm, $user_id) = @_;
1601 my $e = new_editor(authtoken=>$auth);
1602 return $e->event unless $e->checkauth;
1603 if(defined $user_id) {
1604 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1605 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1607 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1610 __PACKAGE__->register_method(
1611 method => 'user_has_work_perm_at_batch',
1612 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1616 sub user_has_work_perm_at_batch {
1617 my($self, $conn, $auth, $perms, $user_id) = @_;
1618 my $e = new_editor(authtoken=>$auth);
1619 return $e->event unless $e->checkauth;
1620 if(defined $user_id) {
1621 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1622 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1625 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1631 __PACKAGE__->register_method(
1632 method => 'check_user_perms4',
1633 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1635 Returns the highest org unit id at which a user has a given permission
1636 If the requestor does not match the target user, the requestor must have
1637 'VIEW_PERMISSION' rights at the home org unit of the target user
1638 @param authtoken The login session key
1639 @param userid The id of the user in question
1640 @param perms An array of perm names to check
1641 @return An array of orgId's representing the org unit
1642 highest in the org tree within which the user has the requested permission
1643 The arrah of orgId's has matches the order of the perms array
1646 sub check_user_perms4 {
1647 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1649 my( $staff, $target, $org, $evt );
1651 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1652 $authtoken, $userid, 'VIEW_PERMISSION' );
1653 return $evt if $evt;
1656 return [] unless ref($perms);
1657 my $tree = $U->get_org_tree();
1659 for my $p (@$perms) {
1660 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1666 __PACKAGE__->register_method(
1667 method => "user_fines_summary",
1668 api_name => "open-ils.actor.user.fines.summary",
1671 desc => 'Returns a short summary of the users total open fines, ' .
1672 'excluding voided fines Params are login_session, user_id' ,
1674 {desc => 'Authentication token', type => 'string'},
1675 {desc => 'User ID', type => 'string'} # number?
1678 desc => "a 'mous' object, event on error",
1683 sub user_fines_summary {
1684 my( $self, $client, $auth, $user_id ) = @_;
1686 my $e = new_editor(authtoken=>$auth);
1687 return $e->event unless $e->checkauth;
1689 if( $user_id ne $e->requestor->id ) {
1690 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1691 return $e->event unless
1692 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1695 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1699 __PACKAGE__->register_method(
1700 method => "user_opac_vitals",
1701 api_name => "open-ils.actor.user.opac.vital_stats",
1705 desc => 'Returns a short summary of the users vital stats, including ' .
1706 'identification information, accumulated balance, number of holds, ' .
1707 'and current open circulation stats' ,
1709 {desc => 'Authentication token', type => 'string'},
1710 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1713 desc => "An object with four properties: user, fines, checkouts and holds."
1718 sub user_opac_vitals {
1719 my( $self, $client, $auth, $user_id ) = @_;
1721 my $e = new_editor(authtoken=>$auth);
1722 return $e->event unless $e->checkauth;
1724 $user_id ||= $e->requestor->id;
1726 my $user = $e->retrieve_actor_user( $user_id );
1729 ->method_lookup('open-ils.actor.user.fines.summary')
1730 ->run($auth => $user_id);
1731 return $fines if (defined($U->event_code($fines)));
1734 $fines = new Fieldmapper::money::open_user_summary ();
1735 $fines->balance_owed(0.00);
1736 $fines->total_owed(0.00);
1737 $fines->total_paid(0.00);
1738 $fines->usr($user_id);
1742 ->method_lookup('open-ils.actor.user.hold_requests.count')
1743 ->run($auth => $user_id);
1744 return $holds if (defined($U->event_code($holds)));
1747 ->method_lookup('open-ils.actor.user.checked_out.count')
1748 ->run($auth => $user_id);
1749 return $out if (defined($U->event_code($out)));
1751 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1753 my $unread_msgs = $e->search_actor_usr_message([
1754 {usr => $user_id, read_date => undef, deleted => 'f'},
1760 first_given_name => $user->first_given_name,
1761 second_given_name => $user->second_given_name,
1762 family_name => $user->family_name,
1763 alias => $user->alias,
1764 usrname => $user->usrname
1766 fines => $fines->to_bare_hash,
1769 messages => { unread => scalar(@$unread_msgs) }
1774 ##### a small consolidation of related method registrations
1775 my $common_params = [
1776 { desc => 'Authentication token', type => 'string' },
1777 { desc => 'User ID', type => 'string' },
1778 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1779 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1782 'open-ils.actor.user.transactions' => '',
1783 'open-ils.actor.user.transactions.fleshed' => '',
1784 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1785 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1786 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1787 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1790 foreach (keys %methods) {
1792 method => "user_transactions",
1795 desc => 'For a given user, retrieve a list of '
1796 . (/\.fleshed/ ? 'fleshed ' : '')
1797 . 'transactions' . $methods{$_}
1798 . ' optionally limited to transactions of a given type.',
1799 params => $common_params,
1801 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1802 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1806 $args{authoritative} = 1;
1807 __PACKAGE__->register_method(%args);
1810 # Now for the counts
1812 'open-ils.actor.user.transactions.count' => '',
1813 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1814 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1817 foreach (keys %methods) {
1819 method => "user_transactions",
1822 desc => 'For a given user, retrieve a count of open '
1823 . 'transactions' . $methods{$_}
1824 . ' optionally limited to transactions of a given type.',
1825 params => $common_params,
1826 return => { desc => "Integer count of transactions, or event on error" }
1829 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1830 __PACKAGE__->register_method(%args);
1833 __PACKAGE__->register_method(
1834 method => "user_transactions",
1835 api_name => "open-ils.actor.user.transactions.have_balance.total",
1838 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1839 . ' optionally limited to transactions of a given type.',
1840 params => $common_params,
1841 return => { desc => "Decimal balance value, or event on error" }
1846 sub user_transactions {
1847 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1850 my $e = new_editor(authtoken => $auth);
1851 return $e->event unless $e->checkauth;
1853 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1855 return $e->event unless
1856 $e->requestor->id == $user_id or
1857 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1859 my $api = $self->api_name();
1861 my $filter = ($api =~ /have_balance/o) ?
1862 { 'balance_owed' => { '<>' => 0 } }:
1863 { 'total_owed' => { '>' => 0 } };
1865 my $method = 'open-ils.actor.user.transactions.history.still_open';
1866 $method = "$method.authoritative" if $api =~ /authoritative/;
1867 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1869 if($api =~ /total/o) {
1871 $total += $_->balance_owed for @$trans;
1875 ($api =~ /count/o ) and return scalar @$trans;
1876 ($api !~ /fleshed/o) and return $trans;
1879 for my $t (@$trans) {
1881 if( $t->xact_type ne 'circulation' ) {
1882 push @resp, {transaction => $t};
1886 my $circ_data = flesh_circ($e, $t->id);
1887 push @resp, {transaction => $t, %$circ_data};
1894 __PACKAGE__->register_method(
1895 method => "user_transaction_retrieve",
1896 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1899 notes => "Returns a fleshed transaction record"
1902 __PACKAGE__->register_method(
1903 method => "user_transaction_retrieve",
1904 api_name => "open-ils.actor.user.transaction.retrieve",
1907 notes => "Returns a transaction record"
1910 sub user_transaction_retrieve {
1911 my($self, $client, $auth, $bill_id) = @_;
1913 my $e = new_editor(authtoken => $auth);
1914 return $e->event unless $e->checkauth;
1916 my $trans = $e->retrieve_money_billable_transaction_summary(
1917 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1919 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1921 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1923 return $trans unless $self->api_name =~ /flesh/;
1924 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1926 my $circ_data = flesh_circ($e, $trans->id, 1);
1928 return {transaction => $trans, %$circ_data};
1933 my $circ_id = shift;
1934 my $flesh_copy = shift;
1936 my $circ = $e->retrieve_action_circulation([
1940 circ => ['target_copy'],
1941 acp => ['call_number'],
1948 my $copy = $circ->target_copy;
1950 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1951 $mods = new Fieldmapper::metabib::virtual_record;
1952 $mods->doc_id(OILS_PRECAT_RECORD);
1953 $mods->title($copy->dummy_title);
1954 $mods->author($copy->dummy_author);
1957 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1961 $circ->target_copy($circ->target_copy->id);
1962 $copy->call_number($copy->call_number->id);
1964 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1968 __PACKAGE__->register_method(
1969 method => "hold_request_count",
1970 api_name => "open-ils.actor.user.hold_requests.count",
1974 Returns hold ready vs. total counts.
1975 If a context org unit is provided, a third value
1976 is returned with key 'behind_desk', which reports
1977 how many holds are ready at the pickup library
1978 with the behind_desk flag set to true.
1982 sub hold_request_count {
1983 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
1984 my $e = new_editor(authtoken => $authtoken);
1985 return $e->event unless $e->checkauth;
1987 $user_id = $e->requestor->id unless defined $user_id;
1989 if($e->requestor->id ne $user_id) {
1990 my $user = $e->retrieve_actor_user($user_id);
1991 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1994 my $holds = $e->json_query({
1995 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
1999 fulfillment_time => {"=" => undef },
2000 cancel_time => undef,
2005 $_->{current_shelf_lib} and # avoid undef warnings
2006 $_->{pickup_lib} eq $_->{current_shelf_lib}
2010 total => scalar(@$holds),
2011 ready => scalar(@ready)
2015 # count of holds ready at pickup lib with behind_desk true.
2016 $resp->{behind_desk} = scalar(
2018 $_->{pickup_lib} == $ctx_org and
2019 $U->is_true($_->{behind_desk})
2027 __PACKAGE__->register_method(
2028 method => "checked_out",
2029 api_name => "open-ils.actor.user.checked_out",
2033 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2034 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2035 . "(i.e., outstanding balance or some other pending action on the circ). "
2036 . "The .count method also includes a 'total' field which sums all open circs.",
2038 { desc => 'Authentication Token', type => 'string'},
2039 { desc => 'User ID', type => 'string'},
2042 desc => 'Returns event on error, or an object with ID lists, like: '
2043 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2048 __PACKAGE__->register_method(
2049 method => "checked_out",
2050 api_name => "open-ils.actor.user.checked_out.count",
2053 signature => q/@see open-ils.actor.user.checked_out/
2057 my( $self, $conn, $auth, $userid ) = @_;
2059 my $e = new_editor(authtoken=>$auth);
2060 return $e->event unless $e->checkauth;
2062 if( $userid ne $e->requestor->id ) {
2063 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2064 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2066 # see if there is a friend link allowing circ.view perms
2067 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2068 $e, $userid, $e->requestor->id, 'circ.view');
2069 return $e->event unless $allowed;
2073 my $count = $self->api_name =~ /count/;
2074 return _checked_out( $count, $e, $userid );
2078 my( $iscount, $e, $userid ) = @_;
2084 claims_returned => [],
2087 my $meth = 'retrieve_action_open_circ_';
2095 claims_returned => 0,
2102 my $data = $e->$meth($userid);
2106 $result{$_} += $data->$_() for (keys %result);
2107 $result{total} += $data->$_() for (keys %result);
2109 for my $k (keys %result) {
2110 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2120 __PACKAGE__->register_method(
2121 method => "checked_in_with_fines",
2122 api_name => "open-ils.actor.user.checked_in_with_fines",
2125 signature => q/@see open-ils.actor.user.checked_out/
2128 sub checked_in_with_fines {
2129 my( $self, $conn, $auth, $userid ) = @_;
2131 my $e = new_editor(authtoken=>$auth);
2132 return $e->event unless $e->checkauth;
2134 if( $userid ne $e->requestor->id ) {
2135 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2138 # money is owed on these items and they are checked in
2139 my $open = $e->search_action_circulation(
2142 xact_finish => undef,
2143 checkin_time => { "!=" => undef },
2148 my( @lost, @cr, @lo );
2149 for my $c (@$open) {
2150 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2151 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2152 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2157 claims_returned => \@cr,
2158 long_overdue => \@lo
2164 my ($api, $desc, $auth) = @_;
2165 $desc = $desc ? (" " . $desc) : '';
2166 my $ids = ($api =~ /ids$/) ? 1 : 0;
2169 method => "user_transaction_history",
2170 api_name => "open-ils.actor.user.transactions.$api",
2172 desc => "For a given User ID, returns a list of billable transaction" .
2173 ($ids ? " id" : '') .
2174 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2175 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2177 {desc => 'Authentication token', type => 'string'},
2178 {desc => 'User ID', type => 'number'},
2179 {desc => 'Transaction type (optional)', type => 'number'},
2180 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2183 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2187 $auth and push @sig, (authoritative => 1);
2191 my %auth_hist_methods = (
2193 'history.have_charge' => 'that have an initial charge',
2194 'history.still_open' => 'that are not finished',
2195 'history.have_balance' => 'that have a balance',
2196 'history.have_bill' => 'that have billings',
2197 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2198 'history.have_payment' => 'that have at least 1 payment',
2201 foreach (keys %auth_hist_methods) {
2202 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2203 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2204 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2207 sub user_transaction_history {
2208 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2212 my $e = new_editor(authtoken=>$auth);
2213 return $e->die_event unless $e->checkauth;
2215 if ($e->requestor->id ne $userid) {
2216 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2219 my $api = $self->api_name;
2220 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2222 if(defined($type)) {
2223 $filter->{'xact_type'} = $type;
2226 if($api =~ /have_bill_or_payment/o) {
2228 # transactions that have a non-zero sum across all billings or at least 1 payment
2229 $filter->{'-or'} = {
2230 'balance_owed' => { '<>' => 0 },
2231 'last_payment_ts' => { '<>' => undef }
2234 } elsif($api =~ /have_payment/) {
2236 $filter->{last_payment_ts} ||= {'<>' => undef};
2238 } elsif( $api =~ /have_balance/o) {
2240 # transactions that have a non-zero overall balance
2241 $filter->{'balance_owed'} = { '<>' => 0 };
2243 } elsif( $api =~ /have_charge/o) {
2245 # transactions that have at least 1 billing, regardless of whether it was voided
2246 $filter->{'last_billing_ts'} = { '<>' => undef };
2248 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2250 # transactions that have non-zero sum across all billings. This will exclude
2251 # xacts where all billings have been voided
2252 $filter->{'total_owed'} = { '<>' => 0 };
2255 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2256 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2257 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2259 my $mbts = $e->search_money_billable_transaction_summary(
2260 [ { usr => $userid, @xact_finish, %$filter },
2265 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2266 return $mbts unless $api =~ /fleshed/;
2269 for my $t (@$mbts) {
2271 if( $t->xact_type ne 'circulation' ) {
2272 push @resp, {transaction => $t};
2276 my $circ_data = flesh_circ($e, $t->id);
2277 push @resp, {transaction => $t, %$circ_data};
2285 __PACKAGE__->register_method(
2286 method => "user_perms",
2287 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2289 notes => "Returns a list of permissions"
2293 my( $self, $client, $authtoken, $user ) = @_;
2295 my( $staff, $evt ) = $apputils->checkses($authtoken);
2296 return $evt if $evt;
2298 $user ||= $staff->id;
2300 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2304 return $apputils->simple_scalar_request(
2306 "open-ils.storage.permission.user_perms.atomic",
2310 __PACKAGE__->register_method(
2311 method => "retrieve_perms",
2312 api_name => "open-ils.actor.permissions.retrieve",
2313 notes => "Returns a list of permissions"
2315 sub retrieve_perms {
2316 my( $self, $client ) = @_;
2317 return $apputils->simple_scalar_request(
2319 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2320 { id => { '!=' => undef } }
2324 __PACKAGE__->register_method(
2325 method => "retrieve_groups",
2326 api_name => "open-ils.actor.groups.retrieve",
2327 notes => "Returns a list of user groups"
2329 sub retrieve_groups {
2330 my( $self, $client ) = @_;
2331 return new_editor()->retrieve_all_permission_grp_tree();
2334 __PACKAGE__->register_method(
2335 method => "retrieve_org_address",
2336 api_name => "open-ils.actor.org_unit.address.retrieve",
2337 notes => <<' NOTES');
2338 Returns an org_unit address by ID
2339 @param An org_address ID
2341 sub retrieve_org_address {
2342 my( $self, $client, $id ) = @_;
2343 return $apputils->simple_scalar_request(
2345 "open-ils.cstore.direct.actor.org_address.retrieve",
2350 __PACKAGE__->register_method(
2351 method => "retrieve_groups_tree",
2352 api_name => "open-ils.actor.groups.tree.retrieve",
2353 notes => "Returns a list of user groups"
2356 sub retrieve_groups_tree {
2357 my( $self, $client ) = @_;
2358 return new_editor()->search_permission_grp_tree(
2363 flesh_fields => { pgt => ["children"] },
2364 order_by => { pgt => 'name'}
2371 __PACKAGE__->register_method(
2372 method => "add_user_to_groups",
2373 api_name => "open-ils.actor.user.set_groups",
2374 notes => "Adds a user to one or more permission groups"
2377 sub add_user_to_groups {
2378 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2380 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2381 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2382 return $evt if $evt;
2384 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2385 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2386 return $evt if $evt;
2388 $apputils->simplereq(
2390 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2392 for my $group (@$groups) {
2393 my $link = Fieldmapper::permission::usr_grp_map->new;
2395 $link->usr($userid);
2397 my $id = $apputils->simplereq(
2399 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2405 __PACKAGE__->register_method(
2406 method => "get_user_perm_groups",
2407 api_name => "open-ils.actor.user.get_groups",
2408 notes => "Retrieve a user's permission groups."
2412 sub get_user_perm_groups {
2413 my( $self, $client, $authtoken, $userid ) = @_;
2415 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2416 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2417 return $evt if $evt;
2419 return $apputils->simplereq(
2421 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2425 __PACKAGE__->register_method(
2426 method => "get_user_work_ous",
2427 api_name => "open-ils.actor.user.get_work_ous",
2428 notes => "Retrieve a user's work org units."
2431 __PACKAGE__->register_method(
2432 method => "get_user_work_ous",
2433 api_name => "open-ils.actor.user.get_work_ous.ids",
2434 notes => "Retrieve a user's work org units."
2437 sub get_user_work_ous {
2438 my( $self, $client, $auth, $userid ) = @_;
2439 my $e = new_editor(authtoken=>$auth);
2440 return $e->event unless $e->checkauth;
2441 $userid ||= $e->requestor->id;
2443 if($e->requestor->id != $userid) {
2444 my $user = $e->retrieve_actor_user($userid)
2445 or return $e->event;
2446 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2449 return $e->search_permission_usr_work_ou_map({usr => $userid})
2450 unless $self->api_name =~ /.ids$/;
2452 # client just wants a list of org IDs
2453 return $U->get_user_work_ou_ids($e, $userid);
2458 __PACKAGE__->register_method(
2459 method => 'register_workstation',
2460 api_name => 'open-ils.actor.workstation.register.override',
2461 signature => q/@see open-ils.actor.workstation.register/
2464 __PACKAGE__->register_method(
2465 method => 'register_workstation',
2466 api_name => 'open-ils.actor.workstation.register',
2468 Registers a new workstion in the system
2469 @param authtoken The login session key
2470 @param name The name of the workstation id
2471 @param owner The org unit that owns this workstation
2472 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2473 if the name is already in use.
2477 sub register_workstation {
2478 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2480 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2481 return $e->die_event unless $e->checkauth;
2482 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2483 my $existing = $e->search_actor_workstation({name => $name})->[0];
2484 $oargs = { all => 1 } unless defined $oargs;
2488 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2489 # workstation with the given name exists.
2491 if($owner ne $existing->owning_lib) {
2492 # if necessary, update the owning_lib of the workstation
2494 $logger->info("changing owning lib of workstation ".$existing->id.
2495 " from ".$existing->owning_lib." to $owner");
2496 return $e->die_event unless
2497 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2499 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2501 $existing->owning_lib($owner);
2502 return $e->die_event unless $e->update_actor_workstation($existing);
2508 "attempt to register an existing workstation. returning existing ID");
2511 return $existing->id;
2514 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2518 my $ws = Fieldmapper::actor::workstation->new;
2519 $ws->owning_lib($owner);
2521 $e->create_actor_workstation($ws) or return $e->die_event;
2523 return $ws->id; # note: editor sets the id on the new object for us
2526 __PACKAGE__->register_method(
2527 method => 'workstation_list',
2528 api_name => 'open-ils.actor.workstation.list',
2530 Returns a list of workstations registered at the given location
2531 @param authtoken The login session key
2532 @param ids A list of org_unit.id's for the workstation owners
2536 sub workstation_list {
2537 my( $self, $conn, $authtoken, @orgs ) = @_;
2539 my $e = new_editor(authtoken=>$authtoken);
2540 return $e->event unless $e->checkauth;
2545 unless $e->allowed('REGISTER_WORKSTATION', $o);
2546 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2552 __PACKAGE__->register_method(
2553 method => 'fetch_patron_note',
2554 api_name => 'open-ils.actor.note.retrieve.all',
2557 Returns a list of notes for a given user
2558 Requestor must have VIEW_USER permission if pub==false and
2559 @param authtoken The login session key
2560 @param args Hash of params including
2561 patronid : the patron's id
2562 pub : true if retrieving only public notes
2566 sub fetch_patron_note {
2567 my( $self, $conn, $authtoken, $args ) = @_;
2568 my $patronid = $$args{patronid};
2570 my($reqr, $evt) = $U->checkses($authtoken);
2571 return $evt if $evt;
2574 ($patron, $evt) = $U->fetch_user($patronid);
2575 return $evt if $evt;
2578 if( $patronid ne $reqr->id ) {
2579 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2580 return $evt if $evt;
2582 return $U->cstorereq(
2583 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2584 { usr => $patronid, pub => 't' } );
2587 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2588 return $evt if $evt;
2590 return $U->cstorereq(
2591 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2594 __PACKAGE__->register_method(
2595 method => 'create_user_note',
2596 api_name => 'open-ils.actor.note.create',
2598 Creates a new note for the given user
2599 @param authtoken The login session key
2600 @param note The note object
2603 sub create_user_note {
2604 my( $self, $conn, $authtoken, $note ) = @_;
2605 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2606 return $e->die_event unless $e->checkauth;
2608 my $user = $e->retrieve_actor_user($note->usr)
2609 or return $e->die_event;
2611 return $e->die_event unless
2612 $e->allowed('UPDATE_USER',$user->home_ou);
2614 $note->creator($e->requestor->id);
2615 $e->create_actor_usr_note($note) or return $e->die_event;
2621 __PACKAGE__->register_method(
2622 method => 'delete_user_note',
2623 api_name => 'open-ils.actor.note.delete',
2625 Deletes a note for the given user
2626 @param authtoken The login session key
2627 @param noteid The note id
2630 sub delete_user_note {
2631 my( $self, $conn, $authtoken, $noteid ) = @_;
2633 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2634 return $e->die_event unless $e->checkauth;
2635 my $note = $e->retrieve_actor_usr_note($noteid)
2636 or return $e->die_event;
2637 my $user = $e->retrieve_actor_user($note->usr)
2638 or return $e->die_event;
2639 return $e->die_event unless
2640 $e->allowed('UPDATE_USER', $user->home_ou);
2642 $e->delete_actor_usr_note($note) or return $e->die_event;
2648 __PACKAGE__->register_method(
2649 method => 'update_user_note',
2650 api_name => 'open-ils.actor.note.update',
2652 @param authtoken The login session key
2653 @param note The note
2657 sub update_user_note {
2658 my( $self, $conn, $auth, $note ) = @_;
2659 my $e = new_editor(authtoken=>$auth, xact=>1);
2660 return $e->die_event unless $e->checkauth;
2661 my $patron = $e->retrieve_actor_user($note->usr)
2662 or return $e->die_event;
2663 return $e->die_event unless
2664 $e->allowed('UPDATE_USER', $patron->home_ou);
2665 $e->update_actor_user_note($note)
2666 or return $e->die_event;
2671 __PACKAGE__->register_method(
2672 method => 'fetch_patron_messages',
2673 api_name => 'open-ils.actor.message.retrieve',
2676 Returns a list of notes for a given user, not
2677 including ones marked deleted
2678 @param authtoken The login session key
2679 @param patronid patron ID
2680 @param options hash containing optional limit and offset
2684 sub fetch_patron_messages {
2685 my( $self, $conn, $auth, $patronid, $options ) = @_;
2689 my $e = new_editor(authtoken => $auth);
2690 return $e->die_event unless $e->checkauth;
2692 if ($e->requestor->id ne $patronid) {
2693 return $e->die_event unless $e->allowed('VIEW_USER');
2696 my $select_clause = { usr => $patronid };
2697 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2698 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2699 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2701 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2706 __PACKAGE__->register_method(
2707 method => 'create_closed_date',
2708 api_name => 'open-ils.actor.org_unit.closed_date.create',
2710 Creates a new closing entry for the given org_unit
2711 @param authtoken The login session key
2712 @param note The closed_date object
2715 sub create_closed_date {
2716 my( $self, $conn, $authtoken, $cd ) = @_;
2718 my( $user, $evt ) = $U->checkses($authtoken);
2719 return $evt if $evt;
2721 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2722 return $evt if $evt;
2724 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2726 my $id = $U->storagereq(
2727 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2728 return $U->DB_UPDATE_FAILED($cd) unless $id;
2733 __PACKAGE__->register_method(
2734 method => 'delete_closed_date',
2735 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2737 Deletes a closing entry for the given org_unit
2738 @param authtoken The login session key
2739 @param noteid The close_date id
2742 sub delete_closed_date {
2743 my( $self, $conn, $authtoken, $cd ) = @_;
2745 my( $user, $evt ) = $U->checkses($authtoken);
2746 return $evt if $evt;
2749 ($cd_obj, $evt) = fetch_closed_date($cd);
2750 return $evt if $evt;
2752 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2753 return $evt if $evt;
2755 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2757 my $stat = $U->storagereq(
2758 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2759 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2764 __PACKAGE__->register_method(
2765 method => 'usrname_exists',
2766 api_name => 'open-ils.actor.username.exists',
2768 desc => 'Check if a username is already taken (by an undeleted patron)',
2770 {desc => 'Authentication token', type => 'string'},
2771 {desc => 'Username', type => 'string'}
2774 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2779 sub usrname_exists {
2780 my( $self, $conn, $auth, $usrname ) = @_;
2781 my $e = new_editor(authtoken=>$auth);
2782 return $e->event unless $e->checkauth;
2783 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2784 return $$a[0] if $a and @$a;
2788 __PACKAGE__->register_method(
2789 method => 'barcode_exists',
2790 api_name => 'open-ils.actor.barcode.exists',
2792 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2795 sub barcode_exists {
2796 my( $self, $conn, $auth, $barcode ) = @_;
2797 my $e = new_editor(authtoken=>$auth);
2798 return $e->event unless $e->checkauth;
2799 my $card = $e->search_actor_card({barcode => $barcode});
2805 #return undef unless @$card;
2806 #return $card->[0]->usr;
2810 __PACKAGE__->register_method(
2811 method => 'retrieve_net_levels',
2812 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2815 sub retrieve_net_levels {
2816 my( $self, $conn, $auth ) = @_;
2817 my $e = new_editor(authtoken=>$auth);
2818 return $e->event unless $e->checkauth;
2819 return $e->retrieve_all_config_net_access_level();
2822 # Retain the old typo API name just in case
2823 __PACKAGE__->register_method(
2824 method => 'fetch_org_by_shortname',
2825 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2827 __PACKAGE__->register_method(
2828 method => 'fetch_org_by_shortname',
2829 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2831 sub fetch_org_by_shortname {
2832 my( $self, $conn, $sname ) = @_;
2833 my $e = new_editor();
2834 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2835 return $e->event unless $org;
2840 __PACKAGE__->register_method(
2841 method => 'session_home_lib',
2842 api_name => 'open-ils.actor.session.home_lib',
2845 sub session_home_lib {
2846 my( $self, $conn, $auth ) = @_;
2847 my $e = new_editor(authtoken=>$auth);
2848 return undef unless $e->checkauth;
2849 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2850 return $org->shortname;
2853 __PACKAGE__->register_method(
2854 method => 'session_safe_token',
2855 api_name => 'open-ils.actor.session.safe_token',
2857 Returns a hashed session ID that is safe for export to the world.
2858 This safe token will expire after 1 hour of non-use.
2859 @param auth Active authentication token
2863 sub session_safe_token {
2864 my( $self, $conn, $auth ) = @_;
2865 my $e = new_editor(authtoken=>$auth);
2866 return undef unless $e->checkauth;
2868 my $safe_token = md5_hex($auth);
2870 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2872 # Add more like the following if needed...
2874 "safe-token-home_lib-shortname-$safe_token",
2875 $e->retrieve_actor_org_unit(
2876 $e->requestor->home_ou
2885 __PACKAGE__->register_method(
2886 method => 'safe_token_home_lib',
2887 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2889 Returns the home library shortname from the session
2890 asscociated with a safe token from generated by
2891 open-ils.actor.session.safe_token.
2892 @param safe_token Active safe token
2896 sub safe_token_home_lib {
2897 my( $self, $conn, $safe_token ) = @_;
2899 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2900 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2904 __PACKAGE__->register_method(
2905 method => "update_penalties",
2906 api_name => "open-ils.actor.user.penalties.update"
2909 sub update_penalties {
2910 my($self, $conn, $auth, $user_id) = @_;
2911 my $e = new_editor(authtoken=>$auth, xact => 1);
2912 return $e->die_event unless $e->checkauth;
2913 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2914 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2915 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2916 return $evt if $evt;
2922 __PACKAGE__->register_method(
2923 method => "apply_penalty",
2924 api_name => "open-ils.actor.user.penalty.apply"
2928 my($self, $conn, $auth, $penalty) = @_;
2930 my $e = new_editor(authtoken=>$auth, xact => 1);
2931 return $e->die_event unless $e->checkauth;
2933 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2934 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2936 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2939 (defined $ptype->org_depth) ?
2940 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2943 $penalty->org_unit($ctx_org);
2944 $penalty->staff($e->requestor->id);
2945 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2948 return $penalty->id;
2951 __PACKAGE__->register_method(
2952 method => "remove_penalty",
2953 api_name => "open-ils.actor.user.penalty.remove"
2956 sub remove_penalty {
2957 my($self, $conn, $auth, $penalty) = @_;
2958 my $e = new_editor(authtoken=>$auth, xact => 1);
2959 return $e->die_event unless $e->checkauth;
2960 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2961 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2963 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2968 __PACKAGE__->register_method(
2969 method => "update_penalty_note",
2970 api_name => "open-ils.actor.user.penalty.note.update"
2973 sub update_penalty_note {
2974 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2975 my $e = new_editor(authtoken=>$auth, xact => 1);
2976 return $e->die_event unless $e->checkauth;
2977 for my $penalty_id (@$penalty_ids) {
2978 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2979 if (! $penalty ) { return $e->die_event; }
2980 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2981 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2983 $penalty->note( $note ); $penalty->ischanged( 1 );
2985 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2991 __PACKAGE__->register_method(
2992 method => "ranged_penalty_thresholds",
2993 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2997 sub ranged_penalty_thresholds {
2998 my($self, $conn, $auth, $context_org) = @_;
2999 my $e = new_editor(authtoken=>$auth);
3000 return $e->event unless $e->checkauth;
3001 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
3002 my $list = $e->search_permission_grp_penalty_threshold([
3003 {org_unit => $U->get_org_ancestors($context_org)},
3004 {order_by => {pgpt => 'id'}}
3006 $conn->respond($_) for @$list;
3012 __PACKAGE__->register_method(
3013 method => "user_retrieve_fleshed_by_id",
3015 api_name => "open-ils.actor.user.fleshed.retrieve",
3018 sub user_retrieve_fleshed_by_id {
3019 my( $self, $client, $auth, $user_id, $fields ) = @_;
3020 my $e = new_editor(authtoken => $auth);
3021 return $e->event unless $e->checkauth;
3023 if( $e->requestor->id != $user_id ) {
3024 return $e->event unless $e->allowed('VIEW_USER');
3031 "standing_penalties",
3037 return new_flesh_user($user_id, $fields, $e);
3041 sub new_flesh_user {
3044 my $fields = shift || [];
3047 my $fetch_penalties = 0;
3048 if(grep {$_ eq 'standing_penalties'} @$fields) {
3049 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3050 $fetch_penalties = 1;
3053 my $fetch_usr_act = 0;
3054 if(grep {$_ eq 'usr_activity'} @$fields) {
3055 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3059 my $user = $e->retrieve_actor_user(
3064 "flesh_fields" => { "au" => $fields }
3067 ) or return $e->die_event;
3070 if( grep { $_ eq 'addresses' } @$fields ) {
3072 $user->addresses([]) unless @{$user->addresses};
3073 # don't expose "replaced" addresses by default
3074 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3076 if( ref $user->billing_address ) {
3077 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3078 push( @{$user->addresses}, $user->billing_address );
3082 if( ref $user->mailing_address ) {
3083 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3084 push( @{$user->addresses}, $user->mailing_address );
3089 if($fetch_penalties) {
3090 # grab the user penalties ranged for this location
3091 $user->standing_penalties(
3092 $e->search_actor_user_standing_penalty([
3095 {stop_date => undef},
3096 {stop_date => {'>' => 'now'}}
3098 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3101 flesh_fields => {ausp => ['standing_penalty']}
3107 # retrieve the most recent usr_activity entry
3108 if ($fetch_usr_act) {
3110 # max number to return for simple patron fleshing
3111 my $limit = $U->ou_ancestor_setting_value(
3112 $e->requestor->ws_ou,
3113 'circ.patron.usr_activity_retrieve.max');
3117 flesh_fields => {auact => ['etype']},
3118 order_by => {auact => 'event_time DESC'},
3121 # 0 == none, <0 == return all
3122 $limit = 1 unless defined $limit;
3123 $opts->{limit} = $limit if $limit > 0;
3125 $user->usr_activity(
3127 [] : # skip the DB call
3128 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3133 $user->clear_passwd();
3140 __PACKAGE__->register_method(
3141 method => "user_retrieve_parts",
3142 api_name => "open-ils.actor.user.retrieve.parts",
3145 sub user_retrieve_parts {
3146 my( $self, $client, $auth, $user_id, $fields ) = @_;
3147 my $e = new_editor(authtoken => $auth);
3148 return $e->event unless $e->checkauth;
3149 $user_id ||= $e->requestor->id;
3150 if( $e->requestor->id != $user_id ) {
3151 return $e->event unless $e->allowed('VIEW_USER');
3154 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3155 push(@resp, $user->$_()) for(@$fields);
3161 __PACKAGE__->register_method(
3162 method => 'user_opt_in_enabled',
3163 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3164 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3167 sub user_opt_in_enabled {
3168 my($self, $conn) = @_;
3169 my $sc = OpenSRF::Utils::SettingsClient->new;
3170 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3175 __PACKAGE__->register_method(
3176 method => 'user_opt_in_at_org',
3177 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3179 @param $auth The auth token
3180 @param user_id The ID of the user to test
3181 @return 1 if the user has opted in at the specified org,
3182 event on error, and 0 otherwise. /
3184 sub user_opt_in_at_org {
3185 my($self, $conn, $auth, $user_id) = @_;
3187 # see if we even need to enforce the opt-in value
3188 return 1 unless user_opt_in_enabled($self);
3190 my $e = new_editor(authtoken => $auth);
3191 return $e->event unless $e->checkauth;
3193 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3194 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3196 my $ws_org = $e->requestor->ws_ou;
3197 # user is automatically opted-in if they are from the local org
3198 return 1 if $user->home_ou eq $ws_org;
3200 # get the boundary setting
3201 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3203 # auto opt in if user falls within the opt boundary
3204 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3206 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3208 my $vals = $e->search_actor_usr_org_unit_opt_in(
3209 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3215 __PACKAGE__->register_method(
3216 method => 'create_user_opt_in_at_org',
3217 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3219 @param $auth The auth token
3220 @param user_id The ID of the user to test
3221 @return The ID of the newly created object, event on error./
3224 sub create_user_opt_in_at_org {
3225 my($self, $conn, $auth, $user_id, $org_id) = @_;
3227 my $e = new_editor(authtoken => $auth, xact=>1);
3228 return $e->die_event unless $e->checkauth;
3230 # if a specific org unit wasn't passed in, get one based on the defaults;
3232 my $wsou = $e->requestor->ws_ou;
3233 # get the default opt depth
3234 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3235 # get the org unit at that depth
3236 my $org = $e->json_query({
3237 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3238 $org_id = $org->{id};
3241 # fall back to the workstation OU, the pre-opt-in-boundary way
3242 $org_id = $e->requestor->ws_ou;
3245 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3246 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3248 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3250 $opt_in->org_unit($org_id);
3251 $opt_in->usr($user_id);
3252 $opt_in->staff($e->requestor->id);
3253 $opt_in->opt_in_ts('now');
3254 $opt_in->opt_in_ws($e->requestor->wsid);
3256 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3257 or return $e->die_event;
3265 __PACKAGE__->register_method (
3266 method => 'retrieve_org_hours',
3267 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3269 Returns the hours of operation for a specified org unit
3270 @param authtoken The login session key
3271 @param org_id The org_unit ID
3275 sub retrieve_org_hours {
3276 my($self, $conn, $auth, $org_id) = @_;
3277 my $e = new_editor(authtoken => $auth);
3278 return $e->die_event unless $e->checkauth;
3279 $org_id ||= $e->requestor->ws_ou;
3280 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3284 __PACKAGE__->register_method (
3285 method => 'verify_user_password',
3286 api_name => 'open-ils.actor.verify_user_password',
3288 Given a barcode or username and the MD5 encoded password,
3289 returns 1 if the password is correct. Returns 0 otherwise.
3293 sub verify_user_password {
3294 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3295 my $e = new_editor(authtoken => $auth);
3296 return $e->die_event unless $e->checkauth;
3298 my $user_by_barcode;
3299 my $user_by_username;
3301 my $card = $e->search_actor_card([
3302 {barcode => $barcode},
3303 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3304 $user_by_barcode = $card->usr;
3305 $user = $user_by_barcode;
3308 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3309 $user = $user_by_username;
3311 return 0 if (!$user);
3312 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3313 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3314 return 1 if $user->passwd eq $password;
3318 __PACKAGE__->register_method (
3319 method => 'retrieve_usr_id_via_barcode_or_usrname',
3320 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3322 Given a barcode or username returns the id for the user or
3327 sub retrieve_usr_id_via_barcode_or_usrname {
3328 my($self, $conn, $auth, $barcode, $username) = @_;
3329 my $e = new_editor(authtoken => $auth);
3330 return $e->die_event unless $e->checkauth;
3331 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3333 my $user_by_barcode;
3334 my $user_by_username;
3335 $logger->info("$id_as_barcode is the ID as BARCODE");
3337 my $card = $e->search_actor_card([
3338 {barcode => $barcode},
3339 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3340 if ($id_as_barcode =~ /^t/i) {
3342 $user = $e->retrieve_actor_user($barcode);
3343 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3345 $user_by_barcode = $card->usr;
3346 $user = $user_by_barcode;
3349 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3350 $user_by_barcode = $card->usr;
3351 $user = $user_by_barcode;
3356 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3358 $user = $user_by_username;
3360 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3361 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3362 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3367 __PACKAGE__->register_method (
3368 method => 'merge_users',
3369 api_name => 'open-ils.actor.user.merge',
3372 Given a list of source users and destination user, transfer all data from the source
3373 to the dest user and delete the source user. All user related data is
3374 transferred, including circulations, holds, bookbags, etc.
3380 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3381 my $e = new_editor(xact => 1, authtoken => $auth);
3382 return $e->die_event unless $e->checkauth;
3384 # disallow the merge if any subordinate accounts are in collections
3385 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3386 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3388 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3389 my $del_addrs = ($U->ou_ancestor_setting_value(
3390 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3391 my $del_cards = ($U->ou_ancestor_setting_value(
3392 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3393 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3394 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3396 for my $src_id (@$user_ids) {
3397 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3399 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3400 if($src_user->home_ou ne $master_user->home_ou) {
3401 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3404 return $e->die_event unless
3405 $e->json_query({from => [
3420 __PACKAGE__->register_method (
3421 method => 'approve_user_address',
3422 api_name => 'open-ils.actor.user.pending_address.approve',
3429 sub approve_user_address {
3430 my($self, $conn, $auth, $addr) = @_;
3431 my $e = new_editor(xact => 1, authtoken => $auth);
3432 return $e->die_event unless $e->checkauth;
3434 # if the caller passes an address object, assume they want to
3435 # update it first before approving it
3436 $e->update_actor_user_address($addr) or return $e->die_event;
3438 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3440 my $user = $e->retrieve_actor_user($addr->usr);
3441 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3442 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3443 or return $e->die_event;
3445 return [values %$result]->[0];
3449 __PACKAGE__->register_method (
3450 method => 'retrieve_friends',
3451 api_name => 'open-ils.actor.friends.retrieve',
3454 returns { confirmed: [], pending_out: [], pending_in: []}
3455 pending_out are users I'm requesting friendship with
3456 pending_in are users requesting friendship with me
3461 sub retrieve_friends {
3462 my($self, $conn, $auth, $user_id, $options) = @_;
3463 my $e = new_editor(authtoken => $auth);
3464 return $e->event unless $e->checkauth;
3465 $user_id ||= $e->requestor->id;
3467 if($user_id != $e->requestor->id) {
3468 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3469 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3472 return OpenILS::Application::Actor::Friends->retrieve_friends(
3473 $e, $user_id, $options);
3478 __PACKAGE__->register_method (
3479 method => 'apply_friend_perms',
3480 api_name => 'open-ils.actor.friends.perms.apply',
3486 sub apply_friend_perms {
3487 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3488 my $e = new_editor(authtoken => $auth, xact => 1);
3489 return $e->die_event unless $e->checkauth;
3491 if($user_id != $e->requestor->id) {
3492 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3493 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3496 for my $perm (@perms) {
3498 OpenILS::Application::Actor::Friends->apply_friend_perm(
3499 $e, $user_id, $delegate_id, $perm);
3500 return $evt if $evt;
3508 __PACKAGE__->register_method (
3509 method => 'update_user_pending_address',
3510 api_name => 'open-ils.actor.user.address.pending.cud'
3513 sub update_user_pending_address {
3514 my($self, $conn, $auth, $addr) = @_;
3515 my $e = new_editor(authtoken => $auth, xact => 1);
3516 return $e->die_event unless $e->checkauth;
3518 if($addr->usr != $e->requestor->id) {
3519 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3520 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3524 $e->create_actor_user_address($addr) or return $e->die_event;
3525 } elsif($addr->isdeleted) {
3526 $e->delete_actor_user_address($addr) or return $e->die_event;
3528 $e->update_actor_user_address($addr) or return $e->die_event;
3536 __PACKAGE__->register_method (
3537 method => 'user_events',
3538 api_name => 'open-ils.actor.user.events.circ',
3541 __PACKAGE__->register_method (
3542 method => 'user_events',
3543 api_name => 'open-ils.actor.user.events.ahr',
3548 my($self, $conn, $auth, $user_id, $filters) = @_;
3549 my $e = new_editor(authtoken => $auth);
3550 return $e->event unless $e->checkauth;
3552 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3553 my $user_field = 'usr';
3556 $filters->{target} = {
3557 select => { $obj_type => ['id'] },
3559 where => {usr => $user_id}
3562 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3563 if($e->requestor->id != $user_id) {
3564 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3567 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3568 my $req = $ses->request('open-ils.trigger.events_by_target',
3569 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3571 while(my $resp = $req->recv) {
3572 my $val = $resp->content;
3573 my $tgt = $val->target;
3575 if($obj_type eq 'circ') {
3576 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3578 } elsif($obj_type eq 'ahr') {
3579 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3580 if $tgt->current_copy;
3583 $conn->respond($val) if $val;
3589 __PACKAGE__->register_method (
3590 method => 'copy_events',
3591 api_name => 'open-ils.actor.copy.events.circ',
3594 __PACKAGE__->register_method (
3595 method => 'copy_events',
3596 api_name => 'open-ils.actor.copy.events.ahr',
3601 my($self, $conn, $auth, $copy_id, $filters) = @_;
3602 my $e = new_editor(authtoken => $auth);
3603 return $e->event unless $e->checkauth;
3605 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3607 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3609 my $copy_field = 'target_copy';
3610 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3613 $filters->{target} = {
3614 select => { $obj_type => ['id'] },
3616 where => {$copy_field => $copy_id}
3620 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3621 my $req = $ses->request('open-ils.trigger.events_by_target',
3622 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3624 while(my $resp = $req->recv) {
3625 my $val = $resp->content;
3626 my $tgt = $val->target;
3628 my $user = $e->retrieve_actor_user($tgt->usr);
3629 if($e->requestor->id != $user->id) {
3630 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3633 $tgt->$copy_field($copy);
3636 $conn->respond($val) if $val;
3645 __PACKAGE__->register_method (
3646 method => 'update_events',
3647 api_name => 'open-ils.actor.user.event.cancel.batch',
3650 __PACKAGE__->register_method (
3651 method => 'update_events',
3652 api_name => 'open-ils.actor.user.event.reset.batch',
3657 my($self, $conn, $auth, $event_ids) = @_;
3658 my $e = new_editor(xact => 1, authtoken => $auth);
3659 return $e->die_event unless $e->checkauth;
3662 for my $id (@$event_ids) {
3664 # do a little dance to determine what user we are ultimately affecting
3665 my $event = $e->retrieve_action_trigger_event([
3668 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3670 ]) or return $e->die_event;
3673 if($event->event_def->hook->core_type eq 'circ') {
3674 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3675 } elsif($event->event_def->hook->core_type eq 'ahr') {
3676 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3681 my $user = $e->retrieve_actor_user($user_id);
3682 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3684 if($self->api_name =~ /cancel/) {
3685 $event->state('invalid');
3686 } elsif($self->api_name =~ /reset/) {
3687 $event->clear_start_time;
3688 $event->clear_update_time;
3689 $event->state('pending');
3692 $e->update_action_trigger_event($event) or return $e->die_event;
3693 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3697 return {complete => 1};
3701 __PACKAGE__->register_method (
3702 method => 'really_delete_user',
3703 api_name => 'open-ils.actor.user.delete.override',
3704 signature => q/@see open-ils.actor.user.delete/
3707 __PACKAGE__->register_method (
3708 method => 'really_delete_user',
3709 api_name => 'open-ils.actor.user.delete',
3711 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3712 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3713 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3714 dest_usr_id is only required when deleting a user that performs staff functions.
3718 sub really_delete_user {
3719 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3720 my $e = new_editor(authtoken => $auth, xact => 1);
3721 return $e->die_event unless $e->checkauth;
3722 $oargs = { all => 1 } unless defined $oargs;
3724 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3725 my $open_bills = $e->json_query({
3726 select => { mbts => ['id'] },
3729 xact_finish => { '=' => undef },
3730 usr => { '=' => $user_id },
3732 }) or return $e->die_event;
3734 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3736 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3738 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3739 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3740 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3742 # No deleting yourself - UI is supposed to stop you first, though.
3743 return $e->die_event unless $e->requestor->id != $user->id;
3744 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3745 # Check if you are allowed to mess with this patron permission group at all
3746 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3747 my $evt = group_perm_failed($session, $e->requestor, $user);
3748 return $e->die_event($evt) if $evt;
3749 my $stat = $e->json_query(
3750 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3751 or return $e->die_event;
3757 __PACKAGE__->register_method (
3758 method => 'user_payments',
3759 api_name => 'open-ils.actor.user.payments.retrieve',
3762 Returns all payments for a given user. Default order is newest payments first.
3763 @param auth Authentication token
3764 @param user_id The user ID
3765 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3770 my($self, $conn, $auth, $user_id, $filters) = @_;
3773 my $e = new_editor(authtoken => $auth);
3774 return $e->die_event unless $e->checkauth;
3776 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3777 return $e->event unless
3778 $e->requestor->id == $user_id or
3779 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3781 # Find all payments for all transactions for user $user_id
3783 select => {mp => ['id']},
3788 select => {mbt => ['id']},
3790 where => {usr => $user_id}
3795 { # by default, order newest payments first
3797 field => 'payment_ts',
3800 # secondary sort in ID as a tie-breaker, since payments created
3801 # within the same transaction will have identical payment_ts's
3808 for (qw/order_by limit offset/) {
3809 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3812 if(defined $filters->{where}) {
3813 foreach (keys %{$filters->{where}}) {
3814 # don't allow the caller to expand the result set to other users
3815 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3819 my $payment_ids = $e->json_query($query);
3820 for my $pid (@$payment_ids) {
3821 my $pay = $e->retrieve_money_payment([
3826 mbt => ['summary', 'circulation', 'grocery'],
3827 circ => ['target_copy'],
3828 acp => ['call_number'],
3836 xact_type => $pay->xact->summary->xact_type,
3837 last_billing_type => $pay->xact->summary->last_billing_type,
3840 if($pay->xact->summary->xact_type eq 'circulation') {
3841 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3842 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3845 $pay->xact($pay->xact->id); # de-flesh
3846 $conn->respond($resp);
3854 __PACKAGE__->register_method (
3855 method => 'negative_balance_users',
3856 api_name => 'open-ils.actor.users.negative_balance',
3859 Returns all users that have an overall negative balance
3860 @param auth Authentication token
3861 @param org_id The context org unit as an ID or list of IDs. This will be the home
3862 library of the user. If no org_unit is specified, no org unit filter is applied
3866 sub negative_balance_users {
3867 my($self, $conn, $auth, $org_id) = @_;
3869 my $e = new_editor(authtoken => $auth);
3870 return $e->die_event unless $e->checkauth;
3871 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3875 mous => ['usr', 'balance_owed'],
3878 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3879 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3896 where => {'+mous' => {balance_owed => {'<' => 0}}}
3899 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3901 my $list = $e->json_query($query, {timeout => 600});
3903 for my $data (@$list) {
3905 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3906 balance_owed => $data->{balance_owed},
3907 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3914 __PACKAGE__->register_method(
3915 method => "request_password_reset",
3916 api_name => "open-ils.actor.patron.password_reset.request",
3918 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3919 "method for changing a user's password. The UUID token is distributed via A/T " .
3920 "templates (i.e. email to the user).",
3922 { desc => 'user_id_type', type => 'string' },
3923 { desc => 'user_id', type => 'string' },
3924 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3926 return => {desc => '1 on success, Event on error'}
3929 sub request_password_reset {
3930 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3932 # Check to see if password reset requests are already being throttled:
3933 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3935 my $e = new_editor(xact => 1);
3938 # Get the user, if any, depending on the input value
3939 if ($user_id_type eq 'username') {
3940 $user = $e->search_actor_user({usrname => $user_id})->[0];
3943 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3945 } elsif ($user_id_type eq 'barcode') {
3946 my $card = $e->search_actor_card([
3947 {barcode => $user_id},
3948 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3951 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3956 # If the user doesn't have an email address, we can't help them
3957 if (!$user->email) {
3959 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3962 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3963 if ($email_must_match) {
3964 if ($user->email ne $email) {
3965 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3969 _reset_password_request($conn, $e, $user);
3972 # Once we have the user, we can issue the password reset request
3973 # XXX Add a wrapper method that accepts barcode + email input
3974 sub _reset_password_request {
3975 my ($conn, $e, $user) = @_;
3977 # 1. Get throttle threshold and time-to-live from OU_settings
3978 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3979 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3981 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3983 # 2. Get time of last request and number of active requests (num_active)
3984 my $active_requests = $e->json_query({
3990 transform => 'COUNT'
3993 column => 'request_time',
3999 has_been_reset => { '=' => 'f' },
4000 request_time => { '>' => $threshold_time }
4004 # Guard against no active requests
4005 if ($active_requests->[0]->{'request_time'}) {
4006 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
4007 my $now = DateTime::Format::ISO8601->new();
4009 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4010 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4011 ($last_request->add_duration('1 minute') > $now)) {
4012 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4014 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4018 # TODO Check to see if the user is in a password-reset-restricted group
4020 # Otherwise, go ahead and try to get the user.
4022 # Check the number of active requests for this user
4023 $active_requests = $e->json_query({
4029 transform => 'COUNT'
4034 usr => { '=' => $user->id },
4035 has_been_reset => { '=' => 'f' },
4036 request_time => { '>' => $threshold_time }
4040 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4042 # if less than or equal to per-user threshold, proceed; otherwise, return event
4043 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4044 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4046 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4049 # Create the aupr object and insert into the database
4050 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4051 my $uuid = create_uuid_as_string(UUID_V4);
4052 $reset_request->uuid($uuid);
4053 $reset_request->usr($user->id);
4055 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4058 # Create an event to notify user of the URL to reset their password
4060 # Can we stuff this in the user_data param for trigger autocreate?
4061 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4063 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4064 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4067 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4072 __PACKAGE__->register_method(
4073 method => "commit_password_reset",
4074 api_name => "open-ils.actor.patron.password_reset.commit",
4076 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4077 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4078 "with the supplied password.",
4080 { desc => 'uuid', type => 'string' },
4081 { desc => 'password', type => 'string' },
4083 return => {desc => '1 on success, Event on error'}
4086 sub commit_password_reset {
4087 my($self, $conn, $uuid, $password) = @_;
4089 # Check to see if password reset requests are already being throttled:
4090 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4091 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4092 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4094 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4097 my $e = new_editor(xact => 1);
4099 my $aupr = $e->search_actor_usr_password_reset({
4106 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4108 my $user_id = $aupr->[0]->usr;
4109 my $user = $e->retrieve_actor_user($user_id);
4111 # Ensure we're still within the TTL for the request
4112 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4113 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4114 if ($threshold < DateTime->now(time_zone => 'local')) {
4116 $logger->info("Password reset request needed to be submitted before $threshold");
4117 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4120 # Check complexity of password against OU-defined regex
4121 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4125 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4126 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4127 $is_strong = check_password_strength_custom($password, $pw_regex);
4129 $is_strong = check_password_strength_default($password);
4134 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4137 # All is well; update the password
4138 $user->passwd($password);
4139 $e->update_actor_user($user);
4141 # And flag that this password reset request has been honoured
4142 $aupr->[0]->has_been_reset('t');
4143 $e->update_actor_usr_password_reset($aupr->[0]);
4149 sub check_password_strength_default {
4150 my $password = shift;
4151 # Use the default set of checks
4152 if ( (length($password) < 7) or
4153 ($password !~ m/.*\d+.*/) or
4154 ($password !~ m/.*[A-Za-z]+.*/)
4161 sub check_password_strength_custom {
4162 my ($password, $pw_regex) = @_;
4164 $pw_regex = qr/$pw_regex/;
4165 if ($password !~ /$pw_regex/) {
4173 __PACKAGE__->register_method(
4174 method => "event_def_opt_in_settings",
4175 api_name => "open-ils.actor.event_def.opt_in.settings",
4178 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4180 { desc => 'Authentication token', type => 'string'},
4182 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4187 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4194 sub event_def_opt_in_settings {
4195 my($self, $conn, $auth, $org_id) = @_;
4196 my $e = new_editor(authtoken => $auth);
4197 return $e->event unless $e->checkauth;
4199 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4200 return $e->event unless
4201 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4203 $org_id = $e->requestor->home_ou;
4206 # find all config.user_setting_type's related to event_defs for the requested org unit
4207 my $types = $e->json_query({
4208 select => {cust => ['name']},
4209 from => {atevdef => 'cust'},
4212 owner => $U->get_org_ancestors($org_id), # context org plus parents
4219 $conn->respond($_) for
4220 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4227 __PACKAGE__->register_method(
4228 method => "user_visible_circs",
4229 api_name => "open-ils.actor.history.circ.visible",
4232 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4234 { desc => 'Authentication token', type => 'string'},
4235 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4236 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4239 desc => q/An object with 2 fields: circulation and summary.
4240 circulation is the "circ" object. summary is the related "accs" object/,
4246 __PACKAGE__->register_method(
4247 method => "user_visible_circs",
4248 api_name => "open-ils.actor.history.circ.visible.print",
4251 desc => 'Returns printable output for the set of opt-in visible circulations',
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 action_trigger.event object or error event./,
4264 __PACKAGE__->register_method(
4265 method => "user_visible_circs",
4266 api_name => "open-ils.actor.history.circ.visible.email",
4269 desc => 'Emails the set of opt-in visible circulations to the requestor',
4271 { desc => 'Authentication token', type => 'string'},
4272 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4273 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4276 desc => q/undef, or event on error/
4281 __PACKAGE__->register_method(
4282 method => "user_visible_circs",
4283 api_name => "open-ils.actor.history.hold.visible",
4286 desc => 'Returns the set of opt-in visible holds',
4288 { desc => 'Authentication token', type => 'string'},
4289 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4290 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4293 desc => q/An object with 1 field: "hold"/,
4299 __PACKAGE__->register_method(
4300 method => "user_visible_circs",
4301 api_name => "open-ils.actor.history.hold.visible.print",
4304 desc => 'Returns printable output for the set of opt-in visible holds',
4306 { desc => 'Authentication token', type => 'string'},
4307 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4308 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4311 desc => q/An action_trigger.event object or error event./,
4317 __PACKAGE__->register_method(
4318 method => "user_visible_circs",
4319 api_name => "open-ils.actor.history.hold.visible.email",
4322 desc => 'Emails the set of opt-in visible holds to the requestor',
4324 { desc => 'Authentication token', type => 'string'},
4325 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4326 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4329 desc => q/undef, or event on error/
4334 sub user_visible_circs {
4335 my($self, $conn, $auth, $user_id, $options) = @_;
4337 my $is_hold = ($self->api_name =~ /hold/);
4338 my $for_print = ($self->api_name =~ /print/);
4339 my $for_email = ($self->api_name =~ /email/);
4340 my $e = new_editor(authtoken => $auth);
4341 return $e->event unless $e->checkauth;
4343 $user_id ||= $e->requestor->id;
4345 $options->{limit} ||= 50;
4346 $options->{offset} ||= 0;
4348 if($user_id != $e->requestor->id) {
4349 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4350 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4351 return $e->event unless $e->allowed($perm, $user->home_ou);
4354 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4356 my $data = $e->json_query({
4357 from => [$db_func, $user_id],
4358 limit => $$options{limit},
4359 offset => $$options{offset}
4361 # TODO: I only want IDs. code below didn't get me there
4362 # {"select":{"au":[{"column":"id", "result_field":"id",
4363 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4368 return undef unless @$data;
4372 # collect the batch of objects
4376 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4377 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4381 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4382 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4385 } elsif ($for_email) {
4387 $conn->respond_complete(1) if $for_email; # no sense in waiting
4395 my $hold = $e->retrieve_action_hold_request($id);
4396 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4397 # events will be fired from action_trigger_runner
4401 my $circ = $e->retrieve_action_circulation($id);
4402 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4403 # events will be fired from action_trigger_runner
4407 } else { # just give me the data please
4415 my $hold = $e->retrieve_action_hold_request($id);
4416 $conn->respond({hold => $hold});
4420 my $circ = $e->retrieve_action_circulation($id);
4423 summary => $U->create_circ_chain_summary($e, $id)
4432 __PACKAGE__->register_method(
4433 method => "user_saved_search_cud",
4434 api_name => "open-ils.actor.user.saved_search.cud",
4437 desc => 'Create/Update/Delete Access to user saved searches',
4439 { desc => 'Authentication token', type => 'string' },
4440 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4443 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4449 __PACKAGE__->register_method(
4450 method => "user_saved_search_cud",
4451 api_name => "open-ils.actor.user.saved_search.retrieve",
4454 desc => 'Retrieve a saved search object',
4456 { desc => 'Authentication token', type => 'string' },
4457 { desc => 'Saved Search ID', type => 'number' }
4460 desc => q/The saved search object, Event on error/,
4466 sub user_saved_search_cud {
4467 my( $self, $client, $auth, $search ) = @_;
4468 my $e = new_editor( authtoken=>$auth );
4469 return $e->die_event unless $e->checkauth;
4471 my $o_search; # prior version of the object, if any
4472 my $res; # to be returned
4474 # branch on the operation type
4476 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4478 # Get the old version, to check ownership
4479 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4480 or return $e->die_event;
4482 # You can't read somebody else's search
4483 return OpenILS::Event->new('BAD_PARAMS')
4484 unless $o_search->owner == $e->requestor->id;
4490 $e->xact_begin; # start an editor transaction
4492 if( $search->isnew ) { # Create
4494 # You can't create a search for somebody else
4495 return OpenILS::Event->new('BAD_PARAMS')
4496 unless $search->owner == $e->requestor->id;
4498 $e->create_actor_usr_saved_search( $search )
4499 or return $e->die_event;
4503 } elsif( $search->ischanged ) { # Update
4505 # You can't change ownership of a search
4506 return OpenILS::Event->new('BAD_PARAMS')
4507 unless $search->owner == $e->requestor->id;
4509 # Get the old version, to check ownership
4510 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4511 or return $e->die_event;
4513 # You can't update somebody else's search
4514 return OpenILS::Event->new('BAD_PARAMS')
4515 unless $o_search->owner == $e->requestor->id;
4518 $e->update_actor_usr_saved_search( $search )
4519 or return $e->die_event;
4523 } elsif( $search->isdeleted ) { # Delete
4525 # Get the old version, to check ownership
4526 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4527 or return $e->die_event;
4529 # You can't delete somebody else's search
4530 return OpenILS::Event->new('BAD_PARAMS')
4531 unless $o_search->owner == $e->requestor->id;
4534 $e->delete_actor_usr_saved_search( $o_search )
4535 or return $e->die_event;
4546 __PACKAGE__->register_method(
4547 method => "get_barcodes",
4548 api_name => "open-ils.actor.get_barcodes"
4552 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4553 my $e = new_editor(authtoken => $auth);
4554 return $e->event unless $e->checkauth;
4555 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4557 my $db_result = $e->json_query(
4559 'evergreen.get_barcodes',
4560 $org_id, $context, $barcode,
4564 if($context =~ /actor/) {
4565 my $filter_result = ();
4567 foreach my $result (@$db_result) {
4568 if($result->{type} eq 'actor') {
4569 if($e->requestor->id != $result->{id}) {
4570 $patron = $e->retrieve_actor_user($result->{id});
4572 push(@$filter_result, $e->event);
4575 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4576 push(@$filter_result, $result);
4579 push(@$filter_result, $e->event);
4583 push(@$filter_result, $result);
4587 push(@$filter_result, $result);
4590 return $filter_result;
4596 __PACKAGE__->register_method(
4597 method => 'address_alert_test',
4598 api_name => 'open-ils.actor.address_alert.test',
4600 desc => "Tests a set of address fields to determine if they match with an address_alert",
4602 {desc => 'Authentication token', type => 'string'},
4603 {desc => 'Org Unit', type => 'number'},
4604 {desc => 'Fields', type => 'hash'},
4606 return => {desc => 'List of matching address_alerts'}
4610 sub address_alert_test {
4611 my ($self, $client, $auth, $org_unit, $fields) = @_;
4612 return [] unless $fields and grep {$_} values %$fields;
4614 my $e = new_editor(authtoken => $auth);
4615 return $e->event unless $e->checkauth;
4616 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4617 $org_unit ||= $e->requestor->ws_ou;
4619 my $alerts = $e->json_query({
4621 'actor.address_alert_matches',
4629 $$fields{post_code},
4630 $$fields{mailing_address},
4631 $$fields{billing_address}
4635 # map the json_query hashes to real objects
4637 map {$e->retrieve_actor_address_alert($_)}
4638 (map {$_->{id}} @$alerts)
4642 __PACKAGE__->register_method(
4643 method => "mark_users_contact_invalid",
4644 api_name => "open-ils.actor.invalidate.email",
4646 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",
4648 {desc => "Authentication token", type => "string"},
4649 {desc => "Patron ID", type => "number"},
4650 {desc => "Additional note text (optional)", type => "string"},
4651 {desc => "penalty org unit ID (optional)", type => "number"}
4653 return => {desc => "Event describing success or failure", type => "object"}
4657 __PACKAGE__->register_method(
4658 method => "mark_users_contact_invalid",
4659 api_name => "open-ils.actor.invalidate.day_phone",
4661 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",
4663 {desc => "Authentication token", type => "string"},
4664 {desc => "Patron ID", type => "number"},
4665 {desc => "Additional note text (optional)", type => "string"},
4666 {desc => "penalty org unit ID (optional)", type => "number"}
4668 return => {desc => "Event describing success or failure", type => "object"}
4672 __PACKAGE__->register_method(
4673 method => "mark_users_contact_invalid",
4674 api_name => "open-ils.actor.invalidate.evening_phone",
4676 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",
4678 {desc => "Authentication token", type => "string"},
4679 {desc => "Patron ID", type => "number"},
4680 {desc => "Additional note text (optional)", type => "string"},
4681 {desc => "penalty org unit ID (optional)", type => "number"}
4683 return => {desc => "Event describing success or failure", type => "object"}
4687 __PACKAGE__->register_method(
4688 method => "mark_users_contact_invalid",
4689 api_name => "open-ils.actor.invalidate.other_phone",
4691 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",
4693 {desc => "Authentication token", type => "string"},
4694 {desc => "Patron ID", type => "number"},
4695 {desc => "Additional note text (optional)", type => "string"},
4696 {desc => "penalty org unit ID (optional, default to top of org tree)",
4699 return => {desc => "Event describing success or failure", type => "object"}
4703 sub mark_users_contact_invalid {
4704 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4706 # This method invalidates an email address or a phone_number which
4707 # removes the bad email address or phone number, copying its contents
4708 # to a patron note, and institutes a standing penalty for "bad email"
4709 # or "bad phone number" which is cleared when the user is saved or
4710 # optionally only when the user is saved with an email address or
4711 # phone number (or staff manually delete the penalty).
4713 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4715 my $e = new_editor(authtoken => $auth, xact => 1);
4716 return $e->die_event unless $e->checkauth;
4718 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4719 $e, $contact_type, {usr => $patron_id},
4720 $addl_note, $penalty_ou, $e->requestor->id
4724 # Putting the following method in open-ils.actor is a bad fit, except in that
4725 # it serves an interface that lives under 'actor' in the templates directory,
4726 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4728 __PACKAGE__->register_method(
4729 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4730 method => "get_all_at_reactors_in_use",
4735 { name => 'authtoken', type => 'string' }
4738 desc => 'list of reactor names', type => 'array'
4743 sub get_all_at_reactors_in_use {
4744 my ($self, $conn, $auth) = @_;
4746 my $e = new_editor(authtoken => $auth);
4747 $e->checkauth or return $e->die_event;
4748 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4750 my $reactors = $e->json_query({
4752 atevdef => [{column => "reactor", transform => "distinct"}]
4754 from => {atevdef => {}}
4757 return $e->die_event unless ref $reactors eq "ARRAY";
4760 return [ map { $_->{reactor} } @$reactors ];
4763 __PACKAGE__->register_method(
4764 method => "filter_group_entry_crud",
4765 api_name => "open-ils.actor.filter_group_entry.crud",
4768 Provides CRUD access to filter group entry objects. These are not full accessible
4769 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4770 are not accessible via PCRUD (because they have no fields against which to link perms)
4773 {desc => "Authentication token", type => "string"},
4774 {desc => "Entry ID / Entry Object", type => "number"},
4775 {desc => "Additional note text (optional)", type => "string"},
4776 {desc => "penalty org unit ID (optional, default to top of org tree)",
4780 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4786 sub filter_group_entry_crud {
4787 my ($self, $conn, $auth, $arg) = @_;
4789 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4790 my $e = new_editor(authtoken => $auth, xact => 1);
4791 return $e->die_event unless $e->checkauth;
4797 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4798 or return $e->die_event;
4800 return $e->die_event unless $e->allowed(
4801 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4803 my $query = $arg->query;
4804 $query = $e->create_actor_search_query($query) or return $e->die_event;
4805 $arg->query($query->id);
4806 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4807 $entry->query($query);
4812 } elsif ($arg->ischanged) {
4814 my $entry = $e->retrieve_actor_search_filter_group_entry([
4817 flesh_fields => {asfge => ['grp']}
4819 ]) or return $e->die_event;
4821 return $e->die_event unless $e->allowed(
4822 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4824 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4825 $arg->query($arg->query->id);
4826 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4827 $arg->query($query);
4832 } elsif ($arg->isdeleted) {
4834 my $entry = $e->retrieve_actor_search_filter_group_entry([
4837 flesh_fields => {asfge => ['grp', 'query']}
4839 ]) or return $e->die_event;
4841 return $e->die_event unless $e->allowed(
4842 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4844 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
4845 $e->delete_actor_search_query($entry->query) or return $e->die_event;
4858 my $entry = $e->retrieve_actor_search_filter_group_entry([
4861 flesh_fields => {asfge => ['grp', 'query']}
4863 ]) or return $e->die_event;
4865 return $e->die_event unless $e->allowed(
4866 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
4867 $entry->grp->owner);
4870 $entry->grp($entry->grp->id); # for consistency