1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use OpenILS::Utils::BadContact;
38 use List::Util qw/max reduce/;
40 use UUID::Tiny qw/:std/;
43 OpenILS::Application::Actor::Container->initialize();
44 OpenILS::Application::Actor::UserGroups->initialize();
45 OpenILS::Application::Actor::ClosedDates->initialize();
48 my $apputils = "OpenILS::Application::AppUtils";
51 sub _d { warn "Patron:\n" . Dumper(shift()); }
54 my $set_user_settings;
58 #__PACKAGE__->register_method(
59 # method => "allowed_test",
60 # api_name => "open-ils.actor.allowed_test",
63 # my($self, $conn, $auth, $orgid, $permcode) = @_;
64 # my $e = new_editor(authtoken => $auth);
65 # return $e->die_event unless $e->checkauth;
69 # permcode => $permcode,
70 # result => $e->allowed($permcode, $orgid)
74 __PACKAGE__->register_method(
75 method => "update_user_setting",
76 api_name => "open-ils.actor.patron.settings.update",
78 sub update_user_setting {
79 my($self, $conn, $auth, $user_id, $settings) = @_;
80 my $e = new_editor(xact => 1, authtoken => $auth);
81 return $e->die_event unless $e->checkauth;
83 $user_id = $e->requestor->id unless defined $user_id;
85 unless($e->requestor->id == $user_id) {
86 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
87 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
90 for my $name (keys %$settings) {
91 my $val = $$settings{$name};
92 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
95 $val = OpenSRF::Utils::JSON->perl2JSON($val);
98 $e->update_actor_user_setting($set) or return $e->die_event;
100 $set = Fieldmapper::actor::user_setting->new;
104 $e->create_actor_user_setting($set) or return $e->die_event;
107 $e->delete_actor_user_setting($set) or return $e->die_event;
116 __PACKAGE__->register_method(
117 method => "set_ou_settings",
118 api_name => "open-ils.actor.org_unit.settings.update",
120 desc => "Updates the value for a given org unit setting. The permission to update " .
121 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
122 "permission specified in the update_perm column of the config.org_unit_setting_type " .
123 "table's row corresponding to the setting being changed." ,
125 {desc => 'Authentication token', type => 'string'},
126 {desc => 'Org unit ID', type => 'number'},
127 {desc => 'Hash of setting name-value pairs', type => 'object'}
129 return => {desc => '1 on success, Event on error'}
133 sub set_ou_settings {
134 my( $self, $client, $auth, $org_id, $settings ) = @_;
136 my $e = new_editor(authtoken => $auth, xact => 1);
137 return $e->die_event unless $e->checkauth;
139 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
141 for my $name (keys %$settings) {
142 my $val = $$settings{$name};
144 my $type = $e->retrieve_config_org_unit_setting_type([
146 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
147 ]) or return $e->die_event;
148 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
150 # If there is no relevant permission, the default assumption will
151 # be, "no, the caller cannot change that value."
152 return $e->die_event unless ($all_allowed ||
153 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
156 $val = OpenSRF::Utils::JSON->perl2JSON($val);
159 $e->update_actor_org_unit_setting($set) or return $e->die_event;
161 $set = Fieldmapper::actor::org_unit_setting->new;
162 $set->org_unit($org_id);
165 $e->create_actor_org_unit_setting($set) or return $e->die_event;
168 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
176 __PACKAGE__->register_method(
177 method => "user_settings",
179 api_name => "open-ils.actor.patron.settings.retrieve",
182 my( $self, $client, $auth, $user_id, $setting ) = @_;
184 my $e = new_editor(authtoken => $auth);
185 return $e->event unless $e->checkauth;
186 $user_id = $e->requestor->id unless defined $user_id;
188 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
189 if($e->requestor->id != $user_id) {
190 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
194 my($e, $user_id, $setting) = @_;
195 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
196 return undef unless $val; # XXX this should really return undef, but needs testing
197 return OpenSRF::Utils::JSON->JSON2perl($val->value);
201 if(ref $setting eq 'ARRAY') {
203 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
206 return get_setting($e, $user_id, $setting);
209 my $s = $e->search_actor_user_setting({usr => $user_id});
210 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
215 __PACKAGE__->register_method(
216 method => "ranged_ou_settings",
217 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
219 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
220 "is implied for retrieving OU settings by the authenticated users' permissions.",
222 {desc => 'Authentication token', type => 'string'},
223 {desc => 'Org unit ID', type => 'number'},
225 return => {desc => 'A hashref of "ranged" settings, event on error'}
228 sub ranged_ou_settings {
229 my( $self, $client, $auth, $org_id ) = @_;
231 my $e = new_editor(authtoken => $auth);
232 return $e->event unless $e->checkauth;
235 my $org_list = $U->get_org_ancestors($org_id);
236 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
237 $org_list = [ reverse @$org_list ];
239 # start at the context org and capture the setting value
240 # without clobbering settings we've already captured
241 for my $this_org_id (@$org_list) {
243 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
245 for my $set (@sets) {
246 my $type = $e->retrieve_config_org_unit_setting_type([
248 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
251 # If there is no relevant permission, the default assumption will
252 # be, "yes, the caller can have that value."
253 if ($type && $type->view_perm) {
254 next if not $e->allowed($type->view_perm->code, $org_id);
257 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
258 unless defined $ranged_settings{$set->name};
262 return \%ranged_settings;
267 __PACKAGE__->register_method(
268 api_name => 'open-ils.actor.ou_setting.ancestor_default',
269 method => 'ou_ancestor_setting',
271 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
272 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
273 'user has permission to view that setting, if there is a permission associated with the setting.' ,
275 { desc => 'Org unit ID', type => 'number' },
276 { desc => 'setting name', type => 'string' },
277 { desc => 'authtoken (optional)', type => 'string' }
279 return => {desc => 'A value for the org unit setting, or undef'}
283 # ------------------------------------------------------------------
284 # Attempts to find the org setting value for a given org. if not
285 # found at the requested org, searches up the org tree until it
286 # finds a parent that has the requested setting.
287 # when found, returns { org => $id, value => $value }
288 # otherwise, returns NULL
289 # ------------------------------------------------------------------
290 sub ou_ancestor_setting {
291 my( $self, $client, $orgid, $name, $auth ) = @_;
292 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
295 __PACKAGE__->register_method(
296 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
297 method => 'ou_ancestor_setting_batch',
299 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
300 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
301 'user has permission to view that setting, if there is a permission associated with the setting.' ,
303 { desc => 'Org unit ID', type => 'number' },
304 { desc => 'setting name list', type => 'array' },
305 { desc => 'authtoken (optional)', type => 'string' }
307 return => {desc => 'A hash with name => value pairs for the org unit settings'}
310 sub ou_ancestor_setting_batch {
311 my( $self, $client, $orgid, $name_list, $auth ) = @_;
313 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
319 __PACKAGE__->register_method(
320 method => "update_patron",
321 api_name => "open-ils.actor.patron.update",
324 Update an existing user, or create a new one. Related objects,
325 like cards, addresses, survey responses, and stat cats,
326 can be updated by attaching them to the user object in their
327 respective fields. For examples, the billing address object
328 may be inserted into the 'billing_address' field, etc. For each
329 attached object, indicate if the object should be created,
330 updated, or deleted using the built-in 'isnew', 'ischanged',
331 and 'isdeleted' fields on the object.
334 { desc => 'Authentication token', type => 'string' },
335 { desc => 'Patron data object', type => 'object' }
337 return => {desc => 'A fleshed user object, event on error'}
342 my( $self, $client, $user_session, $patron ) = @_;
344 my $session = $apputils->start_db_session();
346 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
348 my( $user_obj, $evt ) = $U->checkses($user_session);
351 $evt = check_group_perm($session, $user_obj, $patron);
355 # $new_patron is the patron in progress. $patron is the original patron
356 # passed in with the method. new_patron will change as the components
357 # of patron are added/updated.
361 # unflesh the real items on the patron
362 $patron->card( $patron->card->id ) if(ref($patron->card));
363 $patron->billing_address( $patron->billing_address->id )
364 if(ref($patron->billing_address));
365 $patron->mailing_address( $patron->mailing_address->id )
366 if(ref($patron->mailing_address));
368 # create/update the patron first so we can use his id
370 # $patron is the obj from the client (new data) and $new_patron is the
371 # patron object properly built for db insertion, so we need a third variable
372 # if we want to represent the old patron.
376 if($patron->isnew()) {
377 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
380 $new_patron = $patron;
382 # Did auth checking above already.
384 $old_patron = $e->retrieve_actor_user($patron->id) or
385 return $e->die_event;
389 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
392 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
395 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
398 # re-update the patron if anything has happened to him during this process
399 if($new_patron->ischanged()) {
400 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
404 ( $new_patron, $evt ) = _clear_badcontact_penalties($session, $old_patron, $new_patron, $user_obj);
407 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
410 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
413 $apputils->commit_db_session($session);
415 $evt = apply_invalid_addr_penalty($patron);
418 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
420 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
422 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
425 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
428 sub apply_invalid_addr_penalty {
430 my $e = new_editor(xact => 1);
432 # grab the invalid address penalty if set
433 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
435 my ($addr_penalty) = grep
436 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
438 # do we enforce invalid address penalty
439 my $enforce = $U->ou_ancestor_setting_value(
440 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
442 my $addrs = $e->search_actor_user_address(
443 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
444 my $addr_count = scalar(@$addrs);
446 if($addr_count == 0 and $addr_penalty) {
448 # regardless of any settings, remove the penalty when the user has no invalid addresses
449 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
452 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
454 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
455 my $depth = $ptype->org_depth;
456 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
457 $ctx_org = $patron->home_ou unless defined $ctx_org;
459 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
460 $penalty->usr($patron->id);
461 $penalty->org_unit($ctx_org);
462 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
464 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
483 "standing_penalties",
490 push @$fields, "home_ou" if $home_ou;
491 return new_flesh_user($id, $fields, $e );
499 # clone and clear stuff that would break the database
503 my $new_patron = $patron->clone;
505 $new_patron->clear_billing_address();
506 $new_patron->clear_mailing_address();
507 $new_patron->clear_addresses();
508 $new_patron->clear_card();
509 $new_patron->clear_cards();
510 $new_patron->clear_id();
511 $new_patron->clear_isnew();
512 $new_patron->clear_ischanged();
513 $new_patron->clear_isdeleted();
514 $new_patron->clear_stat_cat_entries();
515 $new_patron->clear_permissions();
516 $new_patron->clear_standing_penalties();
526 my $user_obj = shift;
528 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
529 return (undef, $evt) if $evt;
531 my $ex = $session->request(
532 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
534 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
537 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
539 my $id = $session->request(
540 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
541 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
543 $logger->info("Successfully created new user [$id] in DB");
545 return ( $session->request(
546 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
550 sub check_group_perm {
551 my( $session, $requestor, $patron ) = @_;
554 # first let's see if the requestor has
555 # priveleges to update this user in any way
556 if( ! $patron->isnew ) {
557 my $p = $session->request(
558 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
560 # If we are the requestor (trying to update our own account)
561 # and we are not trying to change our profile, we're good
562 if( $p->id == $requestor->id and
563 $p->profile == $patron->profile ) {
568 $evt = group_perm_failed($session, $requestor, $p);
572 # They are allowed to edit this patron.. can they put the
573 # patron into the group requested?
574 $evt = group_perm_failed($session, $requestor, $patron);
580 sub group_perm_failed {
581 my( $session, $requestor, $patron ) = @_;
585 my $grpid = $patron->profile;
589 $logger->debug("user update looking for group perm for group $grpid");
590 $grp = $session->request(
591 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
592 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
594 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
596 $logger->info("user update checking perm $perm on user ".
597 $requestor->id." for update/create on user username=".$patron->usrname);
599 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
607 my( $session, $patron, $user_obj, $noperm) = @_;
609 $logger->info("Updating patron ".$patron->id." in DB");
614 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
615 return (undef, $evt) if $evt;
618 # update the password by itself to avoid the password protection magic
619 if( $patron->passwd ) {
620 my $s = $session->request(
621 'open-ils.storage.direct.actor.user.remote_update',
622 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
623 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
624 $patron->clear_passwd;
627 if(!$patron->ident_type) {
628 $patron->clear_ident_type;
629 $patron->clear_ident_value;
632 $evt = verify_last_xact($session, $patron);
633 return (undef, $evt) if $evt;
635 my $stat = $session->request(
636 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
637 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
642 sub verify_last_xact {
643 my( $session, $patron ) = @_;
644 return undef unless $patron->id and $patron->id > 0;
645 my $p = $session->request(
646 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
647 my $xact = $p->last_xact_id;
648 return undef unless $xact;
649 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
650 return OpenILS::Event->new('XACT_COLLISION')
651 if $xact ne $patron->last_xact_id;
656 sub _check_dup_ident {
657 my( $session, $patron ) = @_;
659 return undef unless $patron->ident_value;
662 ident_type => $patron->ident_type,
663 ident_value => $patron->ident_value,
666 $logger->debug("patron update searching for dup ident values: " .
667 $patron->ident_type . ':' . $patron->ident_value);
669 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
671 my $dups = $session->request(
672 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
675 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
682 sub _add_update_addresses {
686 my $new_patron = shift;
690 my $current_id; # id of the address before creation
692 my $addresses = $patron->addresses();
694 for my $address (@$addresses) {
696 next unless ref $address;
697 $current_id = $address->id();
699 if( $patron->billing_address() and
700 $patron->billing_address() == $current_id ) {
701 $logger->info("setting billing addr to $current_id");
702 $new_patron->billing_address($address->id());
703 $new_patron->ischanged(1);
706 if( $patron->mailing_address() and
707 $patron->mailing_address() == $current_id ) {
708 $new_patron->mailing_address($address->id());
709 $logger->info("setting mailing addr to $current_id");
710 $new_patron->ischanged(1);
714 if($address->isnew()) {
716 $address->usr($new_patron->id());
718 ($address, $evt) = _add_address($session,$address);
719 return (undef, $evt) if $evt;
721 # we need to get the new id
722 if( $patron->billing_address() and
723 $patron->billing_address() == $current_id ) {
724 $new_patron->billing_address($address->id());
725 $logger->info("setting billing addr to $current_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);
736 } elsif($address->ischanged() ) {
738 ($address, $evt) = _update_address($session, $address);
739 return (undef, $evt) if $evt;
741 } elsif($address->isdeleted() ) {
743 if( $address->id() == $new_patron->mailing_address() ) {
744 $new_patron->clear_mailing_address();
745 ($new_patron, $evt) = _update_patron($session, $new_patron);
746 return (undef, $evt) if $evt;
749 if( $address->id() == $new_patron->billing_address() ) {
750 $new_patron->clear_billing_address();
751 ($new_patron, $evt) = _update_patron($session, $new_patron);
752 return (undef, $evt) if $evt;
755 $evt = _delete_address($session, $address);
756 return (undef, $evt) if $evt;
760 return ( $new_patron, undef );
764 # adds an address to the db and returns the address with new id
766 my($session, $address) = @_;
767 $address->clear_id();
769 $logger->info("Creating new address at street ".$address->street1);
771 # put the address into the database
772 my $id = $session->request(
773 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
774 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
777 return ($address, undef);
781 sub _update_address {
782 my( $session, $address ) = @_;
784 $logger->info("Updating address ".$address->id." in the DB");
786 my $stat = $session->request(
787 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
789 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
790 return ($address, undef);
795 sub _add_update_cards {
799 my $new_patron = shift;
803 my $virtual_id; #id of the card before creation
805 my $cards = $patron->cards();
806 for my $card (@$cards) {
808 $card->usr($new_patron->id());
810 if(ref($card) and $card->isnew()) {
812 $virtual_id = $card->id();
813 ( $card, $evt ) = _add_card($session,$card);
814 return (undef, $evt) if $evt;
816 #if(ref($patron->card)) { $patron->card($patron->card->id); }
817 if($patron->card() == $virtual_id) {
818 $new_patron->card($card->id());
819 $new_patron->ischanged(1);
822 } elsif( ref($card) and $card->ischanged() ) {
823 $evt = _update_card($session, $card);
824 return (undef, $evt) if $evt;
828 return ( $new_patron, undef );
832 # adds an card to the db and returns the card with new id
834 my( $session, $card ) = @_;
837 $logger->info("Adding new patron card ".$card->barcode);
839 my $id = $session->request(
840 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
841 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
842 $logger->info("Successfully created patron card $id");
845 return ( $card, undef );
849 # returns event on error. returns undef otherwise
851 my( $session, $card ) = @_;
852 $logger->info("Updating patron card ".$card->id);
854 my $stat = $session->request(
855 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
856 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
863 # returns event on error. returns undef otherwise
864 sub _delete_address {
865 my( $session, $address ) = @_;
867 $logger->info("Deleting address ".$address->id." from DB");
869 my $stat = $session->request(
870 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
872 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
878 sub _add_survey_responses {
879 my ($session, $patron, $new_patron) = @_;
881 $logger->info( "Updating survey responses for patron ".$new_patron->id );
883 my $responses = $patron->survey_responses;
887 $_->usr($new_patron->id) for (@$responses);
889 my $evt = $U->simplereq( "open-ils.circ",
890 "open-ils.circ.survey.submit.user_id", $responses );
892 return (undef, $evt) if defined($U->event_code($evt));
896 return ( $new_patron, undef );
899 sub _clear_badcontact_penalties {
900 my ($session, $old_patron, $new_patron, $user_obj) = @_;
902 return ($new_patron, undef) unless $old_patron;
904 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
905 my $e = new_editor(xact => 1);
907 # This ignores whether the caller of update_patron has any permission
908 # to remove penalties, but these penalties no longer make sense
909 # if an email address field (for example) is changed (and the caller must
910 # have perms to do *that*) so there's no reason not to clear the penalties.
912 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
914 "+csp" => {"name" => [values(%$PNM)]},
915 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
917 "join" => {"csp" => {}},
919 "flesh_fields" => {"ausp" => ["standing_penalty"]}
921 ]) or return (undef, $e->die_event);
923 return ($new_patron, undef) unless @$bad_contact_penalties;
925 my @penalties_to_clear;
926 my ($field, $penalty_name);
928 # For each field that might have an associated bad contact penalty,
929 # check for such penalties and add them to the to-clear list if that
931 while (($field, $penalty_name) = each(%$PNM)) {
932 if ($old_patron->$field ne $new_patron->$field) {
933 push @penalties_to_clear, grep {
934 $_->standing_penalty->name eq $penalty_name
935 } @$bad_contact_penalties;
939 foreach (@penalties_to_clear) {
940 # Note that this "archives" penalties, in the terminology of the staff
941 # client, instead of just deleting them. This may assist reporting,
942 # or preserving old contact information when it is still potentially
944 $_->standing_penalty($_->standing_penalty->id); # deflesh
945 $_->stop_date('now');
946 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
950 return ($new_patron, undef);
954 sub _create_stat_maps {
956 my($session, $user_session, $patron, $new_patron) = @_;
958 my $maps = $patron->stat_cat_entries();
960 for my $map (@$maps) {
962 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
964 if ($map->isdeleted()) {
965 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
967 } elsif ($map->isnew()) {
968 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
973 $map->target_usr($new_patron->id);
976 $logger->info("Updating stat entry with method $method and map $map");
978 my $stat = $session->request($method, $map)->gather(1);
979 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
983 return ($new_patron, undef);
986 sub _create_perm_maps {
988 my($session, $user_session, $patron, $new_patron) = @_;
990 my $maps = $patron->permissions;
992 for my $map (@$maps) {
994 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
995 if ($map->isdeleted()) {
996 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
997 } elsif ($map->isnew()) {
998 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1003 $map->usr($new_patron->id);
1005 #warn( "Updating permissions with method $method and session $user_session and map $map" );
1006 $logger->info( "Updating permissions with method $method and map $map" );
1008 my $stat = $session->request($method, $map)->gather(1);
1009 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
1013 return ($new_patron, undef);
1017 __PACKAGE__->register_method(
1018 method => "set_user_work_ous",
1019 api_name => "open-ils.actor.user.work_ous.update",
1022 sub set_user_work_ous {
1028 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1029 return $evt if $evt;
1031 my $session = $apputils->start_db_session();
1033 for my $map (@$maps) {
1035 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1036 if ($map->isdeleted()) {
1037 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1038 } elsif ($map->isnew()) {
1039 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1043 #warn( "Updating permissions with method $method and session $ses and map $map" );
1044 $logger->info( "Updating work_ou map with method $method and map $map" );
1046 my $stat = $session->request($method, $map)->gather(1);
1047 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1051 $apputils->commit_db_session($session);
1053 return scalar(@$maps);
1057 __PACKAGE__->register_method(
1058 method => "set_user_perms",
1059 api_name => "open-ils.actor.user.permissions.update",
1062 sub set_user_perms {
1068 my $session = $apputils->start_db_session();
1070 my( $user_obj, $evt ) = $U->checkses($ses);
1071 return $evt if $evt;
1073 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1076 $all = 1 if ($U->is_true($user_obj->super_user()));
1077 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1079 for my $map (@$maps) {
1081 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1082 if ($map->isdeleted()) {
1083 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1084 } elsif ($map->isnew()) {
1085 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1089 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1090 #warn( "Updating permissions with method $method and session $ses and map $map" );
1091 $logger->info( "Updating permissions with method $method and map $map" );
1093 my $stat = $session->request($method, $map)->gather(1);
1094 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1098 $apputils->commit_db_session($session);
1100 return scalar(@$maps);
1104 __PACKAGE__->register_method(
1105 method => "user_retrieve_by_barcode",
1107 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1109 sub user_retrieve_by_barcode {
1110 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1112 my $e = new_editor(authtoken => $auth);
1113 return $e->event unless $e->checkauth;
1115 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1116 or return $e->event;
1118 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1119 return $e->event unless $e->allowed(
1120 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1127 __PACKAGE__->register_method(
1128 method => "get_user_by_id",
1130 api_name => "open-ils.actor.user.retrieve",
1133 sub get_user_by_id {
1134 my ($self, $client, $auth, $id) = @_;
1135 my $e = new_editor(authtoken=>$auth);
1136 return $e->event unless $e->checkauth;
1137 my $user = $e->retrieve_actor_user($id) or return $e->event;
1138 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1143 __PACKAGE__->register_method(
1144 method => "get_org_types",
1145 api_name => "open-ils.actor.org_types.retrieve",
1148 return $U->get_org_types();
1152 __PACKAGE__->register_method(
1153 method => "get_user_ident_types",
1154 api_name => "open-ils.actor.user.ident_types.retrieve",
1157 sub get_user_ident_types {
1158 return $ident_types if $ident_types;
1159 return $ident_types =
1160 new_editor()->retrieve_all_config_identification_type();
1164 __PACKAGE__->register_method(
1165 method => "get_org_unit",
1166 api_name => "open-ils.actor.org_unit.retrieve",
1170 my( $self, $client, $user_session, $org_id ) = @_;
1171 my $e = new_editor(authtoken => $user_session);
1173 return $e->event unless $e->checkauth;
1174 $org_id = $e->requestor->ws_ou;
1176 my $o = $e->retrieve_actor_org_unit($org_id)
1177 or return $e->event;
1181 __PACKAGE__->register_method(
1182 method => "search_org_unit",
1183 api_name => "open-ils.actor.org_unit_list.search",
1186 sub search_org_unit {
1188 my( $self, $client, $field, $value ) = @_;
1190 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1192 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1193 { $field => $value } );
1199 # build the org tree
1201 __PACKAGE__->register_method(
1202 method => "get_org_tree",
1203 api_name => "open-ils.actor.org_tree.retrieve",
1205 note => "Returns the entire org tree structure",
1211 return $U->get_org_tree($client->session->session_locale);
1215 __PACKAGE__->register_method(
1216 method => "get_org_descendants",
1217 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1220 # depth is optional. org_unit is the id
1221 sub get_org_descendants {
1222 my( $self, $client, $org_unit, $depth ) = @_;
1224 if(ref $org_unit eq 'ARRAY') {
1227 for my $i (0..scalar(@$org_unit)-1) {
1228 my $list = $U->simple_scalar_request(
1230 "open-ils.storage.actor.org_unit.descendants.atomic",
1231 $org_unit->[$i], $depth->[$i] );
1232 push(@trees, $U->build_org_tree($list));
1237 my $orglist = $apputils->simple_scalar_request(
1239 "open-ils.storage.actor.org_unit.descendants.atomic",
1240 $org_unit, $depth );
1241 return $U->build_org_tree($orglist);
1246 __PACKAGE__->register_method(
1247 method => "get_org_ancestors",
1248 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1251 # depth is optional. org_unit is the id
1252 sub get_org_ancestors {
1253 my( $self, $client, $org_unit, $depth ) = @_;
1254 my $orglist = $apputils->simple_scalar_request(
1256 "open-ils.storage.actor.org_unit.ancestors.atomic",
1257 $org_unit, $depth );
1258 return $U->build_org_tree($orglist);
1262 __PACKAGE__->register_method(
1263 method => "get_standings",
1264 api_name => "open-ils.actor.standings.retrieve"
1269 return $user_standings if $user_standings;
1270 return $user_standings =
1271 $apputils->simple_scalar_request(
1273 "open-ils.cstore.direct.config.standing.search.atomic",
1274 { id => { "!=" => undef } }
1279 __PACKAGE__->register_method(
1280 method => "get_my_org_path",
1281 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1284 sub get_my_org_path {
1285 my( $self, $client, $auth, $org_id ) = @_;
1286 my $e = new_editor(authtoken=>$auth);
1287 return $e->event unless $e->checkauth;
1288 $org_id = $e->requestor->ws_ou unless defined $org_id;
1290 return $apputils->simple_scalar_request(
1292 "open-ils.storage.actor.org_unit.full_path.atomic",
1297 __PACKAGE__->register_method(
1298 method => "patron_adv_search",
1299 api_name => "open-ils.actor.patron.search.advanced"
1301 sub patron_adv_search {
1302 my( $self, $client, $auth, $search_hash,
1303 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1305 my $e = new_editor(authtoken=>$auth);
1306 return $e->event unless $e->checkauth;
1307 return $e->event unless $e->allowed('VIEW_USER');
1309 # depth boundary outside of which patrons must opt-in, default to 0
1310 my $opt_boundary = 0;
1311 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1313 return $U->storagereq(
1314 "open-ils.storage.actor.user.crazy_search", $search_hash,
1315 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1319 __PACKAGE__->register_method(
1320 method => "update_passwd",
1321 api_name => "open-ils.actor.user.password.update",
1323 desc => "Update the operator's password",
1325 { desc => 'Authentication token', type => 'string' },
1326 { desc => 'New password', type => 'string' },
1327 { desc => 'Current password', type => 'string' }
1329 return => {desc => '1 on success, Event on error or incorrect current password'}
1333 __PACKAGE__->register_method(
1334 method => "update_passwd",
1335 api_name => "open-ils.actor.user.username.update",
1337 desc => "Update the operator's username",
1339 { desc => 'Authentication token', type => 'string' },
1340 { desc => 'New username', type => 'string' },
1341 { desc => 'Current password', type => 'string' }
1343 return => {desc => '1 on success, Event on error or incorrect current password'}
1347 __PACKAGE__->register_method(
1348 method => "update_passwd",
1349 api_name => "open-ils.actor.user.email.update",
1351 desc => "Update the operator's email address",
1353 { desc => 'Authentication token', type => 'string' },
1354 { desc => 'New email address', type => 'string' },
1355 { desc => 'Current password', type => 'string' }
1357 return => {desc => '1 on success, Event on error or incorrect current password'}
1362 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1363 my $e = new_editor(xact=>1, authtoken=>$auth);
1364 return $e->die_event unless $e->checkauth;
1366 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1367 or return $e->die_event;
1368 my $api = $self->api_name;
1370 # make sure the original password matches the in-database password
1371 if (md5_hex($orig_pw) ne $db_user->passwd) {
1373 return new OpenILS::Event('INCORRECT_PASSWORD');
1376 if( $api =~ /password/o ) {
1378 $db_user->passwd($new_val);
1382 # if we don't clear the password, the user will be updated with
1383 # a hashed version of the hashed version of their password
1384 $db_user->clear_passwd;
1386 if( $api =~ /username/o ) {
1388 # make sure no one else has this username
1389 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1392 return new OpenILS::Event('USERNAME_EXISTS');
1394 $db_user->usrname($new_val);
1396 } elsif( $api =~ /email/o ) {
1397 $db_user->email($new_val);
1401 $e->update_actor_user($db_user) or return $e->die_event;
1404 # update the cached user to pick up these changes
1405 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1411 __PACKAGE__->register_method(
1412 method => "check_user_perms",
1413 api_name => "open-ils.actor.user.perm.check",
1414 notes => <<" NOTES");
1415 Takes a login session, user id, an org id, and an array of perm type strings. For each
1416 perm type, if the user does *not* have the given permission it is added
1417 to a list which is returned from the method. If all permissions
1418 are allowed, an empty list is returned
1419 if the logged in user does not match 'user_id', then the logged in user must
1420 have VIEW_PERMISSION priveleges.
1423 sub check_user_perms {
1424 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1426 my( $staff, $evt ) = $apputils->checkses($login_session);
1427 return $evt if $evt;
1429 if($staff->id ne $user_id) {
1430 if( $evt = $apputils->check_perms(
1431 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1437 for my $perm (@$perm_types) {
1438 if($apputils->check_perms($user_id, $org_id, $perm)) {
1439 push @not_allowed, $perm;
1443 return \@not_allowed
1446 __PACKAGE__->register_method(
1447 method => "check_user_perms2",
1448 api_name => "open-ils.actor.user.perm.check.multi_org",
1450 Checks the permissions on a list of perms and orgs for a user
1451 @param authtoken The login session key
1452 @param user_id The id of the user to check
1453 @param orgs The array of org ids
1454 @param perms The array of permission names
1455 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1456 if the logged in user does not match 'user_id', then the logged in user must
1457 have VIEW_PERMISSION priveleges.
1460 sub check_user_perms2 {
1461 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1463 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1464 $authtoken, $user_id, 'VIEW_PERMISSION' );
1465 return $evt if $evt;
1468 for my $org (@$orgs) {
1469 for my $perm (@$perms) {
1470 if($apputils->check_perms($user_id, $org, $perm)) {
1471 push @not_allowed, [ $org, $perm ];
1476 return \@not_allowed
1480 __PACKAGE__->register_method(
1481 method => 'check_user_perms3',
1482 api_name => 'open-ils.actor.user.perm.highest_org',
1484 Returns the highest org unit id at which a user has a given permission
1485 If the requestor does not match the target user, the requestor must have
1486 'VIEW_PERMISSION' rights at the home org unit of the target user
1487 @param authtoken The login session key
1488 @param userid The id of the user in question
1489 @param perm The permission to check
1490 @return The org unit highest in the org tree within which the user has
1491 the requested permission
1494 sub check_user_perms3 {
1495 my($self, $client, $authtoken, $user_id, $perm) = @_;
1496 my $e = new_editor(authtoken=>$authtoken);
1497 return $e->event unless $e->checkauth;
1499 my $tree = $U->get_org_tree();
1501 unless($e->requestor->id == $user_id) {
1502 my $user = $e->retrieve_actor_user($user_id)
1503 or return $e->event;
1504 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1505 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1508 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1511 __PACKAGE__->register_method(
1512 method => 'user_has_work_perm_at',
1513 api_name => 'open-ils.actor.user.has_work_perm_at',
1517 Returns a set of org unit IDs which represent the highest orgs in
1518 the org tree where the user has the requested permission. The
1519 purpose of this method is to return the smallest set of org units
1520 which represent the full expanse of the user's ability to perform
1521 the requested action. The user whose perms this method should
1522 check is implied by the authtoken. /,
1524 {desc => 'authtoken', type => 'string'},
1525 {desc => 'permission name', type => 'string'},
1526 {desc => q/user id, optional. If present, check perms for
1527 this user instead of the logged in user/, type => 'number'},
1529 return => {desc => 'An array of org IDs'}
1533 sub user_has_work_perm_at {
1534 my($self, $conn, $auth, $perm, $user_id) = @_;
1535 my $e = new_editor(authtoken=>$auth);
1536 return $e->event unless $e->checkauth;
1537 if(defined $user_id) {
1538 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1539 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1541 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1544 __PACKAGE__->register_method(
1545 method => 'user_has_work_perm_at_batch',
1546 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1550 sub user_has_work_perm_at_batch {
1551 my($self, $conn, $auth, $perms, $user_id) = @_;
1552 my $e = new_editor(authtoken=>$auth);
1553 return $e->event unless $e->checkauth;
1554 if(defined $user_id) {
1555 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1556 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1559 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1565 __PACKAGE__->register_method(
1566 method => 'check_user_perms4',
1567 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1569 Returns the highest org unit id at which a user has a given permission
1570 If the requestor does not match the target user, the requestor must have
1571 'VIEW_PERMISSION' rights at the home org unit of the target user
1572 @param authtoken The login session key
1573 @param userid The id of the user in question
1574 @param perms An array of perm names to check
1575 @return An array of orgId's representing the org unit
1576 highest in the org tree within which the user has the requested permission
1577 The arrah of orgId's has matches the order of the perms array
1580 sub check_user_perms4 {
1581 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1583 my( $staff, $target, $org, $evt );
1585 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1586 $authtoken, $userid, 'VIEW_PERMISSION' );
1587 return $evt if $evt;
1590 return [] unless ref($perms);
1591 my $tree = $U->get_org_tree();
1593 for my $p (@$perms) {
1594 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1600 __PACKAGE__->register_method(
1601 method => "user_fines_summary",
1602 api_name => "open-ils.actor.user.fines.summary",
1605 desc => 'Returns a short summary of the users total open fines, ' .
1606 'excluding voided fines Params are login_session, user_id' ,
1608 {desc => 'Authentication token', type => 'string'},
1609 {desc => 'User ID', type => 'string'} # number?
1612 desc => "a 'mous' object, event on error",
1617 sub user_fines_summary {
1618 my( $self, $client, $auth, $user_id ) = @_;
1620 my $e = new_editor(authtoken=>$auth);
1621 return $e->event unless $e->checkauth;
1623 if( $user_id ne $e->requestor->id ) {
1624 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1625 return $e->event unless
1626 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1629 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1633 __PACKAGE__->register_method(
1634 method => "user_opac_vitals",
1635 api_name => "open-ils.actor.user.opac.vital_stats",
1639 desc => 'Returns a short summary of the users vital stats, including ' .
1640 'identification information, accumulated balance, number of holds, ' .
1641 'and current open circulation stats' ,
1643 {desc => 'Authentication token', type => 'string'},
1644 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1647 desc => "An object with four properties: user, fines, checkouts and holds."
1652 sub user_opac_vitals {
1653 my( $self, $client, $auth, $user_id ) = @_;
1655 my $e = new_editor(authtoken=>$auth);
1656 return $e->event unless $e->checkauth;
1658 $user_id ||= $e->requestor->id;
1660 my $user = $e->retrieve_actor_user( $user_id );
1663 ->method_lookup('open-ils.actor.user.fines.summary')
1664 ->run($auth => $user_id);
1665 return $fines if (defined($U->event_code($fines)));
1668 $fines = new Fieldmapper::money::open_user_summary ();
1669 $fines->balance_owed(0.00);
1670 $fines->total_owed(0.00);
1671 $fines->total_paid(0.00);
1672 $fines->usr($user_id);
1676 ->method_lookup('open-ils.actor.user.hold_requests.count')
1677 ->run($auth => $user_id);
1678 return $holds if (defined($U->event_code($holds)));
1681 ->method_lookup('open-ils.actor.user.checked_out.count')
1682 ->run($auth => $user_id);
1683 return $out if (defined($U->event_code($out)));
1685 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1689 first_given_name => $user->first_given_name,
1690 second_given_name => $user->second_given_name,
1691 family_name => $user->family_name,
1692 alias => $user->alias,
1693 usrname => $user->usrname
1695 fines => $fines->to_bare_hash,
1702 ##### a small consolidation of related method registrations
1703 my $common_params = [
1704 { desc => 'Authentication token', type => 'string' },
1705 { desc => 'User ID', type => 'string' },
1706 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1707 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1710 'open-ils.actor.user.transactions' => '',
1711 'open-ils.actor.user.transactions.fleshed' => '',
1712 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1713 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1714 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1715 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1718 foreach (keys %methods) {
1720 method => "user_transactions",
1723 desc => 'For a given user, retrieve a list of '
1724 . (/\.fleshed/ ? 'fleshed ' : '')
1725 . 'transactions' . $methods{$_}
1726 . ' optionally limited to transactions of a given type.',
1727 params => $common_params,
1729 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1730 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1734 $args{authoritative} = 1;
1735 __PACKAGE__->register_method(%args);
1738 # Now for the counts
1740 'open-ils.actor.user.transactions.count' => '',
1741 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1742 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1745 foreach (keys %methods) {
1747 method => "user_transactions",
1750 desc => 'For a given user, retrieve a count of open '
1751 . 'transactions' . $methods{$_}
1752 . ' optionally limited to transactions of a given type.',
1753 params => $common_params,
1754 return => { desc => "Integer count of transactions, or event on error" }
1757 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1758 __PACKAGE__->register_method(%args);
1761 __PACKAGE__->register_method(
1762 method => "user_transactions",
1763 api_name => "open-ils.actor.user.transactions.have_balance.total",
1766 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1767 . ' optionally limited to transactions of a given type.',
1768 params => $common_params,
1769 return => { desc => "Decimal balance value, or event on error" }
1774 sub user_transactions {
1775 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1778 my $e = new_editor(authtoken => $auth);
1779 return $e->event unless $e->checkauth;
1781 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1783 return $e->event unless
1784 $e->requestor->id == $user_id or
1785 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1787 my $api = $self->api_name();
1789 my $filter = ($api =~ /have_balance/o) ?
1790 { 'balance_owed' => { '<>' => 0 } }:
1791 { 'total_owed' => { '>' => 0 } };
1793 my $method = 'open-ils.actor.user.transactions.history.still_open';
1794 $method = "$method.authoritative" if $api =~ /authoritative/;
1795 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1797 if($api =~ /total/o) {
1799 $total += $_->balance_owed for @$trans;
1803 ($api =~ /count/o ) and return scalar @$trans;
1804 ($api !~ /fleshed/o) and return $trans;
1807 for my $t (@$trans) {
1809 if( $t->xact_type ne 'circulation' ) {
1810 push @resp, {transaction => $t};
1814 my $circ_data = flesh_circ($e, $t->id);
1815 push @resp, {transaction => $t, %$circ_data};
1822 __PACKAGE__->register_method(
1823 method => "user_transaction_retrieve",
1824 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1827 notes => "Returns a fleshed transaction record"
1830 __PACKAGE__->register_method(
1831 method => "user_transaction_retrieve",
1832 api_name => "open-ils.actor.user.transaction.retrieve",
1835 notes => "Returns a transaction record"
1838 sub user_transaction_retrieve {
1839 my($self, $client, $auth, $bill_id) = @_;
1841 my $e = new_editor(authtoken => $auth);
1842 return $e->event unless $e->checkauth;
1844 my $trans = $e->retrieve_money_billable_transaction_summary(
1845 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1847 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1849 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1851 return $trans unless $self->api_name =~ /flesh/;
1852 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1854 my $circ_data = flesh_circ($e, $trans->id, 1);
1856 return {transaction => $trans, %$circ_data};
1861 my $circ_id = shift;
1862 my $flesh_copy = shift;
1864 my $circ = $e->retrieve_action_circulation([
1868 circ => ['target_copy'],
1869 acp => ['call_number'],
1876 my $copy = $circ->target_copy;
1878 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1879 $mods = new Fieldmapper::metabib::virtual_record;
1880 $mods->doc_id(OILS_PRECAT_RECORD);
1881 $mods->title($copy->dummy_title);
1882 $mods->author($copy->dummy_author);
1885 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1889 $circ->target_copy($circ->target_copy->id);
1890 $copy->call_number($copy->call_number->id);
1892 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1896 __PACKAGE__->register_method(
1897 method => "hold_request_count",
1898 api_name => "open-ils.actor.user.hold_requests.count",
1901 notes => 'Returns hold ready/total counts'
1904 sub hold_request_count {
1905 my( $self, $client, $authtoken, $user_id ) = @_;
1906 my $e = new_editor(authtoken => $authtoken);
1907 return $e->event unless $e->checkauth;
1909 $user_id = $e->requestor->id unless defined $user_id;
1911 if($e->requestor->id ne $user_id) {
1912 my $user = $e->retrieve_actor_user($user_id);
1913 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1916 my $holds = $e->json_query({
1917 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1921 fulfillment_time => {"=" => undef },
1922 cancel_time => undef,
1927 total => scalar(@$holds),
1930 $_->{current_shelf_lib} and # avoid undef warnings
1931 $_->{pickup_lib} eq $_->{current_shelf_lib}
1937 __PACKAGE__->register_method(
1938 method => "checked_out",
1939 api_name => "open-ils.actor.user.checked_out",
1943 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1944 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1945 . "(i.e., outstanding balance or some other pending action on the circ). "
1946 . "The .count method also includes a 'total' field which sums all open circs.",
1948 { desc => 'Authentication Token', type => 'string'},
1949 { desc => 'User ID', type => 'string'},
1952 desc => 'Returns event on error, or an object with ID lists, like: '
1953 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1958 __PACKAGE__->register_method(
1959 method => "checked_out",
1960 api_name => "open-ils.actor.user.checked_out.count",
1963 signature => q/@see open-ils.actor.user.checked_out/
1967 my( $self, $conn, $auth, $userid ) = @_;
1969 my $e = new_editor(authtoken=>$auth);
1970 return $e->event unless $e->checkauth;
1972 if( $userid ne $e->requestor->id ) {
1973 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1974 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1976 # see if there is a friend link allowing circ.view perms
1977 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1978 $e, $userid, $e->requestor->id, 'circ.view');
1979 return $e->event unless $allowed;
1983 my $count = $self->api_name =~ /count/;
1984 return _checked_out( $count, $e, $userid );
1988 my( $iscount, $e, $userid ) = @_;
1994 claims_returned => [],
1997 my $meth = 'retrieve_action_open_circ_';
2005 claims_returned => 0,
2012 my $data = $e->$meth($userid);
2016 $result{$_} += $data->$_() for (keys %result);
2017 $result{total} += $data->$_() for (keys %result);
2019 for my $k (keys %result) {
2020 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2030 __PACKAGE__->register_method(
2031 method => "checked_in_with_fines",
2032 api_name => "open-ils.actor.user.checked_in_with_fines",
2035 signature => q/@see open-ils.actor.user.checked_out/
2038 sub checked_in_with_fines {
2039 my( $self, $conn, $auth, $userid ) = @_;
2041 my $e = new_editor(authtoken=>$auth);
2042 return $e->event unless $e->checkauth;
2044 if( $userid ne $e->requestor->id ) {
2045 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2048 # money is owed on these items and they are checked in
2049 my $open = $e->search_action_circulation(
2052 xact_finish => undef,
2053 checkin_time => { "!=" => undef },
2058 my( @lost, @cr, @lo );
2059 for my $c (@$open) {
2060 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2061 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2062 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2067 claims_returned => \@cr,
2068 long_overdue => \@lo
2074 my ($api, $desc, $auth) = @_;
2075 $desc = $desc ? (" " . $desc) : '';
2076 my $ids = ($api =~ /ids$/) ? 1 : 0;
2079 method => "user_transaction_history",
2080 api_name => "open-ils.actor.user.transactions.$api",
2082 desc => "For a given User ID, returns a list of billable transaction" .
2083 ($ids ? " id" : '') .
2084 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2085 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2087 {desc => 'Authentication token', type => 'string'},
2088 {desc => 'User ID', type => 'number'},
2089 {desc => 'Transaction type (optional)', type => 'number'},
2090 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2093 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2097 $auth and push @sig, (authoritative => 1);
2101 my %auth_hist_methods = (
2103 'history.have_charge' => 'that have an initial charge',
2104 'history.still_open' => 'that are not finished',
2105 'history.have_balance' => 'that have a balance',
2106 'history.have_bill' => 'that have billings',
2107 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2108 'history.have_payment' => 'that have at least 1 payment',
2111 foreach (keys %auth_hist_methods) {
2112 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2113 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2114 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2117 sub user_transaction_history {
2118 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2122 my $e = new_editor(authtoken=>$auth);
2123 return $e->die_event unless $e->checkauth;
2125 if ($e->requestor->id ne $userid) {
2126 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2129 my $api = $self->api_name;
2130 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2132 if(defined($type)) {
2133 $filter->{'xact_type'} = $type;
2136 if($api =~ /have_bill_or_payment/o) {
2138 # transactions that have a non-zero sum across all billings or at least 1 payment
2139 $filter->{'-or'} = {
2140 'balance_owed' => { '<>' => 0 },
2141 'last_payment_ts' => { '<>' => undef }
2144 } elsif($api =~ /have_payment/) {
2146 $filter->{last_payment_ts} ||= {'<>' => undef};
2148 } elsif( $api =~ /have_balance/o) {
2150 # transactions that have a non-zero overall balance
2151 $filter->{'balance_owed'} = { '<>' => 0 };
2153 } elsif( $api =~ /have_charge/o) {
2155 # transactions that have at least 1 billing, regardless of whether it was voided
2156 $filter->{'last_billing_ts'} = { '<>' => undef };
2158 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2160 # transactions that have non-zero sum across all billings. This will exclude
2161 # xacts where all billings have been voided
2162 $filter->{'total_owed'} = { '<>' => 0 };
2165 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2166 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2167 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2169 my $mbts = $e->search_money_billable_transaction_summary(
2170 [ { usr => $userid, @xact_finish, %$filter },
2175 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2176 return $mbts unless $api =~ /fleshed/;
2179 for my $t (@$mbts) {
2181 if( $t->xact_type ne 'circulation' ) {
2182 push @resp, {transaction => $t};
2186 my $circ_data = flesh_circ($e, $t->id);
2187 push @resp, {transaction => $t, %$circ_data};
2195 __PACKAGE__->register_method(
2196 method => "user_perms",
2197 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2199 notes => "Returns a list of permissions"
2203 my( $self, $client, $authtoken, $user ) = @_;
2205 my( $staff, $evt ) = $apputils->checkses($authtoken);
2206 return $evt if $evt;
2208 $user ||= $staff->id;
2210 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2214 return $apputils->simple_scalar_request(
2216 "open-ils.storage.permission.user_perms.atomic",
2220 __PACKAGE__->register_method(
2221 method => "retrieve_perms",
2222 api_name => "open-ils.actor.permissions.retrieve",
2223 notes => "Returns a list of permissions"
2225 sub retrieve_perms {
2226 my( $self, $client ) = @_;
2227 return $apputils->simple_scalar_request(
2229 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2230 { id => { '!=' => undef } }
2234 __PACKAGE__->register_method(
2235 method => "retrieve_groups",
2236 api_name => "open-ils.actor.groups.retrieve",
2237 notes => "Returns a list of user groups"
2239 sub retrieve_groups {
2240 my( $self, $client ) = @_;
2241 return new_editor()->retrieve_all_permission_grp_tree();
2244 __PACKAGE__->register_method(
2245 method => "retrieve_org_address",
2246 api_name => "open-ils.actor.org_unit.address.retrieve",
2247 notes => <<' NOTES');
2248 Returns an org_unit address by ID
2249 @param An org_address ID
2251 sub retrieve_org_address {
2252 my( $self, $client, $id ) = @_;
2253 return $apputils->simple_scalar_request(
2255 "open-ils.cstore.direct.actor.org_address.retrieve",
2260 __PACKAGE__->register_method(
2261 method => "retrieve_groups_tree",
2262 api_name => "open-ils.actor.groups.tree.retrieve",
2263 notes => "Returns a list of user groups"
2266 sub retrieve_groups_tree {
2267 my( $self, $client ) = @_;
2268 return new_editor()->search_permission_grp_tree(
2273 flesh_fields => { pgt => ["children"] },
2274 order_by => { pgt => 'name'}
2281 __PACKAGE__->register_method(
2282 method => "add_user_to_groups",
2283 api_name => "open-ils.actor.user.set_groups",
2284 notes => "Adds a user to one or more permission groups"
2287 sub add_user_to_groups {
2288 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2290 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2291 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2292 return $evt if $evt;
2294 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2295 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2296 return $evt if $evt;
2298 $apputils->simplereq(
2300 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2302 for my $group (@$groups) {
2303 my $link = Fieldmapper::permission::usr_grp_map->new;
2305 $link->usr($userid);
2307 my $id = $apputils->simplereq(
2309 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2315 __PACKAGE__->register_method(
2316 method => "get_user_perm_groups",
2317 api_name => "open-ils.actor.user.get_groups",
2318 notes => "Retrieve a user's permission groups."
2322 sub get_user_perm_groups {
2323 my( $self, $client, $authtoken, $userid ) = @_;
2325 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2326 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2327 return $evt if $evt;
2329 return $apputils->simplereq(
2331 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2335 __PACKAGE__->register_method(
2336 method => "get_user_work_ous",
2337 api_name => "open-ils.actor.user.get_work_ous",
2338 notes => "Retrieve a user's work org units."
2341 __PACKAGE__->register_method(
2342 method => "get_user_work_ous",
2343 api_name => "open-ils.actor.user.get_work_ous.ids",
2344 notes => "Retrieve a user's work org units."
2347 sub get_user_work_ous {
2348 my( $self, $client, $auth, $userid ) = @_;
2349 my $e = new_editor(authtoken=>$auth);
2350 return $e->event unless $e->checkauth;
2351 $userid ||= $e->requestor->id;
2353 if($e->requestor->id != $userid) {
2354 my $user = $e->retrieve_actor_user($userid)
2355 or return $e->event;
2356 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2359 return $e->search_permission_usr_work_ou_map({usr => $userid})
2360 unless $self->api_name =~ /.ids$/;
2362 # client just wants a list of org IDs
2363 return $U->get_user_work_ou_ids($e, $userid);
2368 __PACKAGE__->register_method(
2369 method => 'register_workstation',
2370 api_name => 'open-ils.actor.workstation.register.override',
2371 signature => q/@see open-ils.actor.workstation.register/
2374 __PACKAGE__->register_method(
2375 method => 'register_workstation',
2376 api_name => 'open-ils.actor.workstation.register',
2378 Registers a new workstion in the system
2379 @param authtoken The login session key
2380 @param name The name of the workstation id
2381 @param owner The org unit that owns this workstation
2382 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2383 if the name is already in use.
2387 sub register_workstation {
2388 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2390 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2391 return $e->die_event unless $e->checkauth;
2392 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2393 my $existing = $e->search_actor_workstation({name => $name})->[0];
2397 if( $self->api_name =~ /override/o ) {
2398 # workstation with the given name exists.
2400 if($owner ne $existing->owning_lib) {
2401 # if necessary, update the owning_lib of the workstation
2403 $logger->info("changing owning lib of workstation ".$existing->id.
2404 " from ".$existing->owning_lib." to $owner");
2405 return $e->die_event unless
2406 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2408 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2410 $existing->owning_lib($owner);
2411 return $e->die_event unless $e->update_actor_workstation($existing);
2417 "attempt to register an existing workstation. returning existing ID");
2420 return $existing->id;
2423 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2427 my $ws = Fieldmapper::actor::workstation->new;
2428 $ws->owning_lib($owner);
2430 $e->create_actor_workstation($ws) or return $e->die_event;
2432 return $ws->id; # note: editor sets the id on the new object for us
2435 __PACKAGE__->register_method(
2436 method => 'workstation_list',
2437 api_name => 'open-ils.actor.workstation.list',
2439 Returns a list of workstations registered at the given location
2440 @param authtoken The login session key
2441 @param ids A list of org_unit.id's for the workstation owners
2445 sub workstation_list {
2446 my( $self, $conn, $authtoken, @orgs ) = @_;
2448 my $e = new_editor(authtoken=>$authtoken);
2449 return $e->event unless $e->checkauth;
2454 unless $e->allowed('REGISTER_WORKSTATION', $o);
2455 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2461 __PACKAGE__->register_method(
2462 method => 'fetch_patron_note',
2463 api_name => 'open-ils.actor.note.retrieve.all',
2466 Returns a list of notes for a given user
2467 Requestor must have VIEW_USER permission if pub==false and
2468 @param authtoken The login session key
2469 @param args Hash of params including
2470 patronid : the patron's id
2471 pub : true if retrieving only public notes
2475 sub fetch_patron_note {
2476 my( $self, $conn, $authtoken, $args ) = @_;
2477 my $patronid = $$args{patronid};
2479 my($reqr, $evt) = $U->checkses($authtoken);
2480 return $evt if $evt;
2483 ($patron, $evt) = $U->fetch_user($patronid);
2484 return $evt if $evt;
2487 if( $patronid ne $reqr->id ) {
2488 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2489 return $evt if $evt;
2491 return $U->cstorereq(
2492 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2493 { usr => $patronid, pub => 't' } );
2496 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2497 return $evt if $evt;
2499 return $U->cstorereq(
2500 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2503 __PACKAGE__->register_method(
2504 method => 'create_user_note',
2505 api_name => 'open-ils.actor.note.create',
2507 Creates a new note for the given user
2508 @param authtoken The login session key
2509 @param note The note object
2512 sub create_user_note {
2513 my( $self, $conn, $authtoken, $note ) = @_;
2514 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2515 return $e->die_event unless $e->checkauth;
2517 my $user = $e->retrieve_actor_user($note->usr)
2518 or return $e->die_event;
2520 return $e->die_event unless
2521 $e->allowed('UPDATE_USER',$user->home_ou);
2523 $note->creator($e->requestor->id);
2524 $e->create_actor_usr_note($note) or return $e->die_event;
2530 __PACKAGE__->register_method(
2531 method => 'delete_user_note',
2532 api_name => 'open-ils.actor.note.delete',
2534 Deletes a note for the given user
2535 @param authtoken The login session key
2536 @param noteid The note id
2539 sub delete_user_note {
2540 my( $self, $conn, $authtoken, $noteid ) = @_;
2542 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2543 return $e->die_event unless $e->checkauth;
2544 my $note = $e->retrieve_actor_usr_note($noteid)
2545 or return $e->die_event;
2546 my $user = $e->retrieve_actor_user($note->usr)
2547 or return $e->die_event;
2548 return $e->die_event unless
2549 $e->allowed('UPDATE_USER', $user->home_ou);
2551 $e->delete_actor_usr_note($note) or return $e->die_event;
2557 __PACKAGE__->register_method(
2558 method => 'update_user_note',
2559 api_name => 'open-ils.actor.note.update',
2561 @param authtoken The login session key
2562 @param note The note
2566 sub update_user_note {
2567 my( $self, $conn, $auth, $note ) = @_;
2568 my $e = new_editor(authtoken=>$auth, xact=>1);
2569 return $e->die_event unless $e->checkauth;
2570 my $patron = $e->retrieve_actor_user($note->usr)
2571 or return $e->die_event;
2572 return $e->die_event unless
2573 $e->allowed('UPDATE_USER', $patron->home_ou);
2574 $e->update_actor_user_note($note)
2575 or return $e->die_event;
2582 __PACKAGE__->register_method(
2583 method => 'create_closed_date',
2584 api_name => 'open-ils.actor.org_unit.closed_date.create',
2586 Creates a new closing entry for the given org_unit
2587 @param authtoken The login session key
2588 @param note The closed_date object
2591 sub create_closed_date {
2592 my( $self, $conn, $authtoken, $cd ) = @_;
2594 my( $user, $evt ) = $U->checkses($authtoken);
2595 return $evt if $evt;
2597 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2598 return $evt if $evt;
2600 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2602 my $id = $U->storagereq(
2603 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2604 return $U->DB_UPDATE_FAILED($cd) unless $id;
2609 __PACKAGE__->register_method(
2610 method => 'delete_closed_date',
2611 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2613 Deletes a closing entry for the given org_unit
2614 @param authtoken The login session key
2615 @param noteid The close_date id
2618 sub delete_closed_date {
2619 my( $self, $conn, $authtoken, $cd ) = @_;
2621 my( $user, $evt ) = $U->checkses($authtoken);
2622 return $evt if $evt;
2625 ($cd_obj, $evt) = fetch_closed_date($cd);
2626 return $evt if $evt;
2628 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2629 return $evt if $evt;
2631 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2633 my $stat = $U->storagereq(
2634 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2635 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2640 __PACKAGE__->register_method(
2641 method => 'usrname_exists',
2642 api_name => 'open-ils.actor.username.exists',
2644 desc => 'Check if a username is already taken (by an undeleted patron)',
2646 {desc => 'Authentication token', type => 'string'},
2647 {desc => 'Username', type => 'string'}
2650 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2655 sub usrname_exists {
2656 my( $self, $conn, $auth, $usrname ) = @_;
2657 my $e = new_editor(authtoken=>$auth);
2658 return $e->event unless $e->checkauth;
2659 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2660 return $$a[0] if $a and @$a;
2664 __PACKAGE__->register_method(
2665 method => 'barcode_exists',
2666 api_name => 'open-ils.actor.barcode.exists',
2668 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2671 sub barcode_exists {
2672 my( $self, $conn, $auth, $barcode ) = @_;
2673 my $e = new_editor(authtoken=>$auth);
2674 return $e->event unless $e->checkauth;
2675 my $card = $e->search_actor_card({barcode => $barcode});
2681 #return undef unless @$card;
2682 #return $card->[0]->usr;
2686 __PACKAGE__->register_method(
2687 method => 'retrieve_net_levels',
2688 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2691 sub retrieve_net_levels {
2692 my( $self, $conn, $auth ) = @_;
2693 my $e = new_editor(authtoken=>$auth);
2694 return $e->event unless $e->checkauth;
2695 return $e->retrieve_all_config_net_access_level();
2698 # Retain the old typo API name just in case
2699 __PACKAGE__->register_method(
2700 method => 'fetch_org_by_shortname',
2701 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2703 __PACKAGE__->register_method(
2704 method => 'fetch_org_by_shortname',
2705 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2707 sub fetch_org_by_shortname {
2708 my( $self, $conn, $sname ) = @_;
2709 my $e = new_editor();
2710 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2711 return $e->event unless $org;
2716 __PACKAGE__->register_method(
2717 method => 'session_home_lib',
2718 api_name => 'open-ils.actor.session.home_lib',
2721 sub session_home_lib {
2722 my( $self, $conn, $auth ) = @_;
2723 my $e = new_editor(authtoken=>$auth);
2724 return undef unless $e->checkauth;
2725 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2726 return $org->shortname;
2729 __PACKAGE__->register_method(
2730 method => 'session_safe_token',
2731 api_name => 'open-ils.actor.session.safe_token',
2733 Returns a hashed session ID that is safe for export to the world.
2734 This safe token will expire after 1 hour of non-use.
2735 @param auth Active authentication token
2739 sub session_safe_token {
2740 my( $self, $conn, $auth ) = @_;
2741 my $e = new_editor(authtoken=>$auth);
2742 return undef unless $e->checkauth;
2744 my $safe_token = md5_hex($auth);
2746 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2748 # Add more like the following if needed...
2750 "safe-token-home_lib-shortname-$safe_token",
2751 $e->retrieve_actor_org_unit(
2752 $e->requestor->home_ou
2761 __PACKAGE__->register_method(
2762 method => 'safe_token_home_lib',
2763 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2765 Returns the home library shortname from the session
2766 asscociated with a safe token from generated by
2767 open-ils.actor.session.safe_token.
2768 @param safe_token Active safe token
2772 sub safe_token_home_lib {
2773 my( $self, $conn, $safe_token ) = @_;
2775 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2776 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2781 __PACKAGE__->register_method(
2782 method => 'slim_tree',
2783 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2786 my $tree = new_editor()->search_actor_org_unit(
2788 {"parent_ou" => undef },
2791 flesh_fields => { aou => ['children'] },
2792 order_by => { aou => 'name'},
2793 select => { aou => ["id","shortname", "name"]},
2798 return trim_tree($tree);
2804 return undef unless $tree;
2806 code => $tree->shortname,
2807 name => $tree->name,
2809 if( $tree->children and @{$tree->children} ) {
2810 $htree->{children} = [];
2811 for my $c (@{$tree->children}) {
2812 push( @{$htree->{children}}, trim_tree($c) );
2820 __PACKAGE__->register_method(
2821 method => "update_penalties",
2822 api_name => "open-ils.actor.user.penalties.update"
2825 sub update_penalties {
2826 my($self, $conn, $auth, $user_id) = @_;
2827 my $e = new_editor(authtoken=>$auth, xact => 1);
2828 return $e->die_event unless $e->checkauth;
2829 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2830 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2831 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2832 return $evt if $evt;
2838 __PACKAGE__->register_method(
2839 method => "apply_penalty",
2840 api_name => "open-ils.actor.user.penalty.apply"
2844 my($self, $conn, $auth, $penalty) = @_;
2846 my $e = new_editor(authtoken=>$auth, xact => 1);
2847 return $e->die_event unless $e->checkauth;
2849 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2850 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2852 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2855 (defined $ptype->org_depth) ?
2856 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2859 $penalty->org_unit($ctx_org);
2860 $penalty->staff($e->requestor->id);
2861 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2864 return $penalty->id;
2867 __PACKAGE__->register_method(
2868 method => "remove_penalty",
2869 api_name => "open-ils.actor.user.penalty.remove"
2872 sub remove_penalty {
2873 my($self, $conn, $auth, $penalty) = @_;
2874 my $e = new_editor(authtoken=>$auth, xact => 1);
2875 return $e->die_event unless $e->checkauth;
2876 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2877 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2879 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2884 __PACKAGE__->register_method(
2885 method => "update_penalty_note",
2886 api_name => "open-ils.actor.user.penalty.note.update"
2889 sub update_penalty_note {
2890 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2891 my $e = new_editor(authtoken=>$auth, xact => 1);
2892 return $e->die_event unless $e->checkauth;
2893 for my $penalty_id (@$penalty_ids) {
2894 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2895 if (! $penalty ) { return $e->die_event; }
2896 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2897 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2899 $penalty->note( $note ); $penalty->ischanged( 1 );
2901 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2907 __PACKAGE__->register_method(
2908 method => "ranged_penalty_thresholds",
2909 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2913 sub ranged_penalty_thresholds {
2914 my($self, $conn, $auth, $context_org) = @_;
2915 my $e = new_editor(authtoken=>$auth);
2916 return $e->event unless $e->checkauth;
2917 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2918 my $list = $e->search_permission_grp_penalty_threshold([
2919 {org_unit => $U->get_org_ancestors($context_org)},
2920 {order_by => {pgpt => 'id'}}
2922 $conn->respond($_) for @$list;
2928 __PACKAGE__->register_method(
2929 method => "user_retrieve_fleshed_by_id",
2931 api_name => "open-ils.actor.user.fleshed.retrieve",
2934 sub user_retrieve_fleshed_by_id {
2935 my( $self, $client, $auth, $user_id, $fields ) = @_;
2936 my $e = new_editor(authtoken => $auth);
2937 return $e->event unless $e->checkauth;
2939 if( $e->requestor->id != $user_id ) {
2940 return $e->event unless $e->allowed('VIEW_USER');
2946 "standing_penalties",
2950 "stat_cat_entries" ];
2951 return new_flesh_user($user_id, $fields, $e);
2955 sub new_flesh_user {
2958 my $fields = shift || [];
2961 my $fetch_penalties = 0;
2962 if(grep {$_ eq 'standing_penalties'} @$fields) {
2963 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2964 $fetch_penalties = 1;
2967 my $user = $e->retrieve_actor_user(
2972 "flesh_fields" => { "au" => $fields }
2975 ) or return $e->die_event;
2978 if( grep { $_ eq 'addresses' } @$fields ) {
2980 $user->addresses([]) unless @{$user->addresses};
2981 # don't expose "replaced" addresses by default
2982 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2984 if( ref $user->billing_address ) {
2985 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2986 push( @{$user->addresses}, $user->billing_address );
2990 if( ref $user->mailing_address ) {
2991 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2992 push( @{$user->addresses}, $user->mailing_address );
2997 if($fetch_penalties) {
2998 # grab the user penalties ranged for this location
2999 $user->standing_penalties(
3000 $e->search_actor_user_standing_penalty([
3003 {stop_date => undef},
3004 {stop_date => {'>' => 'now'}}
3006 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3009 flesh_fields => {ausp => ['standing_penalty']}
3016 $user->clear_passwd();
3023 __PACKAGE__->register_method(
3024 method => "user_retrieve_parts",
3025 api_name => "open-ils.actor.user.retrieve.parts",
3028 sub user_retrieve_parts {
3029 my( $self, $client, $auth, $user_id, $fields ) = @_;
3030 my $e = new_editor(authtoken => $auth);
3031 return $e->event unless $e->checkauth;
3032 $user_id ||= $e->requestor->id;
3033 if( $e->requestor->id != $user_id ) {
3034 return $e->event unless $e->allowed('VIEW_USER');
3037 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3038 push(@resp, $user->$_()) for(@$fields);
3044 __PACKAGE__->register_method(
3045 method => 'user_opt_in_enabled',
3046 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3047 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3050 sub user_opt_in_enabled {
3051 my($self, $conn) = @_;
3052 my $sc = OpenSRF::Utils::SettingsClient->new;
3053 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3058 __PACKAGE__->register_method(
3059 method => 'user_opt_in_at_org',
3060 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3062 @param $auth The auth token
3063 @param user_id The ID of the user to test
3064 @return 1 if the user has opted in at the specified org,
3065 event on error, and 0 otherwise. /
3067 sub user_opt_in_at_org {
3068 my($self, $conn, $auth, $user_id) = @_;
3070 # see if we even need to enforce the opt-in value
3071 return 1 unless user_opt_in_enabled($self);
3073 my $e = new_editor(authtoken => $auth);
3074 return $e->event unless $e->checkauth;
3076 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3077 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3079 my $ws_org = $e->requestor->ws_ou;
3080 # user is automatically opted-in if they are from the local org
3081 return 1 if $user->home_ou eq $ws_org;
3083 # get the boundary setting
3084 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3086 # auto opt in if user falls within the opt boundary
3087 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3089 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3091 my $vals = $e->search_actor_usr_org_unit_opt_in(
3092 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3098 __PACKAGE__->register_method(
3099 method => 'create_user_opt_in_at_org',
3100 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3102 @param $auth The auth token
3103 @param user_id The ID of the user to test
3104 @return The ID of the newly created object, event on error./
3107 sub create_user_opt_in_at_org {
3108 my($self, $conn, $auth, $user_id, $org_id) = @_;
3110 my $e = new_editor(authtoken => $auth, xact=>1);
3111 return $e->die_event unless $e->checkauth;
3113 # if a specific org unit wasn't passed in, get one based on the defaults;
3115 my $wsou = $e->requestor->ws_ou;
3116 # get the default opt depth
3117 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3118 # get the org unit at that depth
3119 my $org = $e->json_query({
3120 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3122 $org_id = $org->{id};
3125 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3126 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3128 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3130 $opt_in->org_unit($org_id);
3131 $opt_in->usr($user_id);
3132 $opt_in->staff($e->requestor->id);
3133 $opt_in->opt_in_ts('now');
3134 $opt_in->opt_in_ws($e->requestor->wsid);
3136 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3137 or return $e->die_event;
3145 __PACKAGE__->register_method (
3146 method => 'retrieve_org_hours',
3147 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3149 Returns the hours of operation for a specified org unit
3150 @param authtoken The login session key
3151 @param org_id The org_unit ID
3155 sub retrieve_org_hours {
3156 my($self, $conn, $auth, $org_id) = @_;
3157 my $e = new_editor(authtoken => $auth);
3158 return $e->die_event unless $e->checkauth;
3159 $org_id ||= $e->requestor->ws_ou;
3160 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3164 __PACKAGE__->register_method (
3165 method => 'verify_user_password',
3166 api_name => 'open-ils.actor.verify_user_password',
3168 Given a barcode or username and the MD5 encoded password,
3169 returns 1 if the password is correct. Returns 0 otherwise.
3173 sub verify_user_password {
3174 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3175 my $e = new_editor(authtoken => $auth);
3176 return $e->die_event unless $e->checkauth;
3178 my $user_by_barcode;
3179 my $user_by_username;
3181 my $card = $e->search_actor_card([
3182 {barcode => $barcode},
3183 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3184 $user_by_barcode = $card->usr;
3185 $user = $user_by_barcode;
3188 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3189 $user = $user_by_username;
3191 return 0 if (!$user);
3192 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3193 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3194 return 1 if $user->passwd eq $password;
3198 __PACKAGE__->register_method (
3199 method => 'retrieve_usr_id_via_barcode_or_usrname',
3200 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3202 Given a barcode or username returns the id for the user or
3207 sub retrieve_usr_id_via_barcode_or_usrname {
3208 my($self, $conn, $auth, $barcode, $username) = @_;
3209 my $e = new_editor(authtoken => $auth);
3210 return $e->die_event unless $e->checkauth;
3211 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3213 my $user_by_barcode;
3214 my $user_by_username;
3215 $logger->info("$id_as_barcode is the ID as BARCODE");
3217 my $card = $e->search_actor_card([
3218 {barcode => $barcode},
3219 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3220 if ($id_as_barcode =~ /^t/i) {
3222 $user = $e->retrieve_actor_user($barcode);
3223 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3225 $user_by_barcode = $card->usr;
3226 $user = $user_by_barcode;
3229 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3230 $user_by_barcode = $card->usr;
3231 $user = $user_by_barcode;
3236 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3238 $user = $user_by_username;
3240 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3241 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3242 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3247 __PACKAGE__->register_method (
3248 method => 'merge_users',
3249 api_name => 'open-ils.actor.user.merge',
3252 Given a list of source users and destination user, transfer all data from the source
3253 to the dest user and delete the source user. All user related data is
3254 transferred, including circulations, holds, bookbags, etc.
3260 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3261 my $e = new_editor(xact => 1, authtoken => $auth);
3262 return $e->die_event unless $e->checkauth;
3264 # disallow the merge if any subordinate accounts are in collections
3265 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3266 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3268 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3269 my $del_addrs = ($U->ou_ancestor_setting_value(
3270 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3271 my $del_cards = ($U->ou_ancestor_setting_value(
3272 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3273 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3274 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3276 for my $src_id (@$user_ids) {
3277 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3279 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3280 if($src_user->home_ou ne $master_user->home_ou) {
3281 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3284 return $e->die_event unless
3285 $e->json_query({from => [
3300 __PACKAGE__->register_method (
3301 method => 'approve_user_address',
3302 api_name => 'open-ils.actor.user.pending_address.approve',
3309 sub approve_user_address {
3310 my($self, $conn, $auth, $addr) = @_;
3311 my $e = new_editor(xact => 1, authtoken => $auth);
3312 return $e->die_event unless $e->checkauth;
3314 # if the caller passes an address object, assume they want to
3315 # update it first before approving it
3316 $e->update_actor_user_address($addr) or return $e->die_event;
3318 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3320 my $user = $e->retrieve_actor_user($addr->usr);
3321 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3322 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3323 or return $e->die_event;
3325 return [values %$result]->[0];
3329 __PACKAGE__->register_method (
3330 method => 'retrieve_friends',
3331 api_name => 'open-ils.actor.friends.retrieve',
3334 returns { confirmed: [], pending_out: [], pending_in: []}
3335 pending_out are users I'm requesting friendship with
3336 pending_in are users requesting friendship with me
3341 sub retrieve_friends {
3342 my($self, $conn, $auth, $user_id, $options) = @_;
3343 my $e = new_editor(authtoken => $auth);
3344 return $e->event unless $e->checkauth;
3345 $user_id ||= $e->requestor->id;
3347 if($user_id != $e->requestor->id) {
3348 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3349 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3352 return OpenILS::Application::Actor::Friends->retrieve_friends(
3353 $e, $user_id, $options);
3358 __PACKAGE__->register_method (
3359 method => 'apply_friend_perms',
3360 api_name => 'open-ils.actor.friends.perms.apply',
3366 sub apply_friend_perms {
3367 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3368 my $e = new_editor(authtoken => $auth, xact => 1);
3369 return $e->die_event unless $e->checkauth;
3371 if($user_id != $e->requestor->id) {
3372 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3373 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3376 for my $perm (@perms) {
3378 OpenILS::Application::Actor::Friends->apply_friend_perm(
3379 $e, $user_id, $delegate_id, $perm);
3380 return $evt if $evt;
3388 __PACKAGE__->register_method (
3389 method => 'update_user_pending_address',
3390 api_name => 'open-ils.actor.user.address.pending.cud'
3393 sub update_user_pending_address {
3394 my($self, $conn, $auth, $addr) = @_;
3395 my $e = new_editor(authtoken => $auth, xact => 1);
3396 return $e->die_event unless $e->checkauth;
3398 if($addr->usr != $e->requestor->id) {
3399 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3400 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3404 $e->create_actor_user_address($addr) or return $e->die_event;
3405 } elsif($addr->isdeleted) {
3406 $e->delete_actor_user_address($addr) or return $e->die_event;
3408 $e->update_actor_user_address($addr) or return $e->die_event;
3416 __PACKAGE__->register_method (
3417 method => 'user_events',
3418 api_name => 'open-ils.actor.user.events.circ',
3421 __PACKAGE__->register_method (
3422 method => 'user_events',
3423 api_name => 'open-ils.actor.user.events.ahr',
3428 my($self, $conn, $auth, $user_id, $filters) = @_;
3429 my $e = new_editor(authtoken => $auth);
3430 return $e->event unless $e->checkauth;
3432 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3433 my $user_field = 'usr';
3436 $filters->{target} = {
3437 select => { $obj_type => ['id'] },
3439 where => {usr => $user_id}
3442 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3443 if($e->requestor->id != $user_id) {
3444 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3447 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3448 my $req = $ses->request('open-ils.trigger.events_by_target',
3449 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3451 while(my $resp = $req->recv) {
3452 my $val = $resp->content;
3453 my $tgt = $val->target;
3455 if($obj_type eq 'circ') {
3456 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3458 } elsif($obj_type eq 'ahr') {
3459 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3460 if $tgt->current_copy;
3463 $conn->respond($val) if $val;
3469 __PACKAGE__->register_method (
3470 method => 'copy_events',
3471 api_name => 'open-ils.actor.copy.events.circ',
3474 __PACKAGE__->register_method (
3475 method => 'copy_events',
3476 api_name => 'open-ils.actor.copy.events.ahr',
3481 my($self, $conn, $auth, $copy_id, $filters) = @_;
3482 my $e = new_editor(authtoken => $auth);
3483 return $e->event unless $e->checkauth;
3485 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3487 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3489 my $copy_field = 'target_copy';
3490 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3493 $filters->{target} = {
3494 select => { $obj_type => ['id'] },
3496 where => {$copy_field => $copy_id}
3500 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3501 my $req = $ses->request('open-ils.trigger.events_by_target',
3502 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3504 while(my $resp = $req->recv) {
3505 my $val = $resp->content;
3506 my $tgt = $val->target;
3508 my $user = $e->retrieve_actor_user($tgt->usr);
3509 if($e->requestor->id != $user->id) {
3510 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3513 $tgt->$copy_field($copy);
3516 $conn->respond($val) if $val;
3525 __PACKAGE__->register_method (
3526 method => 'update_events',
3527 api_name => 'open-ils.actor.user.event.cancel.batch',
3530 __PACKAGE__->register_method (
3531 method => 'update_events',
3532 api_name => 'open-ils.actor.user.event.reset.batch',
3537 my($self, $conn, $auth, $event_ids) = @_;
3538 my $e = new_editor(xact => 1, authtoken => $auth);
3539 return $e->die_event unless $e->checkauth;
3542 for my $id (@$event_ids) {
3544 # do a little dance to determine what user we are ultimately affecting
3545 my $event = $e->retrieve_action_trigger_event([
3548 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3550 ]) or return $e->die_event;
3553 if($event->event_def->hook->core_type eq 'circ') {
3554 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3555 } elsif($event->event_def->hook->core_type eq 'ahr') {
3556 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3561 my $user = $e->retrieve_actor_user($user_id);
3562 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3564 if($self->api_name =~ /cancel/) {
3565 $event->state('invalid');
3566 } elsif($self->api_name =~ /reset/) {
3567 $event->clear_start_time;
3568 $event->clear_update_time;
3569 $event->state('pending');
3572 $e->update_action_trigger_event($event) or return $e->die_event;
3573 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3577 return {complete => 1};
3581 __PACKAGE__->register_method (
3582 method => 'really_delete_user',
3583 api_name => 'open-ils.actor.user.delete.override',
3584 signature => q/@see open-ils.actor.user.delete/
3587 __PACKAGE__->register_method (
3588 method => 'really_delete_user',
3589 api_name => 'open-ils.actor.user.delete',
3591 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3592 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3593 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3594 dest_usr_id is only required when deleting a user that performs staff functions.
3598 sub really_delete_user {
3599 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3600 my $e = new_editor(authtoken => $auth, xact => 1);
3601 return $e->die_event unless $e->checkauth;
3603 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3604 my $open_bills = $e->json_query({
3605 select => { mbts => ['id'] },
3608 xact_finish => { '=' => undef },
3609 usr => { '=' => $user_id },
3611 }) or return $e->die_event;
3613 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3615 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3617 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3618 unless $self->api_name =~ /override/o
3619 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3621 # No deleting yourself - UI is supposed to stop you first, though.
3622 return $e->die_event unless $e->requestor->id != $user->id;
3623 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3624 # Check if you are allowed to mess with this patron permission group at all
3625 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3626 my $evt = group_perm_failed($session, $e->requestor, $user);
3627 return $e->die_event($evt) if $evt;
3628 my $stat = $e->json_query(
3629 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3630 or return $e->die_event;
3636 __PACKAGE__->register_method (
3637 method => 'user_payments',
3638 api_name => 'open-ils.actor.user.payments.retrieve',
3641 Returns all payments for a given user. Default order is newest payments first.
3642 @param auth Authentication token
3643 @param user_id The user ID
3644 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3649 my($self, $conn, $auth, $user_id, $filters) = @_;
3652 my $e = new_editor(authtoken => $auth);
3653 return $e->die_event unless $e->checkauth;
3655 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3656 return $e->event unless
3657 $e->requestor->id == $user_id or
3658 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3660 # Find all payments for all transactions for user $user_id
3662 select => {mp => ['id']},
3667 select => {mbt => ['id']},
3669 where => {usr => $user_id}
3673 order_by => [{ # by default, order newest payments first
3675 field => 'payment_ts',
3680 for (qw/order_by limit offset/) {
3681 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3684 if(defined $filters->{where}) {
3685 foreach (keys %{$filters->{where}}) {
3686 # don't allow the caller to expand the result set to other users
3687 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3691 my $payment_ids = $e->json_query($query);
3692 for my $pid (@$payment_ids) {
3693 my $pay = $e->retrieve_money_payment([
3698 mbt => ['summary', 'circulation', 'grocery'],
3699 circ => ['target_copy'],
3700 acp => ['call_number'],
3708 xact_type => $pay->xact->summary->xact_type,
3709 last_billing_type => $pay->xact->summary->last_billing_type,
3712 if($pay->xact->summary->xact_type eq 'circulation') {
3713 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3714 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3717 $pay->xact($pay->xact->id); # de-flesh
3718 $conn->respond($resp);
3726 __PACKAGE__->register_method (
3727 method => 'negative_balance_users',
3728 api_name => 'open-ils.actor.users.negative_balance',
3731 Returns all users that have an overall negative balance
3732 @param auth Authentication token
3733 @param org_id The context org unit as an ID or list of IDs. This will be the home
3734 library of the user. If no org_unit is specified, no org unit filter is applied
3738 sub negative_balance_users {
3739 my($self, $conn, $auth, $org_id) = @_;
3741 my $e = new_editor(authtoken => $auth);
3742 return $e->die_event unless $e->checkauth;
3743 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3747 mous => ['usr', 'balance_owed'],
3750 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3751 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3768 where => {'+mous' => {balance_owed => {'<' => 0}}}
3771 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3773 my $list = $e->json_query($query, {timeout => 600});
3775 for my $data (@$list) {
3777 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3778 balance_owed => $data->{balance_owed},
3779 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3786 __PACKAGE__->register_method(
3787 method => "request_password_reset",
3788 api_name => "open-ils.actor.patron.password_reset.request",
3790 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3791 "method for changing a user's password. The UUID token is distributed via A/T " .
3792 "templates (i.e. email to the user).",
3794 { desc => 'user_id_type', type => 'string' },
3795 { desc => 'user_id', type => 'string' },
3796 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3798 return => {desc => '1 on success, Event on error'}
3801 sub request_password_reset {
3802 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3804 # Check to see if password reset requests are already being throttled:
3805 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3807 my $e = new_editor(xact => 1);
3810 # Get the user, if any, depending on the input value
3811 if ($user_id_type eq 'username') {
3812 $user = $e->search_actor_user({usrname => $user_id})->[0];
3815 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3817 } elsif ($user_id_type eq 'barcode') {
3818 my $card = $e->search_actor_card([
3819 {barcode => $user_id},
3820 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3823 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3828 # If the user doesn't have an email address, we can't help them
3829 if (!$user->email) {
3831 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3834 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3835 if ($email_must_match) {
3836 if ($user->email ne $email) {
3837 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3841 _reset_password_request($conn, $e, $user);
3844 # Once we have the user, we can issue the password reset request
3845 # XXX Add a wrapper method that accepts barcode + email input
3846 sub _reset_password_request {
3847 my ($conn, $e, $user) = @_;
3849 # 1. Get throttle threshold and time-to-live from OU_settings
3850 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3851 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3853 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3855 # 2. Get time of last request and number of active requests (num_active)
3856 my $active_requests = $e->json_query({
3862 transform => 'COUNT'
3865 column => 'request_time',
3871 has_been_reset => { '=' => 'f' },
3872 request_time => { '>' => $threshold_time }
3876 # Guard against no active requests
3877 if ($active_requests->[0]->{'request_time'}) {
3878 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3879 my $now = DateTime::Format::ISO8601->new();
3881 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3882 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3883 ($last_request->add_duration('1 minute') > $now)) {
3884 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3886 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3890 # TODO Check to see if the user is in a password-reset-restricted group
3892 # Otherwise, go ahead and try to get the user.
3894 # Check the number of active requests for this user
3895 $active_requests = $e->json_query({
3901 transform => 'COUNT'
3906 usr => { '=' => $user->id },
3907 has_been_reset => { '=' => 'f' },
3908 request_time => { '>' => $threshold_time }
3912 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3914 # if less than or equal to per-user threshold, proceed; otherwise, return event
3915 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3916 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3918 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3921 # Create the aupr object and insert into the database
3922 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3923 my $uuid = create_uuid_as_string(UUID_V4);
3924 $reset_request->uuid($uuid);
3925 $reset_request->usr($user->id);
3927 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3930 # Create an event to notify user of the URL to reset their password
3932 # Can we stuff this in the user_data param for trigger autocreate?
3933 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3935 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3936 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3939 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3944 __PACKAGE__->register_method(
3945 method => "commit_password_reset",
3946 api_name => "open-ils.actor.patron.password_reset.commit",
3948 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3949 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3950 "with the supplied password.",
3952 { desc => 'uuid', type => 'string' },
3953 { desc => 'password', type => 'string' },
3955 return => {desc => '1 on success, Event on error'}
3958 sub commit_password_reset {
3959 my($self, $conn, $uuid, $password) = @_;
3961 # Check to see if password reset requests are already being throttled:
3962 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3963 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3964 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3966 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3969 my $e = new_editor(xact => 1);
3971 my $aupr = $e->search_actor_usr_password_reset({
3978 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3980 my $user_id = $aupr->[0]->usr;
3981 my $user = $e->retrieve_actor_user($user_id);
3983 # Ensure we're still within the TTL for the request
3984 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3985 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3986 if ($threshold < DateTime->now(time_zone => 'local')) {
3988 $logger->info("Password reset request needed to be submitted before $threshold");
3989 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3992 # Check complexity of password against OU-defined regex
3993 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3997 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3998 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3999 $is_strong = check_password_strength_custom($password, $pw_regex);
4001 $is_strong = check_password_strength_default($password);
4006 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4009 # All is well; update the password
4010 $user->passwd($password);
4011 $e->update_actor_user($user);
4013 # And flag that this password reset request has been honoured
4014 $aupr->[0]->has_been_reset('t');
4015 $e->update_actor_usr_password_reset($aupr->[0]);
4021 sub check_password_strength_default {
4022 my $password = shift;
4023 # Use the default set of checks
4024 if ( (length($password) < 7) or
4025 ($password !~ m/.*\d+.*/) or
4026 ($password !~ m/.*[A-Za-z]+.*/)
4033 sub check_password_strength_custom {
4034 my ($password, $pw_regex) = @_;
4036 $pw_regex = qr/$pw_regex/;
4037 if ($password !~ /$pw_regex/) {
4045 __PACKAGE__->register_method(
4046 method => "event_def_opt_in_settings",
4047 api_name => "open-ils.actor.event_def.opt_in.settings",
4050 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4052 { desc => 'Authentication token', type => 'string'},
4054 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4059 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4066 sub event_def_opt_in_settings {
4067 my($self, $conn, $auth, $org_id) = @_;
4068 my $e = new_editor(authtoken => $auth);
4069 return $e->event unless $e->checkauth;
4071 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4072 return $e->event unless
4073 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4075 $org_id = $e->requestor->home_ou;
4078 # find all config.user_setting_type's related to event_defs for the requested org unit
4079 my $types = $e->json_query({
4080 select => {cust => ['name']},
4081 from => {atevdef => 'cust'},
4084 owner => $U->get_org_ancestors($org_id), # context org plus parents
4091 $conn->respond($_) for
4092 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4099 __PACKAGE__->register_method(
4100 method => "user_visible_circs",
4101 api_name => "open-ils.actor.history.circ.visible",
4104 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
4106 { desc => 'Authentication token', type => 'string'},
4107 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4108 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4111 desc => q/An object with 2 fields: circulation and summary.
4112 circulation is the "circ" object. summary is the related "accs" object/,
4118 __PACKAGE__->register_method(
4119 method => "user_visible_circs",
4120 api_name => "open-ils.actor.history.circ.visible.print",
4123 desc => 'Returns printable output for the set of opt-in visible circulations',
4125 { desc => 'Authentication token', type => 'string'},
4126 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4127 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4130 desc => q/An action_trigger.event object or error event./,
4136 __PACKAGE__->register_method(
4137 method => "user_visible_circs",
4138 api_name => "open-ils.actor.history.circ.visible.email",
4141 desc => 'Emails the set of opt-in visible circulations to the requestor',
4143 { desc => 'Authentication token', type => 'string'},
4144 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4145 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4148 desc => q/undef, or event on error/
4153 __PACKAGE__->register_method(
4154 method => "user_visible_circs",
4155 api_name => "open-ils.actor.history.hold.visible",
4158 desc => 'Returns the set of opt-in visible holds',
4160 { desc => 'Authentication token', type => 'string'},
4161 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4162 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4165 desc => q/An object with 1 field: "hold"/,
4171 __PACKAGE__->register_method(
4172 method => "user_visible_circs",
4173 api_name => "open-ils.actor.history.hold.visible.print",
4176 desc => 'Returns printable output for the set of opt-in visible holds',
4178 { desc => 'Authentication token', type => 'string'},
4179 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4180 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4183 desc => q/An action_trigger.event object or error event./,
4189 __PACKAGE__->register_method(
4190 method => "user_visible_circs",
4191 api_name => "open-ils.actor.history.hold.visible.email",
4194 desc => 'Emails the set of opt-in visible holds to the requestor',
4196 { desc => 'Authentication token', type => 'string'},
4197 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4198 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4201 desc => q/undef, or event on error/
4206 sub user_visible_circs {
4207 my($self, $conn, $auth, $user_id, $options) = @_;
4209 my $is_hold = ($self->api_name =~ /hold/);
4210 my $for_print = ($self->api_name =~ /print/);
4211 my $for_email = ($self->api_name =~ /email/);
4212 my $e = new_editor(authtoken => $auth);
4213 return $e->event unless $e->checkauth;
4215 $user_id ||= $e->requestor->id;
4217 $options->{limit} ||= 50;
4218 $options->{offset} ||= 0;
4220 if($user_id != $e->requestor->id) {
4221 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4222 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4223 return $e->event unless $e->allowed($perm, $user->home_ou);
4226 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4228 my $data = $e->json_query({
4229 from => [$db_func, $user_id],
4230 limit => $$options{limit},
4231 offset => $$options{offset}
4233 # TODO: I only want IDs. code below didn't get me there
4234 # {"select":{"au":[{"column":"id", "result_field":"id",
4235 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4240 return undef unless @$data;
4244 # collect the batch of objects
4248 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4249 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4253 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4254 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4257 } elsif ($for_email) {
4259 $conn->respond_complete(1) if $for_email; # no sense in waiting
4267 my $hold = $e->retrieve_action_hold_request($id);
4268 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4269 # events will be fired from action_trigger_runner
4273 my $circ = $e->retrieve_action_circulation($id);
4274 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4275 # events will be fired from action_trigger_runner
4279 } else { # just give me the data please
4287 my $hold = $e->retrieve_action_hold_request($id);
4288 $conn->respond({hold => $hold});
4292 my $circ = $e->retrieve_action_circulation($id);
4295 summary => $U->create_circ_chain_summary($e, $id)
4304 __PACKAGE__->register_method(
4305 method => "user_saved_search_cud",
4306 api_name => "open-ils.actor.user.saved_search.cud",
4309 desc => 'Create/Update/Delete Access to user saved searches',
4311 { desc => 'Authentication token', type => 'string' },
4312 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4315 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4321 __PACKAGE__->register_method(
4322 method => "user_saved_search_cud",
4323 api_name => "open-ils.actor.user.saved_search.retrieve",
4326 desc => 'Retrieve a saved search object',
4328 { desc => 'Authentication token', type => 'string' },
4329 { desc => 'Saved Search ID', type => 'number' }
4332 desc => q/The saved search object, Event on error/,
4338 sub user_saved_search_cud {
4339 my( $self, $client, $auth, $search ) = @_;
4340 my $e = new_editor( authtoken=>$auth );
4341 return $e->die_event unless $e->checkauth;
4343 my $o_search; # prior version of the object, if any
4344 my $res; # to be returned
4346 # branch on the operation type
4348 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4350 # Get the old version, to check ownership
4351 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4352 or return $e->die_event;
4354 # You can't read somebody else's search
4355 return OpenILS::Event->new('BAD_PARAMS')
4356 unless $o_search->owner == $e->requestor->id;
4362 $e->xact_begin; # start an editor transaction
4364 if( $search->isnew ) { # Create
4366 # You can't create a search for somebody else
4367 return OpenILS::Event->new('BAD_PARAMS')
4368 unless $search->owner == $e->requestor->id;
4370 $e->create_actor_usr_saved_search( $search )
4371 or return $e->die_event;
4375 } elsif( $search->ischanged ) { # Update
4377 # You can't change ownership of a search
4378 return OpenILS::Event->new('BAD_PARAMS')
4379 unless $search->owner == $e->requestor->id;
4381 # Get the old version, to check ownership
4382 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4383 or return $e->die_event;
4385 # You can't update somebody else's search
4386 return OpenILS::Event->new('BAD_PARAMS')
4387 unless $o_search->owner == $e->requestor->id;
4390 $e->update_actor_usr_saved_search( $search )
4391 or return $e->die_event;
4395 } elsif( $search->isdeleted ) { # Delete
4397 # Get the old version, to check ownership
4398 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4399 or return $e->die_event;
4401 # You can't delete somebody else's search
4402 return OpenILS::Event->new('BAD_PARAMS')
4403 unless $o_search->owner == $e->requestor->id;
4406 $e->delete_actor_usr_saved_search( $o_search )
4407 or return $e->die_event;
4418 __PACKAGE__->register_method(
4419 method => "get_barcodes",
4420 api_name => "open-ils.actor.get_barcodes"
4424 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4425 my $e = new_editor(authtoken => $auth);
4426 return $e->event unless $e->checkauth;
4427 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4429 my $db_result = $e->json_query(
4431 'evergreen.get_barcodes',
4432 $org_id, $context, $barcode,
4436 if($context =~ /actor/) {
4437 my $filter_result = ();
4439 foreach my $result (@$db_result) {
4440 if($result->{type} eq 'actor') {
4441 if($e->requestor->id != $result->{id}) {
4442 $patron = $e->retrieve_actor_user($result->{id});
4444 push(@$filter_result, $e->event);
4447 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4448 push(@$filter_result, $result);
4451 push(@$filter_result, $e->event);
4455 push(@$filter_result, $result);
4459 push(@$filter_result, $result);
4462 return $filter_result;
4468 __PACKAGE__->register_method(
4469 method => 'address_alert_test',
4470 api_name => 'open-ils.actor.address_alert.test',
4472 desc => "Tests a set of address fields to determine if they match with an address_alert",
4474 {desc => 'Authentication token', type => 'string'},
4475 {desc => 'Org Unit', type => 'number'},
4476 {desc => 'Fields', type => 'hash'},
4478 return => {desc => 'List of matching address_alerts'}
4482 sub address_alert_test {
4483 my ($self, $client, $auth, $org_unit, $fields) = @_;
4484 return [] unless $fields and grep {$_} values %$fields;
4486 my $e = new_editor(authtoken => $auth);
4487 return $e->event unless $e->checkauth;
4488 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4489 $org_unit ||= $e->requestor->ws_ou;
4491 my $alerts = $e->json_query({
4493 'actor.address_alert_matches',
4501 $$fields{post_code},
4502 $$fields{mailing_address},
4503 $$fields{billing_address}
4507 # map the json_query hashes to real objects
4509 map {$e->retrieve_actor_address_alert($_)}
4510 (map {$_->{id}} @$alerts)
4514 __PACKAGE__->register_method(
4515 method => "mark_users_contact_invalid",
4516 api_name => "open-ils.actor.invalidate.email",
4518 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",
4520 {desc => "Authentication token", type => "string"},
4521 {desc => "Patron ID", type => "number"},
4522 {desc => "Additional note text (optional)", type => "string"},
4523 {desc => "penalty org unit ID (optional)", type => "number"}
4525 return => {desc => "Event describing success or failure", type => "object"}
4529 __PACKAGE__->register_method(
4530 method => "mark_users_contact_invalid",
4531 api_name => "open-ils.actor.invalidate.day_phone",
4533 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",
4535 {desc => "Authentication token", type => "string"},
4536 {desc => "Patron ID", type => "number"},
4537 {desc => "Additional note text (optional)", type => "string"},
4538 {desc => "penalty org unit ID (optional)", type => "number"}
4540 return => {desc => "Event describing success or failure", type => "object"}
4544 __PACKAGE__->register_method(
4545 method => "mark_users_contact_invalid",
4546 api_name => "open-ils.actor.invalidate.evening_phone",
4548 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",
4550 {desc => "Authentication token", type => "string"},
4551 {desc => "Patron ID", type => "number"},
4552 {desc => "Additional note text (optional)", type => "string"},
4553 {desc => "penalty org unit ID (optional)", type => "number"}
4555 return => {desc => "Event describing success or failure", type => "object"}
4559 __PACKAGE__->register_method(
4560 method => "mark_users_contact_invalid",
4561 api_name => "open-ils.actor.invalidate.other_phone",
4563 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",
4565 {desc => "Authentication token", type => "string"},
4566 {desc => "Patron ID", type => "number"},
4567 {desc => "Additional note text (optional)", type => "string"},
4568 {desc => "penalty org unit ID (optional, default to top of org tree)",
4571 return => {desc => "Event describing success or failure", type => "object"}
4575 sub mark_users_contact_invalid {
4576 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou) = @_;
4578 # This method invalidates an email address or a phone_number which
4579 # removes the bad email address or phone number, copying its contents
4580 # to a patron note, and institutes a standing penalty for "bad email"
4581 # or "bad phone number" which is cleared when the user is saved or
4582 # optionally only when the user is saved with an email address or
4583 # phone number (or staff manually delete the penalty).
4585 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4587 my $e = new_editor(authtoken => $auth, xact => 1);
4588 return $e->die_event unless $e->checkauth;
4590 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4591 $e, $contact_type, {usr => $patron_id},
4592 $addl_note, $penalty_ou, $e->requestor->id