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/;
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;
1321 __PACKAGE__->register_method(
1322 method => "check_user_perms",
1323 api_name => "open-ils.actor.user.perm.check",
1324 notes => <<" NOTES");
1325 Takes a login session, user id, an org id, and an array of perm type strings. For each
1326 perm type, if the user does *not* have the given permission it is added
1327 to a list which is returned from the method. If all permissions
1328 are allowed, an empty list is returned
1329 if the logged in user does not match 'user_id', then the logged in user must
1330 have VIEW_PERMISSION priveleges.
1333 sub check_user_perms {
1334 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1336 my( $staff, $evt ) = $apputils->checkses($login_session);
1337 return $evt if $evt;
1339 if($staff->id ne $user_id) {
1340 if( $evt = $apputils->check_perms(
1341 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1347 for my $perm (@$perm_types) {
1348 if($apputils->check_perms($user_id, $org_id, $perm)) {
1349 push @not_allowed, $perm;
1353 return \@not_allowed
1356 __PACKAGE__->register_method(
1357 method => "check_user_perms2",
1358 api_name => "open-ils.actor.user.perm.check.multi_org",
1360 Checks the permissions on a list of perms and orgs for a user
1361 @param authtoken The login session key
1362 @param user_id The id of the user to check
1363 @param orgs The array of org ids
1364 @param perms The array of permission names
1365 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1366 if the logged in user does not match 'user_id', then the logged in user must
1367 have VIEW_PERMISSION priveleges.
1370 sub check_user_perms2 {
1371 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1373 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1374 $authtoken, $user_id, 'VIEW_PERMISSION' );
1375 return $evt if $evt;
1378 for my $org (@$orgs) {
1379 for my $perm (@$perms) {
1380 if($apputils->check_perms($user_id, $org, $perm)) {
1381 push @not_allowed, [ $org, $perm ];
1386 return \@not_allowed
1390 __PACKAGE__->register_method(
1391 method => 'check_user_perms3',
1392 api_name => 'open-ils.actor.user.perm.highest_org',
1394 Returns the highest org unit id at which a user has a given permission
1395 If the requestor does not match the target user, the requestor must have
1396 'VIEW_PERMISSION' rights at the home org unit of the target user
1397 @param authtoken The login session key
1398 @param userid The id of the user in question
1399 @param perm The permission to check
1400 @return The org unit highest in the org tree within which the user has
1401 the requested permission
1404 sub check_user_perms3 {
1405 my($self, $client, $authtoken, $user_id, $perm) = @_;
1406 my $e = new_editor(authtoken=>$authtoken);
1407 return $e->event unless $e->checkauth;
1409 my $tree = $U->get_org_tree();
1411 unless($e->requestor->id == $user_id) {
1412 my $user = $e->retrieve_actor_user($user_id)
1413 or return $e->event;
1414 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1415 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1418 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1421 __PACKAGE__->register_method(
1422 method => 'user_has_work_perm_at',
1423 api_name => 'open-ils.actor.user.has_work_perm_at',
1427 Returns a set of org unit IDs which represent the highest orgs in
1428 the org tree where the user has the requested permission. The
1429 purpose of this method is to return the smallest set of org units
1430 which represent the full expanse of the user's ability to perform
1431 the requested action. The user whose perms this method should
1432 check is implied by the authtoken. /,
1434 {desc => 'authtoken', type => 'string'},
1435 {desc => 'permission name', type => 'string'},
1436 {desc => q/user id, optional. If present, check perms for
1437 this user instead of the logged in user/, type => 'number'},
1439 return => {desc => 'An array of org IDs'}
1443 sub user_has_work_perm_at {
1444 my($self, $conn, $auth, $perm, $user_id) = @_;
1445 my $e = new_editor(authtoken=>$auth);
1446 return $e->event unless $e->checkauth;
1447 if(defined $user_id) {
1448 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1449 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1451 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1454 __PACKAGE__->register_method(
1455 method => 'user_has_work_perm_at_batch',
1456 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1460 sub user_has_work_perm_at_batch {
1461 my($self, $conn, $auth, $perms, $user_id) = @_;
1462 my $e = new_editor(authtoken=>$auth);
1463 return $e->event unless $e->checkauth;
1464 if(defined $user_id) {
1465 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1466 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1469 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1475 __PACKAGE__->register_method(
1476 method => 'check_user_perms4',
1477 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1479 Returns the highest org unit id at which a user has a given permission
1480 If the requestor does not match the target user, the requestor must have
1481 'VIEW_PERMISSION' rights at the home org unit of the target user
1482 @param authtoken The login session key
1483 @param userid The id of the user in question
1484 @param perms An array of perm names to check
1485 @return An array of orgId's representing the org unit
1486 highest in the org tree within which the user has the requested permission
1487 The arrah of orgId's has matches the order of the perms array
1490 sub check_user_perms4 {
1491 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1493 my( $staff, $target, $org, $evt );
1495 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1496 $authtoken, $userid, 'VIEW_PERMISSION' );
1497 return $evt if $evt;
1500 return [] unless ref($perms);
1501 my $tree = $U->get_org_tree();
1503 for my $p (@$perms) {
1504 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1510 __PACKAGE__->register_method(
1511 method => "user_fines_summary",
1512 api_name => "open-ils.actor.user.fines.summary",
1515 desc => 'Returns a short summary of the users total open fines, ' .
1516 'excluding voided fines Params are login_session, user_id' ,
1518 {desc => 'Authentication token', type => 'string'},
1519 {desc => 'User ID', type => 'string'} # number?
1522 desc => "a 'mous' object, event on error",
1527 sub user_fines_summary {
1528 my( $self, $client, $auth, $user_id ) = @_;
1530 my $e = new_editor(authtoken=>$auth);
1531 return $e->event unless $e->checkauth;
1533 if( $user_id ne $e->requestor->id ) {
1534 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1535 return $e->event unless
1536 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1539 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1543 __PACKAGE__->register_method(
1544 method => "user_opac_vitals",
1545 api_name => "open-ils.actor.user.opac.vital_stats",
1549 desc => 'Returns a short summary of the users vital stats, including ' .
1550 'identification information, accumulated balance, number of holds, ' .
1551 'and current open circulation stats' ,
1553 {desc => 'Authentication token', type => 'string'},
1554 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1557 desc => "An object with four properties: user, fines, checkouts and holds."
1562 sub user_opac_vitals {
1563 my( $self, $client, $auth, $user_id ) = @_;
1565 my $e = new_editor(authtoken=>$auth);
1566 return $e->event unless $e->checkauth;
1568 $user_id ||= $e->requestor->id;
1570 my $user = $e->retrieve_actor_user( $user_id );
1573 ->method_lookup('open-ils.actor.user.fines.summary')
1574 ->run($auth => $user_id);
1575 return $fines if (defined($U->event_code($fines)));
1578 $fines = new Fieldmapper::money::open_user_summary ();
1579 $fines->balance_owed(0.00);
1580 $fines->total_owed(0.00);
1581 $fines->total_paid(0.00);
1582 $fines->usr($user_id);
1586 ->method_lookup('open-ils.actor.user.hold_requests.count')
1587 ->run($auth => $user_id);
1588 return $holds if (defined($U->event_code($holds)));
1591 ->method_lookup('open-ils.actor.user.checked_out.count')
1592 ->run($auth => $user_id);
1593 return $out if (defined($U->event_code($out)));
1597 first_given_name => $user->first_given_name,
1598 second_given_name => $user->second_given_name,
1599 family_name => $user->family_name,
1600 alias => $user->alias,
1601 usrname => $user->usrname
1603 fines => $fines->to_bare_hash,
1610 ##### a small consolidation of related method registrations
1611 my $common_params = [
1612 { desc => 'Authentication token', type => 'string' },
1613 { desc => 'User ID', type => 'string' },
1614 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1615 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1618 'open-ils.actor.user.transactions' => '',
1619 'open-ils.actor.user.transactions.fleshed' => '',
1620 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1621 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1622 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1623 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1626 foreach (keys %methods) {
1628 method => "user_transactions",
1631 desc => 'For a given user, retrieve a list of '
1632 . (/\.fleshed/ ? 'fleshed ' : '')
1633 . 'transactions' . $methods{$_}
1634 . ' optionally limited to transactions of a given type.',
1635 params => $common_params,
1637 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1638 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1642 $args{authoritative} = 1;
1643 __PACKAGE__->register_method(%args);
1646 # Now for the counts
1648 'open-ils.actor.user.transactions.count' => '',
1649 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1650 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1653 foreach (keys %methods) {
1655 method => "user_transactions",
1658 desc => 'For a given user, retrieve a count of open '
1659 . 'transactions' . $methods{$_}
1660 . ' optionally limited to transactions of a given type.',
1661 params => $common_params,
1662 return => { desc => "Integer count of transactions, or event on error" }
1665 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1666 __PACKAGE__->register_method(%args);
1669 __PACKAGE__->register_method(
1670 method => "user_transactions",
1671 api_name => "open-ils.actor.user.transactions.have_balance.total",
1674 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1675 . ' optionally limited to transactions of a given type.',
1676 params => $common_params,
1677 return => { desc => "Decimal balance value, or event on error" }
1682 sub user_transactions {
1683 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1686 my $e = new_editor(authtoken => $auth);
1687 return $e->event unless $e->checkauth;
1689 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1691 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1693 my $api = $self->api_name();
1695 my $filter = ($api =~ /have_balance/o) ?
1696 { 'balance_owed' => { '<>' => 0 } }:
1697 { 'total_owed' => { '>' => 0 } };
1699 my $method = 'open-ils.actor.user.transactions.history.still_open';
1700 $method = "$method.authoritative" if $api => /authoritative/;
1701 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1703 if($api =~ /total/o) {
1705 $total += $_->balance_owed for @$trans;
1709 ($api =~ /count/o ) and return scalar @$trans;
1710 ($api !~ /fleshed/o) and return $trans;
1713 for my $t (@$trans) {
1715 if( $t->xact_type ne 'circulation' ) {
1716 push @resp, {transaction => $t};
1720 my $circ_data = flesh_circ($e, $t->id);
1721 push @resp, {transaction => $t, %$circ_data};
1728 __PACKAGE__->register_method(
1729 method => "user_transaction_retrieve",
1730 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1733 notes => "Returns a fleshed transaction record"
1736 __PACKAGE__->register_method(
1737 method => "user_transaction_retrieve",
1738 api_name => "open-ils.actor.user.transaction.retrieve",
1741 notes => "Returns a transaction record"
1744 sub user_transaction_retrieve {
1745 my($self, $client, $auth, $bill_id) = @_;
1747 my $e = new_editor(authtoken => $auth);
1748 return $e->event unless $e->checkauth;
1750 my $trans = $e->retrieve_money_billable_transaction_summary(
1751 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1753 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1755 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1757 return $trans unless $self->api_name =~ /flesh/;
1758 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1760 my $circ_data = flesh_circ($e, $trans->id, 1);
1762 return {transaction => $trans, %$circ_data};
1767 my $circ_id = shift;
1768 my $flesh_copy = shift;
1770 my $circ = $e->retrieve_action_circulation([
1774 circ => ['target_copy'],
1775 acp => ['call_number'],
1782 my $copy = $circ->target_copy;
1784 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1785 $mods = new Fieldmapper::metabib::virtual_record;
1786 $mods->doc_id(OILS_PRECAT_RECORD);
1787 $mods->title($copy->dummy_title);
1788 $mods->author($copy->dummy_author);
1791 my $u = OpenILS::Utils::ModsParser->new();
1792 $u->start_mods_batch($circ->target_copy->call_number->record->marc);
1793 $mods = $u->finish_mods_batch();
1797 $circ->target_copy($circ->target_copy->id);
1798 $copy->call_number($copy->call_number->id);
1800 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1804 __PACKAGE__->register_method(
1805 method => "hold_request_count",
1806 api_name => "open-ils.actor.user.hold_requests.count",
1809 notes => 'Returns hold ready/total counts'
1812 sub hold_request_count {
1813 my( $self, $client, $login_session, $userid ) = @_;
1815 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1816 $login_session, $userid, 'VIEW_HOLD' );
1817 return $evt if $evt;
1820 my $holds = $apputils->simple_scalar_request(
1822 "open-ils.cstore.direct.action.hold_request.search.atomic",
1825 fulfillment_time => {"=" => undef },
1826 cancel_time => undef,
1831 for my $h (@$holds) {
1832 next unless $h->capture_time and $h->current_copy;
1834 my $copy = $apputils->simple_scalar_request(
1836 "open-ils.cstore.direct.asset.copy.retrieve",
1840 if ($copy and $copy->status == 8) {
1845 return { total => scalar(@$holds), ready => scalar(@ready) };
1848 __PACKAGE__->register_method(
1849 method => "checked_out",
1850 api_name => "open-ils.actor.user.checked_out",
1854 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1855 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1856 . "(i.e., outstanding balance or some other pending action on the circ). "
1857 . "The .count method also includes a 'total' field which sums all open circs.",
1859 { desc => 'Authentication Token', type => 'string'},
1860 { desc => 'User ID', type => 'string'},
1863 desc => 'Returns event on error, or an object with ID lists, like: '
1864 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1869 __PACKAGE__->register_method(
1870 method => "checked_out",
1871 api_name => "open-ils.actor.user.checked_out.count",
1874 signature => q/@see open-ils.actor.user.checked_out/
1878 my( $self, $conn, $auth, $userid ) = @_;
1880 my $e = new_editor(authtoken=>$auth);
1881 return $e->event unless $e->checkauth;
1883 if( $userid ne $e->requestor->id ) {
1884 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1885 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1887 # see if there is a friend link allowing circ.view perms
1888 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1889 $e, $userid, $e->requestor->id, 'circ.view');
1890 return $e->event unless $allowed;
1894 my $count = $self->api_name =~ /count/;
1895 return _checked_out( $count, $e, $userid );
1899 my( $iscount, $e, $userid ) = @_;
1905 claims_returned => [],
1908 my $meth = 'retrieve_action_open_circ_';
1916 claims_returned => 0,
1923 my $data = $e->$meth($userid);
1927 $result{$_} += $data->$_() for (keys %result);
1928 $result{total} += $data->$_() for (keys %result);
1930 for my $k (keys %result) {
1931 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
1941 __PACKAGE__->register_method(
1942 method => "checked_in_with_fines",
1943 api_name => "open-ils.actor.user.checked_in_with_fines",
1946 signature => q/@see open-ils.actor.user.checked_out/
1949 sub checked_in_with_fines {
1950 my( $self, $conn, $auth, $userid ) = @_;
1952 my $e = new_editor(authtoken=>$auth);
1953 return $e->event unless $e->checkauth;
1955 if( $userid ne $e->requestor->id ) {
1956 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1959 # money is owed on these items and they are checked in
1960 my $open = $e->search_action_circulation(
1963 xact_finish => undef,
1964 checkin_time => { "!=" => undef },
1969 my( @lost, @cr, @lo );
1970 for my $c (@$open) {
1971 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1972 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1973 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1978 claims_returned => \@cr,
1979 long_overdue => \@lo
1985 my ($api, $desc, $auth) = @_;
1986 $desc = $desc ? (" " . $desc) : '';
1987 my $ids = ($api =~ /ids$/) ? 1 : 0;
1990 method => "user_transaction_history",
1991 api_name => "open-ils.actor.user.transactions.$api",
1993 desc => "For a given User ID, returns a list of billable transaction" .
1994 ($ids ? " id" : '') .
1995 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
1996 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
1998 {desc => 'Authentication token', type => 'string'},
1999 {desc => 'User ID', type => 'number'},
2000 {desc => 'Transaction type (optional)', type => 'number'},
2001 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2004 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2008 $auth and push @sig, (authoritative => 1);
2012 my %auth_hist_methods = (
2014 'history.have_charge' => 'that have an initial charge',
2015 'history.still_open' => 'that are not finished',
2016 'history.have_balance' => 'that have a balance',
2017 'history.have_bill' => 'that have billings',
2018 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2019 'history.have_payment' => 'that have at least 1 payment',
2022 foreach (keys %auth_hist_methods) {
2023 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2024 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2025 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2028 sub user_transaction_history {
2029 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2033 my $e = new_editor(authtoken=>$auth);
2034 return $e->die_event unless $e->checkauth;
2036 if ($e->requestor->id ne $userid) {
2037 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2040 my $api = $self->api_name;
2041 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2043 if(defined($type)) {
2044 $filter->{'xact_type'} = $type;
2047 if($api =~ /have_bill_or_payment/o) {
2049 # transactions that have a non-zero sum across all billings or at least 1 payment
2050 $filter->{'-or'} = {
2051 'balance_owed' => { '<>' => 0 },
2052 'last_payment_ts' => { '<>' => undef }
2055 } elsif($api =~ /have_payment/) {
2057 $filter->{last_payment_ts} ||= {'<>' => undef};
2059 } elsif( $api =~ /have_balance/o) {
2061 # transactions that have a non-zero overall balance
2062 $filter->{'balance_owed'} = { '<>' => 0 };
2064 } elsif( $api =~ /have_charge/o) {
2066 # transactions that have at least 1 billing, regardless of whether it was voided
2067 $filter->{'last_billing_ts'} = { '<>' => undef };
2069 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2071 # transactions that have non-zero sum across all billings. This will exclude
2072 # xacts where all billings have been voided
2073 $filter->{'total_owed'} = { '<>' => 0 };
2076 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2077 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2078 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2080 my $mbts = $e->search_money_billable_transaction_summary(
2081 [ { usr => $userid, @xact_finish, %$filter },
2086 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2087 return $mbts unless $api =~ /fleshed/;
2090 for my $t (@$mbts) {
2092 if( $t->xact_type ne 'circulation' ) {
2093 push @resp, {transaction => $t};
2097 my $circ_data = flesh_circ($e, $t->id);
2098 push @resp, {transaction => $t, %$circ_data};
2106 __PACKAGE__->register_method(
2107 method => "user_perms",
2108 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2110 notes => "Returns a list of permissions"
2114 my( $self, $client, $authtoken, $user ) = @_;
2116 my( $staff, $evt ) = $apputils->checkses($authtoken);
2117 return $evt if $evt;
2119 $user ||= $staff->id;
2121 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2125 return $apputils->simple_scalar_request(
2127 "open-ils.storage.permission.user_perms.atomic",
2131 __PACKAGE__->register_method(
2132 method => "retrieve_perms",
2133 api_name => "open-ils.actor.permissions.retrieve",
2134 notes => "Returns a list of permissions"
2136 sub retrieve_perms {
2137 my( $self, $client ) = @_;
2138 return $apputils->simple_scalar_request(
2140 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2141 { id => { '!=' => undef } }
2145 __PACKAGE__->register_method(
2146 method => "retrieve_groups",
2147 api_name => "open-ils.actor.groups.retrieve",
2148 notes => "Returns a list of user groups"
2150 sub retrieve_groups {
2151 my( $self, $client ) = @_;
2152 return new_editor()->retrieve_all_permission_grp_tree();
2155 __PACKAGE__->register_method(
2156 method => "retrieve_org_address",
2157 api_name => "open-ils.actor.org_unit.address.retrieve",
2158 notes => <<' NOTES');
2159 Returns an org_unit address by ID
2160 @param An org_address ID
2162 sub retrieve_org_address {
2163 my( $self, $client, $id ) = @_;
2164 return $apputils->simple_scalar_request(
2166 "open-ils.cstore.direct.actor.org_address.retrieve",
2171 __PACKAGE__->register_method(
2172 method => "retrieve_groups_tree",
2173 api_name => "open-ils.actor.groups.tree.retrieve",
2174 notes => "Returns a list of user groups"
2177 sub retrieve_groups_tree {
2178 my( $self, $client ) = @_;
2179 return new_editor()->search_permission_grp_tree(
2184 flesh_fields => { pgt => ["children"] },
2185 order_by => { pgt => 'name'}
2192 __PACKAGE__->register_method(
2193 method => "add_user_to_groups",
2194 api_name => "open-ils.actor.user.set_groups",
2195 notes => "Adds a user to one or more permission groups"
2198 sub add_user_to_groups {
2199 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2201 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2202 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2203 return $evt if $evt;
2205 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2206 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2207 return $evt if $evt;
2209 $apputils->simplereq(
2211 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2213 for my $group (@$groups) {
2214 my $link = Fieldmapper::permission::usr_grp_map->new;
2216 $link->usr($userid);
2218 my $id = $apputils->simplereq(
2220 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2226 __PACKAGE__->register_method(
2227 method => "get_user_perm_groups",
2228 api_name => "open-ils.actor.user.get_groups",
2229 notes => "Retrieve a user's permission groups."
2233 sub get_user_perm_groups {
2234 my( $self, $client, $authtoken, $userid ) = @_;
2236 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2237 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2238 return $evt if $evt;
2240 return $apputils->simplereq(
2242 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2246 __PACKAGE__->register_method(
2247 method => "get_user_work_ous",
2248 api_name => "open-ils.actor.user.get_work_ous",
2249 notes => "Retrieve a user's work org units."
2252 __PACKAGE__->register_method(
2253 method => "get_user_work_ous",
2254 api_name => "open-ils.actor.user.get_work_ous.ids",
2255 notes => "Retrieve a user's work org units."
2258 sub get_user_work_ous {
2259 my( $self, $client, $auth, $userid ) = @_;
2260 my $e = new_editor(authtoken=>$auth);
2261 return $e->event unless $e->checkauth;
2262 $userid ||= $e->requestor->id;
2264 if($e->requestor->id != $userid) {
2265 my $user = $e->retrieve_actor_user($userid)
2266 or return $e->event;
2267 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2270 return $e->search_permission_usr_work_ou_map({usr => $userid})
2271 unless $self->api_name =~ /.ids$/;
2273 # client just wants a list of org IDs
2274 return $U->get_user_work_ou_ids($e, $userid);
2279 __PACKAGE__->register_method(
2280 method => 'register_workstation',
2281 api_name => 'open-ils.actor.workstation.register.override',
2282 signature => q/@see open-ils.actor.workstation.register/
2285 __PACKAGE__->register_method(
2286 method => 'register_workstation',
2287 api_name => 'open-ils.actor.workstation.register',
2289 Registers a new workstion in the system
2290 @param authtoken The login session key
2291 @param name The name of the workstation id
2292 @param owner The org unit that owns this workstation
2293 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2294 if the name is already in use.
2298 sub register_workstation {
2299 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2301 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2302 return $e->die_event unless $e->checkauth;
2303 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2304 my $existing = $e->search_actor_workstation({name => $name})->[0];
2308 if( $self->api_name =~ /override/o ) {
2309 # workstation with the given name exists.
2311 if($owner ne $existing->owning_lib) {
2312 # if necessary, update the owning_lib of the workstation
2314 $logger->info("changing owning lib of workstation ".$existing->id.
2315 " from ".$existing->owning_lib." to $owner");
2316 return $e->die_event unless
2317 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2319 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2321 $existing->owning_lib($owner);
2322 return $e->die_event unless $e->update_actor_workstation($existing);
2328 "attempt to register an existing workstation. returning existing ID");
2331 return $existing->id;
2334 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2338 my $ws = Fieldmapper::actor::workstation->new;
2339 $ws->owning_lib($owner);
2341 $e->create_actor_workstation($ws) or return $e->die_event;
2343 return $ws->id; # note: editor sets the id on the new object for us
2346 __PACKAGE__->register_method(
2347 method => 'workstation_list',
2348 api_name => 'open-ils.actor.workstation.list',
2350 Returns a list of workstations registered at the given location
2351 @param authtoken The login session key
2352 @param ids A list of org_unit.id's for the workstation owners
2356 sub workstation_list {
2357 my( $self, $conn, $authtoken, @orgs ) = @_;
2359 my $e = new_editor(authtoken=>$authtoken);
2360 return $e->event unless $e->checkauth;
2365 unless $e->allowed('REGISTER_WORKSTATION', $o);
2366 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2372 __PACKAGE__->register_method(
2373 method => 'fetch_patron_note',
2374 api_name => 'open-ils.actor.note.retrieve.all',
2377 Returns a list of notes for a given user
2378 Requestor must have VIEW_USER permission if pub==false and
2379 @param authtoken The login session key
2380 @param args Hash of params including
2381 patronid : the patron's id
2382 pub : true if retrieving only public notes
2386 sub fetch_patron_note {
2387 my( $self, $conn, $authtoken, $args ) = @_;
2388 my $patronid = $$args{patronid};
2390 my($reqr, $evt) = $U->checkses($authtoken);
2391 return $evt if $evt;
2394 ($patron, $evt) = $U->fetch_user($patronid);
2395 return $evt if $evt;
2398 if( $patronid ne $reqr->id ) {
2399 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2400 return $evt if $evt;
2402 return $U->cstorereq(
2403 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2404 { usr => $patronid, pub => 't' } );
2407 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2408 return $evt if $evt;
2410 return $U->cstorereq(
2411 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2414 __PACKAGE__->register_method(
2415 method => 'create_user_note',
2416 api_name => 'open-ils.actor.note.create',
2418 Creates a new note for the given user
2419 @param authtoken The login session key
2420 @param note The note object
2423 sub create_user_note {
2424 my( $self, $conn, $authtoken, $note ) = @_;
2425 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2426 return $e->die_event unless $e->checkauth;
2428 my $user = $e->retrieve_actor_user($note->usr)
2429 or return $e->die_event;
2431 return $e->die_event unless
2432 $e->allowed('UPDATE_USER',$user->home_ou);
2434 $note->creator($e->requestor->id);
2435 $e->create_actor_usr_note($note) or return $e->die_event;
2441 __PACKAGE__->register_method(
2442 method => 'delete_user_note',
2443 api_name => 'open-ils.actor.note.delete',
2445 Deletes a note for the given user
2446 @param authtoken The login session key
2447 @param noteid The note id
2450 sub delete_user_note {
2451 my( $self, $conn, $authtoken, $noteid ) = @_;
2453 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2454 return $e->die_event unless $e->checkauth;
2455 my $note = $e->retrieve_actor_usr_note($noteid)
2456 or return $e->die_event;
2457 my $user = $e->retrieve_actor_user($note->usr)
2458 or return $e->die_event;
2459 return $e->die_event unless
2460 $e->allowed('UPDATE_USER', $user->home_ou);
2462 $e->delete_actor_usr_note($note) or return $e->die_event;
2468 __PACKAGE__->register_method(
2469 method => 'update_user_note',
2470 api_name => 'open-ils.actor.note.update',
2472 @param authtoken The login session key
2473 @param note The note
2477 sub update_user_note {
2478 my( $self, $conn, $auth, $note ) = @_;
2479 my $e = new_editor(authtoken=>$auth, xact=>1);
2480 return $e->die_event unless $e->checkauth;
2481 my $patron = $e->retrieve_actor_user($note->usr)
2482 or return $e->die_event;
2483 return $e->die_event unless
2484 $e->allowed('UPDATE_USER', $patron->home_ou);
2485 $e->update_actor_user_note($note)
2486 or return $e->die_event;
2493 __PACKAGE__->register_method(
2494 method => 'create_closed_date',
2495 api_name => 'open-ils.actor.org_unit.closed_date.create',
2497 Creates a new closing entry for the given org_unit
2498 @param authtoken The login session key
2499 @param note The closed_date object
2502 sub create_closed_date {
2503 my( $self, $conn, $authtoken, $cd ) = @_;
2505 my( $user, $evt ) = $U->checkses($authtoken);
2506 return $evt if $evt;
2508 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2509 return $evt if $evt;
2511 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2513 my $id = $U->storagereq(
2514 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2515 return $U->DB_UPDATE_FAILED($cd) unless $id;
2520 __PACKAGE__->register_method(
2521 method => 'delete_closed_date',
2522 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2524 Deletes a closing entry for the given org_unit
2525 @param authtoken The login session key
2526 @param noteid The close_date id
2529 sub delete_closed_date {
2530 my( $self, $conn, $authtoken, $cd ) = @_;
2532 my( $user, $evt ) = $U->checkses($authtoken);
2533 return $evt if $evt;
2536 ($cd_obj, $evt) = fetch_closed_date($cd);
2537 return $evt if $evt;
2539 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2540 return $evt if $evt;
2542 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2544 my $stat = $U->storagereq(
2545 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2546 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2551 __PACKAGE__->register_method(
2552 method => 'usrname_exists',
2553 api_name => 'open-ils.actor.username.exists',
2555 desc => 'Check if a username is already taken (by an undeleted patron)',
2557 {desc => 'Authentication token', type => 'string'},
2558 {desc => 'Username', type => 'string'}
2561 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2566 sub usrname_exists {
2567 my( $self, $conn, $auth, $usrname ) = @_;
2568 my $e = new_editor(authtoken=>$auth);
2569 return $e->event unless $e->checkauth;
2570 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2571 return $$a[0] if $a and @$a;
2575 __PACKAGE__->register_method(
2576 method => 'barcode_exists',
2577 api_name => 'open-ils.actor.barcode.exists',
2579 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2582 sub barcode_exists {
2583 my( $self, $conn, $auth, $barcode ) = @_;
2584 my $e = new_editor(authtoken=>$auth);
2585 return $e->event unless $e->checkauth;
2586 my $card = $e->search_actor_card({barcode => $barcode});
2592 #return undef unless @$card;
2593 #return $card->[0]->usr;
2597 __PACKAGE__->register_method(
2598 method => 'retrieve_net_levels',
2599 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2602 sub retrieve_net_levels {
2603 my( $self, $conn, $auth ) = @_;
2604 my $e = new_editor(authtoken=>$auth);
2605 return $e->event unless $e->checkauth;
2606 return $e->retrieve_all_config_net_access_level();
2609 # Retain the old typo API name just in case
2610 __PACKAGE__->register_method(
2611 method => 'fetch_org_by_shortname',
2612 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2614 __PACKAGE__->register_method(
2615 method => 'fetch_org_by_shortname',
2616 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2618 sub fetch_org_by_shortname {
2619 my( $self, $conn, $sname ) = @_;
2620 my $e = new_editor();
2621 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2622 return $e->event unless $org;
2627 __PACKAGE__->register_method(
2628 method => 'session_home_lib',
2629 api_name => 'open-ils.actor.session.home_lib',
2632 sub session_home_lib {
2633 my( $self, $conn, $auth ) = @_;
2634 my $e = new_editor(authtoken=>$auth);
2635 return undef unless $e->checkauth;
2636 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2637 return $org->shortname;
2640 __PACKAGE__->register_method(
2641 method => 'session_safe_token',
2642 api_name => 'open-ils.actor.session.safe_token',
2644 Returns a hashed session ID that is safe for export to the world.
2645 This safe token will expire after 1 hour of non-use.
2646 @param auth Active authentication token
2650 sub session_safe_token {
2651 my( $self, $conn, $auth ) = @_;
2652 my $e = new_editor(authtoken=>$auth);
2653 return undef unless $e->checkauth;
2655 my $safe_token = md5_hex($auth);
2657 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2659 # Add more like the following if needed...
2661 "safe-token-home_lib-shortname-$safe_token",
2662 $e->retrieve_actor_org_unit(
2663 $e->requestor->home_ou
2672 __PACKAGE__->register_method(
2673 method => 'safe_token_home_lib',
2674 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2676 Returns the home library shortname from the session
2677 asscociated with a safe token from generated by
2678 open-ils.actor.session.safe_token.
2679 @param safe_token Active safe token
2683 sub safe_token_home_lib {
2684 my( $self, $conn, $safe_token ) = @_;
2686 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2687 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2692 __PACKAGE__->register_method(
2693 method => 'slim_tree',
2694 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2697 my $tree = new_editor()->search_actor_org_unit(
2699 {"parent_ou" => undef },
2702 flesh_fields => { aou => ['children'] },
2703 order_by => { aou => 'name'},
2704 select => { aou => ["id","shortname", "name"]},
2709 return trim_tree($tree);
2715 return undef unless $tree;
2717 code => $tree->shortname,
2718 name => $tree->name,
2720 if( $tree->children and @{$tree->children} ) {
2721 $htree->{children} = [];
2722 for my $c (@{$tree->children}) {
2723 push( @{$htree->{children}}, trim_tree($c) );
2731 __PACKAGE__->register_method(
2732 method => "update_penalties",
2733 api_name => "open-ils.actor.user.penalties.update"
2736 sub update_penalties {
2737 my($self, $conn, $auth, $user_id) = @_;
2738 my $e = new_editor(authtoken=>$auth, xact => 1);
2739 return $e->die_event unless $e->checkauth;
2740 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2741 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2742 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2743 return $evt if $evt;
2749 __PACKAGE__->register_method(
2750 method => "apply_penalty",
2751 api_name => "open-ils.actor.user.penalty.apply"
2755 my($self, $conn, $auth, $penalty) = @_;
2757 my $e = new_editor(authtoken=>$auth, xact => 1);
2758 return $e->die_event unless $e->checkauth;
2760 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2761 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2763 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2766 (defined $ptype->org_depth) ?
2767 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2770 $penalty->org_unit($ctx_org);
2771 $penalty->staff($e->requestor->id);
2772 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2775 return $penalty->id;
2778 __PACKAGE__->register_method(
2779 method => "remove_penalty",
2780 api_name => "open-ils.actor.user.penalty.remove"
2783 sub remove_penalty {
2784 my($self, $conn, $auth, $penalty) = @_;
2785 my $e = new_editor(authtoken=>$auth, xact => 1);
2786 return $e->die_event unless $e->checkauth;
2787 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2788 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2790 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2795 __PACKAGE__->register_method(
2796 method => "update_penalty_note",
2797 api_name => "open-ils.actor.user.penalty.note.update"
2800 sub update_penalty_note {
2801 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2802 my $e = new_editor(authtoken=>$auth, xact => 1);
2803 return $e->die_event unless $e->checkauth;
2804 for my $penalty_id (@$penalty_ids) {
2805 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2806 if (! $penalty ) { return $e->die_event; }
2807 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2808 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2810 $penalty->note( $note ); $penalty->ischanged( 1 );
2812 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2818 __PACKAGE__->register_method(
2819 method => "ranged_penalty_thresholds",
2820 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2824 sub ranged_penalty_thresholds {
2825 my($self, $conn, $auth, $context_org) = @_;
2826 my $e = new_editor(authtoken=>$auth);
2827 return $e->event unless $e->checkauth;
2828 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2829 my $list = $e->search_permission_grp_penalty_threshold([
2830 {org_unit => $U->get_org_ancestors($context_org)},
2831 {order_by => {pgpt => 'id'}}
2833 $conn->respond($_) for @$list;
2839 __PACKAGE__->register_method(
2840 method => "user_retrieve_fleshed_by_id",
2842 api_name => "open-ils.actor.user.fleshed.retrieve",
2845 sub user_retrieve_fleshed_by_id {
2846 my( $self, $client, $auth, $user_id, $fields ) = @_;
2847 my $e = new_editor(authtoken => $auth);
2848 return $e->event unless $e->checkauth;
2850 if( $e->requestor->id != $user_id ) {
2851 return $e->event unless $e->allowed('VIEW_USER');
2857 "standing_penalties",
2861 "stat_cat_entries" ];
2862 return new_flesh_user($user_id, $fields, $e);
2866 sub new_flesh_user {
2869 my $fields = shift || [];
2872 my $fetch_penalties = 0;
2873 if(grep {$_ eq 'standing_penalties'} @$fields) {
2874 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2875 $fetch_penalties = 1;
2878 my $user = $e->retrieve_actor_user(
2883 "flesh_fields" => { "au" => $fields }
2886 ) or return $e->die_event;
2889 if( grep { $_ eq 'addresses' } @$fields ) {
2891 $user->addresses([]) unless @{$user->addresses};
2892 # don't expose "replaced" addresses by default
2893 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2895 if( ref $user->billing_address ) {
2896 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2897 push( @{$user->addresses}, $user->billing_address );
2901 if( ref $user->mailing_address ) {
2902 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2903 push( @{$user->addresses}, $user->mailing_address );
2908 if($fetch_penalties) {
2909 # grab the user penalties ranged for this location
2910 $user->standing_penalties(
2911 $e->search_actor_user_standing_penalty([
2914 {stop_date => undef},
2915 {stop_date => {'>' => 'now'}}
2917 org_unit => $U->get_org_ancestors($e->requestor->ws_ou)
2920 flesh_fields => {ausp => ['standing_penalty']}
2927 $user->clear_passwd();
2934 __PACKAGE__->register_method(
2935 method => "user_retrieve_parts",
2936 api_name => "open-ils.actor.user.retrieve.parts",
2939 sub user_retrieve_parts {
2940 my( $self, $client, $auth, $user_id, $fields ) = @_;
2941 my $e = new_editor(authtoken => $auth);
2942 return $e->event unless $e->checkauth;
2943 $user_id ||= $e->requestor->id;
2944 if( $e->requestor->id != $user_id ) {
2945 return $e->event unless $e->allowed('VIEW_USER');
2948 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2949 push(@resp, $user->$_()) for(@$fields);
2955 __PACKAGE__->register_method(
2956 method => 'user_opt_in_enabled',
2957 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2958 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2961 sub user_opt_in_enabled {
2962 my($self, $conn) = @_;
2963 my $sc = OpenSRF::Utils::SettingsClient->new;
2964 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2969 __PACKAGE__->register_method(
2970 method => 'user_opt_in_at_org',
2971 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2973 @param $auth The auth token
2974 @param user_id The ID of the user to test
2975 @return 1 if the user has opted in at the specified org,
2976 event on error, and 0 otherwise. /
2978 sub user_opt_in_at_org {
2979 my($self, $conn, $auth, $user_id) = @_;
2981 # see if we even need to enforce the opt-in value
2982 return 1 unless user_opt_in_enabled($self);
2984 my $e = new_editor(authtoken => $auth);
2985 return $e->event unless $e->checkauth;
2986 my $org_id = $e->requestor->ws_ou;
2988 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2989 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2991 # user is automatically opted-in at the home org
2992 return 1 if $user->home_ou eq $org_id;
2994 my $vals = $e->search_actor_usr_org_unit_opt_in(
2995 {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
3001 __PACKAGE__->register_method(
3002 method => 'create_user_opt_in_at_org',
3003 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3005 @param $auth The auth token
3006 @param user_id The ID of the user to test
3007 @return The ID of the newly created object, event on error./
3010 sub create_user_opt_in_at_org {
3011 my($self, $conn, $auth, $user_id) = @_;
3013 my $e = new_editor(authtoken => $auth, xact=>1);
3014 return $e->die_event unless $e->checkauth;
3015 my $org_id = $e->requestor->ws_ou;
3017 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3018 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3020 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3022 $opt_in->org_unit($org_id);
3023 $opt_in->usr($user_id);
3024 $opt_in->staff($e->requestor->id);
3025 $opt_in->opt_in_ts('now');
3026 $opt_in->opt_in_ws($e->requestor->wsid);
3028 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3029 or return $e->die_event;
3037 __PACKAGE__->register_method (
3038 method => 'retrieve_org_hours',
3039 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3041 Returns the hours of operation for a specified org unit
3042 @param authtoken The login session key
3043 @param org_id The org_unit ID
3047 sub retrieve_org_hours {
3048 my($self, $conn, $auth, $org_id) = @_;
3049 my $e = new_editor(authtoken => $auth);
3050 return $e->die_event unless $e->checkauth;
3051 $org_id ||= $e->requestor->ws_ou;
3052 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3056 __PACKAGE__->register_method (
3057 method => 'verify_user_password',
3058 api_name => 'open-ils.actor.verify_user_password',
3060 Given a barcode or username and the MD5 encoded password,
3061 returns 1 if the password is correct. Returns 0 otherwise.
3065 sub verify_user_password {
3066 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3067 my $e = new_editor(authtoken => $auth);
3068 return $e->die_event unless $e->checkauth;
3070 my $user_by_barcode;
3071 my $user_by_username;
3073 my $card = $e->search_actor_card([
3074 {barcode => $barcode},
3075 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3076 $user_by_barcode = $card->usr;
3077 $user = $user_by_barcode;
3080 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3081 $user = $user_by_username;
3083 return 0 if (!$user);
3084 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3085 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3086 return 1 if $user->passwd eq $password;
3090 __PACKAGE__->register_method (
3091 method => 'retrieve_usr_id_via_barcode_or_usrname',
3092 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3094 Given a barcode or username returns the id for the user or
3099 sub retrieve_usr_id_via_barcode_or_usrname {
3100 my($self, $conn, $auth, $barcode, $username) = @_;
3101 my $e = new_editor(authtoken => $auth);
3102 return $e->die_event unless $e->checkauth;
3103 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3105 my $user_by_barcode;
3106 my $user_by_username;
3107 $logger->info("$id_as_barcode is the ID as BARCODE");
3109 my $card = $e->search_actor_card([
3110 {barcode => $barcode},
3111 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3112 if ($id_as_barcode =~ /^t/i) {
3114 $user = $e->retrieve_actor_user($barcode);
3115 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3117 $user_by_barcode = $card->usr;
3118 $user = $user_by_barcode;
3121 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3122 $user_by_barcode = $card->usr;
3123 $user = $user_by_barcode;
3128 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3130 $user = $user_by_username;
3132 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3133 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3134 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3139 __PACKAGE__->register_method (
3140 method => 'merge_users',
3141 api_name => 'open-ils.actor.user.merge',
3144 Given a list of source users and destination user, transfer all data from the source
3145 to the dest user and delete the source user. All user related data is
3146 transferred, including circulations, holds, bookbags, etc.
3152 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3153 my $e = new_editor(xact => 1, authtoken => $auth);
3154 return $e->die_event unless $e->checkauth;
3156 # disallow the merge if any subordinate accounts are in collections
3157 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3158 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3160 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3161 my $del_addrs = ($U->ou_ancestor_setting_value(
3162 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3163 my $del_cards = ($U->ou_ancestor_setting_value(
3164 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3165 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3166 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3168 for my $src_id (@$user_ids) {
3169 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3171 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3172 if($src_user->home_ou ne $master_user->home_ou) {
3173 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3176 return $e->die_event unless
3177 $e->json_query({from => [
3192 __PACKAGE__->register_method (
3193 method => 'approve_user_address',
3194 api_name => 'open-ils.actor.user.pending_address.approve',
3201 sub approve_user_address {
3202 my($self, $conn, $auth, $addr) = @_;
3203 my $e = new_editor(xact => 1, authtoken => $auth);
3204 return $e->die_event unless $e->checkauth;
3206 # if the caller passes an address object, assume they want to
3207 # update it first before approving it
3208 $e->update_actor_user_address($addr) or return $e->die_event;
3210 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3212 my $user = $e->retrieve_actor_user($addr->usr);
3213 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3214 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3215 or return $e->die_event;
3217 return [values %$result]->[0];
3221 __PACKAGE__->register_method (
3222 method => 'retrieve_friends',
3223 api_name => 'open-ils.actor.friends.retrieve',
3226 returns { confirmed: [], pending_out: [], pending_in: []}
3227 pending_out are users I'm requesting friendship with
3228 pending_in are users requesting friendship with me
3233 sub retrieve_friends {
3234 my($self, $conn, $auth, $user_id, $options) = @_;
3235 my $e = new_editor(authtoken => $auth);
3236 return $e->event unless $e->checkauth;
3237 $user_id ||= $e->requestor->id;
3239 if($user_id != $e->requestor->id) {
3240 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3241 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3244 return OpenILS::Application::Actor::Friends->retrieve_friends(
3245 $e, $user_id, $options);
3250 __PACKAGE__->register_method (
3251 method => 'apply_friend_perms',
3252 api_name => 'open-ils.actor.friends.perms.apply',
3258 sub apply_friend_perms {
3259 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3260 my $e = new_editor(authtoken => $auth, xact => 1);
3261 return $e->die_event unless $e->checkauth;
3263 if($user_id != $e->requestor->id) {
3264 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3265 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3268 for my $perm (@perms) {
3270 OpenILS::Application::Actor::Friends->apply_friend_perm(
3271 $e, $user_id, $delegate_id, $perm);
3272 return $evt if $evt;
3280 __PACKAGE__->register_method (
3281 method => 'update_user_pending_address',
3282 api_name => 'open-ils.actor.user.address.pending.cud'
3285 sub update_user_pending_address {
3286 my($self, $conn, $auth, $addr) = @_;
3287 my $e = new_editor(authtoken => $auth, xact => 1);
3288 return $e->die_event unless $e->checkauth;
3290 if($addr->usr != $e->requestor->id) {
3291 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3292 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3296 $e->create_actor_user_address($addr) or return $e->die_event;
3297 } elsif($addr->isdeleted) {
3298 $e->delete_actor_user_address($addr) or return $e->die_event;
3300 $e->update_actor_user_address($addr) or return $e->die_event;
3308 __PACKAGE__->register_method (
3309 method => 'user_events',
3310 api_name => 'open-ils.actor.user.events.circ',
3313 __PACKAGE__->register_method (
3314 method => 'user_events',
3315 api_name => 'open-ils.actor.user.events.ahr',
3320 my($self, $conn, $auth, $user_id, $filters) = @_;
3321 my $e = new_editor(authtoken => $auth);
3322 return $e->event unless $e->checkauth;
3324 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3325 my $user_field = 'usr';
3328 $filters->{target} = {
3329 select => { $obj_type => ['id'] },
3331 where => {usr => $user_id}
3334 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3335 if($e->requestor->id != $user_id) {
3336 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3339 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3340 my $req = $ses->request('open-ils.trigger.events_by_target',
3341 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3343 while(my $resp = $req->recv) {
3344 my $val = $resp->content;
3345 my $tgt = $val->target;
3347 if($obj_type eq 'circ') {
3348 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3350 } elsif($obj_type eq 'ahr') {
3351 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3352 if $tgt->current_copy;
3355 $conn->respond($val) if $val;
3361 __PACKAGE__->register_method (
3362 method => 'copy_events',
3363 api_name => 'open-ils.actor.copy.events.circ',
3366 __PACKAGE__->register_method (
3367 method => 'copy_events',
3368 api_name => 'open-ils.actor.copy.events.ahr',
3373 my($self, $conn, $auth, $copy_id, $filters) = @_;
3374 my $e = new_editor(authtoken => $auth);
3375 return $e->event unless $e->checkauth;
3377 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3379 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3381 my $copy_field = 'target_copy';
3382 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3385 $filters->{target} = {
3386 select => { $obj_type => ['id'] },
3388 where => {$copy_field => $copy_id}
3392 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3393 my $req = $ses->request('open-ils.trigger.events_by_target',
3394 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3396 while(my $resp = $req->recv) {
3397 my $val = $resp->content;
3398 my $tgt = $val->target;
3400 my $user = $e->retrieve_actor_user($tgt->usr);
3401 if($e->requestor->id != $user->id) {
3402 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3405 $tgt->$copy_field($copy);
3408 $conn->respond($val) if $val;
3417 __PACKAGE__->register_method (
3418 method => 'update_events',
3419 api_name => 'open-ils.actor.user.event.cancel.batch',
3422 __PACKAGE__->register_method (
3423 method => 'update_events',
3424 api_name => 'open-ils.actor.user.event.reset.batch',
3429 my($self, $conn, $auth, $event_ids) = @_;
3430 my $e = new_editor(xact => 1, authtoken => $auth);
3431 return $e->die_event unless $e->checkauth;
3434 for my $id (@$event_ids) {
3436 # do a little dance to determine what user we are ultimately affecting
3437 my $event = $e->retrieve_action_trigger_event([
3440 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3442 ]) or return $e->die_event;
3445 if($event->event_def->hook->core_type eq 'circ') {
3446 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3447 } elsif($event->event_def->hook->core_type eq 'ahr') {
3448 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3453 my $user = $e->retrieve_actor_user($user_id);
3454 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3456 if($self->api_name =~ /cancel/) {
3457 $event->state('invalid');
3458 } elsif($self->api_name =~ /reset/) {
3459 $event->clear_start_time;
3460 $event->clear_update_time;
3461 $event->state('pending');
3464 $e->update_action_trigger_event($event) or return $e->die_event;
3465 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3469 return {complete => 1};
3473 __PACKAGE__->register_method (
3474 method => 'really_delete_user',
3475 api_name => 'open-ils.actor.user.delete',
3477 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3478 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3479 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3480 dest_usr_id is only required when deleting a user that performs staff functions.
3484 sub really_delete_user {
3485 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3486 my $e = new_editor(authtoken => $auth, xact => 1);
3487 return $e->die_event unless $e->checkauth;
3488 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3489 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3490 my $stat = $e->json_query(
3491 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3492 or return $e->die_event;
3499 __PACKAGE__->register_method (
3500 method => 'user_payments',
3501 api_name => 'open-ils.actor.user.payments.retrieve',
3504 Returns all payments for a given user. Default order is newest payments first.
3505 @param auth Authentication token
3506 @param user_id The user ID
3507 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3512 my($self, $conn, $auth, $user_id, $filters) = @_;
3515 my $e = new_editor(authtoken => $auth);
3516 return $e->die_event unless $e->checkauth;
3518 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3519 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3521 # Find all payments for all transactions for user $user_id
3523 select => {mp => ['id']},
3528 select => {mbt => ['id']},
3530 where => {usr => $user_id}
3534 order_by => [{ # by default, order newest payments first
3536 field => 'payment_ts',
3541 for (qw/order_by limit offset/) {
3542 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3545 if(defined $filters->{where}) {
3546 foreach (keys %{$filters->{where}}) {
3547 # don't allow the caller to expand the result set to other users
3548 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3552 my $payment_ids = $e->json_query($query);
3553 for my $pid (@$payment_ids) {
3554 my $pay = $e->retrieve_money_payment([
3559 mbt => ['summary', 'circulation', 'grocery'],
3560 circ => ['target_copy'],
3561 acp => ['call_number'],
3569 xact_type => $pay->xact->summary->xact_type,
3570 last_billing_type => $pay->xact->summary->last_billing_type,
3573 if($pay->xact->summary->xact_type eq 'circulation') {
3574 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3575 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3578 $pay->xact($pay->xact->id); # de-flesh
3579 $conn->respond($resp);
3587 __PACKAGE__->register_method (
3588 method => 'negative_balance_users',
3589 api_name => 'open-ils.actor.users.negative_balance',
3592 Returns all users that have an overall negative balance
3593 @param auth Authentication token
3594 @param org_id The context org unit as an ID or list of IDs. This will be the home
3595 library of the user. If no org_unit is specified, no org unit filter is applied
3599 sub negative_balance_users {
3600 my($self, $conn, $auth, $org_id) = @_;
3602 my $e = new_editor(authtoken => $auth);
3603 return $e->die_event unless $e->checkauth;
3604 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3608 mous => ['usr', 'balance_owed'],
3611 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3612 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3629 where => {'+mous' => {balance_owed => {'<' => 0}}}
3632 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3634 my $list = $e->json_query($query, {timeout => 600});
3636 for my $data (@$list) {
3638 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3639 balance_owed => $data->{balance_owed},
3640 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3647 __PACKAGE__->register_method(
3648 method => "request_password_reset",
3649 api_name => "open-ils.actor.patron.password_reset.request",
3651 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3652 "method for changing a user's password. The UUID token is distributed via A/T " .
3653 "templates (i.e. email to the user).",
3655 { desc => 'user_id_type', type => 'string' },
3656 { desc => 'user_id', type => 'string' },
3657 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3659 return => {desc => '1 on success, Event on error'}
3662 sub request_password_reset {
3663 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3665 # Check to see if password reset requests are already being throttled:
3666 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3668 my $e = new_editor(xact => 1);
3671 # Get the user, if any, depending on the input value
3672 if ($user_id_type eq 'username') {
3673 $user = $e->search_actor_user({usrname => $user_id})->[0];
3676 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3678 } elsif ($user_id_type eq 'barcode') {
3679 my $card = $e->search_actor_card([
3680 {barcode => $user_id},
3681 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3684 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3689 # If the user doesn't have an email address, we can't help them
3690 if (!$user->email) {
3692 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3695 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3696 if ($email_must_match) {
3697 if ($user->email ne $email) {
3698 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3702 _reset_password_request($conn, $e, $user);
3705 # Once we have the user, we can issue the password reset request
3706 # XXX Add a wrapper method that accepts barcode + email input
3707 sub _reset_password_request {
3708 my ($conn, $e, $user) = @_;
3710 # 1. Get throttle threshold and time-to-live from OU_settings
3711 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3712 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3714 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3716 # 2. Get time of last request and number of active requests (num_active)
3717 my $active_requests = $e->json_query({
3723 transform => 'COUNT'
3726 column => 'request_time',
3732 has_been_reset => { '=' => 'f' },
3733 request_time => { '>' => $threshold_time }
3737 # Guard against no active requests
3738 if ($active_requests->[0]->{'request_time'}) {
3739 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3740 my $now = DateTime::Format::ISO8601->new();
3742 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3743 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3744 ($last_request->add_duration('1 minute') > $now)) {
3745 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3747 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3751 # TODO Check to see if the user is in a password-reset-restricted group
3753 # Otherwise, go ahead and try to get the user.
3755 # Check the number of active requests for this user
3756 $active_requests = $e->json_query({
3762 transform => 'COUNT'
3767 usr => { '=' => $user->id },
3768 has_been_reset => { '=' => 'f' },
3769 request_time => { '>' => $threshold_time }
3773 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3775 # if less than or equal to per-user threshold, proceed; otherwise, return event
3776 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3777 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3779 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3782 # Create the aupr object and insert into the database
3783 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3784 my $uuid = create_uuid_as_string(UUID_V4);
3785 $reset_request->uuid($uuid);
3786 $reset_request->usr($user->id);
3788 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3791 # Create an event to notify user of the URL to reset their password
3793 # Can we stuff this in the user_data param for trigger autocreate?
3794 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3796 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3797 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3800 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3805 __PACKAGE__->register_method(
3806 method => "commit_password_reset",
3807 api_name => "open-ils.actor.patron.password_reset.commit",
3809 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3810 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3811 "with the supplied password.",
3813 { desc => 'uuid', type => 'string' },
3814 { desc => 'password', type => 'string' },
3816 return => {desc => '1 on success, Event on error'}
3819 sub commit_password_reset {
3820 my($self, $conn, $uuid, $password) = @_;
3822 # Check to see if password reset requests are already being throttled:
3823 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3824 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3825 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3827 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3830 my $e = new_editor(xact => 1);
3832 my $aupr = $e->search_actor_usr_password_reset({
3839 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3841 my $user_id = $aupr->[0]->usr;
3842 my $user = $e->retrieve_actor_user($user_id);
3844 # Ensure we're still within the TTL for the request
3845 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3846 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3847 if ($threshold < DateTime->now(time_zone => 'local')) {
3849 $logger->info("Password reset request needed to be submitted before $threshold");
3850 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3853 # Check complexity of password against OU-defined regex
3854 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3858 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3859 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3860 $is_strong = check_password_strength_custom($password, $pw_regex);
3862 $is_strong = check_password_strength_default($password);
3867 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3870 # All is well; update the password
3871 $user->passwd($password);
3872 $e->update_actor_user($user);
3874 # And flag that this password reset request has been honoured
3875 $aupr->[0]->has_been_reset('t');
3876 $e->update_actor_usr_password_reset($aupr->[0]);
3882 sub check_password_strength_default {
3883 my $password = shift;
3884 # Use the default set of checks
3885 if ( (length($password) < 7) or
3886 ($password !~ m/.*\d+.*/) or
3887 ($password !~ m/.*[A-Za-z]+.*/)
3894 sub check_password_strength_custom {
3895 my ($password, $pw_regex) = @_;
3897 $pw_regex = qr/$pw_regex/;
3898 if ($password !~ /$pw_regex/) {
3906 __PACKAGE__->register_method(
3907 method => "event_def_opt_in_settings",
3908 api_name => "open-ils.actor.event_def.opt_in.settings",
3911 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3913 { desc => 'Authentication token', type => 'string'},
3915 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3920 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3927 sub event_def_opt_in_settings {
3928 my($self, $conn, $auth, $org_id) = @_;
3929 my $e = new_editor(authtoken => $auth);
3930 return $e->event unless $e->checkauth;
3932 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3933 return $e->event unless
3934 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3936 $org_id = $e->requestor->home_ou;
3939 # find all config.user_setting_type's related to event_defs for the requested org unit
3940 my $types = $e->json_query({
3941 select => {cust => ['name']},
3942 from => {atevdef => 'cust'},
3945 owner => $U->get_org_ancestors($org_id), # context org plus parents
3952 $conn->respond($_) for
3953 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
3960 __PACKAGE__->register_method(
3961 method => "user_visible_circs",
3962 api_name => "open-ils.actor.history.circ.visible",
3965 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
3967 { desc => 'Authentication token', type => 'string'},
3968 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3969 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3972 desc => q/An object with 2 fields: circulation and summary.
3973 circulation is the "circ" object. summary is the related "accs" object/,
3979 __PACKAGE__->register_method(
3980 method => "user_visible_circs",
3981 api_name => "open-ils.actor.history.circ.visible.print",
3984 desc => 'Returns printable output for the set of opt-in visible circulations',
3986 { desc => 'Authentication token', type => 'string'},
3987 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3988 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3991 desc => q/An action_trigger.event object or error event./,
3997 __PACKAGE__->register_method(
3998 method => "user_visible_circs",
3999 api_name => "open-ils.actor.history.circ.visible.email",
4002 desc => 'Emails the set of opt-in visible circulations to the requestor',
4004 { desc => 'Authentication token', type => 'string'},
4005 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4006 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4009 desc => q/undef, or event on error/
4014 __PACKAGE__->register_method(
4015 method => "user_visible_circs",
4016 api_name => "open-ils.actor.history.hold.visible",
4019 desc => 'Returns the set of opt-in visible holds',
4021 { desc => 'Authentication token', type => 'string'},
4022 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4023 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4026 desc => q/An object with 1 field: "hold"/,
4032 __PACKAGE__->register_method(
4033 method => "user_visible_circs",
4034 api_name => "open-ils.actor.history.hold.visible.print",
4037 desc => 'Returns printable output for the set of opt-in visible holds',
4039 { desc => 'Authentication token', type => 'string'},
4040 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4041 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4044 desc => q/An action_trigger.event object or error event./,
4050 __PACKAGE__->register_method(
4051 method => "user_visible_circs",
4052 api_name => "open-ils.actor.history.hold.visible.email",
4055 desc => 'Emails the set of opt-in visible holds to the requestor',
4057 { desc => 'Authentication token', type => 'string'},
4058 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4059 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4062 desc => q/undef, or event on error/
4067 sub user_visible_circs {
4068 my($self, $conn, $auth, $user_id, $options) = @_;
4070 my $is_hold = ($self->api_name =~ /hold/);
4071 my $for_print = ($self->api_name =~ /print/);
4072 my $for_email = ($self->api_name =~ /email/);
4073 my $e = new_editor(authtoken => $auth);
4074 return $e->event unless $e->checkauth;
4076 $user_id ||= $e->requestor->id;
4078 $options->{limit} ||= 50;
4079 $options->{offset} ||= 0;
4081 if($user_id != $e->requestor->id) {
4082 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4083 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4084 return $e->event unless $e->allowed($perm, $user->home_ou);
4087 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4089 my $data = $e->json_query({
4090 from => [$db_func, $user_id],
4091 limit => $$options{limit},
4092 offset => $$options{offset}
4094 # TODO: I only want IDs. code below didn't get me there
4095 # {"select":{"au":[{"column":"id", "result_field":"id",
4096 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4101 return undef unless @$data;
4105 # collect the batch of objects
4109 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4110 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4114 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4115 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4118 } elsif ($for_email) {
4120 $conn->respond_complete(1) if $for_email; # no sense in waiting
4128 my $hold = $e->retrieve_action_hold_request($id);
4129 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4130 # events will be fired from action_trigger_runner
4134 my $circ = $e->retrieve_action_circulation($id);
4135 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4136 # events will be fired from action_trigger_runner
4140 } else { # just give me the data please
4148 my $hold = $e->retrieve_action_hold_request($id);
4149 $conn->respond({hold => $hold});
4153 my $circ = $e->retrieve_action_circulation($id);
4156 summary => $U->create_circ_chain_summary($e, $id)
4165 __PACKAGE__->register_method(
4166 method => "user_saved_search_cud",
4167 api_name => "open-ils.actor.user.saved_search.cud",
4170 desc => 'Create/Update/Delete Access to user saved searches',
4172 { desc => 'Authentication token', type => 'string' },
4173 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4176 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4182 __PACKAGE__->register_method(
4183 method => "user_saved_search_cud",
4184 api_name => "open-ils.actor.user.saved_search.retrieve",
4187 desc => 'Retrieve a saved search object',
4189 { desc => 'Authentication token', type => 'string' },
4190 { desc => 'Saved Search ID', type => 'number' }
4193 desc => q/The saved search object, Event on error/,
4199 sub user_saved_search_cud {
4200 my( $self, $client, $auth, $search ) = @_;
4201 my $e = new_editor( authtoken=>$auth );
4202 return $e->die_event unless $e->checkauth;
4204 my $o_search; # prior version of the object, if any
4205 my $res; # to be returned
4207 # branch on the operation type
4209 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4211 # Get the old version, to check ownership
4212 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4213 or return $e->die_event;
4215 # You can't read somebody else's search
4216 return OpenILS::Event->new('BAD_PARAMS')
4217 unless $o_search->owner == $e->requestor->id;
4223 $e->xact_begin; # start an editor transaction
4225 if( $search->isnew ) { # Create
4227 # You can't create a search for somebody else
4228 return OpenILS::Event->new('BAD_PARAMS')
4229 unless $search->owner == $e->requestor->id;
4231 $e->create_actor_usr_saved_search( $search )
4232 or return $e->die_event;
4236 } elsif( $search->ischanged ) { # Update
4238 # You can't change ownership of a search
4239 return OpenILS::Event->new('BAD_PARAMS')
4240 unless $search->owner == $e->requestor->id;
4242 # Get the old version, to check ownership
4243 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4244 or return $e->die_event;
4246 # You can't update somebody else's search
4247 return OpenILS::Event->new('BAD_PARAMS')
4248 unless $o_search->owner == $e->requestor->id;
4251 $e->update_actor_usr_saved_search( $search )
4252 or return $e->die_event;
4256 } elsif( $search->isdeleted ) { # Delete
4258 # Get the old version, to check ownership
4259 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4260 or return $e->die_event;
4262 # You can't delete somebody else's search
4263 return OpenILS::Event->new('BAD_PARAMS')
4264 unless $o_search->owner == $e->requestor->id;
4267 $e->delete_actor_usr_saved_search( $o_search )
4268 or return $e->die_event;