1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use List::Util qw/max/;
39 use UUID::Tiny qw/:std/;
42 OpenILS::Application::Actor::Container->initialize();
43 OpenILS::Application::Actor::UserGroups->initialize();
44 OpenILS::Application::Actor::ClosedDates->initialize();
47 my $apputils = "OpenILS::Application::AppUtils";
50 sub _d { warn "Patron:\n" . Dumper(shift()); }
53 my $set_user_settings;
57 #__PACKAGE__->register_method(
58 # method => "allowed_test",
59 # api_name => "open-ils.actor.allowed_test",
62 # my($self, $conn, $auth, $orgid, $permcode) = @_;
63 # my $e = new_editor(authtoken => $auth);
64 # return $e->die_event unless $e->checkauth;
68 # permcode => $permcode,
69 # result => $e->allowed($permcode, $orgid)
73 __PACKAGE__->register_method(
74 method => "update_user_setting",
75 api_name => "open-ils.actor.patron.settings.update",
77 sub update_user_setting {
78 my($self, $conn, $auth, $user_id, $settings) = @_;
79 my $e = new_editor(xact => 1, authtoken => $auth);
80 return $e->die_event unless $e->checkauth;
82 $user_id = $e->requestor->id unless defined $user_id;
84 unless($e->requestor->id == $user_id) {
85 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
86 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
89 for my $name (keys %$settings) {
90 my $val = $$settings{$name};
91 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
94 $val = OpenSRF::Utils::JSON->perl2JSON($val);
97 $e->update_actor_user_setting($set) or return $e->die_event;
99 $set = Fieldmapper::actor::user_setting->new;
103 $e->create_actor_user_setting($set) or return $e->die_event;
106 $e->delete_actor_user_setting($set) or return $e->die_event;
115 __PACKAGE__->register_method(
116 method => "set_ou_settings",
117 api_name => "open-ils.actor.org_unit.settings.update",
119 desc => "Updates the value for a given org unit setting. The permission to update " .
120 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
121 "permission specified in the update_perm column of the config.org_unit_setting_type " .
122 "table's row corresponding to the setting being changed." ,
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'Org unit ID', type => 'number'},
126 {desc => 'Hash of setting name-value pairs', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
132 sub set_ou_settings {
133 my( $self, $client, $auth, $org_id, $settings ) = @_;
135 my $e = new_editor(authtoken => $auth, xact => 1);
136 return $e->die_event unless $e->checkauth;
138 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
140 for my $name (keys %$settings) {
141 my $val = $$settings{$name};
143 my $type = $e->retrieve_config_org_unit_setting_type([
145 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
146 ]) or return $e->die_event;
147 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
149 # If there is no relevant permission, the default assumption will
150 # be, "no, the caller cannot change that value."
151 return $e->die_event unless ($all_allowed ||
152 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
155 $val = OpenSRF::Utils::JSON->perl2JSON($val);
158 $e->update_actor_org_unit_setting($set) or return $e->die_event;
160 $set = Fieldmapper::actor::org_unit_setting->new;
161 $set->org_unit($org_id);
164 $e->create_actor_org_unit_setting($set) or return $e->die_event;
167 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
175 __PACKAGE__->register_method(
176 method => "user_settings",
178 api_name => "open-ils.actor.patron.settings.retrieve",
181 my( $self, $client, $auth, $user_id, $setting ) = @_;
183 my $e = new_editor(authtoken => $auth);
184 return $e->event unless $e->checkauth;
185 $user_id = $e->requestor->id unless defined $user_id;
187 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
188 if($e->requestor->id != $user_id) {
189 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
193 my($e, $user_id, $setting) = @_;
194 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
195 return undef unless $val; # XXX this should really return undef, but needs testing
196 return OpenSRF::Utils::JSON->JSON2perl($val->value);
200 if(ref $setting eq 'ARRAY') {
202 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
205 return get_setting($e, $user_id, $setting);
208 my $s = $e->search_actor_user_setting({usr => $user_id});
209 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
214 __PACKAGE__->register_method(
215 method => "ranged_ou_settings",
216 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
218 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
219 "is implied for retrieving OU settings by the authenticated users' permissions.",
221 {desc => 'Authentication token', type => 'string'},
222 {desc => 'Org unit ID', type => 'number'},
224 return => {desc => 'A hashref of "ranged" settings, event on error'}
227 sub ranged_ou_settings {
228 my( $self, $client, $auth, $org_id ) = @_;
230 my $e = new_editor(authtoken => $auth);
231 return $e->event unless $e->checkauth;
234 my $org_list = $U->get_org_ancestors($org_id);
235 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
236 $org_list = [ reverse @$org_list ];
238 # start at the context org and capture the setting value
239 # without clobbering settings we've already captured
240 for my $this_org_id (@$org_list) {
242 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
244 for my $set (@sets) {
245 my $type = $e->retrieve_config_org_unit_setting_type([
247 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
250 # If there is no relevant permission, the default assumption will
251 # be, "yes, the caller can have that value."
252 if ($type && $type->view_perm) {
253 next if not $e->allowed($type->view_perm->code, $org_id);
256 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
257 unless defined $ranged_settings{$set->name};
261 return \%ranged_settings;
266 __PACKAGE__->register_method(
267 api_name => 'open-ils.actor.ou_setting.ancestor_default',
268 method => 'ou_ancestor_setting',
270 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
271 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
272 'user has permission to view that setting, if there is a permission associated with the setting.' ,
274 { desc => 'Org unit ID', type => 'number' },
275 { desc => 'setting name', type => 'string' },
276 { desc => 'authtoken (optional)', type => 'string' }
278 return => {desc => 'A value for the org unit setting, or undef'}
282 # ------------------------------------------------------------------
283 # Attempts to find the org setting value for a given org. if not
284 # found at the requested org, searches up the org tree until it
285 # finds a parent that has the requested setting.
286 # when found, returns { org => $id, value => $value }
287 # otherwise, returns NULL
288 # ------------------------------------------------------------------
289 sub ou_ancestor_setting {
290 my( $self, $client, $orgid, $name, $auth ) = @_;
291 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
294 __PACKAGE__->register_method(
295 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
296 method => 'ou_ancestor_setting_batch',
298 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
299 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
300 'user has permission to view that setting, if there is a permission associated with the setting.' ,
302 { desc => 'Org unit ID', type => 'number' },
303 { desc => 'setting name list', type => 'array' },
304 { desc => 'authtoken (optional)', type => 'string' }
306 return => {desc => 'A hash with name => value pairs for the org unit settings'}
309 sub ou_ancestor_setting_batch {
310 my( $self, $client, $orgid, $name_list, $auth ) = @_;
312 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
318 __PACKAGE__->register_method(
319 method => "update_patron",
320 api_name => "open-ils.actor.patron.update",
323 Update an existing user, or create a new one. Related objects,
324 like cards, addresses, survey responses, and stat cats,
325 can be updated by attaching them to the user object in their
326 respective fields. For examples, the billing address object
327 may be inserted into the 'billing_address' field, etc. For each
328 attached object, indicate if the object should be created,
329 updated, or deleted using the built-in 'isnew', 'ischanged',
330 and 'isdeleted' fields on the object.
333 { desc => 'Authentication token', type => 'string' },
334 { desc => 'Patron data object', type => 'object' }
336 return => {desc => 'A fleshed user object, event on error'}
341 my( $self, $client, $user_session, $patron ) = @_;
343 my $session = $apputils->start_db_session();
345 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
347 my( $user_obj, $evt ) = $U->checkses($user_session);
350 $evt = check_group_perm($session, $user_obj, $patron);
354 # $new_patron is the patron in progress. $patron is the original patron
355 # passed in with the method. new_patron will change as the components
356 # of patron are added/updated.
360 # unflesh the real items on the patron
361 $patron->card( $patron->card->id ) if(ref($patron->card));
362 $patron->billing_address( $patron->billing_address->id )
363 if(ref($patron->billing_address));
364 $patron->mailing_address( $patron->mailing_address->id )
365 if(ref($patron->mailing_address));
367 # create/update the patron first so we can use his id
368 if($patron->isnew()) {
369 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
371 } else { $new_patron = $patron; }
373 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
376 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
379 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
382 # re-update the patron if anything has happened to him during this process
383 if($new_patron->ischanged()) {
384 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
388 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
391 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
394 $apputils->commit_db_session($session);
396 $evt = apply_invalid_addr_penalty($patron);
399 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
401 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
403 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
406 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
409 sub apply_invalid_addr_penalty {
411 my $e = new_editor(xact => 1);
413 # grab the invalid address penalty if set
414 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
416 my ($addr_penalty) = grep
417 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
419 # do we enforce invalid address penalty
420 my $enforce = $U->ou_ancestor_setting_value(
421 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
423 my $addrs = $e->search_actor_user_address(
424 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
425 my $addr_count = scalar(@$addrs);
427 if($addr_count == 0 and $addr_penalty) {
429 # regardless of any settings, remove the penalty when the user has no invalid addresses
430 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
433 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
435 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
436 my $depth = $ptype->org_depth;
437 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
438 $ctx_org = $patron->home_ou unless defined $ctx_org;
440 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
441 $penalty->usr($patron->id);
442 $penalty->org_unit($ctx_org);
443 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
445 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
459 return new_flesh_user($id, [
462 "standing_penalties",
466 "stat_cat_entries" ], $e );
474 # clone and clear stuff that would break the database
478 my $new_patron = $patron->clone;
480 $new_patron->clear_billing_address();
481 $new_patron->clear_mailing_address();
482 $new_patron->clear_addresses();
483 $new_patron->clear_card();
484 $new_patron->clear_cards();
485 $new_patron->clear_id();
486 $new_patron->clear_isnew();
487 $new_patron->clear_ischanged();
488 $new_patron->clear_isdeleted();
489 $new_patron->clear_stat_cat_entries();
490 $new_patron->clear_permissions();
491 $new_patron->clear_standing_penalties();
501 my $user_obj = shift;
503 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
504 return (undef, $evt) if $evt;
506 my $ex = $session->request(
507 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
509 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
512 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
514 my $id = $session->request(
515 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
516 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
518 $logger->info("Successfully created new user [$id] in DB");
520 return ( $session->request(
521 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
525 sub check_group_perm {
526 my( $session, $requestor, $patron ) = @_;
529 # first let's see if the requestor has
530 # priveleges to update this user in any way
531 if( ! $patron->isnew ) {
532 my $p = $session->request(
533 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
535 # If we are the requestor (trying to update our own account)
536 # and we are not trying to change our profile, we're good
537 if( $p->id == $requestor->id and
538 $p->profile == $patron->profile ) {
543 $evt = group_perm_failed($session, $requestor, $p);
547 # They are allowed to edit this patron.. can they put the
548 # patron into the group requested?
549 $evt = group_perm_failed($session, $requestor, $patron);
555 sub group_perm_failed {
556 my( $session, $requestor, $patron ) = @_;
560 my $grpid = $patron->profile;
564 $logger->debug("user update looking for group perm for group $grpid");
565 $grp = $session->request(
566 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
567 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
569 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
571 $logger->info("user update checking perm $perm on user ".
572 $requestor->id." for update/create on user username=".$patron->usrname);
574 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
582 my( $session, $patron, $user_obj, $noperm) = @_;
584 $logger->info("Updating patron ".$patron->id." in DB");
589 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
590 return (undef, $evt) if $evt;
593 # update the password by itself to avoid the password protection magic
594 if( $patron->passwd ) {
595 my $s = $session->request(
596 'open-ils.storage.direct.actor.user.remote_update',
597 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
598 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
599 $patron->clear_passwd;
602 if(!$patron->ident_type) {
603 $patron->clear_ident_type;
604 $patron->clear_ident_value;
607 $evt = verify_last_xact($session, $patron);
608 return (undef, $evt) if $evt;
610 my $stat = $session->request(
611 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
612 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
617 sub verify_last_xact {
618 my( $session, $patron ) = @_;
619 return undef unless $patron->id and $patron->id > 0;
620 my $p = $session->request(
621 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
622 my $xact = $p->last_xact_id;
623 return undef unless $xact;
624 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
625 return OpenILS::Event->new('XACT_COLLISION')
626 if $xact != $patron->last_xact_id;
631 sub _check_dup_ident {
632 my( $session, $patron ) = @_;
634 return undef unless $patron->ident_value;
637 ident_type => $patron->ident_type,
638 ident_value => $patron->ident_value,
641 $logger->debug("patron update searching for dup ident values: " .
642 $patron->ident_type . ':' . $patron->ident_value);
644 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
646 my $dups = $session->request(
647 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
650 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
657 sub _add_update_addresses {
661 my $new_patron = shift;
665 my $current_id; # id of the address before creation
667 for my $address (@{$patron->addresses()}) {
669 next unless ref $address;
670 $current_id = $address->id();
672 if( $patron->billing_address() and
673 $patron->billing_address() == $current_id ) {
674 $logger->info("setting billing addr to $current_id");
675 $new_patron->billing_address($address->id());
676 $new_patron->ischanged(1);
679 if( $patron->mailing_address() and
680 $patron->mailing_address() == $current_id ) {
681 $new_patron->mailing_address($address->id());
682 $logger->info("setting mailing addr to $current_id");
683 $new_patron->ischanged(1);
687 if($address->isnew()) {
689 $address->usr($new_patron->id());
691 ($address, $evt) = _add_address($session,$address);
692 return (undef, $evt) if $evt;
694 # we need to get the new id
695 if( $patron->billing_address() and
696 $patron->billing_address() == $current_id ) {
697 $new_patron->billing_address($address->id());
698 $logger->info("setting billing addr to $current_id");
699 $new_patron->ischanged(1);
702 if( $patron->mailing_address() and
703 $patron->mailing_address() == $current_id ) {
704 $new_patron->mailing_address($address->id());
705 $logger->info("setting mailing addr to $current_id");
706 $new_patron->ischanged(1);
709 } elsif($address->ischanged() ) {
711 ($address, $evt) = _update_address($session, $address);
712 return (undef, $evt) if $evt;
714 } elsif($address->isdeleted() ) {
716 if( $address->id() == $new_patron->mailing_address() ) {
717 $new_patron->clear_mailing_address();
718 ($new_patron, $evt) = _update_patron($session, $new_patron);
719 return (undef, $evt) if $evt;
722 if( $address->id() == $new_patron->billing_address() ) {
723 $new_patron->clear_billing_address();
724 ($new_patron, $evt) = _update_patron($session, $new_patron);
725 return (undef, $evt) if $evt;
728 $evt = _delete_address($session, $address);
729 return (undef, $evt) if $evt;
733 return ( $new_patron, undef );
737 # adds an address to the db and returns the address with new id
739 my($session, $address) = @_;
740 $address->clear_id();
742 $logger->info("Creating new address at street ".$address->street1);
744 # put the address into the database
745 my $id = $session->request(
746 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
747 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
750 return ($address, undef);
754 sub _update_address {
755 my( $session, $address ) = @_;
757 $logger->info("Updating address ".$address->id." in the DB");
759 my $stat = $session->request(
760 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
762 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
763 return ($address, undef);
768 sub _add_update_cards {
772 my $new_patron = shift;
776 my $virtual_id; #id of the card before creation
777 for my $card (@{$patron->cards()}) {
779 $card->usr($new_patron->id());
781 if(ref($card) and $card->isnew()) {
783 $virtual_id = $card->id();
784 ( $card, $evt ) = _add_card($session,$card);
785 return (undef, $evt) if $evt;
787 #if(ref($patron->card)) { $patron->card($patron->card->id); }
788 if($patron->card() == $virtual_id) {
789 $new_patron->card($card->id());
790 $new_patron->ischanged(1);
793 } elsif( ref($card) and $card->ischanged() ) {
794 $evt = _update_card($session, $card);
795 return (undef, $evt) if $evt;
799 return ( $new_patron, undef );
803 # adds an card to the db and returns the card with new id
805 my( $session, $card ) = @_;
808 $logger->info("Adding new patron card ".$card->barcode);
810 my $id = $session->request(
811 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
812 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
813 $logger->info("Successfully created patron card $id");
816 return ( $card, undef );
820 # returns event on error. returns undef otherwise
822 my( $session, $card ) = @_;
823 $logger->info("Updating patron card ".$card->id);
825 my $stat = $session->request(
826 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
827 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
834 # returns event on error. returns undef otherwise
835 sub _delete_address {
836 my( $session, $address ) = @_;
838 $logger->info("Deleting address ".$address->id." from DB");
840 my $stat = $session->request(
841 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
843 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
849 sub _add_survey_responses {
850 my ($session, $patron, $new_patron) = @_;
852 $logger->info( "Updating survey responses for patron ".$new_patron->id );
854 my $responses = $patron->survey_responses;
858 $_->usr($new_patron->id) for (@$responses);
860 my $evt = $U->simplereq( "open-ils.circ",
861 "open-ils.circ.survey.submit.user_id", $responses );
863 return (undef, $evt) if defined($U->event_code($evt));
867 return ( $new_patron, undef );
871 sub _create_stat_maps {
873 my($session, $user_session, $patron, $new_patron) = @_;
875 my $maps = $patron->stat_cat_entries();
877 for my $map (@$maps) {
879 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
881 if ($map->isdeleted()) {
882 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
884 } elsif ($map->isnew()) {
885 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
890 $map->target_usr($new_patron->id);
893 $logger->info("Updating stat entry with method $method and map $map");
895 my $stat = $session->request($method, $map)->gather(1);
896 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
900 return ($new_patron, undef);
903 sub _create_perm_maps {
905 my($session, $user_session, $patron, $new_patron) = @_;
907 my $maps = $patron->permissions;
909 for my $map (@$maps) {
911 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
912 if ($map->isdeleted()) {
913 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
914 } elsif ($map->isnew()) {
915 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
920 $map->usr($new_patron->id);
922 #warn( "Updating permissions with method $method and session $user_session and map $map" );
923 $logger->info( "Updating permissions with method $method and map $map" );
925 my $stat = $session->request($method, $map)->gather(1);
926 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
930 return ($new_patron, undef);
934 __PACKAGE__->register_method(
935 method => "set_user_work_ous",
936 api_name => "open-ils.actor.user.work_ous.update",
939 sub set_user_work_ous {
945 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
948 my $session = $apputils->start_db_session();
950 for my $map (@$maps) {
952 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
953 if ($map->isdeleted()) {
954 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
955 } elsif ($map->isnew()) {
956 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
960 #warn( "Updating permissions with method $method and session $ses and map $map" );
961 $logger->info( "Updating work_ou map with method $method and map $map" );
963 my $stat = $session->request($method, $map)->gather(1);
964 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
968 $apputils->commit_db_session($session);
970 return scalar(@$maps);
974 __PACKAGE__->register_method(
975 method => "set_user_perms",
976 api_name => "open-ils.actor.user.permissions.update",
985 my $session = $apputils->start_db_session();
987 my( $user_obj, $evt ) = $U->checkses($ses);
990 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
993 $all = 1 if ($U->is_true($user_obj->super_user()));
994 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
996 for my $map (@$maps) {
998 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
999 if ($map->isdeleted()) {
1000 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1001 } elsif ($map->isnew()) {
1002 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1006 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1007 #warn( "Updating permissions with method $method and session $ses and map $map" );
1008 $logger->info( "Updating permissions with method $method and map $map" );
1010 my $stat = $session->request($method, $map)->gather(1);
1011 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1015 $apputils->commit_db_session($session);
1017 return scalar(@$maps);
1021 __PACKAGE__->register_method(
1022 method => "user_retrieve_by_barcode",
1024 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1026 sub user_retrieve_by_barcode {
1027 my($self, $client, $auth, $barcode) = @_;
1029 my $e = new_editor(authtoken => $auth);
1030 return $e->event unless $e->checkauth;
1032 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1033 or return $e->event;
1035 my $user = flesh_user($card->usr, $e);
1036 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1042 __PACKAGE__->register_method(
1043 method => "get_user_by_id",
1045 api_name => "open-ils.actor.user.retrieve",
1048 sub get_user_by_id {
1049 my ($self, $client, $auth, $id) = @_;
1050 my $e = new_editor(authtoken=>$auth);
1051 return $e->event unless $e->checkauth;
1052 my $user = $e->retrieve_actor_user($id) or return $e->event;
1053 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1058 __PACKAGE__->register_method(
1059 method => "get_org_types",
1060 api_name => "open-ils.actor.org_types.retrieve",
1063 return $U->get_org_types();
1067 __PACKAGE__->register_method(
1068 method => "get_user_ident_types",
1069 api_name => "open-ils.actor.user.ident_types.retrieve",
1072 sub get_user_ident_types {
1073 return $ident_types if $ident_types;
1074 return $ident_types =
1075 new_editor()->retrieve_all_config_identification_type();
1079 __PACKAGE__->register_method(
1080 method => "get_org_unit",
1081 api_name => "open-ils.actor.org_unit.retrieve",
1085 my( $self, $client, $user_session, $org_id ) = @_;
1086 my $e = new_editor(authtoken => $user_session);
1088 return $e->event unless $e->checkauth;
1089 $org_id = $e->requestor->ws_ou;
1091 my $o = $e->retrieve_actor_org_unit($org_id)
1092 or return $e->event;
1096 __PACKAGE__->register_method(
1097 method => "search_org_unit",
1098 api_name => "open-ils.actor.org_unit_list.search",
1101 sub search_org_unit {
1103 my( $self, $client, $field, $value ) = @_;
1105 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1107 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1108 { $field => $value } );
1114 # build the org tree
1116 __PACKAGE__->register_method(
1117 method => "get_org_tree",
1118 api_name => "open-ils.actor.org_tree.retrieve",
1120 note => "Returns the entire org tree structure",
1126 return $U->get_org_tree($client->session->session_locale);
1130 __PACKAGE__->register_method(
1131 method => "get_org_descendants",
1132 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1135 # depth is optional. org_unit is the id
1136 sub get_org_descendants {
1137 my( $self, $client, $org_unit, $depth ) = @_;
1139 if(ref $org_unit eq 'ARRAY') {
1142 for my $i (0..scalar(@$org_unit)-1) {
1143 my $list = $U->simple_scalar_request(
1145 "open-ils.storage.actor.org_unit.descendants.atomic",
1146 $org_unit->[$i], $depth->[$i] );
1147 push(@trees, $U->build_org_tree($list));
1152 my $orglist = $apputils->simple_scalar_request(
1154 "open-ils.storage.actor.org_unit.descendants.atomic",
1155 $org_unit, $depth );
1156 return $U->build_org_tree($orglist);
1161 __PACKAGE__->register_method(
1162 method => "get_org_ancestors",
1163 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1166 # depth is optional. org_unit is the id
1167 sub get_org_ancestors {
1168 my( $self, $client, $org_unit, $depth ) = @_;
1169 my $orglist = $apputils->simple_scalar_request(
1171 "open-ils.storage.actor.org_unit.ancestors.atomic",
1172 $org_unit, $depth );
1173 return $U->build_org_tree($orglist);
1177 __PACKAGE__->register_method(
1178 method => "get_standings",
1179 api_name => "open-ils.actor.standings.retrieve"
1184 return $user_standings if $user_standings;
1185 return $user_standings =
1186 $apputils->simple_scalar_request(
1188 "open-ils.cstore.direct.config.standing.search.atomic",
1189 { id => { "!=" => undef } }
1194 __PACKAGE__->register_method(
1195 method => "get_my_org_path",
1196 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1199 sub get_my_org_path {
1200 my( $self, $client, $auth, $org_id ) = @_;
1201 my $e = new_editor(authtoken=>$auth);
1202 return $e->event unless $e->checkauth;
1203 $org_id = $e->requestor->ws_ou unless defined $org_id;
1205 return $apputils->simple_scalar_request(
1207 "open-ils.storage.actor.org_unit.full_path.atomic",
1212 __PACKAGE__->register_method(
1213 method => "patron_adv_search",
1214 api_name => "open-ils.actor.patron.search.advanced"
1216 sub patron_adv_search {
1217 my( $self, $client, $auth, $search_hash,
1218 $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1220 my $e = new_editor(authtoken=>$auth);
1221 return $e->event unless $e->checkauth;
1222 return $e->event unless $e->allowed('VIEW_USER');
1223 return $U->storagereq(
1224 "open-ils.storage.actor.user.crazy_search", $search_hash,
1225 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1229 __PACKAGE__->register_method(
1230 method => "update_passwd",
1231 api_name => "open-ils.actor.user.password.update",
1233 desc => "Update the operator's password",
1235 { desc => 'Authentication token', type => 'string' },
1236 { desc => 'New password', type => 'string' },
1237 { desc => 'Current password', type => 'string' }
1239 return => {desc => '1 on success, Event on error or incorrect current password'}
1243 __PACKAGE__->register_method(
1244 method => "update_passwd",
1245 api_name => "open-ils.actor.user.username.update",
1247 desc => "Update the operator's username",
1249 { desc => 'Authentication token', type => 'string' },
1250 { desc => 'New username', type => 'string' }
1252 return => {desc => '1 on success, Event on error'}
1256 __PACKAGE__->register_method(
1257 method => "update_passwd",
1258 api_name => "open-ils.actor.user.email.update",
1260 desc => "Update the operator's email address",
1262 { desc => 'Authentication token', type => 'string' },
1263 { desc => 'New email address', type => 'string' }
1265 return => {desc => '1 on success, Event on error'}
1270 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1271 my $e = new_editor(xact=>1, authtoken=>$auth);
1272 return $e->die_event unless $e->checkauth;
1274 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1275 or return $e->die_event;
1276 my $api = $self->api_name;
1278 if( $api =~ /password/o ) {
1279 # make sure the original password matches the in-database password
1280 if (md5_hex($orig_pw) ne $db_user->passwd) {
1282 return new OpenILS::Event('INCORRECT_PASSWORD');
1284 $db_user->passwd($new_val);
1288 # if we don't clear the password, the user will be updated with
1289 # a hashed version of the hashed version of their password
1290 $db_user->clear_passwd;
1292 if( $api =~ /username/o ) {
1294 # make sure no one else has this username
1295 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1298 return new OpenILS::Event('USERNAME_EXISTS');
1300 $db_user->usrname($new_val);
1302 } elsif( $api =~ /email/o ) {
1303 $db_user->email($new_val);
1307 $e->update_actor_user($db_user) or return $e->die_event;
1314 __PACKAGE__->register_method(
1315 method => "check_user_perms",
1316 api_name => "open-ils.actor.user.perm.check",
1317 notes => <<" NOTES");
1318 Takes a login session, user id, an org id, and an array of perm type strings. For each
1319 perm type, if the user does *not* have the given permission it is added
1320 to a list which is returned from the method. If all permissions
1321 are allowed, an empty list is returned
1322 if the logged in user does not match 'user_id', then the logged in user must
1323 have VIEW_PERMISSION priveleges.
1326 sub check_user_perms {
1327 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1329 my( $staff, $evt ) = $apputils->checkses($login_session);
1330 return $evt if $evt;
1332 if($staff->id ne $user_id) {
1333 if( $evt = $apputils->check_perms(
1334 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1340 for my $perm (@$perm_types) {
1341 if($apputils->check_perms($user_id, $org_id, $perm)) {
1342 push @not_allowed, $perm;
1346 return \@not_allowed
1349 __PACKAGE__->register_method(
1350 method => "check_user_perms2",
1351 api_name => "open-ils.actor.user.perm.check.multi_org",
1353 Checks the permissions on a list of perms and orgs for a user
1354 @param authtoken The login session key
1355 @param user_id The id of the user to check
1356 @param orgs The array of org ids
1357 @param perms The array of permission names
1358 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1359 if the logged in user does not match 'user_id', then the logged in user must
1360 have VIEW_PERMISSION priveleges.
1363 sub check_user_perms2 {
1364 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1366 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1367 $authtoken, $user_id, 'VIEW_PERMISSION' );
1368 return $evt if $evt;
1371 for my $org (@$orgs) {
1372 for my $perm (@$perms) {
1373 if($apputils->check_perms($user_id, $org, $perm)) {
1374 push @not_allowed, [ $org, $perm ];
1379 return \@not_allowed
1383 __PACKAGE__->register_method(
1384 method => 'check_user_perms3',
1385 api_name => 'open-ils.actor.user.perm.highest_org',
1387 Returns the highest org unit id at which a user has a given permission
1388 If the requestor does not match the target user, the requestor must have
1389 'VIEW_PERMISSION' rights at the home org unit of the target user
1390 @param authtoken The login session key
1391 @param userid The id of the user in question
1392 @param perm The permission to check
1393 @return The org unit highest in the org tree within which the user has
1394 the requested permission
1397 sub check_user_perms3 {
1398 my($self, $client, $authtoken, $user_id, $perm) = @_;
1399 my $e = new_editor(authtoken=>$authtoken);
1400 return $e->event unless $e->checkauth;
1402 my $tree = $U->get_org_tree();
1404 unless($e->requestor->id == $user_id) {
1405 my $user = $e->retrieve_actor_user($user_id)
1406 or return $e->event;
1407 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1408 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1411 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1414 __PACKAGE__->register_method(
1415 method => 'user_has_work_perm_at',
1416 api_name => 'open-ils.actor.user.has_work_perm_at',
1420 Returns a set of org unit IDs which represent the highest orgs in
1421 the org tree where the user has the requested permission. The
1422 purpose of this method is to return the smallest set of org units
1423 which represent the full expanse of the user's ability to perform
1424 the requested action. The user whose perms this method should
1425 check is implied by the authtoken. /,
1427 {desc => 'authtoken', type => 'string'},
1428 {desc => 'permission name', type => 'string'},
1429 {desc => q/user id, optional. If present, check perms for
1430 this user instead of the logged in user/, type => 'number'},
1432 return => {desc => 'An array of org IDs'}
1436 sub user_has_work_perm_at {
1437 my($self, $conn, $auth, $perm, $user_id) = @_;
1438 my $e = new_editor(authtoken=>$auth);
1439 return $e->event unless $e->checkauth;
1440 if(defined $user_id) {
1441 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1442 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1444 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1447 __PACKAGE__->register_method(
1448 method => 'user_has_work_perm_at_batch',
1449 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1453 sub user_has_work_perm_at_batch {
1454 my($self, $conn, $auth, $perms, $user_id) = @_;
1455 my $e = new_editor(authtoken=>$auth);
1456 return $e->event unless $e->checkauth;
1457 if(defined $user_id) {
1458 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1459 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1462 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1468 __PACKAGE__->register_method(
1469 method => 'check_user_perms4',
1470 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1472 Returns the highest org unit id at which a user has a given permission
1473 If the requestor does not match the target user, the requestor must have
1474 'VIEW_PERMISSION' rights at the home org unit of the target user
1475 @param authtoken The login session key
1476 @param userid The id of the user in question
1477 @param perms An array of perm names to check
1478 @return An array of orgId's representing the org unit
1479 highest in the org tree within which the user has the requested permission
1480 The arrah of orgId's has matches the order of the perms array
1483 sub check_user_perms4 {
1484 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1486 my( $staff, $target, $org, $evt );
1488 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1489 $authtoken, $userid, 'VIEW_PERMISSION' );
1490 return $evt if $evt;
1493 return [] unless ref($perms);
1494 my $tree = $U->get_org_tree();
1496 for my $p (@$perms) {
1497 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1503 __PACKAGE__->register_method(
1504 method => "user_fines_summary",
1505 api_name => "open-ils.actor.user.fines.summary",
1508 desc => 'Returns a short summary of the users total open fines, ' .
1509 'excluding voided fines Params are login_session, user_id' ,
1511 {desc => 'Authentication token', type => 'string'},
1512 {desc => 'User ID', type => 'string'} # number?
1515 desc => "a 'mous' object, event on error",
1520 sub user_fines_summary {
1521 my( $self, $client, $auth, $user_id ) = @_;
1523 my $e = new_editor(authtoken=>$auth);
1524 return $e->event unless $e->checkauth;
1526 if( $user_id ne $e->requestor->id ) {
1527 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1528 return $e->event unless
1529 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1532 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1536 __PACKAGE__->register_method(
1537 method => "user_opac_vitals",
1538 api_name => "open-ils.actor.user.opac.vital_stats",
1542 desc => 'Returns a short summary of the users vital stats, including ' .
1543 'identification information, accumulated balance, number of holds, ' .
1544 'and current open circulation stats' ,
1546 {desc => 'Authentication token', type => 'string'},
1547 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1550 desc => "An object with four properties: user, fines, checkouts and holds."
1555 sub user_opac_vitals {
1556 my( $self, $client, $auth, $user_id ) = @_;
1558 my $e = new_editor(authtoken=>$auth);
1559 return $e->event unless $e->checkauth;
1561 $user_id ||= $e->requestor->id;
1563 my $user = $e->retrieve_actor_user( $user_id );
1566 ->method_lookup('open-ils.actor.user.fines.summary')
1567 ->run($auth => $user_id);
1568 return $fines if (defined($U->event_code($fines)));
1571 $fines = new Fieldmapper::money::open_user_summary ();
1572 $fines->balance_owed(0.00);
1573 $fines->total_owed(0.00);
1574 $fines->total_paid(0.00);
1575 $fines->usr($user_id);
1579 ->method_lookup('open-ils.actor.user.hold_requests.count')
1580 ->run($auth => $user_id);
1581 return $holds if (defined($U->event_code($holds)));
1584 ->method_lookup('open-ils.actor.user.checked_out.count')
1585 ->run($auth => $user_id);
1586 return $out if (defined($U->event_code($out)));
1590 first_given_name => $user->first_given_name,
1591 second_given_name => $user->second_given_name,
1592 family_name => $user->family_name,
1593 alias => $user->alias,
1594 usrname => $user->usrname
1596 fines => $fines->to_bare_hash,
1603 ##### a small consolidation of related method registrations
1604 my $common_params = [
1605 { desc => 'Authentication token', type => 'string' },
1606 { desc => 'User ID', type => 'string' },
1607 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1608 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1611 'open-ils.actor.user.transactions' => '',
1612 'open-ils.actor.user.transactions.fleshed' => '',
1613 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1614 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1615 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1616 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1619 foreach (keys %methods) {
1621 method => "user_transactions",
1624 desc => 'For a given user, retrieve a list of '
1625 . (/\.fleshed/ ? 'fleshed ' : '')
1626 . 'transactions' . $methods{$_}
1627 . ' optionally limited to transactions of a given type.',
1628 params => $common_params,
1630 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1631 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1635 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1636 __PACKAGE__->register_method(%args);
1639 # Now for the counts
1641 'open-ils.actor.user.transactions.count' => '',
1642 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1643 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1646 foreach (keys %methods) {
1648 method => "user_transactions",
1651 desc => 'For a given user, retrieve a count of open '
1652 . 'transactions' . $methods{$_}
1653 . ' optionally limited to transactions of a given type.',
1654 params => $common_params,
1655 return => { desc => "Integer count of transactions, or event on error" }
1658 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1659 __PACKAGE__->register_method(%args);
1662 __PACKAGE__->register_method(
1663 method => "user_transactions",
1664 api_name => "open-ils.actor.user.transactions.have_balance.total",
1667 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1668 . ' optionally limited to transactions of a given type.',
1669 params => $common_params,
1670 return => { desc => "Decimal balance value, or event on error" }
1675 sub user_transactions {
1676 my( $self, $client, $login_session, $user_id, $type, $options ) = @_;
1679 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1680 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1681 return $evt if $evt;
1683 my $api = $self->api_name();
1685 my $filter = ($api =~ /have_balance/o) ?
1686 { 'balance_owed' => { '<>' => 0 } }:
1687 { 'total_owed' => { '>' => 0 } };
1689 my ($trans) = $self->method_lookup(
1690 'open-ils.actor.user.transactions.history.still_open')
1691 ->run( $login_session, $user_id, $type, $filter, $options );
1693 if($api =~ /total/o) {
1695 for my $t (@$trans) {
1696 $total += $t->balance_owed;
1699 $logger->debug("Total balance owed by user $user_id: $total");
1703 ($api =~ /count/o ) and return scalar @$trans;
1704 ($api !~ /fleshed/o) and return $trans;
1707 for my $t (@$trans) {
1709 if( $t->xact_type ne 'circulation' ) {
1710 push @resp, {transaction => $t};
1714 my $circ = $apputils->simple_scalar_request(
1716 "open-ils.cstore.direct.action.circulation.retrieve",
1721 my $title = $apputils->simple_scalar_request(
1723 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1724 $circ->target_copy );
1728 my $u = OpenILS::Utils::ModsParser->new();
1729 $u->start_mods_batch($title->marc());
1730 my $mods = $u->finish_mods_batch();
1731 $mods->doc_id($title->id) if $mods;
1733 push @resp, {transaction => $t, circ => $circ, record => $mods };
1741 __PACKAGE__->register_method(
1742 method => "user_transaction_retrieve",
1743 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1745 notes => "Returns a fleshed transaction record"
1748 __PACKAGE__->register_method(
1749 method => "user_transaction_retrieve",
1750 api_name => "open-ils.actor.user.transaction.retrieve",
1752 notes => "Returns a transaction record"
1755 sub user_transaction_retrieve {
1756 my( $self, $client, $login_session, $bill_id ) = @_;
1758 # I think I'm deprecated... make sure. phasefx says, "No, I'll use you :)
1760 my $trans = $apputils->simple_scalar_request(
1762 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1766 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1767 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1768 return $evt if $evt;
1770 my $api = $self->api_name();
1771 if($api !~ /fleshed/o) { return $trans; }
1773 if( $trans->xact_type ne 'circulation' ) {
1774 $logger->debug("Returning non-circ transaction");
1775 return {transaction => $trans};
1778 my $circ = $apputils->simple_scalar_request(
1780 "open-ils.cstore.direct.action.circulation.retrieve",
1783 return {transaction => $trans} unless $circ;
1784 $logger->debug("Found the circ transaction");
1786 my $title = $apputils->simple_scalar_request(
1788 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1789 $circ->target_copy );
1791 return {transaction => $trans, circ => $circ } unless $title;
1792 $logger->debug("Found the circ title");
1795 my $copy = $apputils->simple_scalar_request(
1797 "open-ils.cstore.direct.asset.copy.retrieve",
1798 $circ->target_copy );
1801 my $u = OpenILS::Utils::ModsParser->new();
1802 $u->start_mods_batch($title->marc());
1803 $mods = $u->finish_mods_batch();
1805 if ($title->id == OILS_PRECAT_RECORD) {
1806 $mods = new Fieldmapper::metabib::virtual_record;
1807 $mods->doc_id(OILS_PRECAT_RECORD);
1808 $mods->title($copy->dummy_title);
1809 $mods->author($copy->dummy_author);
1813 $logger->debug("MODSized the circ title");
1815 return {transaction => $trans, circ => $circ, record => $mods, copy => $copy };
1819 __PACKAGE__->register_method(
1820 method => "hold_request_count",
1821 api_name => "open-ils.actor.user.hold_requests.count",
1824 notes => 'Returns hold ready/total counts'
1827 sub hold_request_count {
1828 my( $self, $client, $login_session, $userid ) = @_;
1830 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1831 $login_session, $userid, 'VIEW_HOLD' );
1832 return $evt if $evt;
1835 my $holds = $apputils->simple_scalar_request(
1837 "open-ils.cstore.direct.action.hold_request.search.atomic",
1840 fulfillment_time => {"=" => undef },
1841 cancel_time => undef,
1846 for my $h (@$holds) {
1847 next unless $h->capture_time and $h->current_copy;
1849 my $copy = $apputils->simple_scalar_request(
1851 "open-ils.cstore.direct.asset.copy.retrieve",
1855 if ($copy and $copy->status == 8) {
1860 return { total => scalar(@$holds), ready => scalar(@ready) };
1863 __PACKAGE__->register_method(
1864 method => "checked_out",
1865 api_name => "open-ils.actor.user.checked_out",
1869 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1870 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1871 . "(i.e., outstanding balance or some other pending action on the circ). "
1872 . "The .count method also includes a 'total' field which sums all open circs.",
1874 { desc => 'Authentication Token', type => 'string'},
1875 { desc => 'User ID', type => 'string'},
1878 desc => 'Returns event on error, or an object with ID lists, like: '
1879 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1884 __PACKAGE__->register_method(
1885 method => "checked_out",
1886 api_name => "open-ils.actor.user.checked_out.count",
1889 signature => q/@see open-ils.actor.user.checked_out/
1893 my( $self, $conn, $auth, $userid ) = @_;
1895 my $e = new_editor(authtoken=>$auth);
1896 return $e->event unless $e->checkauth;
1898 if( $userid ne $e->requestor->id ) {
1899 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1900 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1902 # see if there is a friend link allowing circ.view perms
1903 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1904 $e, $userid, $e->requestor->id, 'circ.view');
1905 return $e->event unless $allowed;
1909 my $count = $self->api_name =~ /count/;
1910 return _checked_out( $count, $e, $userid );
1914 my( $iscount, $e, $userid ) = @_;
1920 claims_returned => [],
1923 my $meth = 'retrieve_action_open_circ_';
1931 claims_returned => 0,
1938 my $data = $e->$meth($userid);
1942 $result{$_} += $data->$_() for (keys %result);
1943 $result{total} += $data->$_() for (keys %result);
1945 for my $k (keys %result) {
1946 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
1956 __PACKAGE__->register_method(
1957 method => "checked_in_with_fines",
1958 api_name => "open-ils.actor.user.checked_in_with_fines",
1961 signature => q/@see open-ils.actor.user.checked_out/
1964 sub checked_in_with_fines {
1965 my( $self, $conn, $auth, $userid ) = @_;
1967 my $e = new_editor(authtoken=>$auth);
1968 return $e->event unless $e->checkauth;
1970 if( $userid ne $e->requestor->id ) {
1971 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1974 # money is owed on these items and they are checked in
1975 my $open = $e->search_action_circulation(
1978 xact_finish => undef,
1979 checkin_time => { "!=" => undef },
1984 my( @lost, @cr, @lo );
1985 for my $c (@$open) {
1986 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1987 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1988 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1993 claims_returned => \@cr,
1994 long_overdue => \@lo
2000 my ($api, $desc, $auth) = @_;
2001 $desc = $desc ? (" " . $desc) : '';
2002 my $ids = ($api =~ /ids$/) ? 1 : 0;
2005 method => "user_transaction_history",
2006 api_name => "open-ils.actor.user.transactions.$api",
2008 desc => "For a given User ID, returns a list of billable transaction" .
2009 ($ids ? " id" : '') .
2010 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2011 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2013 {desc => 'Authentication token', type => 'string'},
2014 {desc => 'User ID', type => 'number'},
2015 {desc => 'Transaction type (optional)', type => 'number'},
2016 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2019 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2023 $auth and push @sig, (authoritative => 1);
2027 my %hist_methods = (
2029 'history.have_charge' => 'that have an initial charge',
2030 'history.still_open' => 'that are not finished',
2032 my %auth_hist_methods = (
2033 'history.have_balance' => 'that have a balance',
2034 'history.have_bill' => 'that have billings',
2035 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2037 foreach (keys %hist_methods) {
2038 __PACKAGE__->register_method(_sigmaker($_, $hist_methods{$_}));
2039 __PACKAGE__->register_method(_sigmaker("$_.ids", $hist_methods{$_}));
2041 foreach (keys %auth_hist_methods) {
2042 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2043 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2046 sub user_transaction_history {
2047 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2051 # run inside of a transaction to prevent replication delays
2052 my $e = new_editor(authtoken=>$auth);
2053 return $e->die_event unless $e->checkauth;
2055 if ($e->requestor->id ne $userid) {
2056 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2059 my $api = $self->api_name;
2060 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2062 if(defined($type)) {
2063 $filter->{'xact_type'} = $type;
2066 if($api =~ /have_bill_or_payment/o) {
2068 # transactions that have a non-zero sum across all billings or at least 1 payment
2069 $filter->{'-or'} = {
2070 'balance_owed' => { '<>' => 0 },
2071 'last_payment_ts' => { '<>' => undef }
2074 } elsif( $api =~ /have_balance/o) {
2076 # transactions that have a non-zero overall balance
2077 $filter->{'balance_owed'} = { '<>' => 0 };
2079 } elsif( $api =~ /have_charge/o) {
2081 # transactions that have at least 1 billing, regardless of whether it was voided
2082 $filter->{'last_billing_ts'} = { '<>' => undef };
2084 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2086 # transactions that have non-zero sum across all billings. This will exclude
2087 # xacts where all billings have been voided
2088 $filter->{'total_owed'} = { '<>' => 0 };
2091 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2092 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2093 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2095 my $mbts = $e->search_money_billable_transaction_summary(
2097 { usr => $userid, @xact_finish, %$filter },
2102 if ($api =~ /\.ids/) {
2103 return [map {$_->id} @$mbts];
2111 __PACKAGE__->register_method(
2112 method => "user_perms",
2113 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2115 notes => "Returns a list of permissions"
2119 my( $self, $client, $authtoken, $user ) = @_;
2121 my( $staff, $evt ) = $apputils->checkses($authtoken);
2122 return $evt if $evt;
2124 $user ||= $staff->id;
2126 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2130 return $apputils->simple_scalar_request(
2132 "open-ils.storage.permission.user_perms.atomic",
2136 __PACKAGE__->register_method(
2137 method => "retrieve_perms",
2138 api_name => "open-ils.actor.permissions.retrieve",
2139 notes => "Returns a list of permissions"
2141 sub retrieve_perms {
2142 my( $self, $client ) = @_;
2143 return $apputils->simple_scalar_request(
2145 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2146 { id => { '!=' => undef } }
2150 __PACKAGE__->register_method(
2151 method => "retrieve_groups",
2152 api_name => "open-ils.actor.groups.retrieve",
2153 notes => "Returns a list of user groups"
2155 sub retrieve_groups {
2156 my( $self, $client ) = @_;
2157 return new_editor()->retrieve_all_permission_grp_tree();
2160 __PACKAGE__->register_method(
2161 method => "retrieve_org_address",
2162 api_name => "open-ils.actor.org_unit.address.retrieve",
2163 notes => <<' NOTES');
2164 Returns an org_unit address by ID
2165 @param An org_address ID
2167 sub retrieve_org_address {
2168 my( $self, $client, $id ) = @_;
2169 return $apputils->simple_scalar_request(
2171 "open-ils.cstore.direct.actor.org_address.retrieve",
2176 __PACKAGE__->register_method(
2177 method => "retrieve_groups_tree",
2178 api_name => "open-ils.actor.groups.tree.retrieve",
2179 notes => "Returns a list of user groups"
2182 sub retrieve_groups_tree {
2183 my( $self, $client ) = @_;
2184 return new_editor()->search_permission_grp_tree(
2189 flesh_fields => { pgt => ["children"] },
2190 order_by => { pgt => 'name'}
2197 __PACKAGE__->register_method(
2198 method => "add_user_to_groups",
2199 api_name => "open-ils.actor.user.set_groups",
2200 notes => "Adds a user to one or more permission groups"
2203 sub add_user_to_groups {
2204 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2206 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2207 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2208 return $evt if $evt;
2210 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2211 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2212 return $evt if $evt;
2214 $apputils->simplereq(
2216 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2218 for my $group (@$groups) {
2219 my $link = Fieldmapper::permission::usr_grp_map->new;
2221 $link->usr($userid);
2223 my $id = $apputils->simplereq(
2225 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2231 __PACKAGE__->register_method(
2232 method => "get_user_perm_groups",
2233 api_name => "open-ils.actor.user.get_groups",
2234 notes => "Retrieve a user's permission groups."
2238 sub get_user_perm_groups {
2239 my( $self, $client, $authtoken, $userid ) = @_;
2241 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2242 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2243 return $evt if $evt;
2245 return $apputils->simplereq(
2247 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2251 __PACKAGE__->register_method(
2252 method => "get_user_work_ous",
2253 api_name => "open-ils.actor.user.get_work_ous",
2254 notes => "Retrieve a user's work org units."
2257 __PACKAGE__->register_method(
2258 method => "get_user_work_ous",
2259 api_name => "open-ils.actor.user.get_work_ous.ids",
2260 notes => "Retrieve a user's work org units."
2263 sub get_user_work_ous {
2264 my( $self, $client, $auth, $userid ) = @_;
2265 my $e = new_editor(authtoken=>$auth);
2266 return $e->event unless $e->checkauth;
2267 $userid ||= $e->requestor->id;
2269 if($e->requestor->id != $userid) {
2270 my $user = $e->retrieve_actor_user($userid)
2271 or return $e->event;
2272 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2275 return $e->search_permission_usr_work_ou_map({usr => $userid})
2276 unless $self->api_name =~ /.ids$/;
2278 # client just wants a list of org IDs
2279 return $U->get_user_work_ou_ids($e, $userid);
2284 __PACKAGE__->register_method(
2285 method => 'register_workstation',
2286 api_name => 'open-ils.actor.workstation.register.override',
2287 signature => q/@see open-ils.actor.workstation.register/
2290 __PACKAGE__->register_method(
2291 method => 'register_workstation',
2292 api_name => 'open-ils.actor.workstation.register',
2294 Registers a new workstion in the system
2295 @param authtoken The login session key
2296 @param name The name of the workstation id
2297 @param owner The org unit that owns this workstation
2298 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2299 if the name is already in use.
2303 sub register_workstation {
2304 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2306 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2307 return $e->die_event unless $e->checkauth;
2308 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2309 my $existing = $e->search_actor_workstation({name => $name})->[0];
2313 if( $self->api_name =~ /override/o ) {
2314 # workstation with the given name exists.
2316 if($owner ne $existing->owning_lib) {
2317 # if necessary, update the owning_lib of the workstation
2319 $logger->info("changing owning lib of workstation ".$existing->id.
2320 " from ".$existing->owning_lib." to $owner");
2321 return $e->die_event unless
2322 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2324 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2326 $existing->owning_lib($owner);
2327 return $e->die_event unless $e->update_actor_workstation($existing);
2333 "attempt to register an existing workstation. returning existing ID");
2336 return $existing->id;
2339 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2343 my $ws = Fieldmapper::actor::workstation->new;
2344 $ws->owning_lib($owner);
2346 $e->create_actor_workstation($ws) or return $e->die_event;
2348 return $ws->id; # note: editor sets the id on the new object for us
2351 __PACKAGE__->register_method(
2352 method => 'workstation_list',
2353 api_name => 'open-ils.actor.workstation.list',
2355 Returns a list of workstations registered at the given location
2356 @param authtoken The login session key
2357 @param ids A list of org_unit.id's for the workstation owners
2361 sub workstation_list {
2362 my( $self, $conn, $authtoken, @orgs ) = @_;
2364 my $e = new_editor(authtoken=>$authtoken);
2365 return $e->event unless $e->checkauth;
2370 unless $e->allowed('REGISTER_WORKSTATION', $o);
2371 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2377 __PACKAGE__->register_method(
2378 method => 'fetch_patron_note',
2379 api_name => 'open-ils.actor.note.retrieve.all',
2382 Returns a list of notes for a given user
2383 Requestor must have VIEW_USER permission if pub==false and
2384 @param authtoken The login session key
2385 @param args Hash of params including
2386 patronid : the patron's id
2387 pub : true if retrieving only public notes
2391 sub fetch_patron_note {
2392 my( $self, $conn, $authtoken, $args ) = @_;
2393 my $patronid = $$args{patronid};
2395 my($reqr, $evt) = $U->checkses($authtoken);
2396 return $evt if $evt;
2399 ($patron, $evt) = $U->fetch_user($patronid);
2400 return $evt if $evt;
2403 if( $patronid ne $reqr->id ) {
2404 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2405 return $evt if $evt;
2407 return $U->cstorereq(
2408 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2409 { usr => $patronid, pub => 't' } );
2412 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2413 return $evt if $evt;
2415 return $U->cstorereq(
2416 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2419 __PACKAGE__->register_method(
2420 method => 'create_user_note',
2421 api_name => 'open-ils.actor.note.create',
2423 Creates a new note for the given user
2424 @param authtoken The login session key
2425 @param note The note object
2428 sub create_user_note {
2429 my( $self, $conn, $authtoken, $note ) = @_;
2430 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2431 return $e->die_event unless $e->checkauth;
2433 my $user = $e->retrieve_actor_user($note->usr)
2434 or return $e->die_event;
2436 return $e->die_event unless
2437 $e->allowed('UPDATE_USER',$user->home_ou);
2439 $note->creator($e->requestor->id);
2440 $e->create_actor_usr_note($note) or return $e->die_event;
2446 __PACKAGE__->register_method(
2447 method => 'delete_user_note',
2448 api_name => 'open-ils.actor.note.delete',
2450 Deletes a note for the given user
2451 @param authtoken The login session key
2452 @param noteid The note id
2455 sub delete_user_note {
2456 my( $self, $conn, $authtoken, $noteid ) = @_;
2458 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2459 return $e->die_event unless $e->checkauth;
2460 my $note = $e->retrieve_actor_usr_note($noteid)
2461 or return $e->die_event;
2462 my $user = $e->retrieve_actor_user($note->usr)
2463 or return $e->die_event;
2464 return $e->die_event unless
2465 $e->allowed('UPDATE_USER', $user->home_ou);
2467 $e->delete_actor_usr_note($note) or return $e->die_event;
2473 __PACKAGE__->register_method(
2474 method => 'update_user_note',
2475 api_name => 'open-ils.actor.note.update',
2477 @param authtoken The login session key
2478 @param note The note
2482 sub update_user_note {
2483 my( $self, $conn, $auth, $note ) = @_;
2484 my $e = new_editor(authtoken=>$auth, xact=>1);
2485 return $e->die_event unless $e->checkauth;
2486 my $patron = $e->retrieve_actor_user($note->usr)
2487 or return $e->die_event;
2488 return $e->die_event unless
2489 $e->allowed('UPDATE_USER', $patron->home_ou);
2490 $e->update_actor_user_note($note)
2491 or return $e->die_event;
2498 __PACKAGE__->register_method(
2499 method => 'create_closed_date',
2500 api_name => 'open-ils.actor.org_unit.closed_date.create',
2502 Creates a new closing entry for the given org_unit
2503 @param authtoken The login session key
2504 @param note The closed_date object
2507 sub create_closed_date {
2508 my( $self, $conn, $authtoken, $cd ) = @_;
2510 my( $user, $evt ) = $U->checkses($authtoken);
2511 return $evt if $evt;
2513 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2514 return $evt if $evt;
2516 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2518 my $id = $U->storagereq(
2519 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2520 return $U->DB_UPDATE_FAILED($cd) unless $id;
2525 __PACKAGE__->register_method(
2526 method => 'delete_closed_date',
2527 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2529 Deletes a closing entry for the given org_unit
2530 @param authtoken The login session key
2531 @param noteid The close_date id
2534 sub delete_closed_date {
2535 my( $self, $conn, $authtoken, $cd ) = @_;
2537 my( $user, $evt ) = $U->checkses($authtoken);
2538 return $evt if $evt;
2541 ($cd_obj, $evt) = fetch_closed_date($cd);
2542 return $evt if $evt;
2544 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2545 return $evt if $evt;
2547 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2549 my $stat = $U->storagereq(
2550 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2551 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2556 __PACKAGE__->register_method(
2557 method => 'usrname_exists',
2558 api_name => 'open-ils.actor.username.exists',
2560 desc => 'Check if a username is already taken (by an undeleted patron)',
2562 {desc => 'Authentication token', type => 'string'},
2563 {desc => 'Username', type => 'string'}
2566 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2571 sub usrname_exists {
2572 my( $self, $conn, $auth, $usrname ) = @_;
2573 my $e = new_editor(authtoken=>$auth);
2574 return $e->event unless $e->checkauth;
2575 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2576 return $$a[0] if $a and @$a;
2580 __PACKAGE__->register_method(
2581 method => 'barcode_exists',
2582 api_name => 'open-ils.actor.barcode.exists',
2584 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2587 sub barcode_exists {
2588 my( $self, $conn, $auth, $barcode ) = @_;
2589 my $e = new_editor(authtoken=>$auth);
2590 return $e->event unless $e->checkauth;
2591 my $card = $e->search_actor_card({barcode => $barcode});
2597 #return undef unless @$card;
2598 #return $card->[0]->usr;
2602 __PACKAGE__->register_method(
2603 method => 'retrieve_net_levels',
2604 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2607 sub retrieve_net_levels {
2608 my( $self, $conn, $auth ) = @_;
2609 my $e = new_editor(authtoken=>$auth);
2610 return $e->event unless $e->checkauth;
2611 return $e->retrieve_all_config_net_access_level();
2614 # Retain the old typo API name just in case
2615 __PACKAGE__->register_method(
2616 method => 'fetch_org_by_shortname',
2617 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2619 __PACKAGE__->register_method(
2620 method => 'fetch_org_by_shortname',
2621 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2623 sub fetch_org_by_shortname {
2624 my( $self, $conn, $sname ) = @_;
2625 my $e = new_editor();
2626 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2627 return $e->event unless $org;
2632 __PACKAGE__->register_method(
2633 method => 'session_home_lib',
2634 api_name => 'open-ils.actor.session.home_lib',
2637 sub session_home_lib {
2638 my( $self, $conn, $auth ) = @_;
2639 my $e = new_editor(authtoken=>$auth);
2640 return undef unless $e->checkauth;
2641 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2642 return $org->shortname;
2645 __PACKAGE__->register_method(
2646 method => 'session_safe_token',
2647 api_name => 'open-ils.actor.session.safe_token',
2649 Returns a hashed session ID that is safe for export to the world.
2650 This safe token will expire after 1 hour of non-use.
2651 @param auth Active authentication token
2655 sub session_safe_token {
2656 my( $self, $conn, $auth ) = @_;
2657 my $e = new_editor(authtoken=>$auth);
2658 return undef unless $e->checkauth;
2660 my $safe_token = md5_hex($auth);
2662 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2664 # Add more like the following if needed...
2666 "safe-token-home_lib-shortname-$safe_token",
2667 $e->retrieve_actor_org_unit(
2668 $e->requestor->home_ou
2677 __PACKAGE__->register_method(
2678 method => 'safe_token_home_lib',
2679 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2681 Returns the home library shortname from the session
2682 asscociated with a safe token from generated by
2683 open-ils.actor.session.safe_token.
2684 @param safe_token Active safe token
2688 sub safe_token_home_lib {
2689 my( $self, $conn, $safe_token ) = @_;
2691 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2692 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2697 __PACKAGE__->register_method(
2698 method => 'slim_tree',
2699 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2702 my $tree = new_editor()->search_actor_org_unit(
2704 {"parent_ou" => undef },
2707 flesh_fields => { aou => ['children'] },
2708 order_by => { aou => 'name'},
2709 select => { aou => ["id","shortname", "name"]},
2714 return trim_tree($tree);
2720 return undef unless $tree;
2722 code => $tree->shortname,
2723 name => $tree->name,
2725 if( $tree->children and @{$tree->children} ) {
2726 $htree->{children} = [];
2727 for my $c (@{$tree->children}) {
2728 push( @{$htree->{children}}, trim_tree($c) );
2736 __PACKAGE__->register_method(
2737 method => "update_penalties",
2738 api_name => "open-ils.actor.user.penalties.update"
2741 sub update_penalties {
2742 my($self, $conn, $auth, $user_id) = @_;
2743 my $e = new_editor(authtoken=>$auth, xact => 1);
2744 return $e->die_event unless $e->checkauth;
2745 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2746 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2747 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2748 return $evt if $evt;
2754 __PACKAGE__->register_method(
2755 method => "apply_penalty",
2756 api_name => "open-ils.actor.user.penalty.apply"
2760 my($self, $conn, $auth, $penalty) = @_;
2762 my $e = new_editor(authtoken=>$auth, xact => 1);
2763 return $e->die_event unless $e->checkauth;
2765 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2766 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2768 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2771 (defined $ptype->org_depth) ?
2772 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2775 $penalty->org_unit($ctx_org);
2776 $penalty->staff($e->requestor->id);
2777 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2780 return $penalty->id;
2783 __PACKAGE__->register_method(
2784 method => "remove_penalty",
2785 api_name => "open-ils.actor.user.penalty.remove"
2788 sub remove_penalty {
2789 my($self, $conn, $auth, $penalty) = @_;
2790 my $e = new_editor(authtoken=>$auth, xact => 1);
2791 return $e->die_event unless $e->checkauth;
2792 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2793 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2795 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2800 __PACKAGE__->register_method(
2801 method => "update_penalty_note",
2802 api_name => "open-ils.actor.user.penalty.note.update"
2805 sub update_penalty_note {
2806 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2807 my $e = new_editor(authtoken=>$auth, xact => 1);
2808 return $e->die_event unless $e->checkauth;
2809 for my $penalty_id (@$penalty_ids) {
2810 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2811 if (! $penalty ) { return $e->die_event; }
2812 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2813 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2815 $penalty->note( $note ); $penalty->ischanged( 1 );
2817 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2823 __PACKAGE__->register_method(
2824 method => "ranged_penalty_thresholds",
2825 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2829 sub ranged_penalty_thresholds {
2830 my($self, $conn, $auth, $context_org) = @_;
2831 my $e = new_editor(authtoken=>$auth);
2832 return $e->event unless $e->checkauth;
2833 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2834 my $list = $e->search_permission_grp_penalty_threshold([
2835 {org_unit => $U->get_org_ancestors($context_org)},
2836 {order_by => {pgpt => 'id'}}
2838 $conn->respond($_) for @$list;
2844 __PACKAGE__->register_method(
2845 method => "user_retrieve_fleshed_by_id",
2847 api_name => "open-ils.actor.user.fleshed.retrieve",
2850 sub user_retrieve_fleshed_by_id {
2851 my( $self, $client, $auth, $user_id, $fields ) = @_;
2852 my $e = new_editor(authtoken => $auth);
2853 return $e->event unless $e->checkauth;
2855 if( $e->requestor->id != $user_id ) {
2856 return $e->event unless $e->allowed('VIEW_USER');
2862 "standing_penalties",
2866 "stat_cat_entries" ];
2867 return new_flesh_user($user_id, $fields, $e);
2871 sub new_flesh_user {
2874 my $fields = shift || [];
2877 my $fetch_penalties = 0;
2878 if(grep {$_ eq 'standing_penalties'} @$fields) {
2879 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2880 $fetch_penalties = 1;
2883 my $user = $e->retrieve_actor_user(
2888 "flesh_fields" => { "au" => $fields }
2891 ) or return $e->die_event;
2894 if( grep { $_ eq 'addresses' } @$fields ) {
2896 $user->addresses([]) unless @{$user->addresses};
2897 # don't expose "replaced" addresses by default
2898 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2900 if( ref $user->billing_address ) {
2901 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2902 push( @{$user->addresses}, $user->billing_address );
2906 if( ref $user->mailing_address ) {
2907 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2908 push( @{$user->addresses}, $user->mailing_address );
2913 if($fetch_penalties) {
2914 # grab the user penalties ranged for this location
2915 $user->standing_penalties(
2916 $e->search_actor_user_standing_penalty([
2919 {stop_date => undef},
2920 {stop_date => {'>' => 'now'}}
2922 org_unit => $U->get_org_ancestors($e->requestor->ws_ou)
2925 flesh_fields => {ausp => ['standing_penalty']}
2932 $user->clear_passwd();
2939 __PACKAGE__->register_method(
2940 method => "user_retrieve_parts",
2941 api_name => "open-ils.actor.user.retrieve.parts",
2944 sub user_retrieve_parts {
2945 my( $self, $client, $auth, $user_id, $fields ) = @_;
2946 my $e = new_editor(authtoken => $auth);
2947 return $e->event unless $e->checkauth;
2948 $user_id ||= $e->requestor->id;
2949 if( $e->requestor->id != $user_id ) {
2950 return $e->event unless $e->allowed('VIEW_USER');
2953 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2954 push(@resp, $user->$_()) for(@$fields);
2960 __PACKAGE__->register_method(
2961 method => 'user_opt_in_enabled',
2962 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2963 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2966 sub user_opt_in_enabled {
2967 my($self, $conn) = @_;
2968 my $sc = OpenSRF::Utils::SettingsClient->new;
2969 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2974 __PACKAGE__->register_method(
2975 method => 'user_opt_in_at_org',
2976 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2978 @param $auth The auth token
2979 @param user_id The ID of the user to test
2980 @return 1 if the user has opted in at the specified org,
2981 event on error, and 0 otherwise. /
2983 sub user_opt_in_at_org {
2984 my($self, $conn, $auth, $user_id) = @_;
2986 # see if we even need to enforce the opt-in value
2987 return 1 unless user_opt_in_enabled($self);
2989 my $e = new_editor(authtoken => $auth);
2990 return $e->event unless $e->checkauth;
2991 my $org_id = $e->requestor->ws_ou;
2993 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2994 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2996 # user is automatically opted-in at the home org
2997 return 1 if $user->home_ou eq $org_id;
2999 my $vals = $e->search_actor_usr_org_unit_opt_in(
3000 {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
3006 __PACKAGE__->register_method(
3007 method => 'create_user_opt_in_at_org',
3008 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3010 @param $auth The auth token
3011 @param user_id The ID of the user to test
3012 @return The ID of the newly created object, event on error./
3015 sub create_user_opt_in_at_org {
3016 my($self, $conn, $auth, $user_id) = @_;
3018 my $e = new_editor(authtoken => $auth, xact=>1);
3019 return $e->die_event unless $e->checkauth;
3020 my $org_id = $e->requestor->ws_ou;
3022 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3023 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3025 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3027 $opt_in->org_unit($org_id);
3028 $opt_in->usr($user_id);
3029 $opt_in->staff($e->requestor->id);
3030 $opt_in->opt_in_ts('now');
3031 $opt_in->opt_in_ws($e->requestor->wsid);
3033 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3034 or return $e->die_event;
3042 __PACKAGE__->register_method (
3043 method => 'retrieve_org_hours',
3044 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3046 Returns the hours of operation for a specified org unit
3047 @param authtoken The login session key
3048 @param org_id The org_unit ID
3052 sub retrieve_org_hours {
3053 my($self, $conn, $auth, $org_id) = @_;
3054 my $e = new_editor(authtoken => $auth);
3055 return $e->die_event unless $e->checkauth;
3056 $org_id ||= $e->requestor->ws_ou;
3057 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3061 __PACKAGE__->register_method (
3062 method => 'verify_user_password',
3063 api_name => 'open-ils.actor.verify_user_password',
3065 Given a barcode or username and the MD5 encoded password,
3066 returns 1 if the password is correct. Returns 0 otherwise.
3070 sub verify_user_password {
3071 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3072 my $e = new_editor(authtoken => $auth);
3073 return $e->die_event unless $e->checkauth;
3075 my $user_by_barcode;
3076 my $user_by_username;
3078 my $card = $e->search_actor_card([
3079 {barcode => $barcode},
3080 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3081 $user_by_barcode = $card->usr;
3082 $user = $user_by_barcode;
3085 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3086 $user = $user_by_username;
3088 return 0 if (!$user);
3089 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3090 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3091 return 1 if $user->passwd eq $password;
3095 __PACKAGE__->register_method (
3096 method => 'retrieve_usr_id_via_barcode_or_usrname',
3097 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3099 Given a barcode or username returns the id for the user or
3104 sub retrieve_usr_id_via_barcode_or_usrname {
3105 my($self, $conn, $auth, $barcode, $username) = @_;
3106 my $e = new_editor(authtoken => $auth);
3107 return $e->die_event unless $e->checkauth;
3108 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3110 my $user_by_barcode;
3111 my $user_by_username;
3112 $logger->info("$id_as_barcode is the ID as BARCODE");
3114 my $card = $e->search_actor_card([
3115 {barcode => $barcode},
3116 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3117 if ($id_as_barcode =~ /^t/i) {
3119 $user = $e->retrieve_actor_user($barcode);
3120 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3122 $user_by_barcode = $card->usr;
3123 $user = $user_by_barcode;
3126 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3127 $user_by_barcode = $card->usr;
3128 $user = $user_by_barcode;
3133 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3135 $user = $user_by_username;
3137 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3138 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3139 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3144 __PACKAGE__->register_method (
3145 method => 'merge_users',
3146 api_name => 'open-ils.actor.user.merge',
3149 Given a list of source users and destination user, transfer all data from the source
3150 to the dest user and delete the source user. All user related data is
3151 transferred, including circulations, holds, bookbags, etc.
3157 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3158 my $e = new_editor(xact => 1, authtoken => $auth);
3159 return $e->die_event unless $e->checkauth;
3161 # disallow the merge if any subordinate accounts are in collections
3162 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3163 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3165 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3166 my $del_addrs = ($U->ou_ancestor_setting_value(
3167 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3168 my $del_cards = ($U->ou_ancestor_setting_value(
3169 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3170 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3171 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3173 for my $src_id (@$user_ids) {
3174 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3176 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3177 if($src_user->home_ou ne $master_user->home_ou) {
3178 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3181 return $e->die_event unless
3182 $e->json_query({from => [
3197 __PACKAGE__->register_method (
3198 method => 'approve_user_address',
3199 api_name => 'open-ils.actor.user.pending_address.approve',
3206 sub approve_user_address {
3207 my($self, $conn, $auth, $addr) = @_;
3208 my $e = new_editor(xact => 1, authtoken => $auth);
3209 return $e->die_event unless $e->checkauth;
3211 # if the caller passes an address object, assume they want to
3212 # update it first before approving it
3213 $e->update_actor_user_address($addr) or return $e->die_event;
3215 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3217 my $user = $e->retrieve_actor_user($addr->usr);
3218 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3219 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3220 or return $e->die_event;
3222 return [values %$result]->[0];
3226 __PACKAGE__->register_method (
3227 method => 'retrieve_friends',
3228 api_name => 'open-ils.actor.friends.retrieve',
3231 returns { confirmed: [], pending_out: [], pending_in: []}
3232 pending_out are users I'm requesting friendship with
3233 pending_in are users requesting friendship with me
3238 sub retrieve_friends {
3239 my($self, $conn, $auth, $user_id, $options) = @_;
3240 my $e = new_editor(authtoken => $auth);
3241 return $e->event unless $e->checkauth;
3242 $user_id ||= $e->requestor->id;
3244 if($user_id != $e->requestor->id) {
3245 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3246 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3249 return OpenILS::Application::Actor::Friends->retrieve_friends(
3250 $e, $user_id, $options);
3255 __PACKAGE__->register_method (
3256 method => 'apply_friend_perms',
3257 api_name => 'open-ils.actor.friends.perms.apply',
3263 sub apply_friend_perms {
3264 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3265 my $e = new_editor(authtoken => $auth, xact => 1);
3266 return $e->die_event unless $e->checkauth;
3268 if($user_id != $e->requestor->id) {
3269 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3270 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3273 for my $perm (@perms) {
3275 OpenILS::Application::Actor::Friends->apply_friend_perm(
3276 $e, $user_id, $delegate_id, $perm);
3277 return $evt if $evt;
3285 __PACKAGE__->register_method (
3286 method => 'update_user_pending_address',
3287 api_name => 'open-ils.actor.user.address.pending.cud'
3290 sub update_user_pending_address {
3291 my($self, $conn, $auth, $addr) = @_;
3292 my $e = new_editor(authtoken => $auth, xact => 1);
3293 return $e->die_event unless $e->checkauth;
3295 if($addr->usr != $e->requestor->id) {
3296 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3297 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3301 $e->create_actor_user_address($addr) or return $e->die_event;
3302 } elsif($addr->isdeleted) {
3303 $e->delete_actor_user_address($addr) or return $e->die_event;
3305 $e->update_actor_user_address($addr) or return $e->die_event;
3313 __PACKAGE__->register_method (
3314 method => 'user_events',
3315 api_name => 'open-ils.actor.user.events.circ',
3318 __PACKAGE__->register_method (
3319 method => 'user_events',
3320 api_name => 'open-ils.actor.user.events.ahr',
3325 my($self, $conn, $auth, $user_id, $filters) = @_;
3326 my $e = new_editor(authtoken => $auth);
3327 return $e->event unless $e->checkauth;
3329 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3330 my $user_field = 'usr';
3333 $filters->{target} = {
3334 select => { $obj_type => ['id'] },
3336 where => {usr => $user_id}
3339 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3340 if($e->requestor->id != $user_id) {
3341 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3344 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3345 my $req = $ses->request('open-ils.trigger.events_by_target',
3346 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3348 while(my $resp = $req->recv) {
3349 my $val = $resp->content;
3350 my $tgt = $val->target;
3352 if($obj_type eq 'circ') {
3353 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3355 } elsif($obj_type eq 'ahr') {
3356 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3357 if $tgt->current_copy;
3360 $conn->respond($val) if $val;
3366 __PACKAGE__->register_method (
3367 method => 'copy_events',
3368 api_name => 'open-ils.actor.copy.events.circ',
3371 __PACKAGE__->register_method (
3372 method => 'copy_events',
3373 api_name => 'open-ils.actor.copy.events.ahr',
3378 my($self, $conn, $auth, $copy_id, $filters) = @_;
3379 my $e = new_editor(authtoken => $auth);
3380 return $e->event unless $e->checkauth;
3382 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3384 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3386 my $copy_field = 'target_copy';
3387 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3390 $filters->{target} = {
3391 select => { $obj_type => ['id'] },
3393 where => {$copy_field => $copy_id}
3397 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3398 my $req = $ses->request('open-ils.trigger.events_by_target',
3399 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3401 while(my $resp = $req->recv) {
3402 my $val = $resp->content;
3403 my $tgt = $val->target;
3405 my $user = $e->retrieve_actor_user($tgt->usr);
3406 if($e->requestor->id != $user->id) {
3407 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3410 $tgt->$copy_field($copy);
3413 $conn->respond($val) if $val;
3422 __PACKAGE__->register_method (
3423 method => 'update_events',
3424 api_name => 'open-ils.actor.user.event.cancel.batch',
3427 __PACKAGE__->register_method (
3428 method => 'update_events',
3429 api_name => 'open-ils.actor.user.event.reset.batch',
3434 my($self, $conn, $auth, $event_ids) = @_;
3435 my $e = new_editor(xact => 1, authtoken => $auth);
3436 return $e->die_event unless $e->checkauth;
3439 for my $id (@$event_ids) {
3441 # do a little dance to determine what user we are ultimately affecting
3442 my $event = $e->retrieve_action_trigger_event([
3445 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3447 ]) or return $e->die_event;
3450 if($event->event_def->hook->core_type eq 'circ') {
3451 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3452 } elsif($event->event_def->hook->core_type eq 'ahr') {
3453 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3458 my $user = $e->retrieve_actor_user($user_id);
3459 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3461 if($self->api_name =~ /cancel/) {
3462 $event->state('invalid');
3463 } elsif($self->api_name =~ /reset/) {
3464 $event->clear_start_time;
3465 $event->clear_update_time;
3466 $event->state('pending');
3469 $e->update_action_trigger_event($event) or return $e->die_event;
3470 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3474 return {complete => 1};
3478 __PACKAGE__->register_method (
3479 method => 'really_delete_user',
3480 api_name => 'open-ils.actor.user.delete',
3482 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3483 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3484 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3485 dest_usr_id is only required when deleting a user that performs staff functions.
3489 sub really_delete_user {
3490 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3491 my $e = new_editor(authtoken => $auth, xact => 1);
3492 return $e->die_event unless $e->checkauth;
3493 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3494 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3495 my $stat = $e->json_query(
3496 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3497 or return $e->die_event;
3504 __PACKAGE__->register_method (
3505 method => 'user_payments',
3506 api_name => 'open-ils.actor.user.payments.retrieve',
3509 Returns all payments for a given user. Default order is newest payments first.
3510 @param auth Authentication token
3511 @param user_id The user ID
3512 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3517 my($self, $conn, $auth, $user_id, $filters) = @_;
3520 my $e = new_editor(authtoken => $auth);
3521 return $e->die_event unless $e->checkauth;
3523 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3524 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3526 # Find all payments for all transactions for user $user_id
3528 select => {mp => ['id']},
3533 select => {mbt => ['id']},
3535 where => {usr => $user_id}
3539 order_by => [{ # by default, order newest payments first
3541 field => 'payment_ts',
3546 for (qw/order_by limit offset/) {
3547 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3550 if(defined $filters->{where}) {
3551 foreach (keys %{$filters->{where}}) {
3552 # don't allow the caller to expand the result set to other users
3553 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3557 my $payment_ids = $e->json_query($query);
3558 for my $pid (@$payment_ids) {
3559 my $pay = $e->retrieve_money_payment([
3564 mbt => ['summary', 'circulation', 'grocery'],
3565 circ => ['target_copy'],
3566 acp => ['call_number'],
3574 xact_type => $pay->xact->summary->xact_type,
3575 last_billing_type => $pay->xact->summary->last_billing_type,
3578 if($pay->xact->summary->xact_type eq 'circulation') {
3579 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3580 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3583 $pay->xact($pay->xact->id); # de-flesh
3584 $conn->respond($resp);
3592 __PACKAGE__->register_method (
3593 method => 'negative_balance_users',
3594 api_name => 'open-ils.actor.users.negative_balance',
3597 Returns all users that have an overall negative balance
3598 @param auth Authentication token
3599 @param org_id The context org unit as an ID or list of IDs. This will be the home
3600 library of the user. If no org_unit is specified, no org unit filter is applied
3604 sub negative_balance_users {
3605 my($self, $conn, $auth, $org_id) = @_;
3607 my $e = new_editor(authtoken => $auth);
3608 return $e->die_event unless $e->checkauth;
3609 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3613 mous => ['usr', 'balance_owed'],
3616 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3617 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3634 where => {'+mous' => {balance_owed => {'<' => 0}}}
3637 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3639 my $list = $e->json_query($query, {timeout => 600});
3641 for my $data (@$list) {
3643 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3644 balance_owed => $data->{balance_owed},
3645 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3652 __PACKAGE__->register_method(
3653 method => "request_password_reset",
3654 api_name => "open-ils.actor.patron.password_reset.request",
3656 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3657 "method for changing a user's password. The UUID token is distributed via A/T " .
3658 "templates (i.e. email to the user).",
3660 { desc => 'user_id_type', type => 'string' },
3661 { desc => 'user_id', type => 'string' },
3662 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3664 return => {desc => '1 on success, Event on error'}
3667 sub request_password_reset {
3668 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3670 # Check to see if password reset requests are already being throttled:
3671 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3673 my $e = new_editor(xact => 1);
3676 # Get the user, if any, depending on the input value
3677 if ($user_id_type eq 'username') {
3678 $user = $e->search_actor_user({usrname => $user_id})->[0];
3681 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3683 } elsif ($user_id_type eq 'barcode') {
3684 my $card = $e->search_actor_card([
3685 {barcode => $user_id},
3686 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3689 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3694 # If the user doesn't have an email address, we can't help them
3695 if (!$user->email) {
3697 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3700 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3701 if ($email_must_match) {
3702 if ($user->email ne $email) {
3703 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3707 _reset_password_request($conn, $e, $user);
3710 # Once we have the user, we can issue the password reset request
3711 # XXX Add a wrapper method that accepts barcode + email input
3712 sub _reset_password_request {
3713 my ($conn, $e, $user) = @_;
3715 # 1. Get throttle threshold and time-to-live from OU_settings
3716 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3717 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3719 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3721 # 2. Get time of last request and number of active requests (num_active)
3722 my $active_requests = $e->json_query({
3728 transform => 'COUNT'
3731 column => 'request_time',
3737 has_been_reset => { '=' => 'f' },
3738 request_time => { '>' => $threshold_time }
3742 # Guard against no active requests
3743 if ($active_requests->[0]->{'request_time'}) {
3744 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3745 my $now = DateTime::Format::ISO8601->new();
3747 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3748 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3749 ($last_request->add_duration('1 minute') > $now)) {
3750 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3752 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3756 # TODO Check to see if the user is in a password-reset-restricted group
3758 # Otherwise, go ahead and try to get the user.
3760 # Check the number of active requests for this user
3761 $active_requests = $e->json_query({
3767 transform => 'COUNT'
3772 usr => { '=' => $user->id },
3773 has_been_reset => { '=' => 'f' },
3774 request_time => { '>' => $threshold_time }
3778 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3780 # if less than or equal to per-user threshold, proceed; otherwise, return event
3781 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3782 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3784 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3787 # Create the aupr object and insert into the database
3788 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3789 my $uuid = create_uuid_as_string(UUID_V4);
3790 $reset_request->uuid($uuid);
3791 $reset_request->usr($user->id);
3793 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3796 # Create an event to notify user of the URL to reset their password
3798 # Can we stuff this in the user_data param for trigger autocreate?
3799 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3801 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3802 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3805 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3810 __PACKAGE__->register_method(
3811 method => "commit_password_reset",
3812 api_name => "open-ils.actor.patron.password_reset.commit",
3814 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3815 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3816 "with the supplied password.",
3818 { desc => 'uuid', type => 'string' },
3819 { desc => 'password', type => 'string' },
3821 return => {desc => '1 on success, Event on error'}
3824 sub commit_password_reset {
3825 my($self, $conn, $uuid, $password) = @_;
3827 # Check to see if password reset requests are already being throttled:
3828 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3829 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3830 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3832 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3835 my $e = new_editor(xact => 1);
3837 my $aupr = $e->search_actor_usr_password_reset({
3844 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3846 my $user_id = $aupr->[0]->usr;
3847 my $user = $e->retrieve_actor_user($user_id);
3849 # Ensure we're still within the TTL for the request
3850 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3851 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3852 if ($threshold < DateTime->now(time_zone => 'local')) {
3854 $logger->info("Password reset request needed to be submitted before $threshold");
3855 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3858 # Check complexity of password against OU-defined regex
3859 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3863 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3864 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3865 $is_strong = check_password_strength_custom($password, $pw_regex);
3867 $is_strong = check_password_strength_default($password);
3872 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3875 # All is well; update the password
3876 $user->passwd($password);
3877 $e->update_actor_user($user);
3879 # And flag that this password reset request has been honoured
3880 $aupr->[0]->has_been_reset('t');
3881 $e->update_actor_usr_password_reset($aupr->[0]);
3887 sub check_password_strength_default {
3888 my $password = shift;
3889 # Use the default set of checks
3890 if ( (length($password) < 7) or
3891 ($password !~ m/.*\d+.*/) or
3892 ($password !~ m/.*[A-Za-z]+.*/)
3899 sub check_password_strength_custom {
3900 my ($password, $pw_regex) = @_;
3902 $pw_regex = qr/$pw_regex/;
3903 if ($password !~ /$pw_regex/) {
3911 __PACKAGE__->register_method(
3912 method => "event_def_opt_in_settings",
3913 api_name => "open-ils.actor.event_def.opt_in.settings",
3916 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3918 { desc => 'Authentication token', type => 'string'},
3920 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3925 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3932 sub event_def_opt_in_settings {
3933 my($self, $conn, $auth, $org_id) = @_;
3934 my $e = new_editor(authtoken => $auth);
3935 return $e->event unless $e->checkauth;
3937 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3938 return $e->event unless
3939 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3941 $org_id = $e->requestor->home_ou;
3944 # find all config.user_setting_type's related to event_defs for the requested org unit
3945 my $types = $e->json_query({
3946 select => {cust => ['name']},
3947 from => {atevdef => 'cust'},
3950 owner => $U->get_org_ancestors($org_id), # context org plus parents
3957 $conn->respond($_) for
3958 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
3965 __PACKAGE__->register_method(
3966 method => "user_visible_circs",
3967 api_name => "open-ils.actor.history.circ.visible",
3970 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
3972 { desc => 'Authentication token', type => 'string'},
3973 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3974 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3977 desc => q/An object with 2 fields: circulation and summary.
3978 circulation is the "circ" object. summary is the related "accs" object/,
3984 __PACKAGE__->register_method(
3985 method => "user_visible_circs",
3986 api_name => "open-ils.actor.history.circ.visible.print",
3989 desc => 'Returns printable output for the set of opt-in visible circulations',
3991 { desc => 'Authentication token', type => 'string'},
3992 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3993 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3996 desc => q/An action_trigger.event object or error event./,
4002 __PACKAGE__->register_method(
4003 method => "user_visible_circs",
4004 api_name => "open-ils.actor.history.circ.visible.email",
4007 desc => 'Emails the set of opt-in visible circulations to the requestor',
4009 { desc => 'Authentication token', type => 'string'},
4010 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4011 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4014 desc => q/undef, or event on error/
4019 __PACKAGE__->register_method(
4020 method => "user_visible_circs",
4021 api_name => "open-ils.actor.history.hold.visible",
4024 desc => 'Returns the set of opt-in visible holds',
4026 { desc => 'Authentication token', type => 'string'},
4027 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4028 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4031 desc => q/An object with 1 field: "hold"/,
4037 __PACKAGE__->register_method(
4038 method => "user_visible_circs",
4039 api_name => "open-ils.actor.history.hold.visible.print",
4042 desc => 'Returns printable output for the set of opt-in visible holds',
4044 { desc => 'Authentication token', type => 'string'},
4045 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4046 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4049 desc => q/An action_trigger.event object or error event./,
4055 __PACKAGE__->register_method(
4056 method => "user_visible_circs",
4057 api_name => "open-ils.actor.history.hold.visible.email",
4060 desc => 'Emails the set of opt-in visible holds to the requestor',
4062 { desc => 'Authentication token', type => 'string'},
4063 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4064 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4067 desc => q/undef, or event on error/
4072 sub user_visible_circs {
4073 my($self, $conn, $auth, $user_id, $options) = @_;
4075 my $is_hold = ($self->api_name =~ /hold/);
4076 my $for_print = ($self->api_name =~ /print/);
4077 my $for_email = ($self->api_name =~ /email/);
4078 my $e = new_editor(authtoken => $auth);
4079 return $e->event unless $e->checkauth;
4081 $user_id ||= $e->requestor->id;
4083 $options->{limit} ||= 50;
4084 $options->{offset} ||= 0;
4086 if($user_id != $e->requestor->id) {
4087 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4088 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4089 return $e->event unless $e->allowed($perm, $user->home_ou);
4092 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4094 my $data = $e->json_query({
4095 from => [$db_func, $user_id],
4096 limit => $$options{limit},
4097 offset => $$options{offset}
4099 # TODO: I only want IDs. code below didn't get me there
4100 # {"select":{"au":[{"column":"id", "result_field":"id",
4101 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4106 return undef unless @$data;
4110 # collect the batch of objects
4114 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4115 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4119 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4120 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4123 } elsif ($for_email) {
4125 $conn->respond_complete(1) if $for_email; # no sense in waiting
4133 my $hold = $e->retrieve_action_hold_request($id);
4134 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4135 # events will be fired from action_trigger_runner
4139 my $circ = $e->retrieve_action_circulation($id);
4140 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4141 # events will be fired from action_trigger_runner
4145 } else { # just give me the data please
4153 my $hold = $e->retrieve_action_hold_request($id);
4154 $conn->respond({hold => $hold});
4158 my $circ = $e->retrieve_action_circulation($id);
4161 summary => $U->create_circ_chain_summary($e, $id)
4170 __PACKAGE__->register_method(
4171 method => "user_saved_search_cud",
4172 api_name => "open-ils.actor.user.saved_search.cud",
4175 desc => 'Create/Update/Delete Access to user saved searches',
4177 { desc => 'Authentication token', type => 'string' },
4178 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4181 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4187 __PACKAGE__->register_method(
4188 method => "user_saved_search_cud",
4189 api_name => "open-ils.actor.user.saved_search.retrieve",
4192 desc => 'Retrieve a saved search object',
4194 { desc => 'Authentication token', type => 'string' },
4195 { desc => 'Saved Search ID', type => 'number' }
4198 desc => q/The saved search object, Event on error/,
4204 sub user_saved_search_cud {
4205 my( $self, $client, $auth, $search ) = @_;
4206 my $e = new_editor( authtoken=>$auth );
4207 return $e->die_event unless $e->checkauth;
4209 my $o_search; # prior version of the object, if any
4210 my $res; # to be returned
4212 # branch on the operation type
4214 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4216 # Get the old version, to check ownership
4217 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4218 or return $e->die_event;
4220 # You can't read somebody else's search
4221 return OpenILS::Event->new('BAD_PARAMS')
4222 unless $o_search->owner == $e->requestor->id;
4228 $e->xact_begin; # start an editor transaction
4230 if( $search->isnew ) { # Create
4232 # You can't create a search for somebody else
4233 return OpenILS::Event->new('BAD_PARAMS')
4234 unless $search->owner == $e->requestor->id;
4236 $e->create_actor_usr_saved_search( $search )
4237 or return $e->die_event;
4241 } elsif( $search->ischanged ) { # Update
4243 # You can't change ownership of a search
4244 return OpenILS::Event->new('BAD_PARAMS')
4245 unless $search->owner == $e->requestor->id;
4247 # Get the old version, to check ownership
4248 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4249 or return $e->die_event;
4251 # You can't update somebody else's search
4252 return OpenILS::Event->new('BAD_PARAMS')
4253 unless $o_search->owner == $e->requestor->id;
4256 $e->update_actor_usr_saved_search( $search )
4257 or return $e->die_event;
4261 } elsif( $search->isdeleted ) { # Delete
4263 # Get the old version, to check ownership
4264 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4265 or return $e->die_event;
4267 # You can't delete somebody else's search
4268 return OpenILS::Event->new('BAD_PARAMS')
4269 unless $o_search->owner == $e->requestor->id;
4272 $e->delete_actor_usr_saved_search( $o_search )
4273 or return $e->die_event;