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 List::Util qw/max reduce/;
39 use UUID::Tiny qw/:std/;
42 OpenILS::Application::Actor::Container->initialize();
43 OpenILS::Application::Actor::UserGroups->initialize();
44 OpenILS::Application::Actor::ClosedDates->initialize();
47 my $apputils = "OpenILS::Application::AppUtils";
50 sub _d { warn "Patron:\n" . Dumper(shift()); }
53 my $set_user_settings;
57 #__PACKAGE__->register_method(
58 # method => "allowed_test",
59 # api_name => "open-ils.actor.allowed_test",
62 # my($self, $conn, $auth, $orgid, $permcode) = @_;
63 # my $e = new_editor(authtoken => $auth);
64 # return $e->die_event unless $e->checkauth;
68 # permcode => $permcode,
69 # result => $e->allowed($permcode, $orgid)
73 __PACKAGE__->register_method(
74 method => "update_user_setting",
75 api_name => "open-ils.actor.patron.settings.update",
77 sub update_user_setting {
78 my($self, $conn, $auth, $user_id, $settings) = @_;
79 my $e = new_editor(xact => 1, authtoken => $auth);
80 return $e->die_event unless $e->checkauth;
82 $user_id = $e->requestor->id unless defined $user_id;
84 unless($e->requestor->id == $user_id) {
85 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
86 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
89 for my $name (keys %$settings) {
90 my $val = $$settings{$name};
91 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
94 $val = OpenSRF::Utils::JSON->perl2JSON($val);
97 $e->update_actor_user_setting($set) or return $e->die_event;
99 $set = Fieldmapper::actor::user_setting->new;
103 $e->create_actor_user_setting($set) or return $e->die_event;
106 $e->delete_actor_user_setting($set) or return $e->die_event;
115 __PACKAGE__->register_method(
116 method => "set_ou_settings",
117 api_name => "open-ils.actor.org_unit.settings.update",
119 desc => "Updates the value for a given org unit setting. The permission to update " .
120 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
121 "permission specified in the update_perm column of the config.org_unit_setting_type " .
122 "table's row corresponding to the setting being changed." ,
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'Org unit ID', type => 'number'},
126 {desc => 'Hash of setting name-value pairs', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
132 sub set_ou_settings {
133 my( $self, $client, $auth, $org_id, $settings ) = @_;
135 my $e = new_editor(authtoken => $auth, xact => 1);
136 return $e->die_event unless $e->checkauth;
138 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
140 for my $name (keys %$settings) {
141 my $val = $$settings{$name};
143 my $type = $e->retrieve_config_org_unit_setting_type([
145 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
146 ]) or return $e->die_event;
147 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
149 # If there is no relevant permission, the default assumption will
150 # be, "no, the caller cannot change that value."
151 return $e->die_event unless ($all_allowed ||
152 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
155 $val = OpenSRF::Utils::JSON->perl2JSON($val);
158 $e->update_actor_org_unit_setting($set) or return $e->die_event;
160 $set = Fieldmapper::actor::org_unit_setting->new;
161 $set->org_unit($org_id);
164 $e->create_actor_org_unit_setting($set) or return $e->die_event;
167 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
175 __PACKAGE__->register_method(
176 method => "user_settings",
178 api_name => "open-ils.actor.patron.settings.retrieve",
181 my( $self, $client, $auth, $user_id, $setting ) = @_;
183 my $e = new_editor(authtoken => $auth);
184 return $e->event unless $e->checkauth;
185 $user_id = $e->requestor->id unless defined $user_id;
187 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
188 if($e->requestor->id != $user_id) {
189 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
193 my($e, $user_id, $setting) = @_;
194 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
195 return undef unless $val; # XXX this should really return undef, but needs testing
196 return OpenSRF::Utils::JSON->JSON2perl($val->value);
200 if(ref $setting eq 'ARRAY') {
202 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
205 return get_setting($e, $user_id, $setting);
208 my $s = $e->search_actor_user_setting({usr => $user_id});
209 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
214 __PACKAGE__->register_method(
215 method => "ranged_ou_settings",
216 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
218 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
219 "is implied for retrieving OU settings by the authenticated users' permissions.",
221 {desc => 'Authentication token', type => 'string'},
222 {desc => 'Org unit ID', type => 'number'},
224 return => {desc => 'A hashref of "ranged" settings, event on error'}
227 sub ranged_ou_settings {
228 my( $self, $client, $auth, $org_id ) = @_;
230 my $e = new_editor(authtoken => $auth);
231 return $e->event unless $e->checkauth;
234 my $org_list = $U->get_org_ancestors($org_id);
235 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
236 $org_list = [ reverse @$org_list ];
238 # start at the context org and capture the setting value
239 # without clobbering settings we've already captured
240 for my $this_org_id (@$org_list) {
242 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
244 for my $set (@sets) {
245 my $type = $e->retrieve_config_org_unit_setting_type([
247 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
250 # If there is no relevant permission, the default assumption will
251 # be, "yes, the caller can have that value."
252 if ($type && $type->view_perm) {
253 next if not $e->allowed($type->view_perm->code, $org_id);
256 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
257 unless defined $ranged_settings{$set->name};
261 return \%ranged_settings;
266 __PACKAGE__->register_method(
267 api_name => 'open-ils.actor.ou_setting.ancestor_default',
268 method => 'ou_ancestor_setting',
270 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
271 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
272 'user has permission to view that setting, if there is a permission associated with the setting.' ,
274 { desc => 'Org unit ID', type => 'number' },
275 { desc => 'setting name', type => 'string' },
276 { desc => 'authtoken (optional)', type => 'string' }
278 return => {desc => 'A value for the org unit setting, or undef'}
282 # ------------------------------------------------------------------
283 # Attempts to find the org setting value for a given org. if not
284 # found at the requested org, searches up the org tree until it
285 # finds a parent that has the requested setting.
286 # when found, returns { org => $id, value => $value }
287 # otherwise, returns NULL
288 # ------------------------------------------------------------------
289 sub ou_ancestor_setting {
290 my( $self, $client, $orgid, $name, $auth ) = @_;
291 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
294 __PACKAGE__->register_method(
295 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
296 method => 'ou_ancestor_setting_batch',
298 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
299 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
300 'user has permission to view that setting, if there is a permission associated with the setting.' ,
302 { desc => 'Org unit ID', type => 'number' },
303 { desc => 'setting name list', type => 'array' },
304 { desc => 'authtoken (optional)', type => 'string' }
306 return => {desc => 'A hash with name => value pairs for the org unit settings'}
309 sub ou_ancestor_setting_batch {
310 my( $self, $client, $orgid, $name_list, $auth ) = @_;
312 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
318 __PACKAGE__->register_method(
319 method => "update_patron",
320 api_name => "open-ils.actor.patron.update",
323 Update an existing user, or create a new one. Related objects,
324 like cards, addresses, survey responses, and stat cats,
325 can be updated by attaching them to the user object in their
326 respective fields. For examples, the billing address object
327 may be inserted into the 'billing_address' field, etc. For each
328 attached object, indicate if the object should be created,
329 updated, or deleted using the built-in 'isnew', 'ischanged',
330 and 'isdeleted' fields on the object.
333 { desc => 'Authentication token', type => 'string' },
334 { desc => 'Patron data object', type => 'object' }
336 return => {desc => 'A fleshed user object, event on error'}
341 my( $self, $client, $user_session, $patron ) = @_;
343 my $session = $apputils->start_db_session();
345 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
347 my( $user_obj, $evt ) = $U->checkses($user_session);
350 $evt = check_group_perm($session, $user_obj, $patron);
354 # $new_patron is the patron in progress. $patron is the original patron
355 # passed in with the method. new_patron will change as the components
356 # of patron are added/updated.
360 # unflesh the real items on the patron
361 $patron->card( $patron->card->id ) if(ref($patron->card));
362 $patron->billing_address( $patron->billing_address->id )
363 if(ref($patron->billing_address));
364 $patron->mailing_address( $patron->mailing_address->id )
365 if(ref($patron->mailing_address));
367 # create/update the patron first so we can use his id
368 if($patron->isnew()) {
369 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
371 } else { $new_patron = $patron; }
373 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
376 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
379 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
382 # re-update the patron if anything has happened to him during this process
383 if($new_patron->ischanged()) {
384 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
388 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
391 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
394 $apputils->commit_db_session($session);
396 $evt = apply_invalid_addr_penalty($patron);
399 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
401 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
403 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
406 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
409 sub apply_invalid_addr_penalty {
411 my $e = new_editor(xact => 1);
413 # grab the invalid address penalty if set
414 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
416 my ($addr_penalty) = grep
417 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
419 # do we enforce invalid address penalty
420 my $enforce = $U->ou_ancestor_setting_value(
421 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
423 my $addrs = $e->search_actor_user_address(
424 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
425 my $addr_count = scalar(@$addrs);
427 if($addr_count == 0 and $addr_penalty) {
429 # regardless of any settings, remove the penalty when the user has no invalid addresses
430 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
433 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
435 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
436 my $depth = $ptype->org_depth;
437 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
438 $ctx_org = $patron->home_ou unless defined $ctx_org;
440 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
441 $penalty->usr($patron->id);
442 $penalty->org_unit($ctx_org);
443 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
445 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
464 "standing_penalties",
470 push @$fields, "home_ou" if $home_ou;
471 return new_flesh_user($id, $fields, $e );
479 # clone and clear stuff that would break the database
483 my $new_patron = $patron->clone;
485 $new_patron->clear_billing_address();
486 $new_patron->clear_mailing_address();
487 $new_patron->clear_addresses();
488 $new_patron->clear_card();
489 $new_patron->clear_cards();
490 $new_patron->clear_id();
491 $new_patron->clear_isnew();
492 $new_patron->clear_ischanged();
493 $new_patron->clear_isdeleted();
494 $new_patron->clear_stat_cat_entries();
495 $new_patron->clear_permissions();
496 $new_patron->clear_standing_penalties();
506 my $user_obj = shift;
508 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
509 return (undef, $evt) if $evt;
511 my $ex = $session->request(
512 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
514 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
517 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
519 my $id = $session->request(
520 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
521 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
523 $logger->info("Successfully created new user [$id] in DB");
525 return ( $session->request(
526 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
530 sub check_group_perm {
531 my( $session, $requestor, $patron ) = @_;
534 # first let's see if the requestor has
535 # priveleges to update this user in any way
536 if( ! $patron->isnew ) {
537 my $p = $session->request(
538 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
540 # If we are the requestor (trying to update our own account)
541 # and we are not trying to change our profile, we're good
542 if( $p->id == $requestor->id and
543 $p->profile == $patron->profile ) {
548 $evt = group_perm_failed($session, $requestor, $p);
552 # They are allowed to edit this patron.. can they put the
553 # patron into the group requested?
554 $evt = group_perm_failed($session, $requestor, $patron);
560 sub group_perm_failed {
561 my( $session, $requestor, $patron ) = @_;
565 my $grpid = $patron->profile;
569 $logger->debug("user update looking for group perm for group $grpid");
570 $grp = $session->request(
571 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
572 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
574 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
576 $logger->info("user update checking perm $perm on user ".
577 $requestor->id." for update/create on user username=".$patron->usrname);
579 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
587 my( $session, $patron, $user_obj, $noperm) = @_;
589 $logger->info("Updating patron ".$patron->id." in DB");
594 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
595 return (undef, $evt) if $evt;
598 # update the password by itself to avoid the password protection magic
599 if( $patron->passwd ) {
600 my $s = $session->request(
601 'open-ils.storage.direct.actor.user.remote_update',
602 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
603 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
604 $patron->clear_passwd;
607 if(!$patron->ident_type) {
608 $patron->clear_ident_type;
609 $patron->clear_ident_value;
612 $evt = verify_last_xact($session, $patron);
613 return (undef, $evt) if $evt;
615 my $stat = $session->request(
616 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
617 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
622 sub verify_last_xact {
623 my( $session, $patron ) = @_;
624 return undef unless $patron->id and $patron->id > 0;
625 my $p = $session->request(
626 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
627 my $xact = $p->last_xact_id;
628 return undef unless $xact;
629 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
630 return OpenILS::Event->new('XACT_COLLISION')
631 if $xact != $patron->last_xact_id;
636 sub _check_dup_ident {
637 my( $session, $patron ) = @_;
639 return undef unless $patron->ident_value;
642 ident_type => $patron->ident_type,
643 ident_value => $patron->ident_value,
646 $logger->debug("patron update searching for dup ident values: " .
647 $patron->ident_type . ':' . $patron->ident_value);
649 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
651 my $dups = $session->request(
652 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
655 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
662 sub _add_update_addresses {
666 my $new_patron = shift;
670 my $current_id; # id of the address before creation
672 for my $address (@{$patron->addresses()}) {
674 next unless ref $address;
675 $current_id = $address->id();
677 if( $patron->billing_address() and
678 $patron->billing_address() == $current_id ) {
679 $logger->info("setting billing addr to $current_id");
680 $new_patron->billing_address($address->id());
681 $new_patron->ischanged(1);
684 if( $patron->mailing_address() and
685 $patron->mailing_address() == $current_id ) {
686 $new_patron->mailing_address($address->id());
687 $logger->info("setting mailing addr to $current_id");
688 $new_patron->ischanged(1);
692 if($address->isnew()) {
694 $address->usr($new_patron->id());
696 ($address, $evt) = _add_address($session,$address);
697 return (undef, $evt) if $evt;
699 # we need to get the new id
700 if( $patron->billing_address() and
701 $patron->billing_address() == $current_id ) {
702 $new_patron->billing_address($address->id());
703 $logger->info("setting billing addr to $current_id");
704 $new_patron->ischanged(1);
707 if( $patron->mailing_address() and
708 $patron->mailing_address() == $current_id ) {
709 $new_patron->mailing_address($address->id());
710 $logger->info("setting mailing addr to $current_id");
711 $new_patron->ischanged(1);
714 } elsif($address->ischanged() ) {
716 ($address, $evt) = _update_address($session, $address);
717 return (undef, $evt) if $evt;
719 } elsif($address->isdeleted() ) {
721 if( $address->id() == $new_patron->mailing_address() ) {
722 $new_patron->clear_mailing_address();
723 ($new_patron, $evt) = _update_patron($session, $new_patron);
724 return (undef, $evt) if $evt;
727 if( $address->id() == $new_patron->billing_address() ) {
728 $new_patron->clear_billing_address();
729 ($new_patron, $evt) = _update_patron($session, $new_patron);
730 return (undef, $evt) if $evt;
733 $evt = _delete_address($session, $address);
734 return (undef, $evt) if $evt;
738 return ( $new_patron, undef );
742 # adds an address to the db and returns the address with new id
744 my($session, $address) = @_;
745 $address->clear_id();
747 $logger->info("Creating new address at street ".$address->street1);
749 # put the address into the database
750 my $id = $session->request(
751 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
752 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
755 return ($address, undef);
759 sub _update_address {
760 my( $session, $address ) = @_;
762 $logger->info("Updating address ".$address->id." in the DB");
764 my $stat = $session->request(
765 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
767 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
768 return ($address, undef);
773 sub _add_update_cards {
777 my $new_patron = shift;
781 my $virtual_id; #id of the card before creation
782 for my $card (@{$patron->cards()}) {
784 $card->usr($new_patron->id());
786 if(ref($card) and $card->isnew()) {
788 $virtual_id = $card->id();
789 ( $card, $evt ) = _add_card($session,$card);
790 return (undef, $evt) if $evt;
792 #if(ref($patron->card)) { $patron->card($patron->card->id); }
793 if($patron->card() == $virtual_id) {
794 $new_patron->card($card->id());
795 $new_patron->ischanged(1);
798 } elsif( ref($card) and $card->ischanged() ) {
799 $evt = _update_card($session, $card);
800 return (undef, $evt) if $evt;
804 return ( $new_patron, undef );
808 # adds an card to the db and returns the card with new id
810 my( $session, $card ) = @_;
813 $logger->info("Adding new patron card ".$card->barcode);
815 my $id = $session->request(
816 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
817 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
818 $logger->info("Successfully created patron card $id");
821 return ( $card, undef );
825 # returns event on error. returns undef otherwise
827 my( $session, $card ) = @_;
828 $logger->info("Updating patron card ".$card->id);
830 my $stat = $session->request(
831 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
832 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
839 # returns event on error. returns undef otherwise
840 sub _delete_address {
841 my( $session, $address ) = @_;
843 $logger->info("Deleting address ".$address->id." from DB");
845 my $stat = $session->request(
846 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
848 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
854 sub _add_survey_responses {
855 my ($session, $patron, $new_patron) = @_;
857 $logger->info( "Updating survey responses for patron ".$new_patron->id );
859 my $responses = $patron->survey_responses;
863 $_->usr($new_patron->id) for (@$responses);
865 my $evt = $U->simplereq( "open-ils.circ",
866 "open-ils.circ.survey.submit.user_id", $responses );
868 return (undef, $evt) if defined($U->event_code($evt));
872 return ( $new_patron, undef );
876 sub _create_stat_maps {
878 my($session, $user_session, $patron, $new_patron) = @_;
880 my $maps = $patron->stat_cat_entries();
882 for my $map (@$maps) {
884 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
886 if ($map->isdeleted()) {
887 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
889 } elsif ($map->isnew()) {
890 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
895 $map->target_usr($new_patron->id);
898 $logger->info("Updating stat entry with method $method and map $map");
900 my $stat = $session->request($method, $map)->gather(1);
901 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
905 return ($new_patron, undef);
908 sub _create_perm_maps {
910 my($session, $user_session, $patron, $new_patron) = @_;
912 my $maps = $patron->permissions;
914 for my $map (@$maps) {
916 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
917 if ($map->isdeleted()) {
918 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
919 } elsif ($map->isnew()) {
920 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
925 $map->usr($new_patron->id);
927 #warn( "Updating permissions with method $method and session $user_session and map $map" );
928 $logger->info( "Updating permissions with method $method and map $map" );
930 my $stat = $session->request($method, $map)->gather(1);
931 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
935 return ($new_patron, undef);
939 __PACKAGE__->register_method(
940 method => "set_user_work_ous",
941 api_name => "open-ils.actor.user.work_ous.update",
944 sub set_user_work_ous {
950 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
953 my $session = $apputils->start_db_session();
955 for my $map (@$maps) {
957 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
958 if ($map->isdeleted()) {
959 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
960 } elsif ($map->isnew()) {
961 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
965 #warn( "Updating permissions with method $method and session $ses and map $map" );
966 $logger->info( "Updating work_ou map with method $method and map $map" );
968 my $stat = $session->request($method, $map)->gather(1);
969 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
973 $apputils->commit_db_session($session);
975 return scalar(@$maps);
979 __PACKAGE__->register_method(
980 method => "set_user_perms",
981 api_name => "open-ils.actor.user.permissions.update",
990 my $session = $apputils->start_db_session();
992 my( $user_obj, $evt ) = $U->checkses($ses);
995 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
998 $all = 1 if ($U->is_true($user_obj->super_user()));
999 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1001 for my $map (@$maps) {
1003 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1004 if ($map->isdeleted()) {
1005 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1006 } elsif ($map->isnew()) {
1007 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1011 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1012 #warn( "Updating permissions with method $method and session $ses and map $map" );
1013 $logger->info( "Updating permissions with method $method and map $map" );
1015 my $stat = $session->request($method, $map)->gather(1);
1016 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1020 $apputils->commit_db_session($session);
1022 return scalar(@$maps);
1026 __PACKAGE__->register_method(
1027 method => "user_retrieve_by_barcode",
1029 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1031 sub user_retrieve_by_barcode {
1032 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1034 my $e = new_editor(authtoken => $auth);
1035 return $e->event unless $e->checkauth;
1037 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1038 or return $e->event;
1040 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1041 return $e->event unless $e->allowed(
1042 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1049 __PACKAGE__->register_method(
1050 method => "get_user_by_id",
1052 api_name => "open-ils.actor.user.retrieve",
1055 sub get_user_by_id {
1056 my ($self, $client, $auth, $id) = @_;
1057 my $e = new_editor(authtoken=>$auth);
1058 return $e->event unless $e->checkauth;
1059 my $user = $e->retrieve_actor_user($id) or return $e->event;
1060 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1065 __PACKAGE__->register_method(
1066 method => "get_org_types",
1067 api_name => "open-ils.actor.org_types.retrieve",
1070 return $U->get_org_types();
1074 __PACKAGE__->register_method(
1075 method => "get_user_ident_types",
1076 api_name => "open-ils.actor.user.ident_types.retrieve",
1079 sub get_user_ident_types {
1080 return $ident_types if $ident_types;
1081 return $ident_types =
1082 new_editor()->retrieve_all_config_identification_type();
1086 __PACKAGE__->register_method(
1087 method => "get_org_unit",
1088 api_name => "open-ils.actor.org_unit.retrieve",
1092 my( $self, $client, $user_session, $org_id ) = @_;
1093 my $e = new_editor(authtoken => $user_session);
1095 return $e->event unless $e->checkauth;
1096 $org_id = $e->requestor->ws_ou;
1098 my $o = $e->retrieve_actor_org_unit($org_id)
1099 or return $e->event;
1103 __PACKAGE__->register_method(
1104 method => "search_org_unit",
1105 api_name => "open-ils.actor.org_unit_list.search",
1108 sub search_org_unit {
1110 my( $self, $client, $field, $value ) = @_;
1112 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1114 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1115 { $field => $value } );
1121 # build the org tree
1123 __PACKAGE__->register_method(
1124 method => "get_org_tree",
1125 api_name => "open-ils.actor.org_tree.retrieve",
1127 note => "Returns the entire org tree structure",
1133 return $U->get_org_tree($client->session->session_locale);
1137 __PACKAGE__->register_method(
1138 method => "get_org_descendants",
1139 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1142 # depth is optional. org_unit is the id
1143 sub get_org_descendants {
1144 my( $self, $client, $org_unit, $depth ) = @_;
1146 if(ref $org_unit eq 'ARRAY') {
1149 for my $i (0..scalar(@$org_unit)-1) {
1150 my $list = $U->simple_scalar_request(
1152 "open-ils.storage.actor.org_unit.descendants.atomic",
1153 $org_unit->[$i], $depth->[$i] );
1154 push(@trees, $U->build_org_tree($list));
1159 my $orglist = $apputils->simple_scalar_request(
1161 "open-ils.storage.actor.org_unit.descendants.atomic",
1162 $org_unit, $depth );
1163 return $U->build_org_tree($orglist);
1168 __PACKAGE__->register_method(
1169 method => "get_org_ancestors",
1170 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1173 # depth is optional. org_unit is the id
1174 sub get_org_ancestors {
1175 my( $self, $client, $org_unit, $depth ) = @_;
1176 my $orglist = $apputils->simple_scalar_request(
1178 "open-ils.storage.actor.org_unit.ancestors.atomic",
1179 $org_unit, $depth );
1180 return $U->build_org_tree($orglist);
1184 __PACKAGE__->register_method(
1185 method => "get_standings",
1186 api_name => "open-ils.actor.standings.retrieve"
1191 return $user_standings if $user_standings;
1192 return $user_standings =
1193 $apputils->simple_scalar_request(
1195 "open-ils.cstore.direct.config.standing.search.atomic",
1196 { id => { "!=" => undef } }
1201 __PACKAGE__->register_method(
1202 method => "get_my_org_path",
1203 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1206 sub get_my_org_path {
1207 my( $self, $client, $auth, $org_id ) = @_;
1208 my $e = new_editor(authtoken=>$auth);
1209 return $e->event unless $e->checkauth;
1210 $org_id = $e->requestor->ws_ou unless defined $org_id;
1212 return $apputils->simple_scalar_request(
1214 "open-ils.storage.actor.org_unit.full_path.atomic",
1219 __PACKAGE__->register_method(
1220 method => "patron_adv_search",
1221 api_name => "open-ils.actor.patron.search.advanced"
1223 sub patron_adv_search {
1224 my( $self, $client, $auth, $search_hash,
1225 $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1227 my $e = new_editor(authtoken=>$auth);
1228 return $e->event unless $e->checkauth;
1229 return $e->event unless $e->allowed('VIEW_USER');
1230 return $U->storagereq(
1231 "open-ils.storage.actor.user.crazy_search", $search_hash,
1232 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1236 __PACKAGE__->register_method(
1237 method => "update_passwd",
1238 api_name => "open-ils.actor.user.password.update",
1240 desc => "Update the operator's password",
1242 { desc => 'Authentication token', type => 'string' },
1243 { desc => 'New password', type => 'string' },
1244 { desc => 'Current password', type => 'string' }
1246 return => {desc => '1 on success, Event on error or incorrect current password'}
1250 __PACKAGE__->register_method(
1251 method => "update_passwd",
1252 api_name => "open-ils.actor.user.username.update",
1254 desc => "Update the operator's username",
1256 { desc => 'Authentication token', type => 'string' },
1257 { desc => 'New username', type => 'string' }
1259 return => {desc => '1 on success, Event on error'}
1263 __PACKAGE__->register_method(
1264 method => "update_passwd",
1265 api_name => "open-ils.actor.user.email.update",
1267 desc => "Update the operator's email address",
1269 { desc => 'Authentication token', type => 'string' },
1270 { desc => 'New email address', type => 'string' }
1272 return => {desc => '1 on success, Event on error'}
1277 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1278 my $e = new_editor(xact=>1, authtoken=>$auth);
1279 return $e->die_event unless $e->checkauth;
1281 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1282 or return $e->die_event;
1283 my $api = $self->api_name;
1285 if( $api =~ /password/o ) {
1286 # make sure the original password matches the in-database password
1287 if (md5_hex($orig_pw) ne $db_user->passwd) {
1289 return new OpenILS::Event('INCORRECT_PASSWORD');
1291 $db_user->passwd($new_val);
1295 # if we don't clear the password, the user will be updated with
1296 # a hashed version of the hashed version of their password
1297 $db_user->clear_passwd;
1299 if( $api =~ /username/o ) {
1301 # make sure no one else has this username
1302 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1305 return new OpenILS::Event('USERNAME_EXISTS');
1307 $db_user->usrname($new_val);
1309 } elsif( $api =~ /email/o ) {
1310 $db_user->email($new_val);
1314 $e->update_actor_user($db_user) or return $e->die_event;
1317 # update the cached user to pick up these changes
1318 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1324 __PACKAGE__->register_method(
1325 method => "check_user_perms",
1326 api_name => "open-ils.actor.user.perm.check",
1327 notes => <<" NOTES");
1328 Takes a login session, user id, an org id, and an array of perm type strings. For each
1329 perm type, if the user does *not* have the given permission it is added
1330 to a list which is returned from the method. If all permissions
1331 are allowed, an empty list is returned
1332 if the logged in user does not match 'user_id', then the logged in user must
1333 have VIEW_PERMISSION priveleges.
1336 sub check_user_perms {
1337 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1339 my( $staff, $evt ) = $apputils->checkses($login_session);
1340 return $evt if $evt;
1342 if($staff->id ne $user_id) {
1343 if( $evt = $apputils->check_perms(
1344 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1350 for my $perm (@$perm_types) {
1351 if($apputils->check_perms($user_id, $org_id, $perm)) {
1352 push @not_allowed, $perm;
1356 return \@not_allowed
1359 __PACKAGE__->register_method(
1360 method => "check_user_perms2",
1361 api_name => "open-ils.actor.user.perm.check.multi_org",
1363 Checks the permissions on a list of perms and orgs for a user
1364 @param authtoken The login session key
1365 @param user_id The id of the user to check
1366 @param orgs The array of org ids
1367 @param perms The array of permission names
1368 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1369 if the logged in user does not match 'user_id', then the logged in user must
1370 have VIEW_PERMISSION priveleges.
1373 sub check_user_perms2 {
1374 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1376 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1377 $authtoken, $user_id, 'VIEW_PERMISSION' );
1378 return $evt if $evt;
1381 for my $org (@$orgs) {
1382 for my $perm (@$perms) {
1383 if($apputils->check_perms($user_id, $org, $perm)) {
1384 push @not_allowed, [ $org, $perm ];
1389 return \@not_allowed
1393 __PACKAGE__->register_method(
1394 method => 'check_user_perms3',
1395 api_name => 'open-ils.actor.user.perm.highest_org',
1397 Returns the highest org unit id at which a user has a given permission
1398 If the requestor does not match the target user, the requestor must have
1399 'VIEW_PERMISSION' rights at the home org unit of the target user
1400 @param authtoken The login session key
1401 @param userid The id of the user in question
1402 @param perm The permission to check
1403 @return The org unit highest in the org tree within which the user has
1404 the requested permission
1407 sub check_user_perms3 {
1408 my($self, $client, $authtoken, $user_id, $perm) = @_;
1409 my $e = new_editor(authtoken=>$authtoken);
1410 return $e->event unless $e->checkauth;
1412 my $tree = $U->get_org_tree();
1414 unless($e->requestor->id == $user_id) {
1415 my $user = $e->retrieve_actor_user($user_id)
1416 or return $e->event;
1417 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1418 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1421 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1424 __PACKAGE__->register_method(
1425 method => 'user_has_work_perm_at',
1426 api_name => 'open-ils.actor.user.has_work_perm_at',
1430 Returns a set of org unit IDs which represent the highest orgs in
1431 the org tree where the user has the requested permission. The
1432 purpose of this method is to return the smallest set of org units
1433 which represent the full expanse of the user's ability to perform
1434 the requested action. The user whose perms this method should
1435 check is implied by the authtoken. /,
1437 {desc => 'authtoken', type => 'string'},
1438 {desc => 'permission name', type => 'string'},
1439 {desc => q/user id, optional. If present, check perms for
1440 this user instead of the logged in user/, type => 'number'},
1442 return => {desc => 'An array of org IDs'}
1446 sub user_has_work_perm_at {
1447 my($self, $conn, $auth, $perm, $user_id) = @_;
1448 my $e = new_editor(authtoken=>$auth);
1449 return $e->event unless $e->checkauth;
1450 if(defined $user_id) {
1451 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1452 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1454 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1457 __PACKAGE__->register_method(
1458 method => 'user_has_work_perm_at_batch',
1459 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1463 sub user_has_work_perm_at_batch {
1464 my($self, $conn, $auth, $perms, $user_id) = @_;
1465 my $e = new_editor(authtoken=>$auth);
1466 return $e->event unless $e->checkauth;
1467 if(defined $user_id) {
1468 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1469 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1472 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1478 __PACKAGE__->register_method(
1479 method => 'check_user_perms4',
1480 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1482 Returns the highest org unit id at which a user has a given permission
1483 If the requestor does not match the target user, the requestor must have
1484 'VIEW_PERMISSION' rights at the home org unit of the target user
1485 @param authtoken The login session key
1486 @param userid The id of the user in question
1487 @param perms An array of perm names to check
1488 @return An array of orgId's representing the org unit
1489 highest in the org tree within which the user has the requested permission
1490 The arrah of orgId's has matches the order of the perms array
1493 sub check_user_perms4 {
1494 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1496 my( $staff, $target, $org, $evt );
1498 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1499 $authtoken, $userid, 'VIEW_PERMISSION' );
1500 return $evt if $evt;
1503 return [] unless ref($perms);
1504 my $tree = $U->get_org_tree();
1506 for my $p (@$perms) {
1507 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1513 __PACKAGE__->register_method(
1514 method => "user_fines_summary",
1515 api_name => "open-ils.actor.user.fines.summary",
1518 desc => 'Returns a short summary of the users total open fines, ' .
1519 'excluding voided fines Params are login_session, user_id' ,
1521 {desc => 'Authentication token', type => 'string'},
1522 {desc => 'User ID', type => 'string'} # number?
1525 desc => "a 'mous' object, event on error",
1530 sub user_fines_summary {
1531 my( $self, $client, $auth, $user_id ) = @_;
1533 my $e = new_editor(authtoken=>$auth);
1534 return $e->event unless $e->checkauth;
1536 if( $user_id ne $e->requestor->id ) {
1537 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1538 return $e->event unless
1539 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1542 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1546 __PACKAGE__->register_method(
1547 method => "user_opac_vitals",
1548 api_name => "open-ils.actor.user.opac.vital_stats",
1552 desc => 'Returns a short summary of the users vital stats, including ' .
1553 'identification information, accumulated balance, number of holds, ' .
1554 'and current open circulation stats' ,
1556 {desc => 'Authentication token', type => 'string'},
1557 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1560 desc => "An object with four properties: user, fines, checkouts and holds."
1565 sub user_opac_vitals {
1566 my( $self, $client, $auth, $user_id ) = @_;
1568 my $e = new_editor(authtoken=>$auth);
1569 return $e->event unless $e->checkauth;
1571 $user_id ||= $e->requestor->id;
1573 my $user = $e->retrieve_actor_user( $user_id );
1576 ->method_lookup('open-ils.actor.user.fines.summary')
1577 ->run($auth => $user_id);
1578 return $fines if (defined($U->event_code($fines)));
1581 $fines = new Fieldmapper::money::open_user_summary ();
1582 $fines->balance_owed(0.00);
1583 $fines->total_owed(0.00);
1584 $fines->total_paid(0.00);
1585 $fines->usr($user_id);
1589 ->method_lookup('open-ils.actor.user.hold_requests.count')
1590 ->run($auth => $user_id);
1591 return $holds if (defined($U->event_code($holds)));
1594 ->method_lookup('open-ils.actor.user.checked_out.count')
1595 ->run($auth => $user_id);
1596 return $out if (defined($U->event_code($out)));
1598 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1602 first_given_name => $user->first_given_name,
1603 second_given_name => $user->second_given_name,
1604 family_name => $user->family_name,
1605 alias => $user->alias,
1606 usrname => $user->usrname
1608 fines => $fines->to_bare_hash,
1615 ##### a small consolidation of related method registrations
1616 my $common_params = [
1617 { desc => 'Authentication token', type => 'string' },
1618 { desc => 'User ID', type => 'string' },
1619 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1620 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1623 'open-ils.actor.user.transactions' => '',
1624 'open-ils.actor.user.transactions.fleshed' => '',
1625 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1626 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1627 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1628 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1631 foreach (keys %methods) {
1633 method => "user_transactions",
1636 desc => 'For a given user, retrieve a list of '
1637 . (/\.fleshed/ ? 'fleshed ' : '')
1638 . 'transactions' . $methods{$_}
1639 . ' optionally limited to transactions of a given type.',
1640 params => $common_params,
1642 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1643 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1647 $args{authoritative} = 1;
1648 __PACKAGE__->register_method(%args);
1651 # Now for the counts
1653 'open-ils.actor.user.transactions.count' => '',
1654 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1655 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1658 foreach (keys %methods) {
1660 method => "user_transactions",
1663 desc => 'For a given user, retrieve a count of open '
1664 . 'transactions' . $methods{$_}
1665 . ' optionally limited to transactions of a given type.',
1666 params => $common_params,
1667 return => { desc => "Integer count of transactions, or event on error" }
1670 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1671 __PACKAGE__->register_method(%args);
1674 __PACKAGE__->register_method(
1675 method => "user_transactions",
1676 api_name => "open-ils.actor.user.transactions.have_balance.total",
1679 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1680 . ' optionally limited to transactions of a given type.',
1681 params => $common_params,
1682 return => { desc => "Decimal balance value, or event on error" }
1687 sub user_transactions {
1688 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1691 my $e = new_editor(authtoken => $auth);
1692 return $e->event unless $e->checkauth;
1694 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1696 return $e->event unless
1697 $e->requestor->id == $user_id or
1698 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1700 my $api = $self->api_name();
1702 my $filter = ($api =~ /have_balance/o) ?
1703 { 'balance_owed' => { '<>' => 0 } }:
1704 { 'total_owed' => { '>' => 0 } };
1706 my $method = 'open-ils.actor.user.transactions.history.still_open';
1707 $method = "$method.authoritative" if $api =~ /authoritative/;
1708 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1710 if($api =~ /total/o) {
1712 $total += $_->balance_owed for @$trans;
1716 ($api =~ /count/o ) and return scalar @$trans;
1717 ($api !~ /fleshed/o) and return $trans;
1720 for my $t (@$trans) {
1722 if( $t->xact_type ne 'circulation' ) {
1723 push @resp, {transaction => $t};
1727 my $circ_data = flesh_circ($e, $t->id);
1728 push @resp, {transaction => $t, %$circ_data};
1735 __PACKAGE__->register_method(
1736 method => "user_transaction_retrieve",
1737 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1740 notes => "Returns a fleshed transaction record"
1743 __PACKAGE__->register_method(
1744 method => "user_transaction_retrieve",
1745 api_name => "open-ils.actor.user.transaction.retrieve",
1748 notes => "Returns a transaction record"
1751 sub user_transaction_retrieve {
1752 my($self, $client, $auth, $bill_id) = @_;
1754 my $e = new_editor(authtoken => $auth);
1755 return $e->event unless $e->checkauth;
1757 my $trans = $e->retrieve_money_billable_transaction_summary(
1758 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1760 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1762 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1764 return $trans unless $self->api_name =~ /flesh/;
1765 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1767 my $circ_data = flesh_circ($e, $trans->id, 1);
1769 return {transaction => $trans, %$circ_data};
1774 my $circ_id = shift;
1775 my $flesh_copy = shift;
1777 my $circ = $e->retrieve_action_circulation([
1781 circ => ['target_copy'],
1782 acp => ['call_number'],
1789 my $copy = $circ->target_copy;
1791 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1792 $mods = new Fieldmapper::metabib::virtual_record;
1793 $mods->doc_id(OILS_PRECAT_RECORD);
1794 $mods->title($copy->dummy_title);
1795 $mods->author($copy->dummy_author);
1798 my $u = OpenILS::Utils::ModsParser->new();
1799 $u->start_mods_batch($circ->target_copy->call_number->record->marc);
1800 $mods = $u->finish_mods_batch();
1804 $circ->target_copy($circ->target_copy->id);
1805 $copy->call_number($copy->call_number->id);
1807 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1811 __PACKAGE__->register_method(
1812 method => "hold_request_count",
1813 api_name => "open-ils.actor.user.hold_requests.count",
1816 notes => 'Returns hold ready/total counts'
1819 sub hold_request_count {
1820 my( $self, $client, $login_session, $userid ) = @_;
1822 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1823 $login_session, $userid, 'VIEW_HOLD' );
1824 return $evt if $evt;
1827 my $holds = $apputils->simple_scalar_request(
1829 "open-ils.cstore.direct.action.hold_request.search.atomic",
1832 fulfillment_time => {"=" => undef },
1833 cancel_time => undef,
1838 for my $h (@$holds) {
1839 next unless $h->capture_time and $h->current_copy;
1841 my $copy = $apputils->simple_scalar_request(
1843 "open-ils.cstore.direct.asset.copy.retrieve",
1847 if ($copy and $copy->status == 8) {
1852 return { total => scalar(@$holds), ready => scalar(@ready) };
1855 __PACKAGE__->register_method(
1856 method => "checked_out",
1857 api_name => "open-ils.actor.user.checked_out",
1861 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1862 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1863 . "(i.e., outstanding balance or some other pending action on the circ). "
1864 . "The .count method also includes a 'total' field which sums all open circs.",
1866 { desc => 'Authentication Token', type => 'string'},
1867 { desc => 'User ID', type => 'string'},
1870 desc => 'Returns event on error, or an object with ID lists, like: '
1871 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1876 __PACKAGE__->register_method(
1877 method => "checked_out",
1878 api_name => "open-ils.actor.user.checked_out.count",
1881 signature => q/@see open-ils.actor.user.checked_out/
1885 my( $self, $conn, $auth, $userid ) = @_;
1887 my $e = new_editor(authtoken=>$auth);
1888 return $e->event unless $e->checkauth;
1890 if( $userid ne $e->requestor->id ) {
1891 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1892 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1894 # see if there is a friend link allowing circ.view perms
1895 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1896 $e, $userid, $e->requestor->id, 'circ.view');
1897 return $e->event unless $allowed;
1901 my $count = $self->api_name =~ /count/;
1902 return _checked_out( $count, $e, $userid );
1906 my( $iscount, $e, $userid ) = @_;
1912 claims_returned => [],
1915 my $meth = 'retrieve_action_open_circ_';
1923 claims_returned => 0,
1930 my $data = $e->$meth($userid);
1934 $result{$_} += $data->$_() for (keys %result);
1935 $result{total} += $data->$_() for (keys %result);
1937 for my $k (keys %result) {
1938 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
1948 __PACKAGE__->register_method(
1949 method => "checked_in_with_fines",
1950 api_name => "open-ils.actor.user.checked_in_with_fines",
1953 signature => q/@see open-ils.actor.user.checked_out/
1956 sub checked_in_with_fines {
1957 my( $self, $conn, $auth, $userid ) = @_;
1959 my $e = new_editor(authtoken=>$auth);
1960 return $e->event unless $e->checkauth;
1962 if( $userid ne $e->requestor->id ) {
1963 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1966 # money is owed on these items and they are checked in
1967 my $open = $e->search_action_circulation(
1970 xact_finish => undef,
1971 checkin_time => { "!=" => undef },
1976 my( @lost, @cr, @lo );
1977 for my $c (@$open) {
1978 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1979 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1980 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1985 claims_returned => \@cr,
1986 long_overdue => \@lo
1992 my ($api, $desc, $auth) = @_;
1993 $desc = $desc ? (" " . $desc) : '';
1994 my $ids = ($api =~ /ids$/) ? 1 : 0;
1997 method => "user_transaction_history",
1998 api_name => "open-ils.actor.user.transactions.$api",
2000 desc => "For a given User ID, returns a list of billable transaction" .
2001 ($ids ? " id" : '') .
2002 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2003 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2005 {desc => 'Authentication token', type => 'string'},
2006 {desc => 'User ID', type => 'number'},
2007 {desc => 'Transaction type (optional)', type => 'number'},
2008 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2011 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2015 $auth and push @sig, (authoritative => 1);
2019 my %auth_hist_methods = (
2021 'history.have_charge' => 'that have an initial charge',
2022 'history.still_open' => 'that are not finished',
2023 'history.have_balance' => 'that have a balance',
2024 'history.have_bill' => 'that have billings',
2025 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2026 'history.have_payment' => 'that have at least 1 payment',
2029 foreach (keys %auth_hist_methods) {
2030 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2031 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2032 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2035 sub user_transaction_history {
2036 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2040 my $e = new_editor(authtoken=>$auth);
2041 return $e->die_event unless $e->checkauth;
2043 if ($e->requestor->id ne $userid) {
2044 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2047 my $api = $self->api_name;
2048 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2050 if(defined($type)) {
2051 $filter->{'xact_type'} = $type;
2054 if($api =~ /have_bill_or_payment/o) {
2056 # transactions that have a non-zero sum across all billings or at least 1 payment
2057 $filter->{'-or'} = {
2058 'balance_owed' => { '<>' => 0 },
2059 'last_payment_ts' => { '<>' => undef }
2062 } elsif($api =~ /have_payment/) {
2064 $filter->{last_payment_ts} ||= {'<>' => undef};
2066 } elsif( $api =~ /have_balance/o) {
2068 # transactions that have a non-zero overall balance
2069 $filter->{'balance_owed'} = { '<>' => 0 };
2071 } elsif( $api =~ /have_charge/o) {
2073 # transactions that have at least 1 billing, regardless of whether it was voided
2074 $filter->{'last_billing_ts'} = { '<>' => undef };
2076 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2078 # transactions that have non-zero sum across all billings. This will exclude
2079 # xacts where all billings have been voided
2080 $filter->{'total_owed'} = { '<>' => 0 };
2083 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2084 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2085 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2087 my $mbts = $e->search_money_billable_transaction_summary(
2088 [ { usr => $userid, @xact_finish, %$filter },
2093 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2094 return $mbts unless $api =~ /fleshed/;
2097 for my $t (@$mbts) {
2099 if( $t->xact_type ne 'circulation' ) {
2100 push @resp, {transaction => $t};
2104 my $circ_data = flesh_circ($e, $t->id);
2105 push @resp, {transaction => $t, %$circ_data};
2113 __PACKAGE__->register_method(
2114 method => "user_perms",
2115 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2117 notes => "Returns a list of permissions"
2121 my( $self, $client, $authtoken, $user ) = @_;
2123 my( $staff, $evt ) = $apputils->checkses($authtoken);
2124 return $evt if $evt;
2126 $user ||= $staff->id;
2128 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2132 return $apputils->simple_scalar_request(
2134 "open-ils.storage.permission.user_perms.atomic",
2138 __PACKAGE__->register_method(
2139 method => "retrieve_perms",
2140 api_name => "open-ils.actor.permissions.retrieve",
2141 notes => "Returns a list of permissions"
2143 sub retrieve_perms {
2144 my( $self, $client ) = @_;
2145 return $apputils->simple_scalar_request(
2147 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2148 { id => { '!=' => undef } }
2152 __PACKAGE__->register_method(
2153 method => "retrieve_groups",
2154 api_name => "open-ils.actor.groups.retrieve",
2155 notes => "Returns a list of user groups"
2157 sub retrieve_groups {
2158 my( $self, $client ) = @_;
2159 return new_editor()->retrieve_all_permission_grp_tree();
2162 __PACKAGE__->register_method(
2163 method => "retrieve_org_address",
2164 api_name => "open-ils.actor.org_unit.address.retrieve",
2165 notes => <<' NOTES');
2166 Returns an org_unit address by ID
2167 @param An org_address ID
2169 sub retrieve_org_address {
2170 my( $self, $client, $id ) = @_;
2171 return $apputils->simple_scalar_request(
2173 "open-ils.cstore.direct.actor.org_address.retrieve",
2178 __PACKAGE__->register_method(
2179 method => "retrieve_groups_tree",
2180 api_name => "open-ils.actor.groups.tree.retrieve",
2181 notes => "Returns a list of user groups"
2184 sub retrieve_groups_tree {
2185 my( $self, $client ) = @_;
2186 return new_editor()->search_permission_grp_tree(
2191 flesh_fields => { pgt => ["children"] },
2192 order_by => { pgt => 'name'}
2199 __PACKAGE__->register_method(
2200 method => "add_user_to_groups",
2201 api_name => "open-ils.actor.user.set_groups",
2202 notes => "Adds a user to one or more permission groups"
2205 sub add_user_to_groups {
2206 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2208 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2209 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2210 return $evt if $evt;
2212 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2213 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2214 return $evt if $evt;
2216 $apputils->simplereq(
2218 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2220 for my $group (@$groups) {
2221 my $link = Fieldmapper::permission::usr_grp_map->new;
2223 $link->usr($userid);
2225 my $id = $apputils->simplereq(
2227 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2233 __PACKAGE__->register_method(
2234 method => "get_user_perm_groups",
2235 api_name => "open-ils.actor.user.get_groups",
2236 notes => "Retrieve a user's permission groups."
2240 sub get_user_perm_groups {
2241 my( $self, $client, $authtoken, $userid ) = @_;
2243 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2244 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2245 return $evt if $evt;
2247 return $apputils->simplereq(
2249 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2253 __PACKAGE__->register_method(
2254 method => "get_user_work_ous",
2255 api_name => "open-ils.actor.user.get_work_ous",
2256 notes => "Retrieve a user's work org units."
2259 __PACKAGE__->register_method(
2260 method => "get_user_work_ous",
2261 api_name => "open-ils.actor.user.get_work_ous.ids",
2262 notes => "Retrieve a user's work org units."
2265 sub get_user_work_ous {
2266 my( $self, $client, $auth, $userid ) = @_;
2267 my $e = new_editor(authtoken=>$auth);
2268 return $e->event unless $e->checkauth;
2269 $userid ||= $e->requestor->id;
2271 if($e->requestor->id != $userid) {
2272 my $user = $e->retrieve_actor_user($userid)
2273 or return $e->event;
2274 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2277 return $e->search_permission_usr_work_ou_map({usr => $userid})
2278 unless $self->api_name =~ /.ids$/;
2280 # client just wants a list of org IDs
2281 return $U->get_user_work_ou_ids($e, $userid);
2286 __PACKAGE__->register_method(
2287 method => 'register_workstation',
2288 api_name => 'open-ils.actor.workstation.register.override',
2289 signature => q/@see open-ils.actor.workstation.register/
2292 __PACKAGE__->register_method(
2293 method => 'register_workstation',
2294 api_name => 'open-ils.actor.workstation.register',
2296 Registers a new workstion in the system
2297 @param authtoken The login session key
2298 @param name The name of the workstation id
2299 @param owner The org unit that owns this workstation
2300 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2301 if the name is already in use.
2305 sub register_workstation {
2306 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2308 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2309 return $e->die_event unless $e->checkauth;
2310 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2311 my $existing = $e->search_actor_workstation({name => $name})->[0];
2315 if( $self->api_name =~ /override/o ) {
2316 # workstation with the given name exists.
2318 if($owner ne $existing->owning_lib) {
2319 # if necessary, update the owning_lib of the workstation
2321 $logger->info("changing owning lib of workstation ".$existing->id.
2322 " from ".$existing->owning_lib." to $owner");
2323 return $e->die_event unless
2324 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2326 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2328 $existing->owning_lib($owner);
2329 return $e->die_event unless $e->update_actor_workstation($existing);
2335 "attempt to register an existing workstation. returning existing ID");
2338 return $existing->id;
2341 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2345 my $ws = Fieldmapper::actor::workstation->new;
2346 $ws->owning_lib($owner);
2348 $e->create_actor_workstation($ws) or return $e->die_event;
2350 return $ws->id; # note: editor sets the id on the new object for us
2353 __PACKAGE__->register_method(
2354 method => 'workstation_list',
2355 api_name => 'open-ils.actor.workstation.list',
2357 Returns a list of workstations registered at the given location
2358 @param authtoken The login session key
2359 @param ids A list of org_unit.id's for the workstation owners
2363 sub workstation_list {
2364 my( $self, $conn, $authtoken, @orgs ) = @_;
2366 my $e = new_editor(authtoken=>$authtoken);
2367 return $e->event unless $e->checkauth;
2372 unless $e->allowed('REGISTER_WORKSTATION', $o);
2373 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2379 __PACKAGE__->register_method(
2380 method => 'fetch_patron_note',
2381 api_name => 'open-ils.actor.note.retrieve.all',
2384 Returns a list of notes for a given user
2385 Requestor must have VIEW_USER permission if pub==false and
2386 @param authtoken The login session key
2387 @param args Hash of params including
2388 patronid : the patron's id
2389 pub : true if retrieving only public notes
2393 sub fetch_patron_note {
2394 my( $self, $conn, $authtoken, $args ) = @_;
2395 my $patronid = $$args{patronid};
2397 my($reqr, $evt) = $U->checkses($authtoken);
2398 return $evt if $evt;
2401 ($patron, $evt) = $U->fetch_user($patronid);
2402 return $evt if $evt;
2405 if( $patronid ne $reqr->id ) {
2406 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2407 return $evt if $evt;
2409 return $U->cstorereq(
2410 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2411 { usr => $patronid, pub => 't' } );
2414 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2415 return $evt if $evt;
2417 return $U->cstorereq(
2418 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2421 __PACKAGE__->register_method(
2422 method => 'create_user_note',
2423 api_name => 'open-ils.actor.note.create',
2425 Creates a new note for the given user
2426 @param authtoken The login session key
2427 @param note The note object
2430 sub create_user_note {
2431 my( $self, $conn, $authtoken, $note ) = @_;
2432 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2433 return $e->die_event unless $e->checkauth;
2435 my $user = $e->retrieve_actor_user($note->usr)
2436 or return $e->die_event;
2438 return $e->die_event unless
2439 $e->allowed('UPDATE_USER',$user->home_ou);
2441 $note->creator($e->requestor->id);
2442 $e->create_actor_usr_note($note) or return $e->die_event;
2448 __PACKAGE__->register_method(
2449 method => 'delete_user_note',
2450 api_name => 'open-ils.actor.note.delete',
2452 Deletes a note for the given user
2453 @param authtoken The login session key
2454 @param noteid The note id
2457 sub delete_user_note {
2458 my( $self, $conn, $authtoken, $noteid ) = @_;
2460 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2461 return $e->die_event unless $e->checkauth;
2462 my $note = $e->retrieve_actor_usr_note($noteid)
2463 or return $e->die_event;
2464 my $user = $e->retrieve_actor_user($note->usr)
2465 or return $e->die_event;
2466 return $e->die_event unless
2467 $e->allowed('UPDATE_USER', $user->home_ou);
2469 $e->delete_actor_usr_note($note) or return $e->die_event;
2475 __PACKAGE__->register_method(
2476 method => 'update_user_note',
2477 api_name => 'open-ils.actor.note.update',
2479 @param authtoken The login session key
2480 @param note The note
2484 sub update_user_note {
2485 my( $self, $conn, $auth, $note ) = @_;
2486 my $e = new_editor(authtoken=>$auth, xact=>1);
2487 return $e->die_event unless $e->checkauth;
2488 my $patron = $e->retrieve_actor_user($note->usr)
2489 or return $e->die_event;
2490 return $e->die_event unless
2491 $e->allowed('UPDATE_USER', $patron->home_ou);
2492 $e->update_actor_user_note($note)
2493 or return $e->die_event;
2500 __PACKAGE__->register_method(
2501 method => 'create_closed_date',
2502 api_name => 'open-ils.actor.org_unit.closed_date.create',
2504 Creates a new closing entry for the given org_unit
2505 @param authtoken The login session key
2506 @param note The closed_date object
2509 sub create_closed_date {
2510 my( $self, $conn, $authtoken, $cd ) = @_;
2512 my( $user, $evt ) = $U->checkses($authtoken);
2513 return $evt if $evt;
2515 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2516 return $evt if $evt;
2518 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2520 my $id = $U->storagereq(
2521 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2522 return $U->DB_UPDATE_FAILED($cd) unless $id;
2527 __PACKAGE__->register_method(
2528 method => 'delete_closed_date',
2529 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2531 Deletes a closing entry for the given org_unit
2532 @param authtoken The login session key
2533 @param noteid The close_date id
2536 sub delete_closed_date {
2537 my( $self, $conn, $authtoken, $cd ) = @_;
2539 my( $user, $evt ) = $U->checkses($authtoken);
2540 return $evt if $evt;
2543 ($cd_obj, $evt) = fetch_closed_date($cd);
2544 return $evt if $evt;
2546 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2547 return $evt if $evt;
2549 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2551 my $stat = $U->storagereq(
2552 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2553 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2558 __PACKAGE__->register_method(
2559 method => 'usrname_exists',
2560 api_name => 'open-ils.actor.username.exists',
2562 desc => 'Check if a username is already taken (by an undeleted patron)',
2564 {desc => 'Authentication token', type => 'string'},
2565 {desc => 'Username', type => 'string'}
2568 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2573 sub usrname_exists {
2574 my( $self, $conn, $auth, $usrname ) = @_;
2575 my $e = new_editor(authtoken=>$auth);
2576 return $e->event unless $e->checkauth;
2577 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2578 return $$a[0] if $a and @$a;
2582 __PACKAGE__->register_method(
2583 method => 'barcode_exists',
2584 api_name => 'open-ils.actor.barcode.exists',
2586 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2589 sub barcode_exists {
2590 my( $self, $conn, $auth, $barcode ) = @_;
2591 my $e = new_editor(authtoken=>$auth);
2592 return $e->event unless $e->checkauth;
2593 my $card = $e->search_actor_card({barcode => $barcode});
2599 #return undef unless @$card;
2600 #return $card->[0]->usr;
2604 __PACKAGE__->register_method(
2605 method => 'retrieve_net_levels',
2606 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2609 sub retrieve_net_levels {
2610 my( $self, $conn, $auth ) = @_;
2611 my $e = new_editor(authtoken=>$auth);
2612 return $e->event unless $e->checkauth;
2613 return $e->retrieve_all_config_net_access_level();
2616 # Retain the old typo API name just in case
2617 __PACKAGE__->register_method(
2618 method => 'fetch_org_by_shortname',
2619 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2621 __PACKAGE__->register_method(
2622 method => 'fetch_org_by_shortname',
2623 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2625 sub fetch_org_by_shortname {
2626 my( $self, $conn, $sname ) = @_;
2627 my $e = new_editor();
2628 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2629 return $e->event unless $org;
2634 __PACKAGE__->register_method(
2635 method => 'session_home_lib',
2636 api_name => 'open-ils.actor.session.home_lib',
2639 sub session_home_lib {
2640 my( $self, $conn, $auth ) = @_;
2641 my $e = new_editor(authtoken=>$auth);
2642 return undef unless $e->checkauth;
2643 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2644 return $org->shortname;
2647 __PACKAGE__->register_method(
2648 method => 'session_safe_token',
2649 api_name => 'open-ils.actor.session.safe_token',
2651 Returns a hashed session ID that is safe for export to the world.
2652 This safe token will expire after 1 hour of non-use.
2653 @param auth Active authentication token
2657 sub session_safe_token {
2658 my( $self, $conn, $auth ) = @_;
2659 my $e = new_editor(authtoken=>$auth);
2660 return undef unless $e->checkauth;
2662 my $safe_token = md5_hex($auth);
2664 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2666 # Add more like the following if needed...
2668 "safe-token-home_lib-shortname-$safe_token",
2669 $e->retrieve_actor_org_unit(
2670 $e->requestor->home_ou
2679 __PACKAGE__->register_method(
2680 method => 'safe_token_home_lib',
2681 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2683 Returns the home library shortname from the session
2684 asscociated with a safe token from generated by
2685 open-ils.actor.session.safe_token.
2686 @param safe_token Active safe token
2690 sub safe_token_home_lib {
2691 my( $self, $conn, $safe_token ) = @_;
2693 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2694 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2699 __PACKAGE__->register_method(
2700 method => 'slim_tree',
2701 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2704 my $tree = new_editor()->search_actor_org_unit(
2706 {"parent_ou" => undef },
2709 flesh_fields => { aou => ['children'] },
2710 order_by => { aou => 'name'},
2711 select => { aou => ["id","shortname", "name"]},
2716 return trim_tree($tree);
2722 return undef unless $tree;
2724 code => $tree->shortname,
2725 name => $tree->name,
2727 if( $tree->children and @{$tree->children} ) {
2728 $htree->{children} = [];
2729 for my $c (@{$tree->children}) {
2730 push( @{$htree->{children}}, trim_tree($c) );
2738 __PACKAGE__->register_method(
2739 method => "update_penalties",
2740 api_name => "open-ils.actor.user.penalties.update"
2743 sub update_penalties {
2744 my($self, $conn, $auth, $user_id) = @_;
2745 my $e = new_editor(authtoken=>$auth, xact => 1);
2746 return $e->die_event unless $e->checkauth;
2747 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2748 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2749 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2750 return $evt if $evt;
2756 __PACKAGE__->register_method(
2757 method => "apply_penalty",
2758 api_name => "open-ils.actor.user.penalty.apply"
2762 my($self, $conn, $auth, $penalty) = @_;
2764 my $e = new_editor(authtoken=>$auth, xact => 1);
2765 return $e->die_event unless $e->checkauth;
2767 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2768 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2770 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2773 (defined $ptype->org_depth) ?
2774 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2777 $penalty->org_unit($ctx_org);
2778 $penalty->staff($e->requestor->id);
2779 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2782 return $penalty->id;
2785 __PACKAGE__->register_method(
2786 method => "remove_penalty",
2787 api_name => "open-ils.actor.user.penalty.remove"
2790 sub remove_penalty {
2791 my($self, $conn, $auth, $penalty) = @_;
2792 my $e = new_editor(authtoken=>$auth, xact => 1);
2793 return $e->die_event unless $e->checkauth;
2794 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2795 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2797 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2802 __PACKAGE__->register_method(
2803 method => "update_penalty_note",
2804 api_name => "open-ils.actor.user.penalty.note.update"
2807 sub update_penalty_note {
2808 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2809 my $e = new_editor(authtoken=>$auth, xact => 1);
2810 return $e->die_event unless $e->checkauth;
2811 for my $penalty_id (@$penalty_ids) {
2812 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2813 if (! $penalty ) { return $e->die_event; }
2814 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2815 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2817 $penalty->note( $note ); $penalty->ischanged( 1 );
2819 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2825 __PACKAGE__->register_method(
2826 method => "ranged_penalty_thresholds",
2827 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2831 sub ranged_penalty_thresholds {
2832 my($self, $conn, $auth, $context_org) = @_;
2833 my $e = new_editor(authtoken=>$auth);
2834 return $e->event unless $e->checkauth;
2835 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2836 my $list = $e->search_permission_grp_penalty_threshold([
2837 {org_unit => $U->get_org_ancestors($context_org)},
2838 {order_by => {pgpt => 'id'}}
2840 $conn->respond($_) for @$list;
2846 __PACKAGE__->register_method(
2847 method => "user_retrieve_fleshed_by_id",
2849 api_name => "open-ils.actor.user.fleshed.retrieve",
2852 sub user_retrieve_fleshed_by_id {
2853 my( $self, $client, $auth, $user_id, $fields ) = @_;
2854 my $e = new_editor(authtoken => $auth);
2855 return $e->event unless $e->checkauth;
2857 if( $e->requestor->id != $user_id ) {
2858 return $e->event unless $e->allowed('VIEW_USER');
2864 "standing_penalties",
2868 "stat_cat_entries" ];
2869 return new_flesh_user($user_id, $fields, $e);
2873 sub new_flesh_user {
2876 my $fields = shift || [];
2879 my $fetch_penalties = 0;
2880 if(grep {$_ eq 'standing_penalties'} @$fields) {
2881 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2882 $fetch_penalties = 1;
2885 my $user = $e->retrieve_actor_user(
2890 "flesh_fields" => { "au" => $fields }
2893 ) or return $e->die_event;
2896 if( grep { $_ eq 'addresses' } @$fields ) {
2898 $user->addresses([]) unless @{$user->addresses};
2899 # don't expose "replaced" addresses by default
2900 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2902 if( ref $user->billing_address ) {
2903 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2904 push( @{$user->addresses}, $user->billing_address );
2908 if( ref $user->mailing_address ) {
2909 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2910 push( @{$user->addresses}, $user->mailing_address );
2915 if($fetch_penalties) {
2916 # grab the user penalties ranged for this location
2917 $user->standing_penalties(
2918 $e->search_actor_user_standing_penalty([
2921 {stop_date => undef},
2922 {stop_date => {'>' => 'now'}}
2924 org_unit => $U->get_org_ancestors($e->requestor->ws_ou)
2927 flesh_fields => {ausp => ['standing_penalty']}
2934 $user->clear_passwd();
2941 __PACKAGE__->register_method(
2942 method => "user_retrieve_parts",
2943 api_name => "open-ils.actor.user.retrieve.parts",
2946 sub user_retrieve_parts {
2947 my( $self, $client, $auth, $user_id, $fields ) = @_;
2948 my $e = new_editor(authtoken => $auth);
2949 return $e->event unless $e->checkauth;
2950 $user_id ||= $e->requestor->id;
2951 if( $e->requestor->id != $user_id ) {
2952 return $e->event unless $e->allowed('VIEW_USER');
2955 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2956 push(@resp, $user->$_()) for(@$fields);
2962 __PACKAGE__->register_method(
2963 method => 'user_opt_in_enabled',
2964 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2965 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2968 sub user_opt_in_enabled {
2969 my($self, $conn) = @_;
2970 my $sc = OpenSRF::Utils::SettingsClient->new;
2971 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2976 __PACKAGE__->register_method(
2977 method => 'user_opt_in_at_org',
2978 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2980 @param $auth The auth token
2981 @param user_id The ID of the user to test
2982 @return 1 if the user has opted in at the specified org,
2983 event on error, and 0 otherwise. /
2985 sub user_opt_in_at_org {
2986 my($self, $conn, $auth, $user_id) = @_;
2988 # see if we even need to enforce the opt-in value
2989 return 1 unless user_opt_in_enabled($self);
2991 my $e = new_editor(authtoken => $auth);
2992 return $e->event unless $e->checkauth;
2993 my $org_id = $e->requestor->ws_ou;
2995 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2996 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2998 # user is automatically opted-in at the home org
2999 return 1 if $user->home_ou eq $org_id;
3001 my $vals = $e->search_actor_usr_org_unit_opt_in(
3002 {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
3008 __PACKAGE__->register_method(
3009 method => 'create_user_opt_in_at_org',
3010 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3012 @param $auth The auth token
3013 @param user_id The ID of the user to test
3014 @return The ID of the newly created object, event on error./
3017 sub create_user_opt_in_at_org {
3018 my($self, $conn, $auth, $user_id) = @_;
3020 my $e = new_editor(authtoken => $auth, xact=>1);
3021 return $e->die_event unless $e->checkauth;
3022 my $org_id = $e->requestor->ws_ou;
3024 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3025 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3027 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3029 $opt_in->org_unit($org_id);
3030 $opt_in->usr($user_id);
3031 $opt_in->staff($e->requestor->id);
3032 $opt_in->opt_in_ts('now');
3033 $opt_in->opt_in_ws($e->requestor->wsid);
3035 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3036 or return $e->die_event;
3044 __PACKAGE__->register_method (
3045 method => 'retrieve_org_hours',
3046 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3048 Returns the hours of operation for a specified org unit
3049 @param authtoken The login session key
3050 @param org_id The org_unit ID
3054 sub retrieve_org_hours {
3055 my($self, $conn, $auth, $org_id) = @_;
3056 my $e = new_editor(authtoken => $auth);
3057 return $e->die_event unless $e->checkauth;
3058 $org_id ||= $e->requestor->ws_ou;
3059 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3063 __PACKAGE__->register_method (
3064 method => 'verify_user_password',
3065 api_name => 'open-ils.actor.verify_user_password',
3067 Given a barcode or username and the MD5 encoded password,
3068 returns 1 if the password is correct. Returns 0 otherwise.
3072 sub verify_user_password {
3073 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3074 my $e = new_editor(authtoken => $auth);
3075 return $e->die_event unless $e->checkauth;
3077 my $user_by_barcode;
3078 my $user_by_username;
3080 my $card = $e->search_actor_card([
3081 {barcode => $barcode},
3082 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3083 $user_by_barcode = $card->usr;
3084 $user = $user_by_barcode;
3087 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3088 $user = $user_by_username;
3090 return 0 if (!$user);
3091 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3092 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3093 return 1 if $user->passwd eq $password;
3097 __PACKAGE__->register_method (
3098 method => 'retrieve_usr_id_via_barcode_or_usrname',
3099 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3101 Given a barcode or username returns the id for the user or
3106 sub retrieve_usr_id_via_barcode_or_usrname {
3107 my($self, $conn, $auth, $barcode, $username) = @_;
3108 my $e = new_editor(authtoken => $auth);
3109 return $e->die_event unless $e->checkauth;
3110 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3112 my $user_by_barcode;
3113 my $user_by_username;
3114 $logger->info("$id_as_barcode is the ID as BARCODE");
3116 my $card = $e->search_actor_card([
3117 {barcode => $barcode},
3118 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3119 if ($id_as_barcode =~ /^t/i) {
3121 $user = $e->retrieve_actor_user($barcode);
3122 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3124 $user_by_barcode = $card->usr;
3125 $user = $user_by_barcode;
3128 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3129 $user_by_barcode = $card->usr;
3130 $user = $user_by_barcode;
3135 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3137 $user = $user_by_username;
3139 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3140 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3141 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3146 __PACKAGE__->register_method (
3147 method => 'merge_users',
3148 api_name => 'open-ils.actor.user.merge',
3151 Given a list of source users and destination user, transfer all data from the source
3152 to the dest user and delete the source user. All user related data is
3153 transferred, including circulations, holds, bookbags, etc.
3159 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3160 my $e = new_editor(xact => 1, authtoken => $auth);
3161 return $e->die_event unless $e->checkauth;
3163 # disallow the merge if any subordinate accounts are in collections
3164 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3165 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3167 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3168 my $del_addrs = ($U->ou_ancestor_setting_value(
3169 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3170 my $del_cards = ($U->ou_ancestor_setting_value(
3171 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3172 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3173 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3175 for my $src_id (@$user_ids) {
3176 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3178 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3179 if($src_user->home_ou ne $master_user->home_ou) {
3180 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3183 return $e->die_event unless
3184 $e->json_query({from => [
3199 __PACKAGE__->register_method (
3200 method => 'approve_user_address',
3201 api_name => 'open-ils.actor.user.pending_address.approve',
3208 sub approve_user_address {
3209 my($self, $conn, $auth, $addr) = @_;
3210 my $e = new_editor(xact => 1, authtoken => $auth);
3211 return $e->die_event unless $e->checkauth;
3213 # if the caller passes an address object, assume they want to
3214 # update it first before approving it
3215 $e->update_actor_user_address($addr) or return $e->die_event;
3217 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3219 my $user = $e->retrieve_actor_user($addr->usr);
3220 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3221 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3222 or return $e->die_event;
3224 return [values %$result]->[0];
3228 __PACKAGE__->register_method (
3229 method => 'retrieve_friends',
3230 api_name => 'open-ils.actor.friends.retrieve',
3233 returns { confirmed: [], pending_out: [], pending_in: []}
3234 pending_out are users I'm requesting friendship with
3235 pending_in are users requesting friendship with me
3240 sub retrieve_friends {
3241 my($self, $conn, $auth, $user_id, $options) = @_;
3242 my $e = new_editor(authtoken => $auth);
3243 return $e->event unless $e->checkauth;
3244 $user_id ||= $e->requestor->id;
3246 if($user_id != $e->requestor->id) {
3247 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3248 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3251 return OpenILS::Application::Actor::Friends->retrieve_friends(
3252 $e, $user_id, $options);
3257 __PACKAGE__->register_method (
3258 method => 'apply_friend_perms',
3259 api_name => 'open-ils.actor.friends.perms.apply',
3265 sub apply_friend_perms {
3266 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3267 my $e = new_editor(authtoken => $auth, xact => 1);
3268 return $e->die_event unless $e->checkauth;
3270 if($user_id != $e->requestor->id) {
3271 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3272 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3275 for my $perm (@perms) {
3277 OpenILS::Application::Actor::Friends->apply_friend_perm(
3278 $e, $user_id, $delegate_id, $perm);
3279 return $evt if $evt;
3287 __PACKAGE__->register_method (
3288 method => 'update_user_pending_address',
3289 api_name => 'open-ils.actor.user.address.pending.cud'
3292 sub update_user_pending_address {
3293 my($self, $conn, $auth, $addr) = @_;
3294 my $e = new_editor(authtoken => $auth, xact => 1);
3295 return $e->die_event unless $e->checkauth;
3297 if($addr->usr != $e->requestor->id) {
3298 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3299 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3303 $e->create_actor_user_address($addr) or return $e->die_event;
3304 } elsif($addr->isdeleted) {
3305 $e->delete_actor_user_address($addr) or return $e->die_event;
3307 $e->update_actor_user_address($addr) or return $e->die_event;
3315 __PACKAGE__->register_method (
3316 method => 'user_events',
3317 api_name => 'open-ils.actor.user.events.circ',
3320 __PACKAGE__->register_method (
3321 method => 'user_events',
3322 api_name => 'open-ils.actor.user.events.ahr',
3327 my($self, $conn, $auth, $user_id, $filters) = @_;
3328 my $e = new_editor(authtoken => $auth);
3329 return $e->event unless $e->checkauth;
3331 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3332 my $user_field = 'usr';
3335 $filters->{target} = {
3336 select => { $obj_type => ['id'] },
3338 where => {usr => $user_id}
3341 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3342 if($e->requestor->id != $user_id) {
3343 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3346 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3347 my $req = $ses->request('open-ils.trigger.events_by_target',
3348 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3350 while(my $resp = $req->recv) {
3351 my $val = $resp->content;
3352 my $tgt = $val->target;
3354 if($obj_type eq 'circ') {
3355 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3357 } elsif($obj_type eq 'ahr') {
3358 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3359 if $tgt->current_copy;
3362 $conn->respond($val) if $val;
3368 __PACKAGE__->register_method (
3369 method => 'copy_events',
3370 api_name => 'open-ils.actor.copy.events.circ',
3373 __PACKAGE__->register_method (
3374 method => 'copy_events',
3375 api_name => 'open-ils.actor.copy.events.ahr',
3380 my($self, $conn, $auth, $copy_id, $filters) = @_;
3381 my $e = new_editor(authtoken => $auth);
3382 return $e->event unless $e->checkauth;
3384 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3386 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3388 my $copy_field = 'target_copy';
3389 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3392 $filters->{target} = {
3393 select => { $obj_type => ['id'] },
3395 where => {$copy_field => $copy_id}
3399 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3400 my $req = $ses->request('open-ils.trigger.events_by_target',
3401 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3403 while(my $resp = $req->recv) {
3404 my $val = $resp->content;
3405 my $tgt = $val->target;
3407 my $user = $e->retrieve_actor_user($tgt->usr);
3408 if($e->requestor->id != $user->id) {
3409 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3412 $tgt->$copy_field($copy);
3415 $conn->respond($val) if $val;
3424 __PACKAGE__->register_method (
3425 method => 'update_events',
3426 api_name => 'open-ils.actor.user.event.cancel.batch',
3429 __PACKAGE__->register_method (
3430 method => 'update_events',
3431 api_name => 'open-ils.actor.user.event.reset.batch',
3436 my($self, $conn, $auth, $event_ids) = @_;
3437 my $e = new_editor(xact => 1, authtoken => $auth);
3438 return $e->die_event unless $e->checkauth;
3441 for my $id (@$event_ids) {
3443 # do a little dance to determine what user we are ultimately affecting
3444 my $event = $e->retrieve_action_trigger_event([
3447 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3449 ]) or return $e->die_event;
3452 if($event->event_def->hook->core_type eq 'circ') {
3453 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3454 } elsif($event->event_def->hook->core_type eq 'ahr') {
3455 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3460 my $user = $e->retrieve_actor_user($user_id);
3461 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3463 if($self->api_name =~ /cancel/) {
3464 $event->state('invalid');
3465 } elsif($self->api_name =~ /reset/) {
3466 $event->clear_start_time;
3467 $event->clear_update_time;
3468 $event->state('pending');
3471 $e->update_action_trigger_event($event) or return $e->die_event;
3472 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3476 return {complete => 1};
3480 __PACKAGE__->register_method (
3481 method => 'really_delete_user',
3482 api_name => 'open-ils.actor.user.delete',
3484 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3485 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3486 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3487 dest_usr_id is only required when deleting a user that performs staff functions.
3491 sub really_delete_user {
3492 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3493 my $e = new_editor(authtoken => $auth, xact => 1);
3494 return $e->die_event unless $e->checkauth;
3495 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3496 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3497 my $stat = $e->json_query(
3498 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3499 or return $e->die_event;
3506 __PACKAGE__->register_method (
3507 method => 'user_payments',
3508 api_name => 'open-ils.actor.user.payments.retrieve',
3511 Returns all payments for a given user. Default order is newest payments first.
3512 @param auth Authentication token
3513 @param user_id The user ID
3514 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3519 my($self, $conn, $auth, $user_id, $filters) = @_;
3522 my $e = new_editor(authtoken => $auth);
3523 return $e->die_event unless $e->checkauth;
3525 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3526 return $e->event unless
3527 $e->requestor->id == $user_id or
3528 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3530 # Find all payments for all transactions for user $user_id
3532 select => {mp => ['id']},
3537 select => {mbt => ['id']},
3539 where => {usr => $user_id}
3543 order_by => [{ # by default, order newest payments first
3545 field => 'payment_ts',
3550 for (qw/order_by limit offset/) {
3551 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3554 if(defined $filters->{where}) {
3555 foreach (keys %{$filters->{where}}) {
3556 # don't allow the caller to expand the result set to other users
3557 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3561 my $payment_ids = $e->json_query($query);
3562 for my $pid (@$payment_ids) {
3563 my $pay = $e->retrieve_money_payment([
3568 mbt => ['summary', 'circulation', 'grocery'],
3569 circ => ['target_copy'],
3570 acp => ['call_number'],
3578 xact_type => $pay->xact->summary->xact_type,
3579 last_billing_type => $pay->xact->summary->last_billing_type,
3582 if($pay->xact->summary->xact_type eq 'circulation') {
3583 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3584 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3587 $pay->xact($pay->xact->id); # de-flesh
3588 $conn->respond($resp);
3596 __PACKAGE__->register_method (
3597 method => 'negative_balance_users',
3598 api_name => 'open-ils.actor.users.negative_balance',
3601 Returns all users that have an overall negative balance
3602 @param auth Authentication token
3603 @param org_id The context org unit as an ID or list of IDs. This will be the home
3604 library of the user. If no org_unit is specified, no org unit filter is applied
3608 sub negative_balance_users {
3609 my($self, $conn, $auth, $org_id) = @_;
3611 my $e = new_editor(authtoken => $auth);
3612 return $e->die_event unless $e->checkauth;
3613 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3617 mous => ['usr', 'balance_owed'],
3620 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3621 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3638 where => {'+mous' => {balance_owed => {'<' => 0}}}
3641 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3643 my $list = $e->json_query($query, {timeout => 600});
3645 for my $data (@$list) {
3647 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3648 balance_owed => $data->{balance_owed},
3649 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3656 __PACKAGE__->register_method(
3657 method => "request_password_reset",
3658 api_name => "open-ils.actor.patron.password_reset.request",
3660 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3661 "method for changing a user's password. The UUID token is distributed via A/T " .
3662 "templates (i.e. email to the user).",
3664 { desc => 'user_id_type', type => 'string' },
3665 { desc => 'user_id', type => 'string' },
3666 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3668 return => {desc => '1 on success, Event on error'}
3671 sub request_password_reset {
3672 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3674 # Check to see if password reset requests are already being throttled:
3675 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3677 my $e = new_editor(xact => 1);
3680 # Get the user, if any, depending on the input value
3681 if ($user_id_type eq 'username') {
3682 $user = $e->search_actor_user({usrname => $user_id})->[0];
3685 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3687 } elsif ($user_id_type eq 'barcode') {
3688 my $card = $e->search_actor_card([
3689 {barcode => $user_id},
3690 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3693 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3698 # If the user doesn't have an email address, we can't help them
3699 if (!$user->email) {
3701 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3704 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3705 if ($email_must_match) {
3706 if ($user->email ne $email) {
3707 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3711 _reset_password_request($conn, $e, $user);
3714 # Once we have the user, we can issue the password reset request
3715 # XXX Add a wrapper method that accepts barcode + email input
3716 sub _reset_password_request {
3717 my ($conn, $e, $user) = @_;
3719 # 1. Get throttle threshold and time-to-live from OU_settings
3720 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3721 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3723 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3725 # 2. Get time of last request and number of active requests (num_active)
3726 my $active_requests = $e->json_query({
3732 transform => 'COUNT'
3735 column => 'request_time',
3741 has_been_reset => { '=' => 'f' },
3742 request_time => { '>' => $threshold_time }
3746 # Guard against no active requests
3747 if ($active_requests->[0]->{'request_time'}) {
3748 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3749 my $now = DateTime::Format::ISO8601->new();
3751 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3752 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3753 ($last_request->add_duration('1 minute') > $now)) {
3754 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3756 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3760 # TODO Check to see if the user is in a password-reset-restricted group
3762 # Otherwise, go ahead and try to get the user.
3764 # Check the number of active requests for this user
3765 $active_requests = $e->json_query({
3771 transform => 'COUNT'
3776 usr => { '=' => $user->id },
3777 has_been_reset => { '=' => 'f' },
3778 request_time => { '>' => $threshold_time }
3782 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3784 # if less than or equal to per-user threshold, proceed; otherwise, return event
3785 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3786 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3788 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3791 # Create the aupr object and insert into the database
3792 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3793 my $uuid = create_uuid_as_string(UUID_V4);
3794 $reset_request->uuid($uuid);
3795 $reset_request->usr($user->id);
3797 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3800 # Create an event to notify user of the URL to reset their password
3802 # Can we stuff this in the user_data param for trigger autocreate?
3803 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3805 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3806 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3809 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3814 __PACKAGE__->register_method(
3815 method => "commit_password_reset",
3816 api_name => "open-ils.actor.patron.password_reset.commit",
3818 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3819 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3820 "with the supplied password.",
3822 { desc => 'uuid', type => 'string' },
3823 { desc => 'password', type => 'string' },
3825 return => {desc => '1 on success, Event on error'}
3828 sub commit_password_reset {
3829 my($self, $conn, $uuid, $password) = @_;
3831 # Check to see if password reset requests are already being throttled:
3832 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3833 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3834 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3836 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3839 my $e = new_editor(xact => 1);
3841 my $aupr = $e->search_actor_usr_password_reset({
3848 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3850 my $user_id = $aupr->[0]->usr;
3851 my $user = $e->retrieve_actor_user($user_id);
3853 # Ensure we're still within the TTL for the request
3854 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3855 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3856 if ($threshold < DateTime->now(time_zone => 'local')) {
3858 $logger->info("Password reset request needed to be submitted before $threshold");
3859 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3862 # Check complexity of password against OU-defined regex
3863 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3867 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3868 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3869 $is_strong = check_password_strength_custom($password, $pw_regex);
3871 $is_strong = check_password_strength_default($password);
3876 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3879 # All is well; update the password
3880 $user->passwd($password);
3881 $e->update_actor_user($user);
3883 # And flag that this password reset request has been honoured
3884 $aupr->[0]->has_been_reset('t');
3885 $e->update_actor_usr_password_reset($aupr->[0]);
3891 sub check_password_strength_default {
3892 my $password = shift;
3893 # Use the default set of checks
3894 if ( (length($password) < 7) or
3895 ($password !~ m/.*\d+.*/) or
3896 ($password !~ m/.*[A-Za-z]+.*/)
3903 sub check_password_strength_custom {
3904 my ($password, $pw_regex) = @_;
3906 $pw_regex = qr/$pw_regex/;
3907 if ($password !~ /$pw_regex/) {
3915 __PACKAGE__->register_method(
3916 method => "event_def_opt_in_settings",
3917 api_name => "open-ils.actor.event_def.opt_in.settings",
3920 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3922 { desc => 'Authentication token', type => 'string'},
3924 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3929 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3936 sub event_def_opt_in_settings {
3937 my($self, $conn, $auth, $org_id) = @_;
3938 my $e = new_editor(authtoken => $auth);
3939 return $e->event unless $e->checkauth;
3941 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3942 return $e->event unless
3943 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3945 $org_id = $e->requestor->home_ou;
3948 # find all config.user_setting_type's related to event_defs for the requested org unit
3949 my $types = $e->json_query({
3950 select => {cust => ['name']},
3951 from => {atevdef => 'cust'},
3954 owner => $U->get_org_ancestors($org_id), # context org plus parents
3961 $conn->respond($_) for
3962 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
3969 __PACKAGE__->register_method(
3970 method => "user_visible_circs",
3971 api_name => "open-ils.actor.history.circ.visible",
3974 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
3976 { desc => 'Authentication token', type => 'string'},
3977 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3978 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3981 desc => q/An object with 2 fields: circulation and summary.
3982 circulation is the "circ" object. summary is the related "accs" object/,
3988 __PACKAGE__->register_method(
3989 method => "user_visible_circs",
3990 api_name => "open-ils.actor.history.circ.visible.print",
3993 desc => 'Returns printable output for the set of opt-in visible circulations',
3995 { desc => 'Authentication token', type => 'string'},
3996 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3997 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4000 desc => q/An action_trigger.event object or error event./,
4006 __PACKAGE__->register_method(
4007 method => "user_visible_circs",
4008 api_name => "open-ils.actor.history.circ.visible.email",
4011 desc => 'Emails the set of opt-in visible circulations to the requestor',
4013 { desc => 'Authentication token', type => 'string'},
4014 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4015 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4018 desc => q/undef, or event on error/
4023 __PACKAGE__->register_method(
4024 method => "user_visible_circs",
4025 api_name => "open-ils.actor.history.hold.visible",
4028 desc => 'Returns the set of opt-in visible holds',
4030 { desc => 'Authentication token', type => 'string'},
4031 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4032 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4035 desc => q/An object with 1 field: "hold"/,
4041 __PACKAGE__->register_method(
4042 method => "user_visible_circs",
4043 api_name => "open-ils.actor.history.hold.visible.print",
4046 desc => 'Returns printable output for the set of opt-in visible holds',
4048 { desc => 'Authentication token', type => 'string'},
4049 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4050 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4053 desc => q/An action_trigger.event object or error event./,
4059 __PACKAGE__->register_method(
4060 method => "user_visible_circs",
4061 api_name => "open-ils.actor.history.hold.visible.email",
4064 desc => 'Emails the set of opt-in visible holds to the requestor',
4066 { desc => 'Authentication token', type => 'string'},
4067 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4068 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4071 desc => q/undef, or event on error/
4076 sub user_visible_circs {
4077 my($self, $conn, $auth, $user_id, $options) = @_;
4079 my $is_hold = ($self->api_name =~ /hold/);
4080 my $for_print = ($self->api_name =~ /print/);
4081 my $for_email = ($self->api_name =~ /email/);
4082 my $e = new_editor(authtoken => $auth);
4083 return $e->event unless $e->checkauth;
4085 $user_id ||= $e->requestor->id;
4087 $options->{limit} ||= 50;
4088 $options->{offset} ||= 0;
4090 if($user_id != $e->requestor->id) {
4091 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4092 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4093 return $e->event unless $e->allowed($perm, $user->home_ou);
4096 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4098 my $data = $e->json_query({
4099 from => [$db_func, $user_id],
4100 limit => $$options{limit},
4101 offset => $$options{offset}
4103 # TODO: I only want IDs. code below didn't get me there
4104 # {"select":{"au":[{"column":"id", "result_field":"id",
4105 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4110 return undef unless @$data;
4114 # collect the batch of objects
4118 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4119 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4123 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4124 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4127 } elsif ($for_email) {
4129 $conn->respond_complete(1) if $for_email; # no sense in waiting
4137 my $hold = $e->retrieve_action_hold_request($id);
4138 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4139 # events will be fired from action_trigger_runner
4143 my $circ = $e->retrieve_action_circulation($id);
4144 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4145 # events will be fired from action_trigger_runner
4149 } else { # just give me the data please
4157 my $hold = $e->retrieve_action_hold_request($id);
4158 $conn->respond({hold => $hold});
4162 my $circ = $e->retrieve_action_circulation($id);
4165 summary => $U->create_circ_chain_summary($e, $id)
4174 __PACKAGE__->register_method(
4175 method => "user_saved_search_cud",
4176 api_name => "open-ils.actor.user.saved_search.cud",
4179 desc => 'Create/Update/Delete Access to user saved searches',
4181 { desc => 'Authentication token', type => 'string' },
4182 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4185 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4191 __PACKAGE__->register_method(
4192 method => "user_saved_search_cud",
4193 api_name => "open-ils.actor.user.saved_search.retrieve",
4196 desc => 'Retrieve a saved search object',
4198 { desc => 'Authentication token', type => 'string' },
4199 { desc => 'Saved Search ID', type => 'number' }
4202 desc => q/The saved search object, Event on error/,
4208 sub user_saved_search_cud {
4209 my( $self, $client, $auth, $search ) = @_;
4210 my $e = new_editor( authtoken=>$auth );
4211 return $e->die_event unless $e->checkauth;
4213 my $o_search; # prior version of the object, if any
4214 my $res; # to be returned
4216 # branch on the operation type
4218 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4220 # Get the old version, to check ownership
4221 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4222 or return $e->die_event;
4224 # You can't read somebody else's search
4225 return OpenILS::Event->new('BAD_PARAMS')
4226 unless $o_search->owner == $e->requestor->id;
4232 $e->xact_begin; # start an editor transaction
4234 if( $search->isnew ) { # Create
4236 # You can't create a search for somebody else
4237 return OpenILS::Event->new('BAD_PARAMS')
4238 unless $search->owner == $e->requestor->id;
4240 $e->create_actor_usr_saved_search( $search )
4241 or return $e->die_event;
4245 } elsif( $search->ischanged ) { # Update
4247 # You can't change ownership of a search
4248 return OpenILS::Event->new('BAD_PARAMS')
4249 unless $search->owner == $e->requestor->id;
4251 # Get the old version, to check ownership
4252 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4253 or return $e->die_event;
4255 # You can't update somebody else's search
4256 return OpenILS::Event->new('BAD_PARAMS')
4257 unless $o_search->owner == $e->requestor->id;
4260 $e->update_actor_usr_saved_search( $search )
4261 or return $e->die_event;
4265 } elsif( $search->isdeleted ) { # Delete
4267 # Get the old version, to check ownership
4268 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4269 or return $e->die_event;
4271 # You can't delete somebody else's search
4272 return OpenILS::Event->new('BAD_PARAMS')
4273 unless $o_search->owner == $e->requestor->id;
4276 $e->delete_actor_usr_saved_search( $o_search )
4277 or return $e->die_event;