1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use List::Util qw/max reduce/;
39 use UUID::Tiny qw/:std/;
42 OpenILS::Application::Actor::Container->initialize();
43 OpenILS::Application::Actor::UserGroups->initialize();
44 OpenILS::Application::Actor::ClosedDates->initialize();
47 my $apputils = "OpenILS::Application::AppUtils";
50 sub _d { warn "Patron:\n" . Dumper(shift()); }
53 my $set_user_settings;
57 #__PACKAGE__->register_method(
58 # method => "allowed_test",
59 # api_name => "open-ils.actor.allowed_test",
62 # my($self, $conn, $auth, $orgid, $permcode) = @_;
63 # my $e = new_editor(authtoken => $auth);
64 # return $e->die_event unless $e->checkauth;
68 # permcode => $permcode,
69 # result => $e->allowed($permcode, $orgid)
73 __PACKAGE__->register_method(
74 method => "update_user_setting",
75 api_name => "open-ils.actor.patron.settings.update",
77 sub update_user_setting {
78 my($self, $conn, $auth, $user_id, $settings) = @_;
79 my $e = new_editor(xact => 1, authtoken => $auth);
80 return $e->die_event unless $e->checkauth;
82 $user_id = $e->requestor->id unless defined $user_id;
84 unless($e->requestor->id == $user_id) {
85 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
86 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
89 for my $name (keys %$settings) {
90 my $val = $$settings{$name};
91 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
94 $val = OpenSRF::Utils::JSON->perl2JSON($val);
97 $e->update_actor_user_setting($set) or return $e->die_event;
99 $set = Fieldmapper::actor::user_setting->new;
103 $e->create_actor_user_setting($set) or return $e->die_event;
106 $e->delete_actor_user_setting($set) or return $e->die_event;
115 __PACKAGE__->register_method(
116 method => "set_ou_settings",
117 api_name => "open-ils.actor.org_unit.settings.update",
119 desc => "Updates the value for a given org unit setting. The permission to update " .
120 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
121 "permission specified in the update_perm column of the config.org_unit_setting_type " .
122 "table's row corresponding to the setting being changed." ,
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'Org unit ID', type => 'number'},
126 {desc => 'Hash of setting name-value pairs', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
132 sub set_ou_settings {
133 my( $self, $client, $auth, $org_id, $settings ) = @_;
135 my $e = new_editor(authtoken => $auth, xact => 1);
136 return $e->die_event unless $e->checkauth;
138 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
140 for my $name (keys %$settings) {
141 my $val = $$settings{$name};
143 my $type = $e->retrieve_config_org_unit_setting_type([
145 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
146 ]) or return $e->die_event;
147 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
149 # If there is no relevant permission, the default assumption will
150 # be, "no, the caller cannot change that value."
151 return $e->die_event unless ($all_allowed ||
152 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
155 $val = OpenSRF::Utils::JSON->perl2JSON($val);
158 $e->update_actor_org_unit_setting($set) or return $e->die_event;
160 $set = Fieldmapper::actor::org_unit_setting->new;
161 $set->org_unit($org_id);
164 $e->create_actor_org_unit_setting($set) or return $e->die_event;
167 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
175 __PACKAGE__->register_method(
176 method => "user_settings",
178 api_name => "open-ils.actor.patron.settings.retrieve",
181 my( $self, $client, $auth, $user_id, $setting ) = @_;
183 my $e = new_editor(authtoken => $auth);
184 return $e->event unless $e->checkauth;
185 $user_id = $e->requestor->id unless defined $user_id;
187 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
188 if($e->requestor->id != $user_id) {
189 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
193 my($e, $user_id, $setting) = @_;
194 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
195 return undef unless $val; # XXX this should really return undef, but needs testing
196 return OpenSRF::Utils::JSON->JSON2perl($val->value);
200 if(ref $setting eq 'ARRAY') {
202 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
205 return get_setting($e, $user_id, $setting);
208 my $s = $e->search_actor_user_setting({usr => $user_id});
209 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
214 __PACKAGE__->register_method(
215 method => "ranged_ou_settings",
216 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
218 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
219 "is implied for retrieving OU settings by the authenticated users' permissions.",
221 {desc => 'Authentication token', type => 'string'},
222 {desc => 'Org unit ID', type => 'number'},
224 return => {desc => 'A hashref of "ranged" settings, event on error'}
227 sub ranged_ou_settings {
228 my( $self, $client, $auth, $org_id ) = @_;
230 my $e = new_editor(authtoken => $auth);
231 return $e->event unless $e->checkauth;
234 my $org_list = $U->get_org_ancestors($org_id);
235 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
236 $org_list = [ reverse @$org_list ];
238 # start at the context org and capture the setting value
239 # without clobbering settings we've already captured
240 for my $this_org_id (@$org_list) {
242 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
244 for my $set (@sets) {
245 my $type = $e->retrieve_config_org_unit_setting_type([
247 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
250 # If there is no relevant permission, the default assumption will
251 # be, "yes, the caller can have that value."
252 if ($type && $type->view_perm) {
253 next if not $e->allowed($type->view_perm->code, $org_id);
256 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
257 unless defined $ranged_settings{$set->name};
261 return \%ranged_settings;
266 __PACKAGE__->register_method(
267 api_name => 'open-ils.actor.ou_setting.ancestor_default',
268 method => 'ou_ancestor_setting',
270 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
271 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
272 'user has permission to view that setting, if there is a permission associated with the setting.' ,
274 { desc => 'Org unit ID', type => 'number' },
275 { desc => 'setting name', type => 'string' },
276 { desc => 'authtoken (optional)', type => 'string' }
278 return => {desc => 'A value for the org unit setting, or undef'}
282 # ------------------------------------------------------------------
283 # Attempts to find the org setting value for a given org. if not
284 # found at the requested org, searches up the org tree until it
285 # finds a parent that has the requested setting.
286 # when found, returns { org => $id, value => $value }
287 # otherwise, returns NULL
288 # ------------------------------------------------------------------
289 sub ou_ancestor_setting {
290 my( $self, $client, $orgid, $name, $auth ) = @_;
291 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
294 __PACKAGE__->register_method(
295 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
296 method => 'ou_ancestor_setting_batch',
298 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
299 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
300 'user has permission to view that setting, if there is a permission associated with the setting.' ,
302 { desc => 'Org unit ID', type => 'number' },
303 { desc => 'setting name list', type => 'array' },
304 { desc => 'authtoken (optional)', type => 'string' }
306 return => {desc => 'A hash with name => value pairs for the org unit settings'}
309 sub ou_ancestor_setting_batch {
310 my( $self, $client, $orgid, $name_list, $auth ) = @_;
312 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
318 __PACKAGE__->register_method(
319 method => "update_patron",
320 api_name => "open-ils.actor.patron.update",
323 Update an existing user, or create a new one. Related objects,
324 like cards, addresses, survey responses, and stat cats,
325 can be updated by attaching them to the user object in their
326 respective fields. For examples, the billing address object
327 may be inserted into the 'billing_address' field, etc. For each
328 attached object, indicate if the object should be created,
329 updated, or deleted using the built-in 'isnew', 'ischanged',
330 and 'isdeleted' fields on the object.
333 { desc => 'Authentication token', type => 'string' },
334 { desc => 'Patron data object', type => 'object' }
336 return => {desc => 'A fleshed user object, event on error'}
341 my( $self, $client, $user_session, $patron ) = @_;
343 my $session = $apputils->start_db_session();
345 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
347 my( $user_obj, $evt ) = $U->checkses($user_session);
350 $evt = check_group_perm($session, $user_obj, $patron);
354 # $new_patron is the patron in progress. $patron is the original patron
355 # passed in with the method. new_patron will change as the components
356 # of patron are added/updated.
360 # unflesh the real items on the patron
361 $patron->card( $patron->card->id ) if(ref($patron->card));
362 $patron->billing_address( $patron->billing_address->id )
363 if(ref($patron->billing_address));
364 $patron->mailing_address( $patron->mailing_address->id )
365 if(ref($patron->mailing_address));
367 # create/update the patron first so we can use his id
368 if($patron->isnew()) {
369 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
371 } else { $new_patron = $patron; }
373 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
376 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
379 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
382 # re-update the patron if anything has happened to him during this process
383 if($new_patron->ischanged()) {
384 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
388 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
391 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
394 $apputils->commit_db_session($session);
396 $evt = apply_invalid_addr_penalty($patron);
399 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
401 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
403 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
406 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
409 sub apply_invalid_addr_penalty {
411 my $e = new_editor(xact => 1);
413 # grab the invalid address penalty if set
414 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
416 my ($addr_penalty) = grep
417 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
419 # do we enforce invalid address penalty
420 my $enforce = $U->ou_ancestor_setting_value(
421 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
423 my $addrs = $e->search_actor_user_address(
424 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
425 my $addr_count = scalar(@$addrs);
427 if($addr_count == 0 and $addr_penalty) {
429 # regardless of any settings, remove the penalty when the user has no invalid addresses
430 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
433 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
435 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
436 my $depth = $ptype->org_depth;
437 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
438 $ctx_org = $patron->home_ou unless defined $ctx_org;
440 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
441 $penalty->usr($patron->id);
442 $penalty->org_unit($ctx_org);
443 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
445 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
464 "standing_penalties",
470 push @$fields, "home_ou" if $home_ou;
471 return new_flesh_user($id, $fields, $e );
479 # clone and clear stuff that would break the database
483 my $new_patron = $patron->clone;
485 $new_patron->clear_billing_address();
486 $new_patron->clear_mailing_address();
487 $new_patron->clear_addresses();
488 $new_patron->clear_card();
489 $new_patron->clear_cards();
490 $new_patron->clear_id();
491 $new_patron->clear_isnew();
492 $new_patron->clear_ischanged();
493 $new_patron->clear_isdeleted();
494 $new_patron->clear_stat_cat_entries();
495 $new_patron->clear_permissions();
496 $new_patron->clear_standing_penalties();
506 my $user_obj = shift;
508 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
509 return (undef, $evt) if $evt;
511 my $ex = $session->request(
512 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
514 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
517 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
519 my $id = $session->request(
520 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
521 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
523 $logger->info("Successfully created new user [$id] in DB");
525 return ( $session->request(
526 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
530 sub check_group_perm {
531 my( $session, $requestor, $patron ) = @_;
534 # first let's see if the requestor has
535 # priveleges to update this user in any way
536 if( ! $patron->isnew ) {
537 my $p = $session->request(
538 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
540 # If we are the requestor (trying to update our own account)
541 # and we are not trying to change our profile, we're good
542 if( $p->id == $requestor->id and
543 $p->profile == $patron->profile ) {
548 $evt = group_perm_failed($session, $requestor, $p);
552 # They are allowed to edit this patron.. can they put the
553 # patron into the group requested?
554 $evt = group_perm_failed($session, $requestor, $patron);
560 sub group_perm_failed {
561 my( $session, $requestor, $patron ) = @_;
565 my $grpid = $patron->profile;
569 $logger->debug("user update looking for group perm for group $grpid");
570 $grp = $session->request(
571 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
572 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
574 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
576 $logger->info("user update checking perm $perm on user ".
577 $requestor->id." for update/create on user username=".$patron->usrname);
579 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
587 my( $session, $patron, $user_obj, $noperm) = @_;
589 $logger->info("Updating patron ".$patron->id." in DB");
594 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
595 return (undef, $evt) if $evt;
598 # update the password by itself to avoid the password protection magic
599 if( $patron->passwd ) {
600 my $s = $session->request(
601 'open-ils.storage.direct.actor.user.remote_update',
602 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
603 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
604 $patron->clear_passwd;
607 if(!$patron->ident_type) {
608 $patron->clear_ident_type;
609 $patron->clear_ident_value;
612 $evt = verify_last_xact($session, $patron);
613 return (undef, $evt) if $evt;
615 my $stat = $session->request(
616 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
617 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
622 sub verify_last_xact {
623 my( $session, $patron ) = @_;
624 return undef unless $patron->id and $patron->id > 0;
625 my $p = $session->request(
626 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
627 my $xact = $p->last_xact_id;
628 return undef unless $xact;
629 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
630 return OpenILS::Event->new('XACT_COLLISION')
631 if $xact != $patron->last_xact_id;
636 sub _check_dup_ident {
637 my( $session, $patron ) = @_;
639 return undef unless $patron->ident_value;
642 ident_type => $patron->ident_type,
643 ident_value => $patron->ident_value,
646 $logger->debug("patron update searching for dup ident values: " .
647 $patron->ident_type . ':' . $patron->ident_value);
649 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
651 my $dups = $session->request(
652 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
655 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
662 sub _add_update_addresses {
666 my $new_patron = shift;
670 my $current_id; # id of the address before creation
672 for my $address (@{$patron->addresses()}) {
674 next unless ref $address;
675 $current_id = $address->id();
677 if( $patron->billing_address() and
678 $patron->billing_address() == $current_id ) {
679 $logger->info("setting billing addr to $current_id");
680 $new_patron->billing_address($address->id());
681 $new_patron->ischanged(1);
684 if( $patron->mailing_address() and
685 $patron->mailing_address() == $current_id ) {
686 $new_patron->mailing_address($address->id());
687 $logger->info("setting mailing addr to $current_id");
688 $new_patron->ischanged(1);
692 if($address->isnew()) {
694 $address->usr($new_patron->id());
696 ($address, $evt) = _add_address($session,$address);
697 return (undef, $evt) if $evt;
699 # we need to get the new id
700 if( $patron->billing_address() and
701 $patron->billing_address() == $current_id ) {
702 $new_patron->billing_address($address->id());
703 $logger->info("setting billing addr to $current_id");
704 $new_patron->ischanged(1);
707 if( $patron->mailing_address() and
708 $patron->mailing_address() == $current_id ) {
709 $new_patron->mailing_address($address->id());
710 $logger->info("setting mailing addr to $current_id");
711 $new_patron->ischanged(1);
714 } elsif($address->ischanged() ) {
716 ($address, $evt) = _update_address($session, $address);
717 return (undef, $evt) if $evt;
719 } elsif($address->isdeleted() ) {
721 if( $address->id() == $new_patron->mailing_address() ) {
722 $new_patron->clear_mailing_address();
723 ($new_patron, $evt) = _update_patron($session, $new_patron);
724 return (undef, $evt) if $evt;
727 if( $address->id() == $new_patron->billing_address() ) {
728 $new_patron->clear_billing_address();
729 ($new_patron, $evt) = _update_patron($session, $new_patron);
730 return (undef, $evt) if $evt;
733 $evt = _delete_address($session, $address);
734 return (undef, $evt) if $evt;
738 return ( $new_patron, undef );
742 # adds an address to the db and returns the address with new id
744 my($session, $address) = @_;
745 $address->clear_id();
747 $logger->info("Creating new address at street ".$address->street1);
749 # put the address into the database
750 my $id = $session->request(
751 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
752 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
755 return ($address, undef);
759 sub _update_address {
760 my( $session, $address ) = @_;
762 $logger->info("Updating address ".$address->id." in the DB");
764 my $stat = $session->request(
765 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
767 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
768 return ($address, undef);
773 sub _add_update_cards {
777 my $new_patron = shift;
781 my $virtual_id; #id of the card before creation
782 for my $card (@{$patron->cards()}) {
784 $card->usr($new_patron->id());
786 if(ref($card) and $card->isnew()) {
788 $virtual_id = $card->id();
789 ( $card, $evt ) = _add_card($session,$card);
790 return (undef, $evt) if $evt;
792 #if(ref($patron->card)) { $patron->card($patron->card->id); }
793 if($patron->card() == $virtual_id) {
794 $new_patron->card($card->id());
795 $new_patron->ischanged(1);
798 } elsif( ref($card) and $card->ischanged() ) {
799 $evt = _update_card($session, $card);
800 return (undef, $evt) if $evt;
804 return ( $new_patron, undef );
808 # adds an card to the db and returns the card with new id
810 my( $session, $card ) = @_;
813 $logger->info("Adding new patron card ".$card->barcode);
815 my $id = $session->request(
816 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
817 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
818 $logger->info("Successfully created patron card $id");
821 return ( $card, undef );
825 # returns event on error. returns undef otherwise
827 my( $session, $card ) = @_;
828 $logger->info("Updating patron card ".$card->id);
830 my $stat = $session->request(
831 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
832 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
839 # returns event on error. returns undef otherwise
840 sub _delete_address {
841 my( $session, $address ) = @_;
843 $logger->info("Deleting address ".$address->id." from DB");
845 my $stat = $session->request(
846 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
848 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
854 sub _add_survey_responses {
855 my ($session, $patron, $new_patron) = @_;
857 $logger->info( "Updating survey responses for patron ".$new_patron->id );
859 my $responses = $patron->survey_responses;
863 $_->usr($new_patron->id) for (@$responses);
865 my $evt = $U->simplereq( "open-ils.circ",
866 "open-ils.circ.survey.submit.user_id", $responses );
868 return (undef, $evt) if defined($U->event_code($evt));
872 return ( $new_patron, undef );
876 sub _create_stat_maps {
878 my($session, $user_session, $patron, $new_patron) = @_;
880 my $maps = $patron->stat_cat_entries();
882 for my $map (@$maps) {
884 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
886 if ($map->isdeleted()) {
887 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
889 } elsif ($map->isnew()) {
890 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
895 $map->target_usr($new_patron->id);
898 $logger->info("Updating stat entry with method $method and map $map");
900 my $stat = $session->request($method, $map)->gather(1);
901 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
905 return ($new_patron, undef);
908 sub _create_perm_maps {
910 my($session, $user_session, $patron, $new_patron) = @_;
912 my $maps = $patron->permissions;
914 for my $map (@$maps) {
916 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
917 if ($map->isdeleted()) {
918 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
919 } elsif ($map->isnew()) {
920 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
925 $map->usr($new_patron->id);
927 #warn( "Updating permissions with method $method and session $user_session and map $map" );
928 $logger->info( "Updating permissions with method $method and map $map" );
930 my $stat = $session->request($method, $map)->gather(1);
931 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
935 return ($new_patron, undef);
939 __PACKAGE__->register_method(
940 method => "set_user_work_ous",
941 api_name => "open-ils.actor.user.work_ous.update",
944 sub set_user_work_ous {
950 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
953 my $session = $apputils->start_db_session();
955 for my $map (@$maps) {
957 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
958 if ($map->isdeleted()) {
959 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
960 } elsif ($map->isnew()) {
961 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
965 #warn( "Updating permissions with method $method and session $ses and map $map" );
966 $logger->info( "Updating work_ou map with method $method and map $map" );
968 my $stat = $session->request($method, $map)->gather(1);
969 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
973 $apputils->commit_db_session($session);
975 return scalar(@$maps);
979 __PACKAGE__->register_method(
980 method => "set_user_perms",
981 api_name => "open-ils.actor.user.permissions.update",
990 my $session = $apputils->start_db_session();
992 my( $user_obj, $evt ) = $U->checkses($ses);
995 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
998 $all = 1 if ($U->is_true($user_obj->super_user()));
999 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1001 for my $map (@$maps) {
1003 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1004 if ($map->isdeleted()) {
1005 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1006 } elsif ($map->isnew()) {
1007 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1011 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1012 #warn( "Updating permissions with method $method and session $ses and map $map" );
1013 $logger->info( "Updating permissions with method $method and map $map" );
1015 my $stat = $session->request($method, $map)->gather(1);
1016 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1020 $apputils->commit_db_session($session);
1022 return scalar(@$maps);
1026 __PACKAGE__->register_method(
1027 method => "user_retrieve_by_barcode",
1029 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1031 sub user_retrieve_by_barcode {
1032 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1034 my $e = new_editor(authtoken => $auth);
1035 return $e->event unless $e->checkauth;
1037 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1038 or return $e->event;
1040 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1041 return $e->event unless $e->allowed(
1042 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1049 __PACKAGE__->register_method(
1050 method => "get_user_by_id",
1052 api_name => "open-ils.actor.user.retrieve",
1055 sub get_user_by_id {
1056 my ($self, $client, $auth, $id) = @_;
1057 my $e = new_editor(authtoken=>$auth);
1058 return $e->event unless $e->checkauth;
1059 my $user = $e->retrieve_actor_user($id) or return $e->event;
1060 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1065 __PACKAGE__->register_method(
1066 method => "get_org_types",
1067 api_name => "open-ils.actor.org_types.retrieve",
1070 return $U->get_org_types();
1074 __PACKAGE__->register_method(
1075 method => "get_user_ident_types",
1076 api_name => "open-ils.actor.user.ident_types.retrieve",
1079 sub get_user_ident_types {
1080 return $ident_types if $ident_types;
1081 return $ident_types =
1082 new_editor()->retrieve_all_config_identification_type();
1086 __PACKAGE__->register_method(
1087 method => "get_org_unit",
1088 api_name => "open-ils.actor.org_unit.retrieve",
1092 my( $self, $client, $user_session, $org_id ) = @_;
1093 my $e = new_editor(authtoken => $user_session);
1095 return $e->event unless $e->checkauth;
1096 $org_id = $e->requestor->ws_ou;
1098 my $o = $e->retrieve_actor_org_unit($org_id)
1099 or return $e->event;
1103 __PACKAGE__->register_method(
1104 method => "search_org_unit",
1105 api_name => "open-ils.actor.org_unit_list.search",
1108 sub search_org_unit {
1110 my( $self, $client, $field, $value ) = @_;
1112 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1114 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1115 { $field => $value } );
1121 # build the org tree
1123 __PACKAGE__->register_method(
1124 method => "get_org_tree",
1125 api_name => "open-ils.actor.org_tree.retrieve",
1127 note => "Returns the entire org tree structure",
1133 return $U->get_org_tree($client->session->session_locale);
1137 __PACKAGE__->register_method(
1138 method => "get_org_descendants",
1139 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1142 # depth is optional. org_unit is the id
1143 sub get_org_descendants {
1144 my( $self, $client, $org_unit, $depth ) = @_;
1146 if(ref $org_unit eq 'ARRAY') {
1149 for my $i (0..scalar(@$org_unit)-1) {
1150 my $list = $U->simple_scalar_request(
1152 "open-ils.storage.actor.org_unit.descendants.atomic",
1153 $org_unit->[$i], $depth->[$i] );
1154 push(@trees, $U->build_org_tree($list));
1159 my $orglist = $apputils->simple_scalar_request(
1161 "open-ils.storage.actor.org_unit.descendants.atomic",
1162 $org_unit, $depth );
1163 return $U->build_org_tree($orglist);
1168 __PACKAGE__->register_method(
1169 method => "get_org_ancestors",
1170 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1173 # depth is optional. org_unit is the id
1174 sub get_org_ancestors {
1175 my( $self, $client, $org_unit, $depth ) = @_;
1176 my $orglist = $apputils->simple_scalar_request(
1178 "open-ils.storage.actor.org_unit.ancestors.atomic",
1179 $org_unit, $depth );
1180 return $U->build_org_tree($orglist);
1184 __PACKAGE__->register_method(
1185 method => "get_standings",
1186 api_name => "open-ils.actor.standings.retrieve"
1191 return $user_standings if $user_standings;
1192 return $user_standings =
1193 $apputils->simple_scalar_request(
1195 "open-ils.cstore.direct.config.standing.search.atomic",
1196 { id => { "!=" => undef } }
1201 __PACKAGE__->register_method(
1202 method => "get_my_org_path",
1203 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1206 sub get_my_org_path {
1207 my( $self, $client, $auth, $org_id ) = @_;
1208 my $e = new_editor(authtoken=>$auth);
1209 return $e->event unless $e->checkauth;
1210 $org_id = $e->requestor->ws_ou unless defined $org_id;
1212 return $apputils->simple_scalar_request(
1214 "open-ils.storage.actor.org_unit.full_path.atomic",
1219 __PACKAGE__->register_method(
1220 method => "patron_adv_search",
1221 api_name => "open-ils.actor.patron.search.advanced"
1223 sub patron_adv_search {
1224 my( $self, $client, $auth, $search_hash,
1225 $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1227 my $e = new_editor(authtoken=>$auth);
1228 return $e->event unless $e->checkauth;
1229 return $e->event unless $e->allowed('VIEW_USER');
1230 return $U->storagereq(
1231 "open-ils.storage.actor.user.crazy_search", $search_hash,
1232 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1236 __PACKAGE__->register_method(
1237 method => "update_passwd",
1238 api_name => "open-ils.actor.user.password.update",
1240 desc => "Update the operator's password",
1242 { desc => 'Authentication token', type => 'string' },
1243 { desc => 'New password', type => 'string' },
1244 { desc => 'Current password', type => 'string' }
1246 return => {desc => '1 on success, Event on error or incorrect current password'}
1250 __PACKAGE__->register_method(
1251 method => "update_passwd",
1252 api_name => "open-ils.actor.user.username.update",
1254 desc => "Update the operator's username",
1256 { desc => 'Authentication token', type => 'string' },
1257 { desc => 'New username', type => 'string' }
1259 return => {desc => '1 on success, Event on error'}
1263 __PACKAGE__->register_method(
1264 method => "update_passwd",
1265 api_name => "open-ils.actor.user.email.update",
1267 desc => "Update the operator's email address",
1269 { desc => 'Authentication token', type => 'string' },
1270 { desc => 'New email address', type => 'string' }
1272 return => {desc => '1 on success, Event on error'}
1277 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1278 my $e = new_editor(xact=>1, authtoken=>$auth);
1279 return $e->die_event unless $e->checkauth;
1281 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1282 or return $e->die_event;
1283 my $api = $self->api_name;
1285 if( $api =~ /password/o ) {
1286 # make sure the original password matches the in-database password
1287 if (md5_hex($orig_pw) ne $db_user->passwd) {
1289 return new OpenILS::Event('INCORRECT_PASSWORD');
1291 $db_user->passwd($new_val);
1295 # if we don't clear the password, the user will be updated with
1296 # a hashed version of the hashed version of their password
1297 $db_user->clear_passwd;
1299 if( $api =~ /username/o ) {
1301 # make sure no one else has this username
1302 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1305 return new OpenILS::Event('USERNAME_EXISTS');
1307 $db_user->usrname($new_val);
1309 } elsif( $api =~ /email/o ) {
1310 $db_user->email($new_val);
1314 $e->update_actor_user($db_user) or return $e->die_event;
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)));
1595 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1599 first_given_name => $user->first_given_name,
1600 second_given_name => $user->second_given_name,
1601 family_name => $user->family_name,
1602 alias => $user->alias,
1603 usrname => $user->usrname
1605 fines => $fines->to_bare_hash,
1612 ##### a small consolidation of related method registrations
1613 my $common_params = [
1614 { desc => 'Authentication token', type => 'string' },
1615 { desc => 'User ID', type => 'string' },
1616 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1617 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1620 'open-ils.actor.user.transactions' => '',
1621 'open-ils.actor.user.transactions.fleshed' => '',
1622 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1623 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1624 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1625 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1628 foreach (keys %methods) {
1630 method => "user_transactions",
1633 desc => 'For a given user, retrieve a list of '
1634 . (/\.fleshed/ ? 'fleshed ' : '')
1635 . 'transactions' . $methods{$_}
1636 . ' optionally limited to transactions of a given type.',
1637 params => $common_params,
1639 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1640 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1644 $args{authoritative} = 1;
1645 __PACKAGE__->register_method(%args);
1648 # Now for the counts
1650 'open-ils.actor.user.transactions.count' => '',
1651 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1652 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1655 foreach (keys %methods) {
1657 method => "user_transactions",
1660 desc => 'For a given user, retrieve a count of open '
1661 . 'transactions' . $methods{$_}
1662 . ' optionally limited to transactions of a given type.',
1663 params => $common_params,
1664 return => { desc => "Integer count of transactions, or event on error" }
1667 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1668 __PACKAGE__->register_method(%args);
1671 __PACKAGE__->register_method(
1672 method => "user_transactions",
1673 api_name => "open-ils.actor.user.transactions.have_balance.total",
1676 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1677 . ' optionally limited to transactions of a given type.',
1678 params => $common_params,
1679 return => { desc => "Decimal balance value, or event on error" }
1684 sub user_transactions {
1685 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1688 my $e = new_editor(authtoken => $auth);
1689 return $e->event unless $e->checkauth;
1691 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1693 return $e->event unless
1694 $e->requestor->id == $user_id or
1695 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1697 my $api = $self->api_name();
1699 my $filter = ($api =~ /have_balance/o) ?
1700 { 'balance_owed' => { '<>' => 0 } }:
1701 { 'total_owed' => { '>' => 0 } };
1703 my $method = 'open-ils.actor.user.transactions.history.still_open';
1704 $method = "$method.authoritative" if $api => /authoritative/;
1705 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1707 if($api =~ /total/o) {
1709 $total += $_->balance_owed for @$trans;
1713 ($api =~ /count/o ) and return scalar @$trans;
1714 ($api !~ /fleshed/o) and return $trans;
1717 for my $t (@$trans) {
1719 if( $t->xact_type ne 'circulation' ) {
1720 push @resp, {transaction => $t};
1724 my $circ_data = flesh_circ($e, $t->id);
1725 push @resp, {transaction => $t, %$circ_data};
1732 __PACKAGE__->register_method(
1733 method => "user_transaction_retrieve",
1734 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1737 notes => "Returns a fleshed transaction record"
1740 __PACKAGE__->register_method(
1741 method => "user_transaction_retrieve",
1742 api_name => "open-ils.actor.user.transaction.retrieve",
1745 notes => "Returns a transaction record"
1748 sub user_transaction_retrieve {
1749 my($self, $client, $auth, $bill_id) = @_;
1751 my $e = new_editor(authtoken => $auth);
1752 return $e->event unless $e->checkauth;
1754 my $trans = $e->retrieve_money_billable_transaction_summary(
1755 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1757 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1759 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1761 return $trans unless $self->api_name =~ /flesh/;
1762 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1764 my $circ_data = flesh_circ($e, $trans->id, 1);
1766 return {transaction => $trans, %$circ_data};
1771 my $circ_id = shift;
1772 my $flesh_copy = shift;
1774 my $circ = $e->retrieve_action_circulation([
1778 circ => ['target_copy'],
1779 acp => ['call_number'],
1786 my $copy = $circ->target_copy;
1788 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1789 $mods = new Fieldmapper::metabib::virtual_record;
1790 $mods->doc_id(OILS_PRECAT_RECORD);
1791 $mods->title($copy->dummy_title);
1792 $mods->author($copy->dummy_author);
1795 my $u = OpenILS::Utils::ModsParser->new();
1796 $u->start_mods_batch($circ->target_copy->call_number->record->marc);
1797 $mods = $u->finish_mods_batch();
1801 $circ->target_copy($circ->target_copy->id);
1802 $copy->call_number($copy->call_number->id);
1804 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1808 __PACKAGE__->register_method(
1809 method => "hold_request_count",
1810 api_name => "open-ils.actor.user.hold_requests.count",
1813 notes => 'Returns hold ready/total counts'
1816 sub hold_request_count {
1817 my( $self, $client, $login_session, $userid ) = @_;
1819 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1820 $login_session, $userid, 'VIEW_HOLD' );
1821 return $evt if $evt;
1824 my $holds = $apputils->simple_scalar_request(
1826 "open-ils.cstore.direct.action.hold_request.search.atomic",
1829 fulfillment_time => {"=" => undef },
1830 cancel_time => undef,
1835 for my $h (@$holds) {
1836 next unless $h->capture_time and $h->current_copy;
1838 my $copy = $apputils->simple_scalar_request(
1840 "open-ils.cstore.direct.asset.copy.retrieve",
1844 if ($copy and $copy->status == 8) {
1849 return { total => scalar(@$holds), ready => scalar(@ready) };
1852 __PACKAGE__->register_method(
1853 method => "checked_out",
1854 api_name => "open-ils.actor.user.checked_out",
1858 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1859 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1860 . "(i.e., outstanding balance or some other pending action on the circ). "
1861 . "The .count method also includes a 'total' field which sums all open circs.",
1863 { desc => 'Authentication Token', type => 'string'},
1864 { desc => 'User ID', type => 'string'},
1867 desc => 'Returns event on error, or an object with ID lists, like: '
1868 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1873 __PACKAGE__->register_method(
1874 method => "checked_out",
1875 api_name => "open-ils.actor.user.checked_out.count",
1878 signature => q/@see open-ils.actor.user.checked_out/
1882 my( $self, $conn, $auth, $userid ) = @_;
1884 my $e = new_editor(authtoken=>$auth);
1885 return $e->event unless $e->checkauth;
1887 if( $userid ne $e->requestor->id ) {
1888 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1889 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1891 # see if there is a friend link allowing circ.view perms
1892 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1893 $e, $userid, $e->requestor->id, 'circ.view');
1894 return $e->event unless $allowed;
1898 my $count = $self->api_name =~ /count/;
1899 return _checked_out( $count, $e, $userid );
1903 my( $iscount, $e, $userid ) = @_;
1909 claims_returned => [],
1912 my $meth = 'retrieve_action_open_circ_';
1920 claims_returned => 0,
1927 my $data = $e->$meth($userid);
1931 $result{$_} += $data->$_() for (keys %result);
1932 $result{total} += $data->$_() for (keys %result);
1934 for my $k (keys %result) {
1935 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
1945 __PACKAGE__->register_method(
1946 method => "checked_in_with_fines",
1947 api_name => "open-ils.actor.user.checked_in_with_fines",
1950 signature => q/@see open-ils.actor.user.checked_out/
1953 sub checked_in_with_fines {
1954 my( $self, $conn, $auth, $userid ) = @_;
1956 my $e = new_editor(authtoken=>$auth);
1957 return $e->event unless $e->checkauth;
1959 if( $userid ne $e->requestor->id ) {
1960 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1963 # money is owed on these items and they are checked in
1964 my $open = $e->search_action_circulation(
1967 xact_finish => undef,
1968 checkin_time => { "!=" => undef },
1973 my( @lost, @cr, @lo );
1974 for my $c (@$open) {
1975 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1976 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1977 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1982 claims_returned => \@cr,
1983 long_overdue => \@lo
1989 my ($api, $desc, $auth) = @_;
1990 $desc = $desc ? (" " . $desc) : '';
1991 my $ids = ($api =~ /ids$/) ? 1 : 0;
1994 method => "user_transaction_history",
1995 api_name => "open-ils.actor.user.transactions.$api",
1997 desc => "For a given User ID, returns a list of billable transaction" .
1998 ($ids ? " id" : '') .
1999 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2000 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2002 {desc => 'Authentication token', type => 'string'},
2003 {desc => 'User ID', type => 'number'},
2004 {desc => 'Transaction type (optional)', type => 'number'},
2005 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2008 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2012 $auth and push @sig, (authoritative => 1);
2016 my %auth_hist_methods = (
2018 'history.have_charge' => 'that have an initial charge',
2019 'history.still_open' => 'that are not finished',
2020 'history.have_balance' => 'that have a balance',
2021 'history.have_bill' => 'that have billings',
2022 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2023 'history.have_payment' => 'that have at least 1 payment',
2026 foreach (keys %auth_hist_methods) {
2027 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2028 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2029 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2032 sub user_transaction_history {
2033 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2037 my $e = new_editor(authtoken=>$auth);
2038 return $e->die_event unless $e->checkauth;
2040 if ($e->requestor->id ne $userid) {
2041 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2044 my $api = $self->api_name;
2045 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2047 if(defined($type)) {
2048 $filter->{'xact_type'} = $type;
2051 if($api =~ /have_bill_or_payment/o) {
2053 # transactions that have a non-zero sum across all billings or at least 1 payment
2054 $filter->{'-or'} = {
2055 'balance_owed' => { '<>' => 0 },
2056 'last_payment_ts' => { '<>' => undef }
2059 } elsif($api =~ /have_payment/) {
2061 $filter->{last_payment_ts} ||= {'<>' => undef};
2063 } elsif( $api =~ /have_balance/o) {
2065 # transactions that have a non-zero overall balance
2066 $filter->{'balance_owed'} = { '<>' => 0 };
2068 } elsif( $api =~ /have_charge/o) {
2070 # transactions that have at least 1 billing, regardless of whether it was voided
2071 $filter->{'last_billing_ts'} = { '<>' => undef };
2073 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2075 # transactions that have non-zero sum across all billings. This will exclude
2076 # xacts where all billings have been voided
2077 $filter->{'total_owed'} = { '<>' => 0 };
2080 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2081 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2082 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2084 my $mbts = $e->search_money_billable_transaction_summary(
2085 [ { usr => $userid, @xact_finish, %$filter },
2090 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2091 return $mbts unless $api =~ /fleshed/;
2094 for my $t (@$mbts) {
2096 if( $t->xact_type ne 'circulation' ) {
2097 push @resp, {transaction => $t};
2101 my $circ_data = flesh_circ($e, $t->id);
2102 push @resp, {transaction => $t, %$circ_data};
2110 __PACKAGE__->register_method(
2111 method => "user_perms",
2112 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2114 notes => "Returns a list of permissions"
2118 my( $self, $client, $authtoken, $user ) = @_;
2120 my( $staff, $evt ) = $apputils->checkses($authtoken);
2121 return $evt if $evt;
2123 $user ||= $staff->id;
2125 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2129 return $apputils->simple_scalar_request(
2131 "open-ils.storage.permission.user_perms.atomic",
2135 __PACKAGE__->register_method(
2136 method => "retrieve_perms",
2137 api_name => "open-ils.actor.permissions.retrieve",
2138 notes => "Returns a list of permissions"
2140 sub retrieve_perms {
2141 my( $self, $client ) = @_;
2142 return $apputils->simple_scalar_request(
2144 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2145 { id => { '!=' => undef } }
2149 __PACKAGE__->register_method(
2150 method => "retrieve_groups",
2151 api_name => "open-ils.actor.groups.retrieve",
2152 notes => "Returns a list of user groups"
2154 sub retrieve_groups {
2155 my( $self, $client ) = @_;
2156 return new_editor()->retrieve_all_permission_grp_tree();
2159 __PACKAGE__->register_method(
2160 method => "retrieve_org_address",
2161 api_name => "open-ils.actor.org_unit.address.retrieve",
2162 notes => <<' NOTES');
2163 Returns an org_unit address by ID
2164 @param An org_address ID
2166 sub retrieve_org_address {
2167 my( $self, $client, $id ) = @_;
2168 return $apputils->simple_scalar_request(
2170 "open-ils.cstore.direct.actor.org_address.retrieve",
2175 __PACKAGE__->register_method(
2176 method => "retrieve_groups_tree",
2177 api_name => "open-ils.actor.groups.tree.retrieve",
2178 notes => "Returns a list of user groups"
2181 sub retrieve_groups_tree {
2182 my( $self, $client ) = @_;
2183 return new_editor()->search_permission_grp_tree(
2188 flesh_fields => { pgt => ["children"] },
2189 order_by => { pgt => 'name'}
2196 __PACKAGE__->register_method(
2197 method => "add_user_to_groups",
2198 api_name => "open-ils.actor.user.set_groups",
2199 notes => "Adds a user to one or more permission groups"
2202 sub add_user_to_groups {
2203 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2205 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2206 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2207 return $evt if $evt;
2209 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2210 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2211 return $evt if $evt;
2213 $apputils->simplereq(
2215 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2217 for my $group (@$groups) {
2218 my $link = Fieldmapper::permission::usr_grp_map->new;
2220 $link->usr($userid);
2222 my $id = $apputils->simplereq(
2224 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2230 __PACKAGE__->register_method(
2231 method => "get_user_perm_groups",
2232 api_name => "open-ils.actor.user.get_groups",
2233 notes => "Retrieve a user's permission groups."
2237 sub get_user_perm_groups {
2238 my( $self, $client, $authtoken, $userid ) = @_;
2240 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2241 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2242 return $evt if $evt;
2244 return $apputils->simplereq(
2246 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2250 __PACKAGE__->register_method(
2251 method => "get_user_work_ous",
2252 api_name => "open-ils.actor.user.get_work_ous",
2253 notes => "Retrieve a user's work org units."
2256 __PACKAGE__->register_method(
2257 method => "get_user_work_ous",
2258 api_name => "open-ils.actor.user.get_work_ous.ids",
2259 notes => "Retrieve a user's work org units."
2262 sub get_user_work_ous {
2263 my( $self, $client, $auth, $userid ) = @_;
2264 my $e = new_editor(authtoken=>$auth);
2265 return $e->event unless $e->checkauth;
2266 $userid ||= $e->requestor->id;
2268 if($e->requestor->id != $userid) {
2269 my $user = $e->retrieve_actor_user($userid)
2270 or return $e->event;
2271 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2274 return $e->search_permission_usr_work_ou_map({usr => $userid})
2275 unless $self->api_name =~ /.ids$/;
2277 # client just wants a list of org IDs
2278 return $U->get_user_work_ou_ids($e, $userid);
2283 __PACKAGE__->register_method(
2284 method => 'register_workstation',
2285 api_name => 'open-ils.actor.workstation.register.override',
2286 signature => q/@see open-ils.actor.workstation.register/
2289 __PACKAGE__->register_method(
2290 method => 'register_workstation',
2291 api_name => 'open-ils.actor.workstation.register',
2293 Registers a new workstion in the system
2294 @param authtoken The login session key
2295 @param name The name of the workstation id
2296 @param owner The org unit that owns this workstation
2297 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2298 if the name is already in use.
2302 sub register_workstation {
2303 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2305 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2306 return $e->die_event unless $e->checkauth;
2307 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2308 my $existing = $e->search_actor_workstation({name => $name})->[0];
2312 if( $self->api_name =~ /override/o ) {
2313 # workstation with the given name exists.
2315 if($owner ne $existing->owning_lib) {
2316 # if necessary, update the owning_lib of the workstation
2318 $logger->info("changing owning lib of workstation ".$existing->id.
2319 " from ".$existing->owning_lib." to $owner");
2320 return $e->die_event unless
2321 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2323 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2325 $existing->owning_lib($owner);
2326 return $e->die_event unless $e->update_actor_workstation($existing);
2332 "attempt to register an existing workstation. returning existing ID");
2335 return $existing->id;
2338 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2342 my $ws = Fieldmapper::actor::workstation->new;
2343 $ws->owning_lib($owner);
2345 $e->create_actor_workstation($ws) or return $e->die_event;
2347 return $ws->id; # note: editor sets the id on the new object for us
2350 __PACKAGE__->register_method(
2351 method => 'workstation_list',
2352 api_name => 'open-ils.actor.workstation.list',
2354 Returns a list of workstations registered at the given location
2355 @param authtoken The login session key
2356 @param ids A list of org_unit.id's for the workstation owners
2360 sub workstation_list {
2361 my( $self, $conn, $authtoken, @orgs ) = @_;
2363 my $e = new_editor(authtoken=>$authtoken);
2364 return $e->event unless $e->checkauth;
2369 unless $e->allowed('REGISTER_WORKSTATION', $o);
2370 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2376 __PACKAGE__->register_method(
2377 method => 'fetch_patron_note',
2378 api_name => 'open-ils.actor.note.retrieve.all',
2381 Returns a list of notes for a given user
2382 Requestor must have VIEW_USER permission if pub==false and
2383 @param authtoken The login session key
2384 @param args Hash of params including
2385 patronid : the patron's id
2386 pub : true if retrieving only public notes
2390 sub fetch_patron_note {
2391 my( $self, $conn, $authtoken, $args ) = @_;
2392 my $patronid = $$args{patronid};
2394 my($reqr, $evt) = $U->checkses($authtoken);
2395 return $evt if $evt;
2398 ($patron, $evt) = $U->fetch_user($patronid);
2399 return $evt if $evt;
2402 if( $patronid ne $reqr->id ) {
2403 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2404 return $evt if $evt;
2406 return $U->cstorereq(
2407 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2408 { usr => $patronid, pub => 't' } );
2411 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2412 return $evt if $evt;
2414 return $U->cstorereq(
2415 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2418 __PACKAGE__->register_method(
2419 method => 'create_user_note',
2420 api_name => 'open-ils.actor.note.create',
2422 Creates a new note for the given user
2423 @param authtoken The login session key
2424 @param note The note object
2427 sub create_user_note {
2428 my( $self, $conn, $authtoken, $note ) = @_;
2429 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2430 return $e->die_event unless $e->checkauth;
2432 my $user = $e->retrieve_actor_user($note->usr)
2433 or return $e->die_event;
2435 return $e->die_event unless
2436 $e->allowed('UPDATE_USER',$user->home_ou);
2438 $note->creator($e->requestor->id);
2439 $e->create_actor_usr_note($note) or return $e->die_event;
2445 __PACKAGE__->register_method(
2446 method => 'delete_user_note',
2447 api_name => 'open-ils.actor.note.delete',
2449 Deletes a note for the given user
2450 @param authtoken The login session key
2451 @param noteid The note id
2454 sub delete_user_note {
2455 my( $self, $conn, $authtoken, $noteid ) = @_;
2457 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2458 return $e->die_event unless $e->checkauth;
2459 my $note = $e->retrieve_actor_usr_note($noteid)
2460 or return $e->die_event;
2461 my $user = $e->retrieve_actor_user($note->usr)
2462 or return $e->die_event;
2463 return $e->die_event unless
2464 $e->allowed('UPDATE_USER', $user->home_ou);
2466 $e->delete_actor_usr_note($note) or return $e->die_event;
2472 __PACKAGE__->register_method(
2473 method => 'update_user_note',
2474 api_name => 'open-ils.actor.note.update',
2476 @param authtoken The login session key
2477 @param note The note
2481 sub update_user_note {
2482 my( $self, $conn, $auth, $note ) = @_;
2483 my $e = new_editor(authtoken=>$auth, xact=>1);
2484 return $e->die_event unless $e->checkauth;
2485 my $patron = $e->retrieve_actor_user($note->usr)
2486 or return $e->die_event;
2487 return $e->die_event unless
2488 $e->allowed('UPDATE_USER', $patron->home_ou);
2489 $e->update_actor_user_note($note)
2490 or return $e->die_event;
2497 __PACKAGE__->register_method(
2498 method => 'create_closed_date',
2499 api_name => 'open-ils.actor.org_unit.closed_date.create',
2501 Creates a new closing entry for the given org_unit
2502 @param authtoken The login session key
2503 @param note The closed_date object
2506 sub create_closed_date {
2507 my( $self, $conn, $authtoken, $cd ) = @_;
2509 my( $user, $evt ) = $U->checkses($authtoken);
2510 return $evt if $evt;
2512 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2513 return $evt if $evt;
2515 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2517 my $id = $U->storagereq(
2518 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2519 return $U->DB_UPDATE_FAILED($cd) unless $id;
2524 __PACKAGE__->register_method(
2525 method => 'delete_closed_date',
2526 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2528 Deletes a closing entry for the given org_unit
2529 @param authtoken The login session key
2530 @param noteid The close_date id
2533 sub delete_closed_date {
2534 my( $self, $conn, $authtoken, $cd ) = @_;
2536 my( $user, $evt ) = $U->checkses($authtoken);
2537 return $evt if $evt;
2540 ($cd_obj, $evt) = fetch_closed_date($cd);
2541 return $evt if $evt;
2543 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2544 return $evt if $evt;
2546 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2548 my $stat = $U->storagereq(
2549 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2550 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2555 __PACKAGE__->register_method(
2556 method => 'usrname_exists',
2557 api_name => 'open-ils.actor.username.exists',
2559 desc => 'Check if a username is already taken (by an undeleted patron)',
2561 {desc => 'Authentication token', type => 'string'},
2562 {desc => 'Username', type => 'string'}
2565 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2570 sub usrname_exists {
2571 my( $self, $conn, $auth, $usrname ) = @_;
2572 my $e = new_editor(authtoken=>$auth);
2573 return $e->event unless $e->checkauth;
2574 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2575 return $$a[0] if $a and @$a;
2579 __PACKAGE__->register_method(
2580 method => 'barcode_exists',
2581 api_name => 'open-ils.actor.barcode.exists',
2583 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2586 sub barcode_exists {
2587 my( $self, $conn, $auth, $barcode ) = @_;
2588 my $e = new_editor(authtoken=>$auth);
2589 return $e->event unless $e->checkauth;
2590 my $card = $e->search_actor_card({barcode => $barcode});
2596 #return undef unless @$card;
2597 #return $card->[0]->usr;
2601 __PACKAGE__->register_method(
2602 method => 'retrieve_net_levels',
2603 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2606 sub retrieve_net_levels {
2607 my( $self, $conn, $auth ) = @_;
2608 my $e = new_editor(authtoken=>$auth);
2609 return $e->event unless $e->checkauth;
2610 return $e->retrieve_all_config_net_access_level();
2613 # Retain the old typo API name just in case
2614 __PACKAGE__->register_method(
2615 method => 'fetch_org_by_shortname',
2616 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2618 __PACKAGE__->register_method(
2619 method => 'fetch_org_by_shortname',
2620 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2622 sub fetch_org_by_shortname {
2623 my( $self, $conn, $sname ) = @_;
2624 my $e = new_editor();
2625 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2626 return $e->event unless $org;
2631 __PACKAGE__->register_method(
2632 method => 'session_home_lib',
2633 api_name => 'open-ils.actor.session.home_lib',
2636 sub session_home_lib {
2637 my( $self, $conn, $auth ) = @_;
2638 my $e = new_editor(authtoken=>$auth);
2639 return undef unless $e->checkauth;
2640 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2641 return $org->shortname;
2644 __PACKAGE__->register_method(
2645 method => 'session_safe_token',
2646 api_name => 'open-ils.actor.session.safe_token',
2648 Returns a hashed session ID that is safe for export to the world.
2649 This safe token will expire after 1 hour of non-use.
2650 @param auth Active authentication token
2654 sub session_safe_token {
2655 my( $self, $conn, $auth ) = @_;
2656 my $e = new_editor(authtoken=>$auth);
2657 return undef unless $e->checkauth;
2659 my $safe_token = md5_hex($auth);
2661 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2663 # Add more like the following if needed...
2665 "safe-token-home_lib-shortname-$safe_token",
2666 $e->retrieve_actor_org_unit(
2667 $e->requestor->home_ou
2676 __PACKAGE__->register_method(
2677 method => 'safe_token_home_lib',
2678 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2680 Returns the home library shortname from the session
2681 asscociated with a safe token from generated by
2682 open-ils.actor.session.safe_token.
2683 @param safe_token Active safe token
2687 sub safe_token_home_lib {
2688 my( $self, $conn, $safe_token ) = @_;
2690 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2691 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2696 __PACKAGE__->register_method(
2697 method => 'slim_tree',
2698 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2701 my $tree = new_editor()->search_actor_org_unit(
2703 {"parent_ou" => undef },
2706 flesh_fields => { aou => ['children'] },
2707 order_by => { aou => 'name'},
2708 select => { aou => ["id","shortname", "name"]},
2713 return trim_tree($tree);
2719 return undef unless $tree;
2721 code => $tree->shortname,
2722 name => $tree->name,
2724 if( $tree->children and @{$tree->children} ) {
2725 $htree->{children} = [];
2726 for my $c (@{$tree->children}) {
2727 push( @{$htree->{children}}, trim_tree($c) );
2735 __PACKAGE__->register_method(
2736 method => "update_penalties",
2737 api_name => "open-ils.actor.user.penalties.update"
2740 sub update_penalties {
2741 my($self, $conn, $auth, $user_id) = @_;
2742 my $e = new_editor(authtoken=>$auth, xact => 1);
2743 return $e->die_event unless $e->checkauth;
2744 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2745 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2746 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2747 return $evt if $evt;
2753 __PACKAGE__->register_method(
2754 method => "apply_penalty",
2755 api_name => "open-ils.actor.user.penalty.apply"
2759 my($self, $conn, $auth, $penalty) = @_;
2761 my $e = new_editor(authtoken=>$auth, xact => 1);
2762 return $e->die_event unless $e->checkauth;
2764 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2765 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2767 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2770 (defined $ptype->org_depth) ?
2771 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2774 $penalty->org_unit($ctx_org);
2775 $penalty->staff($e->requestor->id);
2776 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2779 return $penalty->id;
2782 __PACKAGE__->register_method(
2783 method => "remove_penalty",
2784 api_name => "open-ils.actor.user.penalty.remove"
2787 sub remove_penalty {
2788 my($self, $conn, $auth, $penalty) = @_;
2789 my $e = new_editor(authtoken=>$auth, xact => 1);
2790 return $e->die_event unless $e->checkauth;
2791 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2792 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2794 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2799 __PACKAGE__->register_method(
2800 method => "update_penalty_note",
2801 api_name => "open-ils.actor.user.penalty.note.update"
2804 sub update_penalty_note {
2805 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2806 my $e = new_editor(authtoken=>$auth, xact => 1);
2807 return $e->die_event unless $e->checkauth;
2808 for my $penalty_id (@$penalty_ids) {
2809 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2810 if (! $penalty ) { return $e->die_event; }
2811 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2812 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2814 $penalty->note( $note ); $penalty->ischanged( 1 );
2816 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2822 __PACKAGE__->register_method(
2823 method => "ranged_penalty_thresholds",
2824 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2828 sub ranged_penalty_thresholds {
2829 my($self, $conn, $auth, $context_org) = @_;
2830 my $e = new_editor(authtoken=>$auth);
2831 return $e->event unless $e->checkauth;
2832 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2833 my $list = $e->search_permission_grp_penalty_threshold([
2834 {org_unit => $U->get_org_ancestors($context_org)},
2835 {order_by => {pgpt => 'id'}}
2837 $conn->respond($_) for @$list;
2843 __PACKAGE__->register_method(
2844 method => "user_retrieve_fleshed_by_id",
2846 api_name => "open-ils.actor.user.fleshed.retrieve",
2849 sub user_retrieve_fleshed_by_id {
2850 my( $self, $client, $auth, $user_id, $fields ) = @_;
2851 my $e = new_editor(authtoken => $auth);
2852 return $e->event unless $e->checkauth;
2854 if( $e->requestor->id != $user_id ) {
2855 return $e->event unless $e->allowed('VIEW_USER');
2861 "standing_penalties",
2865 "stat_cat_entries" ];
2866 return new_flesh_user($user_id, $fields, $e);
2870 sub new_flesh_user {
2873 my $fields = shift || [];
2876 my $fetch_penalties = 0;
2877 if(grep {$_ eq 'standing_penalties'} @$fields) {
2878 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2879 $fetch_penalties = 1;
2882 my $user = $e->retrieve_actor_user(
2887 "flesh_fields" => { "au" => $fields }
2890 ) or return $e->die_event;
2893 if( grep { $_ eq 'addresses' } @$fields ) {
2895 $user->addresses([]) unless @{$user->addresses};
2896 # don't expose "replaced" addresses by default
2897 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2899 if( ref $user->billing_address ) {
2900 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2901 push( @{$user->addresses}, $user->billing_address );
2905 if( ref $user->mailing_address ) {
2906 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2907 push( @{$user->addresses}, $user->mailing_address );
2912 if($fetch_penalties) {
2913 # grab the user penalties ranged for this location
2914 $user->standing_penalties(
2915 $e->search_actor_user_standing_penalty([
2918 {stop_date => undef},
2919 {stop_date => {'>' => 'now'}}
2921 org_unit => $U->get_org_ancestors($e->requestor->ws_ou)
2924 flesh_fields => {ausp => ['standing_penalty']}
2931 $user->clear_passwd();
2938 __PACKAGE__->register_method(
2939 method => "user_retrieve_parts",
2940 api_name => "open-ils.actor.user.retrieve.parts",
2943 sub user_retrieve_parts {
2944 my( $self, $client, $auth, $user_id, $fields ) = @_;
2945 my $e = new_editor(authtoken => $auth);
2946 return $e->event unless $e->checkauth;
2947 $user_id ||= $e->requestor->id;
2948 if( $e->requestor->id != $user_id ) {
2949 return $e->event unless $e->allowed('VIEW_USER');
2952 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2953 push(@resp, $user->$_()) for(@$fields);
2959 __PACKAGE__->register_method(
2960 method => 'user_opt_in_enabled',
2961 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2962 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2965 sub user_opt_in_enabled {
2966 my($self, $conn) = @_;
2967 my $sc = OpenSRF::Utils::SettingsClient->new;
2968 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2973 __PACKAGE__->register_method(
2974 method => 'user_opt_in_at_org',
2975 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2977 @param $auth The auth token
2978 @param user_id The ID of the user to test
2979 @return 1 if the user has opted in at the specified org,
2980 event on error, and 0 otherwise. /
2982 sub user_opt_in_at_org {
2983 my($self, $conn, $auth, $user_id) = @_;
2985 # see if we even need to enforce the opt-in value
2986 return 1 unless user_opt_in_enabled($self);
2988 my $e = new_editor(authtoken => $auth);
2989 return $e->event unless $e->checkauth;
2990 my $org_id = $e->requestor->ws_ou;
2992 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2993 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2995 # user is automatically opted-in at the home org
2996 return 1 if $user->home_ou eq $org_id;
2998 my $vals = $e->search_actor_usr_org_unit_opt_in(
2999 {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
3005 __PACKAGE__->register_method(
3006 method => 'create_user_opt_in_at_org',
3007 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3009 @param $auth The auth token
3010 @param user_id The ID of the user to test
3011 @return The ID of the newly created object, event on error./
3014 sub create_user_opt_in_at_org {
3015 my($self, $conn, $auth, $user_id) = @_;
3017 my $e = new_editor(authtoken => $auth, xact=>1);
3018 return $e->die_event unless $e->checkauth;
3019 my $org_id = $e->requestor->ws_ou;
3021 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3022 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3024 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3026 $opt_in->org_unit($org_id);
3027 $opt_in->usr($user_id);
3028 $opt_in->staff($e->requestor->id);
3029 $opt_in->opt_in_ts('now');
3030 $opt_in->opt_in_ws($e->requestor->wsid);
3032 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3033 or return $e->die_event;
3041 __PACKAGE__->register_method (
3042 method => 'retrieve_org_hours',
3043 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3045 Returns the hours of operation for a specified org unit
3046 @param authtoken The login session key
3047 @param org_id The org_unit ID
3051 sub retrieve_org_hours {
3052 my($self, $conn, $auth, $org_id) = @_;
3053 my $e = new_editor(authtoken => $auth);
3054 return $e->die_event unless $e->checkauth;
3055 $org_id ||= $e->requestor->ws_ou;
3056 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3060 __PACKAGE__->register_method (
3061 method => 'verify_user_password',
3062 api_name => 'open-ils.actor.verify_user_password',
3064 Given a barcode or username and the MD5 encoded password,
3065 returns 1 if the password is correct. Returns 0 otherwise.
3069 sub verify_user_password {
3070 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3071 my $e = new_editor(authtoken => $auth);
3072 return $e->die_event unless $e->checkauth;
3074 my $user_by_barcode;
3075 my $user_by_username;
3077 my $card = $e->search_actor_card([
3078 {barcode => $barcode},
3079 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3080 $user_by_barcode = $card->usr;
3081 $user = $user_by_barcode;
3084 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3085 $user = $user_by_username;
3087 return 0 if (!$user);
3088 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3089 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3090 return 1 if $user->passwd eq $password;
3094 __PACKAGE__->register_method (
3095 method => 'retrieve_usr_id_via_barcode_or_usrname',
3096 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3098 Given a barcode or username returns the id for the user or
3103 sub retrieve_usr_id_via_barcode_or_usrname {
3104 my($self, $conn, $auth, $barcode, $username) = @_;
3105 my $e = new_editor(authtoken => $auth);
3106 return $e->die_event unless $e->checkauth;
3107 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3109 my $user_by_barcode;
3110 my $user_by_username;
3111 $logger->info("$id_as_barcode is the ID as BARCODE");
3113 my $card = $e->search_actor_card([
3114 {barcode => $barcode},
3115 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3116 if ($id_as_barcode =~ /^t/i) {
3118 $user = $e->retrieve_actor_user($barcode);
3119 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3121 $user_by_barcode = $card->usr;
3122 $user = $user_by_barcode;
3125 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3126 $user_by_barcode = $card->usr;
3127 $user = $user_by_barcode;
3132 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3134 $user = $user_by_username;
3136 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3137 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3138 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3143 __PACKAGE__->register_method (
3144 method => 'merge_users',
3145 api_name => 'open-ils.actor.user.merge',
3148 Given a list of source users and destination user, transfer all data from the source
3149 to the dest user and delete the source user. All user related data is
3150 transferred, including circulations, holds, bookbags, etc.
3156 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3157 my $e = new_editor(xact => 1, authtoken => $auth);
3158 return $e->die_event unless $e->checkauth;
3160 # disallow the merge if any subordinate accounts are in collections
3161 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3162 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3164 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3165 my $del_addrs = ($U->ou_ancestor_setting_value(
3166 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3167 my $del_cards = ($U->ou_ancestor_setting_value(
3168 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3169 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3170 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3172 for my $src_id (@$user_ids) {
3173 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3175 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3176 if($src_user->home_ou ne $master_user->home_ou) {
3177 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3180 return $e->die_event unless
3181 $e->json_query({from => [
3196 __PACKAGE__->register_method (
3197 method => 'approve_user_address',
3198 api_name => 'open-ils.actor.user.pending_address.approve',
3205 sub approve_user_address {
3206 my($self, $conn, $auth, $addr) = @_;
3207 my $e = new_editor(xact => 1, authtoken => $auth);
3208 return $e->die_event unless $e->checkauth;
3210 # if the caller passes an address object, assume they want to
3211 # update it first before approving it
3212 $e->update_actor_user_address($addr) or return $e->die_event;
3214 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3216 my $user = $e->retrieve_actor_user($addr->usr);
3217 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3218 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3219 or return $e->die_event;
3221 return [values %$result]->[0];
3225 __PACKAGE__->register_method (
3226 method => 'retrieve_friends',
3227 api_name => 'open-ils.actor.friends.retrieve',
3230 returns { confirmed: [], pending_out: [], pending_in: []}
3231 pending_out are users I'm requesting friendship with
3232 pending_in are users requesting friendship with me
3237 sub retrieve_friends {
3238 my($self, $conn, $auth, $user_id, $options) = @_;
3239 my $e = new_editor(authtoken => $auth);
3240 return $e->event unless $e->checkauth;
3241 $user_id ||= $e->requestor->id;
3243 if($user_id != $e->requestor->id) {
3244 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3245 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3248 return OpenILS::Application::Actor::Friends->retrieve_friends(
3249 $e, $user_id, $options);
3254 __PACKAGE__->register_method (
3255 method => 'apply_friend_perms',
3256 api_name => 'open-ils.actor.friends.perms.apply',
3262 sub apply_friend_perms {
3263 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3264 my $e = new_editor(authtoken => $auth, xact => 1);
3265 return $e->die_event unless $e->checkauth;
3267 if($user_id != $e->requestor->id) {
3268 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3269 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3272 for my $perm (@perms) {
3274 OpenILS::Application::Actor::Friends->apply_friend_perm(
3275 $e, $user_id, $delegate_id, $perm);
3276 return $evt if $evt;
3284 __PACKAGE__->register_method (
3285 method => 'update_user_pending_address',
3286 api_name => 'open-ils.actor.user.address.pending.cud'
3289 sub update_user_pending_address {
3290 my($self, $conn, $auth, $addr) = @_;
3291 my $e = new_editor(authtoken => $auth, xact => 1);
3292 return $e->die_event unless $e->checkauth;
3294 if($addr->usr != $e->requestor->id) {
3295 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3296 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3300 $e->create_actor_user_address($addr) or return $e->die_event;
3301 } elsif($addr->isdeleted) {
3302 $e->delete_actor_user_address($addr) or return $e->die_event;
3304 $e->update_actor_user_address($addr) or return $e->die_event;
3312 __PACKAGE__->register_method (
3313 method => 'user_events',
3314 api_name => 'open-ils.actor.user.events.circ',
3317 __PACKAGE__->register_method (
3318 method => 'user_events',
3319 api_name => 'open-ils.actor.user.events.ahr',
3324 my($self, $conn, $auth, $user_id, $filters) = @_;
3325 my $e = new_editor(authtoken => $auth);
3326 return $e->event unless $e->checkauth;
3328 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3329 my $user_field = 'usr';
3332 $filters->{target} = {
3333 select => { $obj_type => ['id'] },
3335 where => {usr => $user_id}
3338 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3339 if($e->requestor->id != $user_id) {
3340 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3343 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3344 my $req = $ses->request('open-ils.trigger.events_by_target',
3345 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3347 while(my $resp = $req->recv) {
3348 my $val = $resp->content;
3349 my $tgt = $val->target;
3351 if($obj_type eq 'circ') {
3352 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3354 } elsif($obj_type eq 'ahr') {
3355 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3356 if $tgt->current_copy;
3359 $conn->respond($val) if $val;
3365 __PACKAGE__->register_method (
3366 method => 'copy_events',
3367 api_name => 'open-ils.actor.copy.events.circ',
3370 __PACKAGE__->register_method (
3371 method => 'copy_events',
3372 api_name => 'open-ils.actor.copy.events.ahr',
3377 my($self, $conn, $auth, $copy_id, $filters) = @_;
3378 my $e = new_editor(authtoken => $auth);
3379 return $e->event unless $e->checkauth;
3381 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3383 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3385 my $copy_field = 'target_copy';
3386 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3389 $filters->{target} = {
3390 select => { $obj_type => ['id'] },
3392 where => {$copy_field => $copy_id}
3396 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3397 my $req = $ses->request('open-ils.trigger.events_by_target',
3398 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3400 while(my $resp = $req->recv) {
3401 my $val = $resp->content;
3402 my $tgt = $val->target;
3404 my $user = $e->retrieve_actor_user($tgt->usr);
3405 if($e->requestor->id != $user->id) {
3406 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3409 $tgt->$copy_field($copy);
3412 $conn->respond($val) if $val;
3421 __PACKAGE__->register_method (
3422 method => 'update_events',
3423 api_name => 'open-ils.actor.user.event.cancel.batch',
3426 __PACKAGE__->register_method (
3427 method => 'update_events',
3428 api_name => 'open-ils.actor.user.event.reset.batch',
3433 my($self, $conn, $auth, $event_ids) = @_;
3434 my $e = new_editor(xact => 1, authtoken => $auth);
3435 return $e->die_event unless $e->checkauth;
3438 for my $id (@$event_ids) {
3440 # do a little dance to determine what user we are ultimately affecting
3441 my $event = $e->retrieve_action_trigger_event([
3444 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3446 ]) or return $e->die_event;
3449 if($event->event_def->hook->core_type eq 'circ') {
3450 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3451 } elsif($event->event_def->hook->core_type eq 'ahr') {
3452 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3457 my $user = $e->retrieve_actor_user($user_id);
3458 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3460 if($self->api_name =~ /cancel/) {
3461 $event->state('invalid');
3462 } elsif($self->api_name =~ /reset/) {
3463 $event->clear_start_time;
3464 $event->clear_update_time;
3465 $event->state('pending');
3468 $e->update_action_trigger_event($event) or return $e->die_event;
3469 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3473 return {complete => 1};
3477 __PACKAGE__->register_method (
3478 method => 'really_delete_user',
3479 api_name => 'open-ils.actor.user.delete',
3481 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3482 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3483 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3484 dest_usr_id is only required when deleting a user that performs staff functions.
3488 sub really_delete_user {
3489 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3490 my $e = new_editor(authtoken => $auth, xact => 1);
3491 return $e->die_event unless $e->checkauth;
3492 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3493 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3494 my $stat = $e->json_query(
3495 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3496 or return $e->die_event;
3503 __PACKAGE__->register_method (
3504 method => 'user_payments',
3505 api_name => 'open-ils.actor.user.payments.retrieve',
3508 Returns all payments for a given user. Default order is newest payments first.
3509 @param auth Authentication token
3510 @param user_id The user ID
3511 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3516 my($self, $conn, $auth, $user_id, $filters) = @_;
3519 my $e = new_editor(authtoken => $auth);
3520 return $e->die_event unless $e->checkauth;
3522 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3523 return $e->event unless
3524 $e->requestor->id == $user_id or
3525 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3527 # Find all payments for all transactions for user $user_id
3529 select => {mp => ['id']},
3534 select => {mbt => ['id']},
3536 where => {usr => $user_id}
3540 order_by => [{ # by default, order newest payments first
3542 field => 'payment_ts',
3547 for (qw/order_by limit offset/) {
3548 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3551 if(defined $filters->{where}) {
3552 foreach (keys %{$filters->{where}}) {
3553 # don't allow the caller to expand the result set to other users
3554 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3558 my $payment_ids = $e->json_query($query);
3559 for my $pid (@$payment_ids) {
3560 my $pay = $e->retrieve_money_payment([
3565 mbt => ['summary', 'circulation', 'grocery'],
3566 circ => ['target_copy'],
3567 acp => ['call_number'],
3575 xact_type => $pay->xact->summary->xact_type,
3576 last_billing_type => $pay->xact->summary->last_billing_type,
3579 if($pay->xact->summary->xact_type eq 'circulation') {
3580 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3581 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3584 $pay->xact($pay->xact->id); # de-flesh
3585 $conn->respond($resp);
3593 __PACKAGE__->register_method (
3594 method => 'negative_balance_users',
3595 api_name => 'open-ils.actor.users.negative_balance',
3598 Returns all users that have an overall negative balance
3599 @param auth Authentication token
3600 @param org_id The context org unit as an ID or list of IDs. This will be the home
3601 library of the user. If no org_unit is specified, no org unit filter is applied
3605 sub negative_balance_users {
3606 my($self, $conn, $auth, $org_id) = @_;
3608 my $e = new_editor(authtoken => $auth);
3609 return $e->die_event unless $e->checkauth;
3610 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3614 mous => ['usr', 'balance_owed'],
3617 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3618 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3635 where => {'+mous' => {balance_owed => {'<' => 0}}}
3638 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3640 my $list = $e->json_query($query, {timeout => 600});
3642 for my $data (@$list) {
3644 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3645 balance_owed => $data->{balance_owed},
3646 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3653 __PACKAGE__->register_method(
3654 method => "request_password_reset",
3655 api_name => "open-ils.actor.patron.password_reset.request",
3657 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3658 "method for changing a user's password. The UUID token is distributed via A/T " .
3659 "templates (i.e. email to the user).",
3661 { desc => 'user_id_type', type => 'string' },
3662 { desc => 'user_id', type => 'string' },
3663 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3665 return => {desc => '1 on success, Event on error'}
3668 sub request_password_reset {
3669 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3671 # Check to see if password reset requests are already being throttled:
3672 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3674 my $e = new_editor(xact => 1);
3677 # Get the user, if any, depending on the input value
3678 if ($user_id_type eq 'username') {
3679 $user = $e->search_actor_user({usrname => $user_id})->[0];
3682 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3684 } elsif ($user_id_type eq 'barcode') {
3685 my $card = $e->search_actor_card([
3686 {barcode => $user_id},
3687 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3690 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3695 # If the user doesn't have an email address, we can't help them
3696 if (!$user->email) {
3698 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3701 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3702 if ($email_must_match) {
3703 if ($user->email ne $email) {
3704 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3708 _reset_password_request($conn, $e, $user);
3711 # Once we have the user, we can issue the password reset request
3712 # XXX Add a wrapper method that accepts barcode + email input
3713 sub _reset_password_request {
3714 my ($conn, $e, $user) = @_;
3716 # 1. Get throttle threshold and time-to-live from OU_settings
3717 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3718 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3720 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3722 # 2. Get time of last request and number of active requests (num_active)
3723 my $active_requests = $e->json_query({
3729 transform => 'COUNT'
3732 column => 'request_time',
3738 has_been_reset => { '=' => 'f' },
3739 request_time => { '>' => $threshold_time }
3743 # Guard against no active requests
3744 if ($active_requests->[0]->{'request_time'}) {
3745 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3746 my $now = DateTime::Format::ISO8601->new();
3748 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3749 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3750 ($last_request->add_duration('1 minute') > $now)) {
3751 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3753 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3757 # TODO Check to see if the user is in a password-reset-restricted group
3759 # Otherwise, go ahead and try to get the user.
3761 # Check the number of active requests for this user
3762 $active_requests = $e->json_query({
3768 transform => 'COUNT'
3773 usr => { '=' => $user->id },
3774 has_been_reset => { '=' => 'f' },
3775 request_time => { '>' => $threshold_time }
3779 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3781 # if less than or equal to per-user threshold, proceed; otherwise, return event
3782 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3783 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3785 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3788 # Create the aupr object and insert into the database
3789 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3790 my $uuid = create_uuid_as_string(UUID_V4);
3791 $reset_request->uuid($uuid);
3792 $reset_request->usr($user->id);
3794 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3797 # Create an event to notify user of the URL to reset their password
3799 # Can we stuff this in the user_data param for trigger autocreate?
3800 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3802 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3803 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3806 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3811 __PACKAGE__->register_method(
3812 method => "commit_password_reset",
3813 api_name => "open-ils.actor.patron.password_reset.commit",
3815 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3816 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3817 "with the supplied password.",
3819 { desc => 'uuid', type => 'string' },
3820 { desc => 'password', type => 'string' },
3822 return => {desc => '1 on success, Event on error'}
3825 sub commit_password_reset {
3826 my($self, $conn, $uuid, $password) = @_;
3828 # Check to see if password reset requests are already being throttled:
3829 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3830 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3831 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3833 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3836 my $e = new_editor(xact => 1);
3838 my $aupr = $e->search_actor_usr_password_reset({
3845 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3847 my $user_id = $aupr->[0]->usr;
3848 my $user = $e->retrieve_actor_user($user_id);
3850 # Ensure we're still within the TTL for the request
3851 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3852 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3853 if ($threshold < DateTime->now(time_zone => 'local')) {
3855 $logger->info("Password reset request needed to be submitted before $threshold");
3856 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3859 # Check complexity of password against OU-defined regex
3860 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3864 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3865 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3866 $is_strong = check_password_strength_custom($password, $pw_regex);
3868 $is_strong = check_password_strength_default($password);
3873 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3876 # All is well; update the password
3877 $user->passwd($password);
3878 $e->update_actor_user($user);
3880 # And flag that this password reset request has been honoured
3881 $aupr->[0]->has_been_reset('t');
3882 $e->update_actor_usr_password_reset($aupr->[0]);
3888 sub check_password_strength_default {
3889 my $password = shift;
3890 # Use the default set of checks
3891 if ( (length($password) < 7) or
3892 ($password !~ m/.*\d+.*/) or
3893 ($password !~ m/.*[A-Za-z]+.*/)
3900 sub check_password_strength_custom {
3901 my ($password, $pw_regex) = @_;
3903 $pw_regex = qr/$pw_regex/;
3904 if ($password !~ /$pw_regex/) {
3912 __PACKAGE__->register_method(
3913 method => "event_def_opt_in_settings",
3914 api_name => "open-ils.actor.event_def.opt_in.settings",
3917 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3919 { desc => 'Authentication token', type => 'string'},
3921 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3926 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3933 sub event_def_opt_in_settings {
3934 my($self, $conn, $auth, $org_id) = @_;
3935 my $e = new_editor(authtoken => $auth);
3936 return $e->event unless $e->checkauth;
3938 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3939 return $e->event unless
3940 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3942 $org_id = $e->requestor->home_ou;
3945 # find all config.user_setting_type's related to event_defs for the requested org unit
3946 my $types = $e->json_query({
3947 select => {cust => ['name']},
3948 from => {atevdef => 'cust'},
3951 owner => $U->get_org_ancestors($org_id), # context org plus parents
3958 $conn->respond($_) for
3959 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
3966 __PACKAGE__->register_method(
3967 method => "user_visible_circs",
3968 api_name => "open-ils.actor.history.circ.visible",
3971 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
3973 { desc => 'Authentication token', type => 'string'},
3974 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3975 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3978 desc => q/An object with 2 fields: circulation and summary.
3979 circulation is the "circ" object. summary is the related "accs" object/,
3985 __PACKAGE__->register_method(
3986 method => "user_visible_circs",
3987 api_name => "open-ils.actor.history.circ.visible.print",
3990 desc => 'Returns printable output for the set of opt-in visible circulations',
3992 { desc => 'Authentication token', type => 'string'},
3993 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3994 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3997 desc => q/An action_trigger.event object or error event./,
4003 __PACKAGE__->register_method(
4004 method => "user_visible_circs",
4005 api_name => "open-ils.actor.history.circ.visible.email",
4008 desc => 'Emails the set of opt-in visible circulations to the requestor',
4010 { desc => 'Authentication token', type => 'string'},
4011 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4012 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4015 desc => q/undef, or event on error/
4020 __PACKAGE__->register_method(
4021 method => "user_visible_circs",
4022 api_name => "open-ils.actor.history.hold.visible",
4025 desc => 'Returns the set of opt-in visible holds',
4027 { desc => 'Authentication token', type => 'string'},
4028 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4029 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4032 desc => q/An object with 1 field: "hold"/,
4038 __PACKAGE__->register_method(
4039 method => "user_visible_circs",
4040 api_name => "open-ils.actor.history.hold.visible.print",
4043 desc => 'Returns printable output for the set of opt-in visible holds',
4045 { desc => 'Authentication token', type => 'string'},
4046 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4047 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4050 desc => q/An action_trigger.event object or error event./,
4056 __PACKAGE__->register_method(
4057 method => "user_visible_circs",
4058 api_name => "open-ils.actor.history.hold.visible.email",
4061 desc => 'Emails the set of opt-in visible holds to the requestor',
4063 { desc => 'Authentication token', type => 'string'},
4064 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4065 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4068 desc => q/undef, or event on error/
4073 sub user_visible_circs {
4074 my($self, $conn, $auth, $user_id, $options) = @_;
4076 my $is_hold = ($self->api_name =~ /hold/);
4077 my $for_print = ($self->api_name =~ /print/);
4078 my $for_email = ($self->api_name =~ /email/);
4079 my $e = new_editor(authtoken => $auth);
4080 return $e->event unless $e->checkauth;
4082 $user_id ||= $e->requestor->id;
4084 $options->{limit} ||= 50;
4085 $options->{offset} ||= 0;
4087 if($user_id != $e->requestor->id) {
4088 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4089 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4090 return $e->event unless $e->allowed($perm, $user->home_ou);
4093 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4095 my $data = $e->json_query({
4096 from => [$db_func, $user_id],
4097 limit => $$options{limit},
4098 offset => $$options{offset}
4100 # TODO: I only want IDs. code below didn't get me there
4101 # {"select":{"au":[{"column":"id", "result_field":"id",
4102 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4107 return undef unless @$data;
4111 # collect the batch of objects
4115 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4116 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4120 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4121 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4124 } elsif ($for_email) {
4126 $conn->respond_complete(1) if $for_email; # no sense in waiting
4134 my $hold = $e->retrieve_action_hold_request($id);
4135 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4136 # events will be fired from action_trigger_runner
4140 my $circ = $e->retrieve_action_circulation($id);
4141 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4142 # events will be fired from action_trigger_runner
4146 } else { # just give me the data please
4154 my $hold = $e->retrieve_action_hold_request($id);
4155 $conn->respond({hold => $hold});
4159 my $circ = $e->retrieve_action_circulation($id);
4162 summary => $U->create_circ_chain_summary($e, $id)
4171 __PACKAGE__->register_method(
4172 method => "user_saved_search_cud",
4173 api_name => "open-ils.actor.user.saved_search.cud",
4176 desc => 'Create/Update/Delete Access to user saved searches',
4178 { desc => 'Authentication token', type => 'string' },
4179 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4182 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4188 __PACKAGE__->register_method(
4189 method => "user_saved_search_cud",
4190 api_name => "open-ils.actor.user.saved_search.retrieve",
4193 desc => 'Retrieve a saved search object',
4195 { desc => 'Authentication token', type => 'string' },
4196 { desc => 'Saved Search ID', type => 'number' }
4199 desc => q/The saved search object, Event on error/,
4205 sub user_saved_search_cud {
4206 my( $self, $client, $auth, $search ) = @_;
4207 my $e = new_editor( authtoken=>$auth );
4208 return $e->die_event unless $e->checkauth;
4210 my $o_search; # prior version of the object, if any
4211 my $res; # to be returned
4213 # branch on the operation type
4215 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4217 # Get the old version, to check ownership
4218 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4219 or return $e->die_event;
4221 # You can't read somebody else's search
4222 return OpenILS::Event->new('BAD_PARAMS')
4223 unless $o_search->owner == $e->requestor->id;
4229 $e->xact_begin; # start an editor transaction
4231 if( $search->isnew ) { # Create
4233 # You can't create a search for somebody else
4234 return OpenILS::Event->new('BAD_PARAMS')
4235 unless $search->owner == $e->requestor->id;
4237 $e->create_actor_usr_saved_search( $search )
4238 or return $e->die_event;
4242 } elsif( $search->ischanged ) { # Update
4244 # You can't change ownership of a search
4245 return OpenILS::Event->new('BAD_PARAMS')
4246 unless $search->owner == $e->requestor->id;
4248 # Get the old version, to check ownership
4249 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4250 or return $e->die_event;
4252 # You can't update somebody else's search
4253 return OpenILS::Event->new('BAD_PARAMS')
4254 unless $o_search->owner == $e->requestor->id;
4257 $e->update_actor_usr_saved_search( $search )
4258 or return $e->die_event;
4262 } elsif( $search->isdeleted ) { # Delete
4264 # Get the old version, to check ownership
4265 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4266 or return $e->die_event;
4268 # You can't delete somebody else's search
4269 return OpenILS::Event->new('BAD_PARAMS')
4270 unless $o_search->owner == $e->requestor->id;
4273 $e->delete_actor_usr_saved_search( $o_search )
4274 or return $e->die_event;