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 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
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, $login_session, $user_id, $type, $options ) = @_;
1686 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1687 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1688 return $evt if $evt;
1690 my $api = $self->api_name();
1692 my $filter = ($api =~ /have_balance/o) ?
1693 { 'balance_owed' => { '<>' => 0 } }:
1694 { 'total_owed' => { '>' => 0 } };
1696 my ($trans) = $self->method_lookup(
1697 'open-ils.actor.user.transactions.history.still_open')
1698 ->run( $login_session, $user_id, $type, $filter, $options );
1700 if($api =~ /total/o) {
1702 for my $t (@$trans) {
1703 $total += $t->balance_owed;
1706 $logger->debug("Total balance owed by user $user_id: $total");
1710 ($api =~ /count/o ) and return scalar @$trans;
1711 ($api !~ /fleshed/o) and return $trans;
1714 for my $t (@$trans) {
1716 if( $t->xact_type ne 'circulation' ) {
1717 push @resp, {transaction => $t};
1721 my $circ = $apputils->simple_scalar_request(
1723 "open-ils.cstore.direct.action.circulation.retrieve",
1728 my $title = $apputils->simple_scalar_request(
1730 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1731 $circ->target_copy );
1735 my $u = OpenILS::Utils::ModsParser->new();
1736 $u->start_mods_batch($title->marc());
1737 my $mods = $u->finish_mods_batch();
1738 $mods->doc_id($title->id) if $mods;
1740 push @resp, {transaction => $t, circ => $circ, record => $mods };
1748 __PACKAGE__->register_method(
1749 method => "user_transaction_retrieve",
1750 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1752 notes => "Returns a fleshed transaction record"
1755 __PACKAGE__->register_method(
1756 method => "user_transaction_retrieve",
1757 api_name => "open-ils.actor.user.transaction.retrieve",
1759 notes => "Returns a transaction record"
1762 sub user_transaction_retrieve {
1763 my( $self, $client, $login_session, $bill_id ) = @_;
1765 # I think I'm deprecated... make sure. phasefx says, "No, I'll use you :)
1767 my $trans = $apputils->simple_scalar_request(
1769 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1773 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1774 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1775 return $evt if $evt;
1777 my $api = $self->api_name();
1778 if($api !~ /fleshed/o) { return $trans; }
1780 if( $trans->xact_type ne 'circulation' ) {
1781 $logger->debug("Returning non-circ transaction");
1782 return {transaction => $trans};
1785 my $circ = $apputils->simple_scalar_request(
1787 "open-ils.cstore.direct.action.circulation.retrieve",
1790 return {transaction => $trans} unless $circ;
1791 $logger->debug("Found the circ transaction");
1793 my $title = $apputils->simple_scalar_request(
1795 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1796 $circ->target_copy );
1798 return {transaction => $trans, circ => $circ } unless $title;
1799 $logger->debug("Found the circ title");
1802 my $copy = $apputils->simple_scalar_request(
1804 "open-ils.cstore.direct.asset.copy.retrieve",
1805 $circ->target_copy );
1808 my $u = OpenILS::Utils::ModsParser->new();
1809 $u->start_mods_batch($title->marc());
1810 $mods = $u->finish_mods_batch();
1812 if ($title->id == OILS_PRECAT_RECORD) {
1813 $mods = new Fieldmapper::metabib::virtual_record;
1814 $mods->doc_id(OILS_PRECAT_RECORD);
1815 $mods->title($copy->dummy_title);
1816 $mods->author($copy->dummy_author);
1820 $logger->debug("MODSized the circ title");
1822 return {transaction => $trans, circ => $circ, record => $mods, copy => $copy };
1826 __PACKAGE__->register_method(
1827 method => "hold_request_count",
1828 api_name => "open-ils.actor.user.hold_requests.count",
1831 notes => 'Returns hold ready/total counts'
1834 sub hold_request_count {
1835 my( $self, $client, $login_session, $userid ) = @_;
1837 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1838 $login_session, $userid, 'VIEW_HOLD' );
1839 return $evt if $evt;
1842 my $holds = $apputils->simple_scalar_request(
1844 "open-ils.cstore.direct.action.hold_request.search.atomic",
1847 fulfillment_time => {"=" => undef },
1848 cancel_time => undef,
1853 for my $h (@$holds) {
1854 next unless $h->capture_time and $h->current_copy;
1856 my $copy = $apputils->simple_scalar_request(
1858 "open-ils.cstore.direct.asset.copy.retrieve",
1862 if ($copy and $copy->status == 8) {
1867 return { total => scalar(@$holds), ready => scalar(@ready) };
1870 __PACKAGE__->register_method(
1871 method => "checked_out",
1872 api_name => "open-ils.actor.user.checked_out",
1876 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1877 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1878 . "(i.e., outstanding balance or some other pending action on the circ). "
1879 . "The .count method also includes a 'total' field which sums all open circs.",
1881 { desc => 'Authentication Token', type => 'string'},
1882 { desc => 'User ID', type => 'string'},
1885 desc => 'Returns event on error, or an object with ID lists, like: '
1886 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1891 __PACKAGE__->register_method(
1892 method => "checked_out",
1893 api_name => "open-ils.actor.user.checked_out.count",
1896 signature => q/@see open-ils.actor.user.checked_out/
1900 my( $self, $conn, $auth, $userid ) = @_;
1902 my $e = new_editor(authtoken=>$auth);
1903 return $e->event unless $e->checkauth;
1905 if( $userid ne $e->requestor->id ) {
1906 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1907 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1909 # see if there is a friend link allowing circ.view perms
1910 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1911 $e, $userid, $e->requestor->id, 'circ.view');
1912 return $e->event unless $allowed;
1916 my $count = $self->api_name =~ /count/;
1917 return _checked_out( $count, $e, $userid );
1921 my( $iscount, $e, $userid ) = @_;
1927 claims_returned => [],
1930 my $meth = 'retrieve_action_open_circ_';
1938 claims_returned => 0,
1945 my $data = $e->$meth($userid);
1949 $result{$_} += $data->$_() for (keys %result);
1950 $result{total} += $data->$_() for (keys %result);
1952 for my $k (keys %result) {
1953 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
1963 __PACKAGE__->register_method(
1964 method => "checked_in_with_fines",
1965 api_name => "open-ils.actor.user.checked_in_with_fines",
1968 signature => q/@see open-ils.actor.user.checked_out/
1971 sub checked_in_with_fines {
1972 my( $self, $conn, $auth, $userid ) = @_;
1974 my $e = new_editor(authtoken=>$auth);
1975 return $e->event unless $e->checkauth;
1977 if( $userid ne $e->requestor->id ) {
1978 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1981 # money is owed on these items and they are checked in
1982 my $open = $e->search_action_circulation(
1985 xact_finish => undef,
1986 checkin_time => { "!=" => undef },
1991 my( @lost, @cr, @lo );
1992 for my $c (@$open) {
1993 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1994 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1995 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2000 claims_returned => \@cr,
2001 long_overdue => \@lo
2007 my ($api, $desc, $auth) = @_;
2008 $desc = $desc ? (" " . $desc) : '';
2009 my $ids = ($api =~ /ids$/) ? 1 : 0;
2012 method => "user_transaction_history",
2013 api_name => "open-ils.actor.user.transactions.$api",
2015 desc => "For a given User ID, returns a list of billable transaction" .
2016 ($ids ? " id" : '') .
2017 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2018 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2020 {desc => 'Authentication token', type => 'string'},
2021 {desc => 'User ID', type => 'number'},
2022 {desc => 'Transaction type (optional)', type => 'number'},
2023 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2026 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2030 $auth and push @sig, (authoritative => 1);
2034 my %hist_methods = (
2036 'history.have_charge' => 'that have an initial charge',
2037 'history.still_open' => 'that are not finished',
2039 my %auth_hist_methods = (
2040 'history.have_balance' => 'that have a balance',
2041 'history.have_bill' => 'that have billings',
2042 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2044 foreach (keys %hist_methods) {
2045 __PACKAGE__->register_method(_sigmaker($_, $hist_methods{$_}));
2046 __PACKAGE__->register_method(_sigmaker("$_.ids", $hist_methods{$_}));
2048 foreach (keys %auth_hist_methods) {
2049 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2050 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2053 sub user_transaction_history {
2054 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2058 my $e = new_editor(authtoken=>$auth);
2059 return $e->die_event unless $e->checkauth;
2061 if ($e->requestor->id ne $userid) {
2062 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2065 my $api = $self->api_name;
2066 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2068 if(defined($type)) {
2069 $filter->{'xact_type'} = $type;
2072 if($api =~ /have_bill_or_payment/o) {
2074 # transactions that have a non-zero sum across all billings or at least 1 payment
2075 $filter->{'-or'} = {
2076 'balance_owed' => { '<>' => 0 },
2077 'last_payment_ts' => { '<>' => undef }
2080 } elsif( $api =~ /have_balance/o) {
2082 # transactions that have a non-zero overall balance
2083 $filter->{'balance_owed'} = { '<>' => 0 };
2085 } elsif( $api =~ /have_charge/o) {
2087 # transactions that have at least 1 billing, regardless of whether it was voided
2088 $filter->{'last_billing_ts'} = { '<>' => undef };
2090 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2092 # transactions that have non-zero sum across all billings. This will exclude
2093 # xacts where all billings have been voided
2094 $filter->{'total_owed'} = { '<>' => 0 };
2097 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2098 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2099 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2101 my $mbts = $e->search_money_billable_transaction_summary(
2103 { usr => $userid, @xact_finish, %$filter },
2108 if ($api =~ /\.ids/) {
2109 return [map {$_->id} @$mbts];
2117 __PACKAGE__->register_method(
2118 method => "user_perms",
2119 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2121 notes => "Returns a list of permissions"
2125 my( $self, $client, $authtoken, $user ) = @_;
2127 my( $staff, $evt ) = $apputils->checkses($authtoken);
2128 return $evt if $evt;
2130 $user ||= $staff->id;
2132 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2136 return $apputils->simple_scalar_request(
2138 "open-ils.storage.permission.user_perms.atomic",
2142 __PACKAGE__->register_method(
2143 method => "retrieve_perms",
2144 api_name => "open-ils.actor.permissions.retrieve",
2145 notes => "Returns a list of permissions"
2147 sub retrieve_perms {
2148 my( $self, $client ) = @_;
2149 return $apputils->simple_scalar_request(
2151 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2152 { id => { '!=' => undef } }
2156 __PACKAGE__->register_method(
2157 method => "retrieve_groups",
2158 api_name => "open-ils.actor.groups.retrieve",
2159 notes => "Returns a list of user groups"
2161 sub retrieve_groups {
2162 my( $self, $client ) = @_;
2163 return new_editor()->retrieve_all_permission_grp_tree();
2166 __PACKAGE__->register_method(
2167 method => "retrieve_org_address",
2168 api_name => "open-ils.actor.org_unit.address.retrieve",
2169 notes => <<' NOTES');
2170 Returns an org_unit address by ID
2171 @param An org_address ID
2173 sub retrieve_org_address {
2174 my( $self, $client, $id ) = @_;
2175 return $apputils->simple_scalar_request(
2177 "open-ils.cstore.direct.actor.org_address.retrieve",
2182 __PACKAGE__->register_method(
2183 method => "retrieve_groups_tree",
2184 api_name => "open-ils.actor.groups.tree.retrieve",
2185 notes => "Returns a list of user groups"
2188 sub retrieve_groups_tree {
2189 my( $self, $client ) = @_;
2190 return new_editor()->search_permission_grp_tree(
2195 flesh_fields => { pgt => ["children"] },
2196 order_by => { pgt => 'name'}
2203 __PACKAGE__->register_method(
2204 method => "add_user_to_groups",
2205 api_name => "open-ils.actor.user.set_groups",
2206 notes => "Adds a user to one or more permission groups"
2209 sub add_user_to_groups {
2210 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2212 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2213 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2214 return $evt if $evt;
2216 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2217 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2218 return $evt if $evt;
2220 $apputils->simplereq(
2222 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2224 for my $group (@$groups) {
2225 my $link = Fieldmapper::permission::usr_grp_map->new;
2227 $link->usr($userid);
2229 my $id = $apputils->simplereq(
2231 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2237 __PACKAGE__->register_method(
2238 method => "get_user_perm_groups",
2239 api_name => "open-ils.actor.user.get_groups",
2240 notes => "Retrieve a user's permission groups."
2244 sub get_user_perm_groups {
2245 my( $self, $client, $authtoken, $userid ) = @_;
2247 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2248 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2249 return $evt if $evt;
2251 return $apputils->simplereq(
2253 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2257 __PACKAGE__->register_method(
2258 method => "get_user_work_ous",
2259 api_name => "open-ils.actor.user.get_work_ous",
2260 notes => "Retrieve a user's work org units."
2263 __PACKAGE__->register_method(
2264 method => "get_user_work_ous",
2265 api_name => "open-ils.actor.user.get_work_ous.ids",
2266 notes => "Retrieve a user's work org units."
2269 sub get_user_work_ous {
2270 my( $self, $client, $auth, $userid ) = @_;
2271 my $e = new_editor(authtoken=>$auth);
2272 return $e->event unless $e->checkauth;
2273 $userid ||= $e->requestor->id;
2275 if($e->requestor->id != $userid) {
2276 my $user = $e->retrieve_actor_user($userid)
2277 or return $e->event;
2278 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2281 return $e->search_permission_usr_work_ou_map({usr => $userid})
2282 unless $self->api_name =~ /.ids$/;
2284 # client just wants a list of org IDs
2285 return $U->get_user_work_ou_ids($e, $userid);
2290 __PACKAGE__->register_method(
2291 method => 'register_workstation',
2292 api_name => 'open-ils.actor.workstation.register.override',
2293 signature => q/@see open-ils.actor.workstation.register/
2296 __PACKAGE__->register_method(
2297 method => 'register_workstation',
2298 api_name => 'open-ils.actor.workstation.register',
2300 Registers a new workstion in the system
2301 @param authtoken The login session key
2302 @param name The name of the workstation id
2303 @param owner The org unit that owns this workstation
2304 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2305 if the name is already in use.
2309 sub register_workstation {
2310 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2312 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2313 return $e->die_event unless $e->checkauth;
2314 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2315 my $existing = $e->search_actor_workstation({name => $name})->[0];
2319 if( $self->api_name =~ /override/o ) {
2320 # workstation with the given name exists.
2322 if($owner ne $existing->owning_lib) {
2323 # if necessary, update the owning_lib of the workstation
2325 $logger->info("changing owning lib of workstation ".$existing->id.
2326 " from ".$existing->owning_lib." to $owner");
2327 return $e->die_event unless
2328 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2330 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2332 $existing->owning_lib($owner);
2333 return $e->die_event unless $e->update_actor_workstation($existing);
2339 "attempt to register an existing workstation. returning existing ID");
2342 return $existing->id;
2345 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2349 my $ws = Fieldmapper::actor::workstation->new;
2350 $ws->owning_lib($owner);
2352 $e->create_actor_workstation($ws) or return $e->die_event;
2354 return $ws->id; # note: editor sets the id on the new object for us
2357 __PACKAGE__->register_method(
2358 method => 'workstation_list',
2359 api_name => 'open-ils.actor.workstation.list',
2361 Returns a list of workstations registered at the given location
2362 @param authtoken The login session key
2363 @param ids A list of org_unit.id's for the workstation owners
2367 sub workstation_list {
2368 my( $self, $conn, $authtoken, @orgs ) = @_;
2370 my $e = new_editor(authtoken=>$authtoken);
2371 return $e->event unless $e->checkauth;
2376 unless $e->allowed('REGISTER_WORKSTATION', $o);
2377 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2383 __PACKAGE__->register_method(
2384 method => 'fetch_patron_note',
2385 api_name => 'open-ils.actor.note.retrieve.all',
2388 Returns a list of notes for a given user
2389 Requestor must have VIEW_USER permission if pub==false and
2390 @param authtoken The login session key
2391 @param args Hash of params including
2392 patronid : the patron's id
2393 pub : true if retrieving only public notes
2397 sub fetch_patron_note {
2398 my( $self, $conn, $authtoken, $args ) = @_;
2399 my $patronid = $$args{patronid};
2401 my($reqr, $evt) = $U->checkses($authtoken);
2402 return $evt if $evt;
2405 ($patron, $evt) = $U->fetch_user($patronid);
2406 return $evt if $evt;
2409 if( $patronid ne $reqr->id ) {
2410 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2411 return $evt if $evt;
2413 return $U->cstorereq(
2414 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2415 { usr => $patronid, pub => 't' } );
2418 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2419 return $evt if $evt;
2421 return $U->cstorereq(
2422 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2425 __PACKAGE__->register_method(
2426 method => 'create_user_note',
2427 api_name => 'open-ils.actor.note.create',
2429 Creates a new note for the given user
2430 @param authtoken The login session key
2431 @param note The note object
2434 sub create_user_note {
2435 my( $self, $conn, $authtoken, $note ) = @_;
2436 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2437 return $e->die_event unless $e->checkauth;
2439 my $user = $e->retrieve_actor_user($note->usr)
2440 or return $e->die_event;
2442 return $e->die_event unless
2443 $e->allowed('UPDATE_USER',$user->home_ou);
2445 $note->creator($e->requestor->id);
2446 $e->create_actor_usr_note($note) or return $e->die_event;
2452 __PACKAGE__->register_method(
2453 method => 'delete_user_note',
2454 api_name => 'open-ils.actor.note.delete',
2456 Deletes a note for the given user
2457 @param authtoken The login session key
2458 @param noteid The note id
2461 sub delete_user_note {
2462 my( $self, $conn, $authtoken, $noteid ) = @_;
2464 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2465 return $e->die_event unless $e->checkauth;
2466 my $note = $e->retrieve_actor_usr_note($noteid)
2467 or return $e->die_event;
2468 my $user = $e->retrieve_actor_user($note->usr)
2469 or return $e->die_event;
2470 return $e->die_event unless
2471 $e->allowed('UPDATE_USER', $user->home_ou);
2473 $e->delete_actor_usr_note($note) or return $e->die_event;
2479 __PACKAGE__->register_method(
2480 method => 'update_user_note',
2481 api_name => 'open-ils.actor.note.update',
2483 @param authtoken The login session key
2484 @param note The note
2488 sub update_user_note {
2489 my( $self, $conn, $auth, $note ) = @_;
2490 my $e = new_editor(authtoken=>$auth, xact=>1);
2491 return $e->die_event unless $e->checkauth;
2492 my $patron = $e->retrieve_actor_user($note->usr)
2493 or return $e->die_event;
2494 return $e->die_event unless
2495 $e->allowed('UPDATE_USER', $patron->home_ou);
2496 $e->update_actor_user_note($note)
2497 or return $e->die_event;
2504 __PACKAGE__->register_method(
2505 method => 'create_closed_date',
2506 api_name => 'open-ils.actor.org_unit.closed_date.create',
2508 Creates a new closing entry for the given org_unit
2509 @param authtoken The login session key
2510 @param note The closed_date object
2513 sub create_closed_date {
2514 my( $self, $conn, $authtoken, $cd ) = @_;
2516 my( $user, $evt ) = $U->checkses($authtoken);
2517 return $evt if $evt;
2519 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2520 return $evt if $evt;
2522 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2524 my $id = $U->storagereq(
2525 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2526 return $U->DB_UPDATE_FAILED($cd) unless $id;
2531 __PACKAGE__->register_method(
2532 method => 'delete_closed_date',
2533 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2535 Deletes a closing entry for the given org_unit
2536 @param authtoken The login session key
2537 @param noteid The close_date id
2540 sub delete_closed_date {
2541 my( $self, $conn, $authtoken, $cd ) = @_;
2543 my( $user, $evt ) = $U->checkses($authtoken);
2544 return $evt if $evt;
2547 ($cd_obj, $evt) = fetch_closed_date($cd);
2548 return $evt if $evt;
2550 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2551 return $evt if $evt;
2553 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2555 my $stat = $U->storagereq(
2556 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2557 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2562 __PACKAGE__->register_method(
2563 method => 'usrname_exists',
2564 api_name => 'open-ils.actor.username.exists',
2566 desc => 'Check if a username is already taken (by an undeleted patron)',
2568 {desc => 'Authentication token', type => 'string'},
2569 {desc => 'Username', type => 'string'}
2572 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2577 sub usrname_exists {
2578 my( $self, $conn, $auth, $usrname ) = @_;
2579 my $e = new_editor(authtoken=>$auth);
2580 return $e->event unless $e->checkauth;
2581 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2582 return $$a[0] if $a and @$a;
2586 __PACKAGE__->register_method(
2587 method => 'barcode_exists',
2588 api_name => 'open-ils.actor.barcode.exists',
2590 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2593 sub barcode_exists {
2594 my( $self, $conn, $auth, $barcode ) = @_;
2595 my $e = new_editor(authtoken=>$auth);
2596 return $e->event unless $e->checkauth;
2597 my $card = $e->search_actor_card({barcode => $barcode});
2603 #return undef unless @$card;
2604 #return $card->[0]->usr;
2608 __PACKAGE__->register_method(
2609 method => 'retrieve_net_levels',
2610 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2613 sub retrieve_net_levels {
2614 my( $self, $conn, $auth ) = @_;
2615 my $e = new_editor(authtoken=>$auth);
2616 return $e->event unless $e->checkauth;
2617 return $e->retrieve_all_config_net_access_level();
2620 # Retain the old typo API name just in case
2621 __PACKAGE__->register_method(
2622 method => 'fetch_org_by_shortname',
2623 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2625 __PACKAGE__->register_method(
2626 method => 'fetch_org_by_shortname',
2627 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2629 sub fetch_org_by_shortname {
2630 my( $self, $conn, $sname ) = @_;
2631 my $e = new_editor();
2632 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2633 return $e->event unless $org;
2638 __PACKAGE__->register_method(
2639 method => 'session_home_lib',
2640 api_name => 'open-ils.actor.session.home_lib',
2643 sub session_home_lib {
2644 my( $self, $conn, $auth ) = @_;
2645 my $e = new_editor(authtoken=>$auth);
2646 return undef unless $e->checkauth;
2647 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2648 return $org->shortname;
2651 __PACKAGE__->register_method(
2652 method => 'session_safe_token',
2653 api_name => 'open-ils.actor.session.safe_token',
2655 Returns a hashed session ID that is safe for export to the world.
2656 This safe token will expire after 1 hour of non-use.
2657 @param auth Active authentication token
2661 sub session_safe_token {
2662 my( $self, $conn, $auth ) = @_;
2663 my $e = new_editor(authtoken=>$auth);
2664 return undef unless $e->checkauth;
2666 my $safe_token = md5_hex($auth);
2668 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2670 # Add more like the following if needed...
2672 "safe-token-home_lib-shortname-$safe_token",
2673 $e->retrieve_actor_org_unit(
2674 $e->requestor->home_ou
2683 __PACKAGE__->register_method(
2684 method => 'safe_token_home_lib',
2685 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2687 Returns the home library shortname from the session
2688 asscociated with a safe token from generated by
2689 open-ils.actor.session.safe_token.
2690 @param safe_token Active safe token
2694 sub safe_token_home_lib {
2695 my( $self, $conn, $safe_token ) = @_;
2697 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2698 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2703 __PACKAGE__->register_method(
2704 method => 'slim_tree',
2705 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2708 my $tree = new_editor()->search_actor_org_unit(
2710 {"parent_ou" => undef },
2713 flesh_fields => { aou => ['children'] },
2714 order_by => { aou => 'name'},
2715 select => { aou => ["id","shortname", "name"]},
2720 return trim_tree($tree);
2726 return undef unless $tree;
2728 code => $tree->shortname,
2729 name => $tree->name,
2731 if( $tree->children and @{$tree->children} ) {
2732 $htree->{children} = [];
2733 for my $c (@{$tree->children}) {
2734 push( @{$htree->{children}}, trim_tree($c) );
2742 __PACKAGE__->register_method(
2743 method => "update_penalties",
2744 api_name => "open-ils.actor.user.penalties.update"
2747 sub update_penalties {
2748 my($self, $conn, $auth, $user_id) = @_;
2749 my $e = new_editor(authtoken=>$auth, xact => 1);
2750 return $e->die_event unless $e->checkauth;
2751 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2752 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2753 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2754 return $evt if $evt;
2760 __PACKAGE__->register_method(
2761 method => "apply_penalty",
2762 api_name => "open-ils.actor.user.penalty.apply"
2766 my($self, $conn, $auth, $penalty) = @_;
2768 my $e = new_editor(authtoken=>$auth, xact => 1);
2769 return $e->die_event unless $e->checkauth;
2771 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2772 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2774 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2777 (defined $ptype->org_depth) ?
2778 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2781 $penalty->org_unit($ctx_org);
2782 $penalty->staff($e->requestor->id);
2783 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2786 return $penalty->id;
2789 __PACKAGE__->register_method(
2790 method => "remove_penalty",
2791 api_name => "open-ils.actor.user.penalty.remove"
2794 sub remove_penalty {
2795 my($self, $conn, $auth, $penalty) = @_;
2796 my $e = new_editor(authtoken=>$auth, xact => 1);
2797 return $e->die_event unless $e->checkauth;
2798 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2799 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2801 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2806 __PACKAGE__->register_method(
2807 method => "update_penalty_note",
2808 api_name => "open-ils.actor.user.penalty.note.update"
2811 sub update_penalty_note {
2812 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2813 my $e = new_editor(authtoken=>$auth, xact => 1);
2814 return $e->die_event unless $e->checkauth;
2815 for my $penalty_id (@$penalty_ids) {
2816 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2817 if (! $penalty ) { return $e->die_event; }
2818 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2819 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2821 $penalty->note( $note ); $penalty->ischanged( 1 );
2823 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2829 __PACKAGE__->register_method(
2830 method => "ranged_penalty_thresholds",
2831 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2835 sub ranged_penalty_thresholds {
2836 my($self, $conn, $auth, $context_org) = @_;
2837 my $e = new_editor(authtoken=>$auth);
2838 return $e->event unless $e->checkauth;
2839 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2840 my $list = $e->search_permission_grp_penalty_threshold([
2841 {org_unit => $U->get_org_ancestors($context_org)},
2842 {order_by => {pgpt => 'id'}}
2844 $conn->respond($_) for @$list;
2850 __PACKAGE__->register_method(
2851 method => "user_retrieve_fleshed_by_id",
2853 api_name => "open-ils.actor.user.fleshed.retrieve",
2856 sub user_retrieve_fleshed_by_id {
2857 my( $self, $client, $auth, $user_id, $fields ) = @_;
2858 my $e = new_editor(authtoken => $auth);
2859 return $e->event unless $e->checkauth;
2861 if( $e->requestor->id != $user_id ) {
2862 return $e->event unless $e->allowed('VIEW_USER');
2868 "standing_penalties",
2872 "stat_cat_entries" ];
2873 return new_flesh_user($user_id, $fields, $e);
2877 sub new_flesh_user {
2880 my $fields = shift || [];
2883 my $fetch_penalties = 0;
2884 if(grep {$_ eq 'standing_penalties'} @$fields) {
2885 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2886 $fetch_penalties = 1;
2889 my $user = $e->retrieve_actor_user(
2894 "flesh_fields" => { "au" => $fields }
2897 ) or return $e->die_event;
2900 if( grep { $_ eq 'addresses' } @$fields ) {
2902 $user->addresses([]) unless @{$user->addresses};
2903 # don't expose "replaced" addresses by default
2904 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2906 if( ref $user->billing_address ) {
2907 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2908 push( @{$user->addresses}, $user->billing_address );
2912 if( ref $user->mailing_address ) {
2913 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2914 push( @{$user->addresses}, $user->mailing_address );
2919 if($fetch_penalties) {
2920 # grab the user penalties ranged for this location
2921 $user->standing_penalties(
2922 $e->search_actor_user_standing_penalty([
2925 {stop_date => undef},
2926 {stop_date => {'>' => 'now'}}
2928 org_unit => $U->get_org_ancestors($e->requestor->ws_ou)
2931 flesh_fields => {ausp => ['standing_penalty']}
2938 $user->clear_passwd();
2945 __PACKAGE__->register_method(
2946 method => "user_retrieve_parts",
2947 api_name => "open-ils.actor.user.retrieve.parts",
2950 sub user_retrieve_parts {
2951 my( $self, $client, $auth, $user_id, $fields ) = @_;
2952 my $e = new_editor(authtoken => $auth);
2953 return $e->event unless $e->checkauth;
2954 $user_id ||= $e->requestor->id;
2955 if( $e->requestor->id != $user_id ) {
2956 return $e->event unless $e->allowed('VIEW_USER');
2959 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2960 push(@resp, $user->$_()) for(@$fields);
2966 __PACKAGE__->register_method(
2967 method => 'user_opt_in_enabled',
2968 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2969 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2972 sub user_opt_in_enabled {
2973 my($self, $conn) = @_;
2974 my $sc = OpenSRF::Utils::SettingsClient->new;
2975 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2980 __PACKAGE__->register_method(
2981 method => 'user_opt_in_at_org',
2982 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2984 @param $auth The auth token
2985 @param user_id The ID of the user to test
2986 @return 1 if the user has opted in at the specified org,
2987 event on error, and 0 otherwise. /
2989 sub user_opt_in_at_org {
2990 my($self, $conn, $auth, $user_id) = @_;
2992 # see if we even need to enforce the opt-in value
2993 return 1 unless user_opt_in_enabled($self);
2995 my $e = new_editor(authtoken => $auth);
2996 return $e->event unless $e->checkauth;
2997 my $org_id = $e->requestor->ws_ou;
2999 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3000 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3002 # user is automatically opted-in at the home org
3003 return 1 if $user->home_ou eq $org_id;
3005 my $vals = $e->search_actor_usr_org_unit_opt_in(
3006 {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
3012 __PACKAGE__->register_method(
3013 method => 'create_user_opt_in_at_org',
3014 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3016 @param $auth The auth token
3017 @param user_id The ID of the user to test
3018 @return The ID of the newly created object, event on error./
3021 sub create_user_opt_in_at_org {
3022 my($self, $conn, $auth, $user_id) = @_;
3024 my $e = new_editor(authtoken => $auth, xact=>1);
3025 return $e->die_event unless $e->checkauth;
3026 my $org_id = $e->requestor->ws_ou;
3028 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3029 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3031 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3033 $opt_in->org_unit($org_id);
3034 $opt_in->usr($user_id);
3035 $opt_in->staff($e->requestor->id);
3036 $opt_in->opt_in_ts('now');
3037 $opt_in->opt_in_ws($e->requestor->wsid);
3039 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3040 or return $e->die_event;
3048 __PACKAGE__->register_method (
3049 method => 'retrieve_org_hours',
3050 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3052 Returns the hours of operation for a specified org unit
3053 @param authtoken The login session key
3054 @param org_id The org_unit ID
3058 sub retrieve_org_hours {
3059 my($self, $conn, $auth, $org_id) = @_;
3060 my $e = new_editor(authtoken => $auth);
3061 return $e->die_event unless $e->checkauth;
3062 $org_id ||= $e->requestor->ws_ou;
3063 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3067 __PACKAGE__->register_method (
3068 method => 'verify_user_password',
3069 api_name => 'open-ils.actor.verify_user_password',
3071 Given a barcode or username and the MD5 encoded password,
3072 returns 1 if the password is correct. Returns 0 otherwise.
3076 sub verify_user_password {
3077 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3078 my $e = new_editor(authtoken => $auth);
3079 return $e->die_event unless $e->checkauth;
3081 my $user_by_barcode;
3082 my $user_by_username;
3084 my $card = $e->search_actor_card([
3085 {barcode => $barcode},
3086 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3087 $user_by_barcode = $card->usr;
3088 $user = $user_by_barcode;
3091 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3092 $user = $user_by_username;
3094 return 0 if (!$user);
3095 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3096 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3097 return 1 if $user->passwd eq $password;
3101 __PACKAGE__->register_method (
3102 method => 'retrieve_usr_id_via_barcode_or_usrname',
3103 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3105 Given a barcode or username returns the id for the user or
3110 sub retrieve_usr_id_via_barcode_or_usrname {
3111 my($self, $conn, $auth, $barcode, $username) = @_;
3112 my $e = new_editor(authtoken => $auth);
3113 return $e->die_event unless $e->checkauth;
3114 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3116 my $user_by_barcode;
3117 my $user_by_username;
3118 $logger->info("$id_as_barcode is the ID as BARCODE");
3120 my $card = $e->search_actor_card([
3121 {barcode => $barcode},
3122 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3123 if ($id_as_barcode =~ /^t/i) {
3125 $user = $e->retrieve_actor_user($barcode);
3126 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3128 $user_by_barcode = $card->usr;
3129 $user = $user_by_barcode;
3132 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3133 $user_by_barcode = $card->usr;
3134 $user = $user_by_barcode;
3139 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3141 $user = $user_by_username;
3143 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3144 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3145 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3150 __PACKAGE__->register_method (
3151 method => 'merge_users',
3152 api_name => 'open-ils.actor.user.merge',
3155 Given a list of source users and destination user, transfer all data from the source
3156 to the dest user and delete the source user. All user related data is
3157 transferred, including circulations, holds, bookbags, etc.
3163 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3164 my $e = new_editor(xact => 1, authtoken => $auth);
3165 return $e->die_event unless $e->checkauth;
3167 # disallow the merge if any subordinate accounts are in collections
3168 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3169 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3171 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3172 my $del_addrs = ($U->ou_ancestor_setting_value(
3173 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3174 my $del_cards = ($U->ou_ancestor_setting_value(
3175 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3176 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3177 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3179 for my $src_id (@$user_ids) {
3180 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3182 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3183 if($src_user->home_ou ne $master_user->home_ou) {
3184 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3187 return $e->die_event unless
3188 $e->json_query({from => [
3203 __PACKAGE__->register_method (
3204 method => 'approve_user_address',
3205 api_name => 'open-ils.actor.user.pending_address.approve',
3212 sub approve_user_address {
3213 my($self, $conn, $auth, $addr) = @_;
3214 my $e = new_editor(xact => 1, authtoken => $auth);
3215 return $e->die_event unless $e->checkauth;
3217 # if the caller passes an address object, assume they want to
3218 # update it first before approving it
3219 $e->update_actor_user_address($addr) or return $e->die_event;
3221 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3223 my $user = $e->retrieve_actor_user($addr->usr);
3224 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3225 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3226 or return $e->die_event;
3228 return [values %$result]->[0];
3232 __PACKAGE__->register_method (
3233 method => 'retrieve_friends',
3234 api_name => 'open-ils.actor.friends.retrieve',
3237 returns { confirmed: [], pending_out: [], pending_in: []}
3238 pending_out are users I'm requesting friendship with
3239 pending_in are users requesting friendship with me
3244 sub retrieve_friends {
3245 my($self, $conn, $auth, $user_id, $options) = @_;
3246 my $e = new_editor(authtoken => $auth);
3247 return $e->event unless $e->checkauth;
3248 $user_id ||= $e->requestor->id;
3250 if($user_id != $e->requestor->id) {
3251 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3252 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3255 return OpenILS::Application::Actor::Friends->retrieve_friends(
3256 $e, $user_id, $options);
3261 __PACKAGE__->register_method (
3262 method => 'apply_friend_perms',
3263 api_name => 'open-ils.actor.friends.perms.apply',
3269 sub apply_friend_perms {
3270 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3271 my $e = new_editor(authtoken => $auth, xact => 1);
3272 return $e->die_event unless $e->checkauth;
3274 if($user_id != $e->requestor->id) {
3275 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3276 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3279 for my $perm (@perms) {
3281 OpenILS::Application::Actor::Friends->apply_friend_perm(
3282 $e, $user_id, $delegate_id, $perm);
3283 return $evt if $evt;
3291 __PACKAGE__->register_method (
3292 method => 'update_user_pending_address',
3293 api_name => 'open-ils.actor.user.address.pending.cud'
3296 sub update_user_pending_address {
3297 my($self, $conn, $auth, $addr) = @_;
3298 my $e = new_editor(authtoken => $auth, xact => 1);
3299 return $e->die_event unless $e->checkauth;
3301 if($addr->usr != $e->requestor->id) {
3302 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3303 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3307 $e->create_actor_user_address($addr) or return $e->die_event;
3308 } elsif($addr->isdeleted) {
3309 $e->delete_actor_user_address($addr) or return $e->die_event;
3311 $e->update_actor_user_address($addr) or return $e->die_event;
3319 __PACKAGE__->register_method (
3320 method => 'user_events',
3321 api_name => 'open-ils.actor.user.events.circ',
3324 __PACKAGE__->register_method (
3325 method => 'user_events',
3326 api_name => 'open-ils.actor.user.events.ahr',
3331 my($self, $conn, $auth, $user_id, $filters) = @_;
3332 my $e = new_editor(authtoken => $auth);
3333 return $e->event unless $e->checkauth;
3335 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3336 my $user_field = 'usr';
3339 $filters->{target} = {
3340 select => { $obj_type => ['id'] },
3342 where => {usr => $user_id}
3345 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3346 if($e->requestor->id != $user_id) {
3347 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3350 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3351 my $req = $ses->request('open-ils.trigger.events_by_target',
3352 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3354 while(my $resp = $req->recv) {
3355 my $val = $resp->content;
3356 my $tgt = $val->target;
3358 if($obj_type eq 'circ') {
3359 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3361 } elsif($obj_type eq 'ahr') {
3362 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3363 if $tgt->current_copy;
3366 $conn->respond($val) if $val;
3372 __PACKAGE__->register_method (
3373 method => 'copy_events',
3374 api_name => 'open-ils.actor.copy.events.circ',
3377 __PACKAGE__->register_method (
3378 method => 'copy_events',
3379 api_name => 'open-ils.actor.copy.events.ahr',
3384 my($self, $conn, $auth, $copy_id, $filters) = @_;
3385 my $e = new_editor(authtoken => $auth);
3386 return $e->event unless $e->checkauth;
3388 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3390 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3392 my $copy_field = 'target_copy';
3393 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3396 $filters->{target} = {
3397 select => { $obj_type => ['id'] },
3399 where => {$copy_field => $copy_id}
3403 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3404 my $req = $ses->request('open-ils.trigger.events_by_target',
3405 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3407 while(my $resp = $req->recv) {
3408 my $val = $resp->content;
3409 my $tgt = $val->target;
3411 my $user = $e->retrieve_actor_user($tgt->usr);
3412 if($e->requestor->id != $user->id) {
3413 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3416 $tgt->$copy_field($copy);
3419 $conn->respond($val) if $val;
3428 __PACKAGE__->register_method (
3429 method => 'update_events',
3430 api_name => 'open-ils.actor.user.event.cancel.batch',
3433 __PACKAGE__->register_method (
3434 method => 'update_events',
3435 api_name => 'open-ils.actor.user.event.reset.batch',
3440 my($self, $conn, $auth, $event_ids) = @_;
3441 my $e = new_editor(xact => 1, authtoken => $auth);
3442 return $e->die_event unless $e->checkauth;
3445 for my $id (@$event_ids) {
3447 # do a little dance to determine what user we are ultimately affecting
3448 my $event = $e->retrieve_action_trigger_event([
3451 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3453 ]) or return $e->die_event;
3456 if($event->event_def->hook->core_type eq 'circ') {
3457 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3458 } elsif($event->event_def->hook->core_type eq 'ahr') {
3459 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3464 my $user = $e->retrieve_actor_user($user_id);
3465 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3467 if($self->api_name =~ /cancel/) {
3468 $event->state('invalid');
3469 } elsif($self->api_name =~ /reset/) {
3470 $event->clear_start_time;
3471 $event->clear_update_time;
3472 $event->state('pending');
3475 $e->update_action_trigger_event($event) or return $e->die_event;
3476 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3480 return {complete => 1};
3484 __PACKAGE__->register_method (
3485 method => 'really_delete_user',
3486 api_name => 'open-ils.actor.user.delete',
3488 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3489 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3490 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3491 dest_usr_id is only required when deleting a user that performs staff functions.
3495 sub really_delete_user {
3496 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3497 my $e = new_editor(authtoken => $auth, xact => 1);
3498 return $e->die_event unless $e->checkauth;
3499 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3500 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3501 my $stat = $e->json_query(
3502 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3503 or return $e->die_event;
3510 __PACKAGE__->register_method (
3511 method => 'user_payments',
3512 api_name => 'open-ils.actor.user.payments.retrieve',
3515 Returns all payments for a given user. Default order is newest payments first.
3516 @param auth Authentication token
3517 @param user_id The user ID
3518 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3523 my($self, $conn, $auth, $user_id, $filters) = @_;
3526 my $e = new_editor(authtoken => $auth);
3527 return $e->die_event unless $e->checkauth;
3529 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3530 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3532 # Find all payments for all transactions for user $user_id
3534 select => {mp => ['id']},
3539 select => {mbt => ['id']},
3541 where => {usr => $user_id}
3545 order_by => [{ # by default, order newest payments first
3547 field => 'payment_ts',
3552 for (qw/order_by limit offset/) {
3553 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3556 if(defined $filters->{where}) {
3557 foreach (keys %{$filters->{where}}) {
3558 # don't allow the caller to expand the result set to other users
3559 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3563 my $payment_ids = $e->json_query($query);
3564 for my $pid (@$payment_ids) {
3565 my $pay = $e->retrieve_money_payment([
3570 mbt => ['summary', 'circulation', 'grocery'],
3571 circ => ['target_copy'],
3572 acp => ['call_number'],
3580 xact_type => $pay->xact->summary->xact_type,
3581 last_billing_type => $pay->xact->summary->last_billing_type,
3584 if($pay->xact->summary->xact_type eq 'circulation') {
3585 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3586 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3589 $pay->xact($pay->xact->id); # de-flesh
3590 $conn->respond($resp);
3598 __PACKAGE__->register_method (
3599 method => 'negative_balance_users',
3600 api_name => 'open-ils.actor.users.negative_balance',
3603 Returns all users that have an overall negative balance
3604 @param auth Authentication token
3605 @param org_id The context org unit as an ID or list of IDs. This will be the home
3606 library of the user. If no org_unit is specified, no org unit filter is applied
3610 sub negative_balance_users {
3611 my($self, $conn, $auth, $org_id) = @_;
3613 my $e = new_editor(authtoken => $auth);
3614 return $e->die_event unless $e->checkauth;
3615 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3619 mous => ['usr', 'balance_owed'],
3622 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3623 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3640 where => {'+mous' => {balance_owed => {'<' => 0}}}
3643 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3645 my $list = $e->json_query($query, {timeout => 600});
3647 for my $data (@$list) {
3649 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3650 balance_owed => $data->{balance_owed},
3651 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3658 __PACKAGE__->register_method(
3659 method => "request_password_reset",
3660 api_name => "open-ils.actor.patron.password_reset.request",
3662 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3663 "method for changing a user's password. The UUID token is distributed via A/T " .
3664 "templates (i.e. email to the user).",
3666 { desc => 'user_id_type', type => 'string' },
3667 { desc => 'user_id', type => 'string' },
3668 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3670 return => {desc => '1 on success, Event on error'}
3673 sub request_password_reset {
3674 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3676 # Check to see if password reset requests are already being throttled:
3677 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3679 my $e = new_editor(xact => 1);
3682 # Get the user, if any, depending on the input value
3683 if ($user_id_type eq 'username') {
3684 $user = $e->search_actor_user({usrname => $user_id})->[0];
3687 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3689 } elsif ($user_id_type eq 'barcode') {
3690 my $card = $e->search_actor_card([
3691 {barcode => $user_id},
3692 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3695 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3700 # If the user doesn't have an email address, we can't help them
3701 if (!$user->email) {
3703 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3706 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3707 if ($email_must_match) {
3708 if ($user->email ne $email) {
3709 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3713 _reset_password_request($conn, $e, $user);
3716 # Once we have the user, we can issue the password reset request
3717 # XXX Add a wrapper method that accepts barcode + email input
3718 sub _reset_password_request {
3719 my ($conn, $e, $user) = @_;
3721 # 1. Get throttle threshold and time-to-live from OU_settings
3722 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3723 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3725 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3727 # 2. Get time of last request and number of active requests (num_active)
3728 my $active_requests = $e->json_query({
3734 transform => 'COUNT'
3737 column => 'request_time',
3743 has_been_reset => { '=' => 'f' },
3744 request_time => { '>' => $threshold_time }
3748 # Guard against no active requests
3749 if ($active_requests->[0]->{'request_time'}) {
3750 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3751 my $now = DateTime::Format::ISO8601->new();
3753 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3754 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3755 ($last_request->add_duration('1 minute') > $now)) {
3756 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3758 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3762 # TODO Check to see if the user is in a password-reset-restricted group
3764 # Otherwise, go ahead and try to get the user.
3766 # Check the number of active requests for this user
3767 $active_requests = $e->json_query({
3773 transform => 'COUNT'
3778 usr => { '=' => $user->id },
3779 has_been_reset => { '=' => 'f' },
3780 request_time => { '>' => $threshold_time }
3784 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3786 # if less than or equal to per-user threshold, proceed; otherwise, return event
3787 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3788 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3790 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3793 # Create the aupr object and insert into the database
3794 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3795 my $uuid = create_uuid_as_string(UUID_V4);
3796 $reset_request->uuid($uuid);
3797 $reset_request->usr($user->id);
3799 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3802 # Create an event to notify user of the URL to reset their password
3804 # Can we stuff this in the user_data param for trigger autocreate?
3805 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3807 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3808 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3811 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3816 __PACKAGE__->register_method(
3817 method => "commit_password_reset",
3818 api_name => "open-ils.actor.patron.password_reset.commit",
3820 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3821 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3822 "with the supplied password.",
3824 { desc => 'uuid', type => 'string' },
3825 { desc => 'password', type => 'string' },
3827 return => {desc => '1 on success, Event on error'}
3830 sub commit_password_reset {
3831 my($self, $conn, $uuid, $password) = @_;
3833 # Check to see if password reset requests are already being throttled:
3834 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3835 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3836 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3838 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3841 my $e = new_editor(xact => 1);
3843 my $aupr = $e->search_actor_usr_password_reset({
3850 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3852 my $user_id = $aupr->[0]->usr;
3853 my $user = $e->retrieve_actor_user($user_id);
3855 # Ensure we're still within the TTL for the request
3856 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3857 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3858 if ($threshold < DateTime->now(time_zone => 'local')) {
3860 $logger->info("Password reset request needed to be submitted before $threshold");
3861 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3864 # Check complexity of password against OU-defined regex
3865 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3869 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3870 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3871 $is_strong = check_password_strength_custom($password, $pw_regex);
3873 $is_strong = check_password_strength_default($password);
3878 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3881 # All is well; update the password
3882 $user->passwd($password);
3883 $e->update_actor_user($user);
3885 # And flag that this password reset request has been honoured
3886 $aupr->[0]->has_been_reset('t');
3887 $e->update_actor_usr_password_reset($aupr->[0]);
3893 sub check_password_strength_default {
3894 my $password = shift;
3895 # Use the default set of checks
3896 if ( (length($password) < 7) or
3897 ($password !~ m/.*\d+.*/) or
3898 ($password !~ m/.*[A-Za-z]+.*/)
3905 sub check_password_strength_custom {
3906 my ($password, $pw_regex) = @_;
3908 $pw_regex = qr/$pw_regex/;
3909 if ($password !~ /$pw_regex/) {
3917 __PACKAGE__->register_method(
3918 method => "event_def_opt_in_settings",
3919 api_name => "open-ils.actor.event_def.opt_in.settings",
3922 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3924 { desc => 'Authentication token', type => 'string'},
3926 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3931 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3938 sub event_def_opt_in_settings {
3939 my($self, $conn, $auth, $org_id) = @_;
3940 my $e = new_editor(authtoken => $auth);
3941 return $e->event unless $e->checkauth;
3943 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3944 return $e->event unless
3945 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3947 $org_id = $e->requestor->home_ou;
3950 # find all config.user_setting_type's related to event_defs for the requested org unit
3951 my $types = $e->json_query({
3952 select => {cust => ['name']},
3953 from => {atevdef => 'cust'},
3956 owner => $U->get_org_ancestors($org_id), # context org plus parents
3963 $conn->respond($_) for
3964 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
3971 __PACKAGE__->register_method(
3972 method => "user_visible_circs",
3973 api_name => "open-ils.actor.history.circ.visible",
3976 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
3978 { desc => 'Authentication token', type => 'string'},
3979 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3980 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3983 desc => q/An object with 2 fields: circulation and summary.
3984 circulation is the "circ" object. summary is the related "accs" object/,
3990 __PACKAGE__->register_method(
3991 method => "user_visible_circs",
3992 api_name => "open-ils.actor.history.circ.visible.print",
3995 desc => 'Returns printable output for the set of opt-in visible circulations',
3997 { desc => 'Authentication token', type => 'string'},
3998 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3999 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4002 desc => q/An action_trigger.event object or error event./,
4008 __PACKAGE__->register_method(
4009 method => "user_visible_circs",
4010 api_name => "open-ils.actor.history.circ.visible.email",
4013 desc => 'Emails the set of opt-in visible circulations to the requestor',
4015 { desc => 'Authentication token', type => 'string'},
4016 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4017 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4020 desc => q/undef, or event on error/
4025 __PACKAGE__->register_method(
4026 method => "user_visible_circs",
4027 api_name => "open-ils.actor.history.hold.visible",
4030 desc => 'Returns the set of opt-in visible holds',
4032 { desc => 'Authentication token', type => 'string'},
4033 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4034 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4037 desc => q/An object with 1 field: "hold"/,
4043 __PACKAGE__->register_method(
4044 method => "user_visible_circs",
4045 api_name => "open-ils.actor.history.hold.visible.print",
4048 desc => 'Returns printable output for the set of opt-in visible holds',
4050 { desc => 'Authentication token', type => 'string'},
4051 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4052 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4055 desc => q/An action_trigger.event object or error event./,
4061 __PACKAGE__->register_method(
4062 method => "user_visible_circs",
4063 api_name => "open-ils.actor.history.hold.visible.email",
4066 desc => 'Emails the set of opt-in visible holds to the requestor',
4068 { desc => 'Authentication token', type => 'string'},
4069 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4070 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4073 desc => q/undef, or event on error/
4078 sub user_visible_circs {
4079 my($self, $conn, $auth, $user_id, $options) = @_;
4081 my $is_hold = ($self->api_name =~ /hold/);
4082 my $for_print = ($self->api_name =~ /print/);
4083 my $for_email = ($self->api_name =~ /email/);
4084 my $e = new_editor(authtoken => $auth);
4085 return $e->event unless $e->checkauth;
4087 $user_id ||= $e->requestor->id;
4089 $options->{limit} ||= 50;
4090 $options->{offset} ||= 0;
4092 if($user_id != $e->requestor->id) {
4093 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4094 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4095 return $e->event unless $e->allowed($perm, $user->home_ou);
4098 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4100 my $data = $e->json_query({
4101 from => [$db_func, $user_id],
4102 limit => $$options{limit},
4103 offset => $$options{offset}
4105 # TODO: I only want IDs. code below didn't get me there
4106 # {"select":{"au":[{"column":"id", "result_field":"id",
4107 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4112 return undef unless @$data;
4116 # collect the batch of objects
4120 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4121 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4125 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4126 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4129 } elsif ($for_email) {
4131 $conn->respond_complete(1) if $for_email; # no sense in waiting
4139 my $hold = $e->retrieve_action_hold_request($id);
4140 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4141 # events will be fired from action_trigger_runner
4145 my $circ = $e->retrieve_action_circulation($id);
4146 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4147 # events will be fired from action_trigger_runner
4151 } else { # just give me the data please
4159 my $hold = $e->retrieve_action_hold_request($id);
4160 $conn->respond({hold => $hold});
4164 my $circ = $e->retrieve_action_circulation($id);
4167 summary => $U->create_circ_chain_summary($e, $id)
4176 __PACKAGE__->register_method(
4177 method => "user_saved_search_cud",
4178 api_name => "open-ils.actor.user.saved_search.cud",
4181 desc => 'Create/Update/Delete Access to user saved searches',
4183 { desc => 'Authentication token', type => 'string' },
4184 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4187 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4193 __PACKAGE__->register_method(
4194 method => "user_saved_search_cud",
4195 api_name => "open-ils.actor.user.saved_search.retrieve",
4198 desc => 'Retrieve a saved search object',
4200 { desc => 'Authentication token', type => 'string' },
4201 { desc => 'Saved Search ID', type => 'number' }
4204 desc => q/The saved search object, Event on error/,
4210 sub user_saved_search_cud {
4211 my( $self, $client, $auth, $search ) = @_;
4212 my $e = new_editor( authtoken=>$auth );
4213 return $e->die_event unless $e->checkauth;
4215 my $o_search; # prior version of the object, if any
4216 my $res; # to be returned
4218 # branch on the operation type
4220 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4222 # Get the old version, to check ownership
4223 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4224 or return $e->die_event;
4226 # You can't read somebody else's search
4227 return OpenILS::Event->new('BAD_PARAMS')
4228 unless $o_search->owner == $e->requestor->id;
4234 $e->xact_begin; # start an editor transaction
4236 if( $search->isnew ) { # Create
4238 # You can't create a search for somebody else
4239 return OpenILS::Event->new('BAD_PARAMS')
4240 unless $search->owner == $e->requestor->id;
4242 $e->create_actor_usr_saved_search( $search )
4243 or return $e->die_event;
4247 } elsif( $search->ischanged ) { # Update
4249 # You can't change ownership of a search
4250 return OpenILS::Event->new('BAD_PARAMS')
4251 unless $search->owner == $e->requestor->id;
4253 # Get the old version, to check ownership
4254 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4255 or return $e->die_event;
4257 # You can't update somebody else's search
4258 return OpenILS::Event->new('BAD_PARAMS')
4259 unless $o_search->owner == $e->requestor->id;
4262 $e->update_actor_usr_saved_search( $search )
4263 or return $e->die_event;
4267 } elsif( $search->isdeleted ) { # Delete
4269 # Get the old version, to check ownership
4270 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4271 or return $e->die_event;
4273 # You can't delete somebody else's search
4274 return OpenILS::Event->new('BAD_PARAMS')
4275 unless $o_search->owner == $e->requestor->id;
4278 $e->delete_actor_usr_saved_search( $o_search )
4279 or return $e->die_event;