1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use List::Util qw/max reduce/;
39 use UUID::Tiny qw/:std/;
42 OpenILS::Application::Actor::Container->initialize();
43 OpenILS::Application::Actor::UserGroups->initialize();
44 OpenILS::Application::Actor::ClosedDates->initialize();
47 my $apputils = "OpenILS::Application::AppUtils";
50 sub _d { warn "Patron:\n" . Dumper(shift()); }
53 my $set_user_settings;
57 #__PACKAGE__->register_method(
58 # method => "allowed_test",
59 # api_name => "open-ils.actor.allowed_test",
62 # my($self, $conn, $auth, $orgid, $permcode) = @_;
63 # my $e = new_editor(authtoken => $auth);
64 # return $e->die_event unless $e->checkauth;
68 # permcode => $permcode,
69 # result => $e->allowed($permcode, $orgid)
73 __PACKAGE__->register_method(
74 method => "update_user_setting",
75 api_name => "open-ils.actor.patron.settings.update",
77 sub update_user_setting {
78 my($self, $conn, $auth, $user_id, $settings) = @_;
79 my $e = new_editor(xact => 1, authtoken => $auth);
80 return $e->die_event unless $e->checkauth;
82 $user_id = $e->requestor->id unless defined $user_id;
84 unless($e->requestor->id == $user_id) {
85 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
86 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
89 for my $name (keys %$settings) {
90 my $val = $$settings{$name};
91 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
94 $val = OpenSRF::Utils::JSON->perl2JSON($val);
97 $e->update_actor_user_setting($set) or return $e->die_event;
99 $set = Fieldmapper::actor::user_setting->new;
103 $e->create_actor_user_setting($set) or return $e->die_event;
106 $e->delete_actor_user_setting($set) or return $e->die_event;
115 __PACKAGE__->register_method(
116 method => "set_ou_settings",
117 api_name => "open-ils.actor.org_unit.settings.update",
119 desc => "Updates the value for a given org unit setting. The permission to update " .
120 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
121 "permission specified in the update_perm column of the config.org_unit_setting_type " .
122 "table's row corresponding to the setting being changed." ,
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'Org unit ID', type => 'number'},
126 {desc => 'Hash of setting name-value pairs', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
132 sub set_ou_settings {
133 my( $self, $client, $auth, $org_id, $settings ) = @_;
135 my $e = new_editor(authtoken => $auth, xact => 1);
136 return $e->die_event unless $e->checkauth;
138 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
140 for my $name (keys %$settings) {
141 my $val = $$settings{$name};
143 my $type = $e->retrieve_config_org_unit_setting_type([
145 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
146 ]) or return $e->die_event;
147 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
149 # If there is no relevant permission, the default assumption will
150 # be, "no, the caller cannot change that value."
151 return $e->die_event unless ($all_allowed ||
152 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
155 $val = OpenSRF::Utils::JSON->perl2JSON($val);
158 $e->update_actor_org_unit_setting($set) or return $e->die_event;
160 $set = Fieldmapper::actor::org_unit_setting->new;
161 $set->org_unit($org_id);
164 $e->create_actor_org_unit_setting($set) or return $e->die_event;
167 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
175 __PACKAGE__->register_method(
176 method => "user_settings",
178 api_name => "open-ils.actor.patron.settings.retrieve",
181 my( $self, $client, $auth, $user_id, $setting ) = @_;
183 my $e = new_editor(authtoken => $auth);
184 return $e->event unless $e->checkauth;
185 $user_id = $e->requestor->id unless defined $user_id;
187 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
188 if($e->requestor->id != $user_id) {
189 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
193 my($e, $user_id, $setting) = @_;
194 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
195 return undef unless $val; # XXX this should really return undef, but needs testing
196 return OpenSRF::Utils::JSON->JSON2perl($val->value);
200 if(ref $setting eq 'ARRAY') {
202 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
205 return get_setting($e, $user_id, $setting);
208 my $s = $e->search_actor_user_setting({usr => $user_id});
209 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
214 __PACKAGE__->register_method(
215 method => "ranged_ou_settings",
216 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
218 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
219 "is implied for retrieving OU settings by the authenticated users' permissions.",
221 {desc => 'Authentication token', type => 'string'},
222 {desc => 'Org unit ID', type => 'number'},
224 return => {desc => 'A hashref of "ranged" settings, event on error'}
227 sub ranged_ou_settings {
228 my( $self, $client, $auth, $org_id ) = @_;
230 my $e = new_editor(authtoken => $auth);
231 return $e->event unless $e->checkauth;
234 my $org_list = $U->get_org_ancestors($org_id);
235 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
236 $org_list = [ reverse @$org_list ];
238 # start at the context org and capture the setting value
239 # without clobbering settings we've already captured
240 for my $this_org_id (@$org_list) {
242 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
244 for my $set (@sets) {
245 my $type = $e->retrieve_config_org_unit_setting_type([
247 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
250 # If there is no relevant permission, the default assumption will
251 # be, "yes, the caller can have that value."
252 if ($type && $type->view_perm) {
253 next if not $e->allowed($type->view_perm->code, $org_id);
256 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
257 unless defined $ranged_settings{$set->name};
261 return \%ranged_settings;
266 __PACKAGE__->register_method(
267 api_name => 'open-ils.actor.ou_setting.ancestor_default',
268 method => 'ou_ancestor_setting',
270 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
271 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
272 'user has permission to view that setting, if there is a permission associated with the setting.' ,
274 { desc => 'Org unit ID', type => 'number' },
275 { desc => 'setting name', type => 'string' },
276 { desc => 'authtoken (optional)', type => 'string' }
278 return => {desc => 'A value for the org unit setting, or undef'}
282 # ------------------------------------------------------------------
283 # Attempts to find the org setting value for a given org. if not
284 # found at the requested org, searches up the org tree until it
285 # finds a parent that has the requested setting.
286 # when found, returns { org => $id, value => $value }
287 # otherwise, returns NULL
288 # ------------------------------------------------------------------
289 sub ou_ancestor_setting {
290 my( $self, $client, $orgid, $name, $auth ) = @_;
291 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
294 __PACKAGE__->register_method(
295 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
296 method => 'ou_ancestor_setting_batch',
298 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
299 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
300 'user has permission to view that setting, if there is a permission associated with the setting.' ,
302 { desc => 'Org unit ID', type => 'number' },
303 { desc => 'setting name list', type => 'array' },
304 { desc => 'authtoken (optional)', type => 'string' }
306 return => {desc => 'A hash with name => value pairs for the org unit settings'}
309 sub ou_ancestor_setting_batch {
310 my( $self, $client, $orgid, $name_list, $auth ) = @_;
312 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
318 __PACKAGE__->register_method(
319 method => "update_patron",
320 api_name => "open-ils.actor.patron.update",
323 Update an existing user, or create a new one. Related objects,
324 like cards, addresses, survey responses, and stat cats,
325 can be updated by attaching them to the user object in their
326 respective fields. For examples, the billing address object
327 may be inserted into the 'billing_address' field, etc. For each
328 attached object, indicate if the object should be created,
329 updated, or deleted using the built-in 'isnew', 'ischanged',
330 and 'isdeleted' fields on the object.
333 { desc => 'Authentication token', type => 'string' },
334 { desc => 'Patron data object', type => 'object' }
336 return => {desc => 'A fleshed user object, event on error'}
341 my( $self, $client, $user_session, $patron ) = @_;
343 my $session = $apputils->start_db_session();
345 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
347 my( $user_obj, $evt ) = $U->checkses($user_session);
350 $evt = check_group_perm($session, $user_obj, $patron);
354 # $new_patron is the patron in progress. $patron is the original patron
355 # passed in with the method. new_patron will change as the components
356 # of patron are added/updated.
360 # unflesh the real items on the patron
361 $patron->card( $patron->card->id ) if(ref($patron->card));
362 $patron->billing_address( $patron->billing_address->id )
363 if(ref($patron->billing_address));
364 $patron->mailing_address( $patron->mailing_address->id )
365 if(ref($patron->mailing_address));
367 # create/update the patron first so we can use his id
368 if($patron->isnew()) {
369 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
371 } else { $new_patron = $patron; }
373 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
376 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
379 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
382 # re-update the patron if anything has happened to him during this process
383 if($new_patron->ischanged()) {
384 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
388 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
391 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
394 $apputils->commit_db_session($session);
396 $evt = apply_invalid_addr_penalty($patron);
399 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
401 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
403 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
406 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
409 sub apply_invalid_addr_penalty {
411 my $e = new_editor(xact => 1);
413 # grab the invalid address penalty if set
414 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
416 my ($addr_penalty) = grep
417 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
419 # do we enforce invalid address penalty
420 my $enforce = $U->ou_ancestor_setting_value(
421 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
423 my $addrs = $e->search_actor_user_address(
424 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
425 my $addr_count = scalar(@$addrs);
427 if($addr_count == 0 and $addr_penalty) {
429 # regardless of any settings, remove the penalty when the user has no invalid addresses
430 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
433 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
435 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
436 my $depth = $ptype->org_depth;
437 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
438 $ctx_org = $patron->home_ou unless defined $ctx_org;
440 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
441 $penalty->usr($patron->id);
442 $penalty->org_unit($ctx_org);
443 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
445 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
464 "standing_penalties",
470 push @$fields, "home_ou" if $home_ou;
471 return new_flesh_user($id, $fields, $e );
479 # clone and clear stuff that would break the database
483 my $new_patron = $patron->clone;
485 $new_patron->clear_billing_address();
486 $new_patron->clear_mailing_address();
487 $new_patron->clear_addresses();
488 $new_patron->clear_card();
489 $new_patron->clear_cards();
490 $new_patron->clear_id();
491 $new_patron->clear_isnew();
492 $new_patron->clear_ischanged();
493 $new_patron->clear_isdeleted();
494 $new_patron->clear_stat_cat_entries();
495 $new_patron->clear_permissions();
496 $new_patron->clear_standing_penalties();
506 my $user_obj = shift;
508 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
509 return (undef, $evt) if $evt;
511 my $ex = $session->request(
512 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
514 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
517 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
519 my $id = $session->request(
520 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
521 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
523 $logger->info("Successfully created new user [$id] in DB");
525 return ( $session->request(
526 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
530 sub check_group_perm {
531 my( $session, $requestor, $patron ) = @_;
534 # first let's see if the requestor has
535 # priveleges to update this user in any way
536 if( ! $patron->isnew ) {
537 my $p = $session->request(
538 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
540 # If we are the requestor (trying to update our own account)
541 # and we are not trying to change our profile, we're good
542 if( $p->id == $requestor->id and
543 $p->profile == $patron->profile ) {
548 $evt = group_perm_failed($session, $requestor, $p);
552 # They are allowed to edit this patron.. can they put the
553 # patron into the group requested?
554 $evt = group_perm_failed($session, $requestor, $patron);
560 sub group_perm_failed {
561 my( $session, $requestor, $patron ) = @_;
565 my $grpid = $patron->profile;
569 $logger->debug("user update looking for group perm for group $grpid");
570 $grp = $session->request(
571 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
572 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
574 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
576 $logger->info("user update checking perm $perm on user ".
577 $requestor->id." for update/create on user username=".$patron->usrname);
579 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
587 my( $session, $patron, $user_obj, $noperm) = @_;
589 $logger->info("Updating patron ".$patron->id." in DB");
594 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
595 return (undef, $evt) if $evt;
598 # update the password by itself to avoid the password protection magic
599 if( $patron->passwd ) {
600 my $s = $session->request(
601 'open-ils.storage.direct.actor.user.remote_update',
602 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
603 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
604 $patron->clear_passwd;
607 if(!$patron->ident_type) {
608 $patron->clear_ident_type;
609 $patron->clear_ident_value;
612 $evt = verify_last_xact($session, $patron);
613 return (undef, $evt) if $evt;
615 my $stat = $session->request(
616 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
617 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
622 sub verify_last_xact {
623 my( $session, $patron ) = @_;
624 return undef unless $patron->id and $patron->id > 0;
625 my $p = $session->request(
626 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
627 my $xact = $p->last_xact_id;
628 return undef unless $xact;
629 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
630 return OpenILS::Event->new('XACT_COLLISION')
631 if $xact ne $patron->last_xact_id;
636 sub _check_dup_ident {
637 my( $session, $patron ) = @_;
639 return undef unless $patron->ident_value;
642 ident_type => $patron->ident_type,
643 ident_value => $patron->ident_value,
646 $logger->debug("patron update searching for dup ident values: " .
647 $patron->ident_type . ':' . $patron->ident_value);
649 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
651 my $dups = $session->request(
652 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
655 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
662 sub _add_update_addresses {
666 my $new_patron = shift;
670 my $current_id; # id of the address before creation
672 my $addresses = $patron->addresses();
674 for my $address (@$addresses) {
676 next unless ref $address;
677 $current_id = $address->id();
679 if( $patron->billing_address() and
680 $patron->billing_address() == $current_id ) {
681 $logger->info("setting billing addr to $current_id");
682 $new_patron->billing_address($address->id());
683 $new_patron->ischanged(1);
686 if( $patron->mailing_address() and
687 $patron->mailing_address() == $current_id ) {
688 $new_patron->mailing_address($address->id());
689 $logger->info("setting mailing addr to $current_id");
690 $new_patron->ischanged(1);
694 if($address->isnew()) {
696 $address->usr($new_patron->id());
698 ($address, $evt) = _add_address($session,$address);
699 return (undef, $evt) if $evt;
701 # we need to get the new id
702 if( $patron->billing_address() and
703 $patron->billing_address() == $current_id ) {
704 $new_patron->billing_address($address->id());
705 $logger->info("setting billing addr to $current_id");
706 $new_patron->ischanged(1);
709 if( $patron->mailing_address() and
710 $patron->mailing_address() == $current_id ) {
711 $new_patron->mailing_address($address->id());
712 $logger->info("setting mailing addr to $current_id");
713 $new_patron->ischanged(1);
716 } elsif($address->ischanged() ) {
718 ($address, $evt) = _update_address($session, $address);
719 return (undef, $evt) if $evt;
721 } elsif($address->isdeleted() ) {
723 if( $address->id() == $new_patron->mailing_address() ) {
724 $new_patron->clear_mailing_address();
725 ($new_patron, $evt) = _update_patron($session, $new_patron);
726 return (undef, $evt) if $evt;
729 if( $address->id() == $new_patron->billing_address() ) {
730 $new_patron->clear_billing_address();
731 ($new_patron, $evt) = _update_patron($session, $new_patron);
732 return (undef, $evt) if $evt;
735 $evt = _delete_address($session, $address);
736 return (undef, $evt) if $evt;
740 return ( $new_patron, undef );
744 # adds an address to the db and returns the address with new id
746 my($session, $address) = @_;
747 $address->clear_id();
749 $logger->info("Creating new address at street ".$address->street1);
751 # put the address into the database
752 my $id = $session->request(
753 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
754 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
757 return ($address, undef);
761 sub _update_address {
762 my( $session, $address ) = @_;
764 $logger->info("Updating address ".$address->id." in the DB");
766 my $stat = $session->request(
767 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
769 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
770 return ($address, undef);
775 sub _add_update_cards {
779 my $new_patron = shift;
783 my $virtual_id; #id of the card before creation
785 my $cards = $patron->cards();
786 for my $card (@$cards) {
788 $card->usr($new_patron->id());
790 if(ref($card) and $card->isnew()) {
792 $virtual_id = $card->id();
793 ( $card, $evt ) = _add_card($session,$card);
794 return (undef, $evt) if $evt;
796 #if(ref($patron->card)) { $patron->card($patron->card->id); }
797 if($patron->card() == $virtual_id) {
798 $new_patron->card($card->id());
799 $new_patron->ischanged(1);
802 } elsif( ref($card) and $card->ischanged() ) {
803 $evt = _update_card($session, $card);
804 return (undef, $evt) if $evt;
808 return ( $new_patron, undef );
812 # adds an card to the db and returns the card with new id
814 my( $session, $card ) = @_;
817 $logger->info("Adding new patron card ".$card->barcode);
819 my $id = $session->request(
820 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
821 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
822 $logger->info("Successfully created patron card $id");
825 return ( $card, undef );
829 # returns event on error. returns undef otherwise
831 my( $session, $card ) = @_;
832 $logger->info("Updating patron card ".$card->id);
834 my $stat = $session->request(
835 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
836 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
843 # returns event on error. returns undef otherwise
844 sub _delete_address {
845 my( $session, $address ) = @_;
847 $logger->info("Deleting address ".$address->id." from DB");
849 my $stat = $session->request(
850 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
852 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
858 sub _add_survey_responses {
859 my ($session, $patron, $new_patron) = @_;
861 $logger->info( "Updating survey responses for patron ".$new_patron->id );
863 my $responses = $patron->survey_responses;
867 $_->usr($new_patron->id) for (@$responses);
869 my $evt = $U->simplereq( "open-ils.circ",
870 "open-ils.circ.survey.submit.user_id", $responses );
872 return (undef, $evt) if defined($U->event_code($evt));
876 return ( $new_patron, undef );
880 sub _create_stat_maps {
882 my($session, $user_session, $patron, $new_patron) = @_;
884 my $maps = $patron->stat_cat_entries();
886 for my $map (@$maps) {
888 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
890 if ($map->isdeleted()) {
891 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
893 } elsif ($map->isnew()) {
894 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
899 $map->target_usr($new_patron->id);
902 $logger->info("Updating stat entry with method $method and map $map");
904 my $stat = $session->request($method, $map)->gather(1);
905 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
909 return ($new_patron, undef);
912 sub _create_perm_maps {
914 my($session, $user_session, $patron, $new_patron) = @_;
916 my $maps = $patron->permissions;
918 for my $map (@$maps) {
920 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
921 if ($map->isdeleted()) {
922 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
923 } elsif ($map->isnew()) {
924 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
929 $map->usr($new_patron->id);
931 #warn( "Updating permissions with method $method and session $user_session and map $map" );
932 $logger->info( "Updating permissions with method $method and map $map" );
934 my $stat = $session->request($method, $map)->gather(1);
935 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
939 return ($new_patron, undef);
943 __PACKAGE__->register_method(
944 method => "set_user_work_ous",
945 api_name => "open-ils.actor.user.work_ous.update",
948 sub set_user_work_ous {
954 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
957 my $session = $apputils->start_db_session();
959 for my $map (@$maps) {
961 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
962 if ($map->isdeleted()) {
963 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
964 } elsif ($map->isnew()) {
965 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
969 #warn( "Updating permissions with method $method and session $ses and map $map" );
970 $logger->info( "Updating work_ou map with method $method and map $map" );
972 my $stat = $session->request($method, $map)->gather(1);
973 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
977 $apputils->commit_db_session($session);
979 return scalar(@$maps);
983 __PACKAGE__->register_method(
984 method => "set_user_perms",
985 api_name => "open-ils.actor.user.permissions.update",
994 my $session = $apputils->start_db_session();
996 my( $user_obj, $evt ) = $U->checkses($ses);
999 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1002 $all = 1 if ($U->is_true($user_obj->super_user()));
1003 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1005 for my $map (@$maps) {
1007 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1008 if ($map->isdeleted()) {
1009 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1010 } elsif ($map->isnew()) {
1011 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1015 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1016 #warn( "Updating permissions with method $method and session $ses and map $map" );
1017 $logger->info( "Updating permissions with method $method and map $map" );
1019 my $stat = $session->request($method, $map)->gather(1);
1020 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1024 $apputils->commit_db_session($session);
1026 return scalar(@$maps);
1030 __PACKAGE__->register_method(
1031 method => "user_retrieve_by_barcode",
1033 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1035 sub user_retrieve_by_barcode {
1036 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1038 my $e = new_editor(authtoken => $auth);
1039 return $e->event unless $e->checkauth;
1041 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1042 or return $e->event;
1044 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1045 return $e->event unless $e->allowed(
1046 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1053 __PACKAGE__->register_method(
1054 method => "get_user_by_id",
1056 api_name => "open-ils.actor.user.retrieve",
1059 sub get_user_by_id {
1060 my ($self, $client, $auth, $id) = @_;
1061 my $e = new_editor(authtoken=>$auth);
1062 return $e->event unless $e->checkauth;
1063 my $user = $e->retrieve_actor_user($id) or return $e->event;
1064 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1069 __PACKAGE__->register_method(
1070 method => "get_org_types",
1071 api_name => "open-ils.actor.org_types.retrieve",
1074 return $U->get_org_types();
1078 __PACKAGE__->register_method(
1079 method => "get_user_ident_types",
1080 api_name => "open-ils.actor.user.ident_types.retrieve",
1083 sub get_user_ident_types {
1084 return $ident_types if $ident_types;
1085 return $ident_types =
1086 new_editor()->retrieve_all_config_identification_type();
1090 __PACKAGE__->register_method(
1091 method => "get_org_unit",
1092 api_name => "open-ils.actor.org_unit.retrieve",
1096 my( $self, $client, $user_session, $org_id ) = @_;
1097 my $e = new_editor(authtoken => $user_session);
1099 return $e->event unless $e->checkauth;
1100 $org_id = $e->requestor->ws_ou;
1102 my $o = $e->retrieve_actor_org_unit($org_id)
1103 or return $e->event;
1107 __PACKAGE__->register_method(
1108 method => "search_org_unit",
1109 api_name => "open-ils.actor.org_unit_list.search",
1112 sub search_org_unit {
1114 my( $self, $client, $field, $value ) = @_;
1116 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1118 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1119 { $field => $value } );
1125 # build the org tree
1127 __PACKAGE__->register_method(
1128 method => "get_org_tree",
1129 api_name => "open-ils.actor.org_tree.retrieve",
1131 note => "Returns the entire org tree structure",
1137 return $U->get_org_tree($client->session->session_locale);
1141 __PACKAGE__->register_method(
1142 method => "get_org_descendants",
1143 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1146 # depth is optional. org_unit is the id
1147 sub get_org_descendants {
1148 my( $self, $client, $org_unit, $depth ) = @_;
1150 if(ref $org_unit eq 'ARRAY') {
1153 for my $i (0..scalar(@$org_unit)-1) {
1154 my $list = $U->simple_scalar_request(
1156 "open-ils.storage.actor.org_unit.descendants.atomic",
1157 $org_unit->[$i], $depth->[$i] );
1158 push(@trees, $U->build_org_tree($list));
1163 my $orglist = $apputils->simple_scalar_request(
1165 "open-ils.storage.actor.org_unit.descendants.atomic",
1166 $org_unit, $depth );
1167 return $U->build_org_tree($orglist);
1172 __PACKAGE__->register_method(
1173 method => "get_org_ancestors",
1174 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1177 # depth is optional. org_unit is the id
1178 sub get_org_ancestors {
1179 my( $self, $client, $org_unit, $depth ) = @_;
1180 my $orglist = $apputils->simple_scalar_request(
1182 "open-ils.storage.actor.org_unit.ancestors.atomic",
1183 $org_unit, $depth );
1184 return $U->build_org_tree($orglist);
1188 __PACKAGE__->register_method(
1189 method => "get_standings",
1190 api_name => "open-ils.actor.standings.retrieve"
1195 return $user_standings if $user_standings;
1196 return $user_standings =
1197 $apputils->simple_scalar_request(
1199 "open-ils.cstore.direct.config.standing.search.atomic",
1200 { id => { "!=" => undef } }
1205 __PACKAGE__->register_method(
1206 method => "get_my_org_path",
1207 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1210 sub get_my_org_path {
1211 my( $self, $client, $auth, $org_id ) = @_;
1212 my $e = new_editor(authtoken=>$auth);
1213 return $e->event unless $e->checkauth;
1214 $org_id = $e->requestor->ws_ou unless defined $org_id;
1216 return $apputils->simple_scalar_request(
1218 "open-ils.storage.actor.org_unit.full_path.atomic",
1223 __PACKAGE__->register_method(
1224 method => "patron_adv_search",
1225 api_name => "open-ils.actor.patron.search.advanced"
1227 sub patron_adv_search {
1228 my( $self, $client, $auth, $search_hash,
1229 $search_limit, $search_sort, $include_inactive, $search_ou ) = @_;
1231 my $e = new_editor(authtoken=>$auth);
1232 return $e->event unless $e->checkauth;
1233 return $e->event unless $e->allowed('VIEW_USER');
1235 # depth boundary outside of which patrons must opt-in, default to 0
1236 my $opt_boundary = 0;
1237 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1239 return $U->storagereq(
1240 "open-ils.storage.actor.user.crazy_search", $search_hash,
1241 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_ou, $opt_boundary);
1245 __PACKAGE__->register_method(
1246 method => "update_passwd",
1247 api_name => "open-ils.actor.user.password.update",
1249 desc => "Update the operator's password",
1251 { desc => 'Authentication token', type => 'string' },
1252 { desc => 'New password', type => 'string' },
1253 { desc => 'Current password', type => 'string' }
1255 return => {desc => '1 on success, Event on error or incorrect current password'}
1259 __PACKAGE__->register_method(
1260 method => "update_passwd",
1261 api_name => "open-ils.actor.user.username.update",
1263 desc => "Update the operator's username",
1265 { desc => 'Authentication token', type => 'string' },
1266 { desc => 'New username', type => 'string' },
1267 { desc => 'Current password', type => 'string' }
1269 return => {desc => '1 on success, Event on error or incorrect current password'}
1273 __PACKAGE__->register_method(
1274 method => "update_passwd",
1275 api_name => "open-ils.actor.user.email.update",
1277 desc => "Update the operator's email address",
1279 { desc => 'Authentication token', type => 'string' },
1280 { desc => 'New email address', type => 'string' },
1281 { desc => 'Current password', type => 'string' }
1283 return => {desc => '1 on success, Event on error or incorrect current password'}
1288 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1289 my $e = new_editor(xact=>1, authtoken=>$auth);
1290 return $e->die_event unless $e->checkauth;
1292 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1293 or return $e->die_event;
1294 my $api = $self->api_name;
1296 # make sure the original password matches the in-database password
1297 if (md5_hex($orig_pw) ne $db_user->passwd) {
1299 return new OpenILS::Event('INCORRECT_PASSWORD');
1302 if( $api =~ /password/o ) {
1304 $db_user->passwd($new_val);
1308 # if we don't clear the password, the user will be updated with
1309 # a hashed version of the hashed version of their password
1310 $db_user->clear_passwd;
1312 if( $api =~ /username/o ) {
1314 # make sure no one else has this username
1315 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1318 return new OpenILS::Event('USERNAME_EXISTS');
1320 $db_user->usrname($new_val);
1322 } elsif( $api =~ /email/o ) {
1323 $db_user->email($new_val);
1327 $e->update_actor_user($db_user) or return $e->die_event;
1330 # update the cached user to pick up these changes
1331 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1337 __PACKAGE__->register_method(
1338 method => "check_user_perms",
1339 api_name => "open-ils.actor.user.perm.check",
1340 notes => <<" NOTES");
1341 Takes a login session, user id, an org id, and an array of perm type strings. For each
1342 perm type, if the user does *not* have the given permission it is added
1343 to a list which is returned from the method. If all permissions
1344 are allowed, an empty list is returned
1345 if the logged in user does not match 'user_id', then the logged in user must
1346 have VIEW_PERMISSION priveleges.
1349 sub check_user_perms {
1350 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1352 my( $staff, $evt ) = $apputils->checkses($login_session);
1353 return $evt if $evt;
1355 if($staff->id ne $user_id) {
1356 if( $evt = $apputils->check_perms(
1357 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1363 for my $perm (@$perm_types) {
1364 if($apputils->check_perms($user_id, $org_id, $perm)) {
1365 push @not_allowed, $perm;
1369 return \@not_allowed
1372 __PACKAGE__->register_method(
1373 method => "check_user_perms2",
1374 api_name => "open-ils.actor.user.perm.check.multi_org",
1376 Checks the permissions on a list of perms and orgs for a user
1377 @param authtoken The login session key
1378 @param user_id The id of the user to check
1379 @param orgs The array of org ids
1380 @param perms The array of permission names
1381 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1382 if the logged in user does not match 'user_id', then the logged in user must
1383 have VIEW_PERMISSION priveleges.
1386 sub check_user_perms2 {
1387 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1389 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1390 $authtoken, $user_id, 'VIEW_PERMISSION' );
1391 return $evt if $evt;
1394 for my $org (@$orgs) {
1395 for my $perm (@$perms) {
1396 if($apputils->check_perms($user_id, $org, $perm)) {
1397 push @not_allowed, [ $org, $perm ];
1402 return \@not_allowed
1406 __PACKAGE__->register_method(
1407 method => 'check_user_perms3',
1408 api_name => 'open-ils.actor.user.perm.highest_org',
1410 Returns the highest org unit id at which a user has a given permission
1411 If the requestor does not match the target user, the requestor must have
1412 'VIEW_PERMISSION' rights at the home org unit of the target user
1413 @param authtoken The login session key
1414 @param userid The id of the user in question
1415 @param perm The permission to check
1416 @return The org unit highest in the org tree within which the user has
1417 the requested permission
1420 sub check_user_perms3 {
1421 my($self, $client, $authtoken, $user_id, $perm) = @_;
1422 my $e = new_editor(authtoken=>$authtoken);
1423 return $e->event unless $e->checkauth;
1425 my $tree = $U->get_org_tree();
1427 unless($e->requestor->id == $user_id) {
1428 my $user = $e->retrieve_actor_user($user_id)
1429 or return $e->event;
1430 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1431 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1434 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1437 __PACKAGE__->register_method(
1438 method => 'user_has_work_perm_at',
1439 api_name => 'open-ils.actor.user.has_work_perm_at',
1443 Returns a set of org unit IDs which represent the highest orgs in
1444 the org tree where the user has the requested permission. The
1445 purpose of this method is to return the smallest set of org units
1446 which represent the full expanse of the user's ability to perform
1447 the requested action. The user whose perms this method should
1448 check is implied by the authtoken. /,
1450 {desc => 'authtoken', type => 'string'},
1451 {desc => 'permission name', type => 'string'},
1452 {desc => q/user id, optional. If present, check perms for
1453 this user instead of the logged in user/, type => 'number'},
1455 return => {desc => 'An array of org IDs'}
1459 sub user_has_work_perm_at {
1460 my($self, $conn, $auth, $perm, $user_id) = @_;
1461 my $e = new_editor(authtoken=>$auth);
1462 return $e->event unless $e->checkauth;
1463 if(defined $user_id) {
1464 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1465 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1467 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1470 __PACKAGE__->register_method(
1471 method => 'user_has_work_perm_at_batch',
1472 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1476 sub user_has_work_perm_at_batch {
1477 my($self, $conn, $auth, $perms, $user_id) = @_;
1478 my $e = new_editor(authtoken=>$auth);
1479 return $e->event unless $e->checkauth;
1480 if(defined $user_id) {
1481 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1482 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1485 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1491 __PACKAGE__->register_method(
1492 method => 'check_user_perms4',
1493 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1495 Returns the highest org unit id at which a user has a given permission
1496 If the requestor does not match the target user, the requestor must have
1497 'VIEW_PERMISSION' rights at the home org unit of the target user
1498 @param authtoken The login session key
1499 @param userid The id of the user in question
1500 @param perms An array of perm names to check
1501 @return An array of orgId's representing the org unit
1502 highest in the org tree within which the user has the requested permission
1503 The arrah of orgId's has matches the order of the perms array
1506 sub check_user_perms4 {
1507 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1509 my( $staff, $target, $org, $evt );
1511 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1512 $authtoken, $userid, 'VIEW_PERMISSION' );
1513 return $evt if $evt;
1516 return [] unless ref($perms);
1517 my $tree = $U->get_org_tree();
1519 for my $p (@$perms) {
1520 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1526 __PACKAGE__->register_method(
1527 method => "user_fines_summary",
1528 api_name => "open-ils.actor.user.fines.summary",
1531 desc => 'Returns a short summary of the users total open fines, ' .
1532 'excluding voided fines Params are login_session, user_id' ,
1534 {desc => 'Authentication token', type => 'string'},
1535 {desc => 'User ID', type => 'string'} # number?
1538 desc => "a 'mous' object, event on error",
1543 sub user_fines_summary {
1544 my( $self, $client, $auth, $user_id ) = @_;
1546 my $e = new_editor(authtoken=>$auth);
1547 return $e->event unless $e->checkauth;
1549 if( $user_id ne $e->requestor->id ) {
1550 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1551 return $e->event unless
1552 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1555 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1559 __PACKAGE__->register_method(
1560 method => "user_opac_vitals",
1561 api_name => "open-ils.actor.user.opac.vital_stats",
1565 desc => 'Returns a short summary of the users vital stats, including ' .
1566 'identification information, accumulated balance, number of holds, ' .
1567 'and current open circulation stats' ,
1569 {desc => 'Authentication token', type => 'string'},
1570 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1573 desc => "An object with four properties: user, fines, checkouts and holds."
1578 sub user_opac_vitals {
1579 my( $self, $client, $auth, $user_id ) = @_;
1581 my $e = new_editor(authtoken=>$auth);
1582 return $e->event unless $e->checkauth;
1584 $user_id ||= $e->requestor->id;
1586 my $user = $e->retrieve_actor_user( $user_id );
1589 ->method_lookup('open-ils.actor.user.fines.summary')
1590 ->run($auth => $user_id);
1591 return $fines if (defined($U->event_code($fines)));
1594 $fines = new Fieldmapper::money::open_user_summary ();
1595 $fines->balance_owed(0.00);
1596 $fines->total_owed(0.00);
1597 $fines->total_paid(0.00);
1598 $fines->usr($user_id);
1602 ->method_lookup('open-ils.actor.user.hold_requests.count')
1603 ->run($auth => $user_id);
1604 return $holds if (defined($U->event_code($holds)));
1607 ->method_lookup('open-ils.actor.user.checked_out.count')
1608 ->run($auth => $user_id);
1609 return $out if (defined($U->event_code($out)));
1611 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue long_overdue/;
1615 first_given_name => $user->first_given_name,
1616 second_given_name => $user->second_given_name,
1617 family_name => $user->family_name,
1618 alias => $user->alias,
1619 usrname => $user->usrname
1621 fines => $fines->to_bare_hash,
1628 ##### a small consolidation of related method registrations
1629 my $common_params = [
1630 { desc => 'Authentication token', type => 'string' },
1631 { desc => 'User ID', type => 'string' },
1632 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1633 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1636 'open-ils.actor.user.transactions' => '',
1637 'open-ils.actor.user.transactions.fleshed' => '',
1638 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1639 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1640 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1641 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1644 foreach (keys %methods) {
1646 method => "user_transactions",
1649 desc => 'For a given user, retrieve a list of '
1650 . (/\.fleshed/ ? 'fleshed ' : '')
1651 . 'transactions' . $methods{$_}
1652 . ' optionally limited to transactions of a given type.',
1653 params => $common_params,
1655 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1656 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1660 $args{authoritative} = 1;
1661 __PACKAGE__->register_method(%args);
1664 # Now for the counts
1666 'open-ils.actor.user.transactions.count' => '',
1667 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1668 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1671 foreach (keys %methods) {
1673 method => "user_transactions",
1676 desc => 'For a given user, retrieve a count of open '
1677 . 'transactions' . $methods{$_}
1678 . ' optionally limited to transactions of a given type.',
1679 params => $common_params,
1680 return => { desc => "Integer count of transactions, or event on error" }
1683 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1684 __PACKAGE__->register_method(%args);
1687 __PACKAGE__->register_method(
1688 method => "user_transactions",
1689 api_name => "open-ils.actor.user.transactions.have_balance.total",
1692 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1693 . ' optionally limited to transactions of a given type.',
1694 params => $common_params,
1695 return => { desc => "Decimal balance value, or event on error" }
1700 sub user_transactions {
1701 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1704 my $e = new_editor(authtoken => $auth);
1705 return $e->event unless $e->checkauth;
1707 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1709 return $e->event unless
1710 $e->requestor->id == $user_id or
1711 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1713 my $api = $self->api_name();
1715 my $filter = ($api =~ /have_balance/o) ?
1716 { 'balance_owed' => { '<>' => 0 } }:
1717 { 'total_owed' => { '>' => 0 } };
1719 my $method = 'open-ils.actor.user.transactions.history.still_open';
1720 $method = "$method.authoritative" if $api =~ /authoritative/;
1721 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1723 if($api =~ /total/o) {
1725 $total += $_->balance_owed for @$trans;
1729 ($api =~ /count/o ) and return scalar @$trans;
1730 ($api !~ /fleshed/o) and return $trans;
1733 for my $t (@$trans) {
1735 if( $t->xact_type ne 'circulation' ) {
1736 push @resp, {transaction => $t};
1740 my $circ_data = flesh_circ($e, $t->id);
1741 push @resp, {transaction => $t, %$circ_data};
1748 __PACKAGE__->register_method(
1749 method => "user_transaction_retrieve",
1750 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1753 notes => "Returns a fleshed transaction record"
1756 __PACKAGE__->register_method(
1757 method => "user_transaction_retrieve",
1758 api_name => "open-ils.actor.user.transaction.retrieve",
1761 notes => "Returns a transaction record"
1764 sub user_transaction_retrieve {
1765 my($self, $client, $auth, $bill_id) = @_;
1767 my $e = new_editor(authtoken => $auth);
1768 return $e->event unless $e->checkauth;
1770 my $trans = $e->retrieve_money_billable_transaction_summary(
1771 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1773 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1775 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1777 return $trans unless $self->api_name =~ /flesh/;
1778 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1780 my $circ_data = flesh_circ($e, $trans->id, 1);
1782 return {transaction => $trans, %$circ_data};
1787 my $circ_id = shift;
1788 my $flesh_copy = shift;
1790 my $circ = $e->retrieve_action_circulation([
1794 circ => ['target_copy'],
1795 acp => ['call_number'],
1802 my $copy = $circ->target_copy;
1804 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1805 $mods = new Fieldmapper::metabib::virtual_record;
1806 $mods->doc_id(OILS_PRECAT_RECORD);
1807 $mods->title($copy->dummy_title);
1808 $mods->author($copy->dummy_author);
1811 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1815 $circ->target_copy($circ->target_copy->id);
1816 $copy->call_number($copy->call_number->id);
1818 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1822 __PACKAGE__->register_method(
1823 method => "hold_request_count",
1824 api_name => "open-ils.actor.user.hold_requests.count",
1827 notes => 'Returns hold ready/total counts'
1830 sub hold_request_count {
1831 my( $self, $client, $authtoken, $user_id ) = @_;
1832 my $e = new_editor(authtoken => $authtoken);
1833 return $e->event unless $e->checkauth;
1835 $user_id = $e->requestor->id unless defined $user_id;
1837 if($e->requestor->id ne $user_id) {
1838 my $user = $e->retrieve_actor_user($user_id);
1839 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
1842 my $holds = $e->json_query({
1843 select => {ahr => ['pickup_lib', 'current_shelf_lib']},
1847 fulfillment_time => {"=" => undef },
1848 cancel_time => undef,
1853 total => scalar(@$holds),
1856 $_->{current_shelf_lib} and # avoid undef warnings
1857 $_->{pickup_lib} eq $_->{current_shelf_lib}
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 %auth_hist_methods = (
2029 'history.have_charge' => 'that have an initial charge',
2030 'history.still_open' => 'that are not finished',
2031 'history.have_balance' => 'that have a balance',
2032 'history.have_bill' => 'that have billings',
2033 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2034 'history.have_payment' => 'that have at least 1 payment',
2037 foreach (keys %auth_hist_methods) {
2038 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2039 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2040 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2043 sub user_transaction_history {
2044 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2048 my $e = new_editor(authtoken=>$auth);
2049 return $e->die_event unless $e->checkauth;
2051 if ($e->requestor->id ne $userid) {
2052 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2055 my $api = $self->api_name;
2056 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2058 if(defined($type)) {
2059 $filter->{'xact_type'} = $type;
2062 if($api =~ /have_bill_or_payment/o) {
2064 # transactions that have a non-zero sum across all billings or at least 1 payment
2065 $filter->{'-or'} = {
2066 'balance_owed' => { '<>' => 0 },
2067 'last_payment_ts' => { '<>' => undef }
2070 } elsif($api =~ /have_payment/) {
2072 $filter->{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(
2096 [ { usr => $userid, @xact_finish, %$filter },
2101 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2102 return $mbts unless $api =~ /fleshed/;
2105 for my $t (@$mbts) {
2107 if( $t->xact_type ne 'circulation' ) {
2108 push @resp, {transaction => $t};
2112 my $circ_data = flesh_circ($e, $t->id);
2113 push @resp, {transaction => $t, %$circ_data};
2121 __PACKAGE__->register_method(
2122 method => "user_perms",
2123 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2125 notes => "Returns a list of permissions"
2129 my( $self, $client, $authtoken, $user ) = @_;
2131 my( $staff, $evt ) = $apputils->checkses($authtoken);
2132 return $evt if $evt;
2134 $user ||= $staff->id;
2136 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2140 return $apputils->simple_scalar_request(
2142 "open-ils.storage.permission.user_perms.atomic",
2146 __PACKAGE__->register_method(
2147 method => "retrieve_perms",
2148 api_name => "open-ils.actor.permissions.retrieve",
2149 notes => "Returns a list of permissions"
2151 sub retrieve_perms {
2152 my( $self, $client ) = @_;
2153 return $apputils->simple_scalar_request(
2155 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2156 { id => { '!=' => undef } }
2160 __PACKAGE__->register_method(
2161 method => "retrieve_groups",
2162 api_name => "open-ils.actor.groups.retrieve",
2163 notes => "Returns a list of user groups"
2165 sub retrieve_groups {
2166 my( $self, $client ) = @_;
2167 return new_editor()->retrieve_all_permission_grp_tree();
2170 __PACKAGE__->register_method(
2171 method => "retrieve_org_address",
2172 api_name => "open-ils.actor.org_unit.address.retrieve",
2173 notes => <<' NOTES');
2174 Returns an org_unit address by ID
2175 @param An org_address ID
2177 sub retrieve_org_address {
2178 my( $self, $client, $id ) = @_;
2179 return $apputils->simple_scalar_request(
2181 "open-ils.cstore.direct.actor.org_address.retrieve",
2186 __PACKAGE__->register_method(
2187 method => "retrieve_groups_tree",
2188 api_name => "open-ils.actor.groups.tree.retrieve",
2189 notes => "Returns a list of user groups"
2192 sub retrieve_groups_tree {
2193 my( $self, $client ) = @_;
2194 return new_editor()->search_permission_grp_tree(
2199 flesh_fields => { pgt => ["children"] },
2200 order_by => { pgt => 'name'}
2207 __PACKAGE__->register_method(
2208 method => "add_user_to_groups",
2209 api_name => "open-ils.actor.user.set_groups",
2210 notes => "Adds a user to one or more permission groups"
2213 sub add_user_to_groups {
2214 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2216 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2217 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2218 return $evt if $evt;
2220 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2221 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2222 return $evt if $evt;
2224 $apputils->simplereq(
2226 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2228 for my $group (@$groups) {
2229 my $link = Fieldmapper::permission::usr_grp_map->new;
2231 $link->usr($userid);
2233 my $id = $apputils->simplereq(
2235 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2241 __PACKAGE__->register_method(
2242 method => "get_user_perm_groups",
2243 api_name => "open-ils.actor.user.get_groups",
2244 notes => "Retrieve a user's permission groups."
2248 sub get_user_perm_groups {
2249 my( $self, $client, $authtoken, $userid ) = @_;
2251 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2252 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2253 return $evt if $evt;
2255 return $apputils->simplereq(
2257 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2261 __PACKAGE__->register_method(
2262 method => "get_user_work_ous",
2263 api_name => "open-ils.actor.user.get_work_ous",
2264 notes => "Retrieve a user's work org units."
2267 __PACKAGE__->register_method(
2268 method => "get_user_work_ous",
2269 api_name => "open-ils.actor.user.get_work_ous.ids",
2270 notes => "Retrieve a user's work org units."
2273 sub get_user_work_ous {
2274 my( $self, $client, $auth, $userid ) = @_;
2275 my $e = new_editor(authtoken=>$auth);
2276 return $e->event unless $e->checkauth;
2277 $userid ||= $e->requestor->id;
2279 if($e->requestor->id != $userid) {
2280 my $user = $e->retrieve_actor_user($userid)
2281 or return $e->event;
2282 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2285 return $e->search_permission_usr_work_ou_map({usr => $userid})
2286 unless $self->api_name =~ /.ids$/;
2288 # client just wants a list of org IDs
2289 return $U->get_user_work_ou_ids($e, $userid);
2294 __PACKAGE__->register_method(
2295 method => 'register_workstation',
2296 api_name => 'open-ils.actor.workstation.register.override',
2297 signature => q/@see open-ils.actor.workstation.register/
2300 __PACKAGE__->register_method(
2301 method => 'register_workstation',
2302 api_name => 'open-ils.actor.workstation.register',
2304 Registers a new workstion in the system
2305 @param authtoken The login session key
2306 @param name The name of the workstation id
2307 @param owner The org unit that owns this workstation
2308 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2309 if the name is already in use.
2313 sub register_workstation {
2314 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2316 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2317 return $e->die_event unless $e->checkauth;
2318 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2319 my $existing = $e->search_actor_workstation({name => $name})->[0];
2323 if( $self->api_name =~ /override/o ) {
2324 # workstation with the given name exists.
2326 if($owner ne $existing->owning_lib) {
2327 # if necessary, update the owning_lib of the workstation
2329 $logger->info("changing owning lib of workstation ".$existing->id.
2330 " from ".$existing->owning_lib." to $owner");
2331 return $e->die_event unless
2332 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2334 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2336 $existing->owning_lib($owner);
2337 return $e->die_event unless $e->update_actor_workstation($existing);
2343 "attempt to register an existing workstation. returning existing ID");
2346 return $existing->id;
2349 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2353 my $ws = Fieldmapper::actor::workstation->new;
2354 $ws->owning_lib($owner);
2356 $e->create_actor_workstation($ws) or return $e->die_event;
2358 return $ws->id; # note: editor sets the id on the new object for us
2361 __PACKAGE__->register_method(
2362 method => 'workstation_list',
2363 api_name => 'open-ils.actor.workstation.list',
2365 Returns a list of workstations registered at the given location
2366 @param authtoken The login session key
2367 @param ids A list of org_unit.id's for the workstation owners
2371 sub workstation_list {
2372 my( $self, $conn, $authtoken, @orgs ) = @_;
2374 my $e = new_editor(authtoken=>$authtoken);
2375 return $e->event unless $e->checkauth;
2380 unless $e->allowed('REGISTER_WORKSTATION', $o);
2381 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2387 __PACKAGE__->register_method(
2388 method => 'fetch_patron_note',
2389 api_name => 'open-ils.actor.note.retrieve.all',
2392 Returns a list of notes for a given user
2393 Requestor must have VIEW_USER permission if pub==false and
2394 @param authtoken The login session key
2395 @param args Hash of params including
2396 patronid : the patron's id
2397 pub : true if retrieving only public notes
2401 sub fetch_patron_note {
2402 my( $self, $conn, $authtoken, $args ) = @_;
2403 my $patronid = $$args{patronid};
2405 my($reqr, $evt) = $U->checkses($authtoken);
2406 return $evt if $evt;
2409 ($patron, $evt) = $U->fetch_user($patronid);
2410 return $evt if $evt;
2413 if( $patronid ne $reqr->id ) {
2414 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2415 return $evt if $evt;
2417 return $U->cstorereq(
2418 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2419 { usr => $patronid, pub => 't' } );
2422 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2423 return $evt if $evt;
2425 return $U->cstorereq(
2426 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2429 __PACKAGE__->register_method(
2430 method => 'create_user_note',
2431 api_name => 'open-ils.actor.note.create',
2433 Creates a new note for the given user
2434 @param authtoken The login session key
2435 @param note The note object
2438 sub create_user_note {
2439 my( $self, $conn, $authtoken, $note ) = @_;
2440 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2441 return $e->die_event unless $e->checkauth;
2443 my $user = $e->retrieve_actor_user($note->usr)
2444 or return $e->die_event;
2446 return $e->die_event unless
2447 $e->allowed('UPDATE_USER',$user->home_ou);
2449 $note->creator($e->requestor->id);
2450 $e->create_actor_usr_note($note) or return $e->die_event;
2456 __PACKAGE__->register_method(
2457 method => 'delete_user_note',
2458 api_name => 'open-ils.actor.note.delete',
2460 Deletes a note for the given user
2461 @param authtoken The login session key
2462 @param noteid The note id
2465 sub delete_user_note {
2466 my( $self, $conn, $authtoken, $noteid ) = @_;
2468 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2469 return $e->die_event unless $e->checkauth;
2470 my $note = $e->retrieve_actor_usr_note($noteid)
2471 or return $e->die_event;
2472 my $user = $e->retrieve_actor_user($note->usr)
2473 or return $e->die_event;
2474 return $e->die_event unless
2475 $e->allowed('UPDATE_USER', $user->home_ou);
2477 $e->delete_actor_usr_note($note) or return $e->die_event;
2483 __PACKAGE__->register_method(
2484 method => 'update_user_note',
2485 api_name => 'open-ils.actor.note.update',
2487 @param authtoken The login session key
2488 @param note The note
2492 sub update_user_note {
2493 my( $self, $conn, $auth, $note ) = @_;
2494 my $e = new_editor(authtoken=>$auth, xact=>1);
2495 return $e->die_event unless $e->checkauth;
2496 my $patron = $e->retrieve_actor_user($note->usr)
2497 or return $e->die_event;
2498 return $e->die_event unless
2499 $e->allowed('UPDATE_USER', $patron->home_ou);
2500 $e->update_actor_user_note($note)
2501 or return $e->die_event;
2508 __PACKAGE__->register_method(
2509 method => 'create_closed_date',
2510 api_name => 'open-ils.actor.org_unit.closed_date.create',
2512 Creates a new closing entry for the given org_unit
2513 @param authtoken The login session key
2514 @param note The closed_date object
2517 sub create_closed_date {
2518 my( $self, $conn, $authtoken, $cd ) = @_;
2520 my( $user, $evt ) = $U->checkses($authtoken);
2521 return $evt if $evt;
2523 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2524 return $evt if $evt;
2526 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2528 my $id = $U->storagereq(
2529 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2530 return $U->DB_UPDATE_FAILED($cd) unless $id;
2535 __PACKAGE__->register_method(
2536 method => 'delete_closed_date',
2537 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2539 Deletes a closing entry for the given org_unit
2540 @param authtoken The login session key
2541 @param noteid The close_date id
2544 sub delete_closed_date {
2545 my( $self, $conn, $authtoken, $cd ) = @_;
2547 my( $user, $evt ) = $U->checkses($authtoken);
2548 return $evt if $evt;
2551 ($cd_obj, $evt) = fetch_closed_date($cd);
2552 return $evt if $evt;
2554 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2555 return $evt if $evt;
2557 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2559 my $stat = $U->storagereq(
2560 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2561 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2566 __PACKAGE__->register_method(
2567 method => 'usrname_exists',
2568 api_name => 'open-ils.actor.username.exists',
2570 desc => 'Check if a username is already taken (by an undeleted patron)',
2572 {desc => 'Authentication token', type => 'string'},
2573 {desc => 'Username', type => 'string'}
2576 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2581 sub usrname_exists {
2582 my( $self, $conn, $auth, $usrname ) = @_;
2583 my $e = new_editor(authtoken=>$auth);
2584 return $e->event unless $e->checkauth;
2585 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2586 return $$a[0] if $a and @$a;
2590 __PACKAGE__->register_method(
2591 method => 'barcode_exists',
2592 api_name => 'open-ils.actor.barcode.exists',
2594 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2597 sub barcode_exists {
2598 my( $self, $conn, $auth, $barcode ) = @_;
2599 my $e = new_editor(authtoken=>$auth);
2600 return $e->event unless $e->checkauth;
2601 my $card = $e->search_actor_card({barcode => $barcode});
2607 #return undef unless @$card;
2608 #return $card->[0]->usr;
2612 __PACKAGE__->register_method(
2613 method => 'retrieve_net_levels',
2614 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2617 sub retrieve_net_levels {
2618 my( $self, $conn, $auth ) = @_;
2619 my $e = new_editor(authtoken=>$auth);
2620 return $e->event unless $e->checkauth;
2621 return $e->retrieve_all_config_net_access_level();
2624 # Retain the old typo API name just in case
2625 __PACKAGE__->register_method(
2626 method => 'fetch_org_by_shortname',
2627 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2629 __PACKAGE__->register_method(
2630 method => 'fetch_org_by_shortname',
2631 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2633 sub fetch_org_by_shortname {
2634 my( $self, $conn, $sname ) = @_;
2635 my $e = new_editor();
2636 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2637 return $e->event unless $org;
2642 __PACKAGE__->register_method(
2643 method => 'session_home_lib',
2644 api_name => 'open-ils.actor.session.home_lib',
2647 sub session_home_lib {
2648 my( $self, $conn, $auth ) = @_;
2649 my $e = new_editor(authtoken=>$auth);
2650 return undef unless $e->checkauth;
2651 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2652 return $org->shortname;
2655 __PACKAGE__->register_method(
2656 method => 'session_safe_token',
2657 api_name => 'open-ils.actor.session.safe_token',
2659 Returns a hashed session ID that is safe for export to the world.
2660 This safe token will expire after 1 hour of non-use.
2661 @param auth Active authentication token
2665 sub session_safe_token {
2666 my( $self, $conn, $auth ) = @_;
2667 my $e = new_editor(authtoken=>$auth);
2668 return undef unless $e->checkauth;
2670 my $safe_token = md5_hex($auth);
2672 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2674 # Add more like the following if needed...
2676 "safe-token-home_lib-shortname-$safe_token",
2677 $e->retrieve_actor_org_unit(
2678 $e->requestor->home_ou
2687 __PACKAGE__->register_method(
2688 method => 'safe_token_home_lib',
2689 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2691 Returns the home library shortname from the session
2692 asscociated with a safe token from generated by
2693 open-ils.actor.session.safe_token.
2694 @param safe_token Active safe token
2698 sub safe_token_home_lib {
2699 my( $self, $conn, $safe_token ) = @_;
2701 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2702 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2707 __PACKAGE__->register_method(
2708 method => 'slim_tree',
2709 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2712 my $tree = new_editor()->search_actor_org_unit(
2714 {"parent_ou" => undef },
2717 flesh_fields => { aou => ['children'] },
2718 order_by => { aou => 'name'},
2719 select => { aou => ["id","shortname", "name"]},
2724 return trim_tree($tree);
2730 return undef unless $tree;
2732 code => $tree->shortname,
2733 name => $tree->name,
2735 if( $tree->children and @{$tree->children} ) {
2736 $htree->{children} = [];
2737 for my $c (@{$tree->children}) {
2738 push( @{$htree->{children}}, trim_tree($c) );
2746 __PACKAGE__->register_method(
2747 method => "update_penalties",
2748 api_name => "open-ils.actor.user.penalties.update"
2751 sub update_penalties {
2752 my($self, $conn, $auth, $user_id) = @_;
2753 my $e = new_editor(authtoken=>$auth, xact => 1);
2754 return $e->die_event unless $e->checkauth;
2755 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2756 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2757 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2758 return $evt if $evt;
2764 __PACKAGE__->register_method(
2765 method => "apply_penalty",
2766 api_name => "open-ils.actor.user.penalty.apply"
2770 my($self, $conn, $auth, $penalty) = @_;
2772 my $e = new_editor(authtoken=>$auth, xact => 1);
2773 return $e->die_event unless $e->checkauth;
2775 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2776 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2778 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2781 (defined $ptype->org_depth) ?
2782 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2785 $penalty->org_unit($ctx_org);
2786 $penalty->staff($e->requestor->id);
2787 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2790 return $penalty->id;
2793 __PACKAGE__->register_method(
2794 method => "remove_penalty",
2795 api_name => "open-ils.actor.user.penalty.remove"
2798 sub remove_penalty {
2799 my($self, $conn, $auth, $penalty) = @_;
2800 my $e = new_editor(authtoken=>$auth, xact => 1);
2801 return $e->die_event unless $e->checkauth;
2802 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2803 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2805 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2810 __PACKAGE__->register_method(
2811 method => "update_penalty_note",
2812 api_name => "open-ils.actor.user.penalty.note.update"
2815 sub update_penalty_note {
2816 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2817 my $e = new_editor(authtoken=>$auth, xact => 1);
2818 return $e->die_event unless $e->checkauth;
2819 for my $penalty_id (@$penalty_ids) {
2820 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2821 if (! $penalty ) { return $e->die_event; }
2822 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2823 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2825 $penalty->note( $note ); $penalty->ischanged( 1 );
2827 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2833 __PACKAGE__->register_method(
2834 method => "ranged_penalty_thresholds",
2835 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2839 sub ranged_penalty_thresholds {
2840 my($self, $conn, $auth, $context_org) = @_;
2841 my $e = new_editor(authtoken=>$auth);
2842 return $e->event unless $e->checkauth;
2843 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2844 my $list = $e->search_permission_grp_penalty_threshold([
2845 {org_unit => $U->get_org_ancestors($context_org)},
2846 {order_by => {pgpt => 'id'}}
2848 $conn->respond($_) for @$list;
2854 __PACKAGE__->register_method(
2855 method => "user_retrieve_fleshed_by_id",
2857 api_name => "open-ils.actor.user.fleshed.retrieve",
2860 sub user_retrieve_fleshed_by_id {
2861 my( $self, $client, $auth, $user_id, $fields ) = @_;
2862 my $e = new_editor(authtoken => $auth);
2863 return $e->event unless $e->checkauth;
2865 if( $e->requestor->id != $user_id ) {
2866 return $e->event unless $e->allowed('VIEW_USER');
2872 "standing_penalties",
2876 "stat_cat_entries" ];
2877 return new_flesh_user($user_id, $fields, $e);
2881 sub new_flesh_user {
2884 my $fields = shift || [];
2887 my $fetch_penalties = 0;
2888 if(grep {$_ eq 'standing_penalties'} @$fields) {
2889 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2890 $fetch_penalties = 1;
2893 my $user = $e->retrieve_actor_user(
2898 "flesh_fields" => { "au" => $fields }
2901 ) or return $e->die_event;
2904 if( grep { $_ eq 'addresses' } @$fields ) {
2906 $user->addresses([]) unless @{$user->addresses};
2907 # don't expose "replaced" addresses by default
2908 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2910 if( ref $user->billing_address ) {
2911 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2912 push( @{$user->addresses}, $user->billing_address );
2916 if( ref $user->mailing_address ) {
2917 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2918 push( @{$user->addresses}, $user->mailing_address );
2923 if($fetch_penalties) {
2924 # grab the user penalties ranged for this location
2925 $user->standing_penalties(
2926 $e->search_actor_user_standing_penalty([
2929 {stop_date => undef},
2930 {stop_date => {'>' => 'now'}}
2932 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
2935 flesh_fields => {ausp => ['standing_penalty']}
2942 $user->clear_passwd();
2949 __PACKAGE__->register_method(
2950 method => "user_retrieve_parts",
2951 api_name => "open-ils.actor.user.retrieve.parts",
2954 sub user_retrieve_parts {
2955 my( $self, $client, $auth, $user_id, $fields ) = @_;
2956 my $e = new_editor(authtoken => $auth);
2957 return $e->event unless $e->checkauth;
2958 $user_id ||= $e->requestor->id;
2959 if( $e->requestor->id != $user_id ) {
2960 return $e->event unless $e->allowed('VIEW_USER');
2963 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2964 push(@resp, $user->$_()) for(@$fields);
2970 __PACKAGE__->register_method(
2971 method => 'user_opt_in_enabled',
2972 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2973 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2976 sub user_opt_in_enabled {
2977 my($self, $conn) = @_;
2978 my $sc = OpenSRF::Utils::SettingsClient->new;
2979 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2984 __PACKAGE__->register_method(
2985 method => 'user_opt_in_at_org',
2986 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2988 @param $auth The auth token
2989 @param user_id The ID of the user to test
2990 @return 1 if the user has opted in at the specified org,
2991 event on error, and 0 otherwise. /
2993 sub user_opt_in_at_org {
2994 my($self, $conn, $auth, $user_id) = @_;
2996 # see if we even need to enforce the opt-in value
2997 return 1 unless user_opt_in_enabled($self);
2999 my $e = new_editor(authtoken => $auth);
3000 return $e->event unless $e->checkauth;
3002 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3003 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3005 my $ws_org = $e->requestor->ws_ou;
3006 # user is automatically opted-in if they are from the local org
3007 return 1 if $user->home_ou eq $ws_org;
3009 # get the boundary setting
3010 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3012 # auto opt in if user falls within the opt boundary
3013 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3015 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3017 my $vals = $e->search_actor_usr_org_unit_opt_in(
3018 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3024 __PACKAGE__->register_method(
3025 method => 'create_user_opt_in_at_org',
3026 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3028 @param $auth The auth token
3029 @param user_id The ID of the user to test
3030 @return The ID of the newly created object, event on error./
3033 sub create_user_opt_in_at_org {
3034 my($self, $conn, $auth, $user_id, $org_id) = @_;
3036 my $e = new_editor(authtoken => $auth, xact=>1);
3037 return $e->die_event unless $e->checkauth;
3039 # if a specific org unit wasn't passed in, get one based on the defaults;
3041 my $wsou = $e->requestor->ws_ou;
3042 # get the default opt depth
3043 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3044 # get the org unit at that depth
3045 my $org = $e->json_query({
3046 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3048 $org_id = $org->{id};
3051 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3052 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3054 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3056 $opt_in->org_unit($org_id);
3057 $opt_in->usr($user_id);
3058 $opt_in->staff($e->requestor->id);
3059 $opt_in->opt_in_ts('now');
3060 $opt_in->opt_in_ws($e->requestor->wsid);
3062 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3063 or return $e->die_event;
3071 __PACKAGE__->register_method (
3072 method => 'retrieve_org_hours',
3073 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3075 Returns the hours of operation for a specified org unit
3076 @param authtoken The login session key
3077 @param org_id The org_unit ID
3081 sub retrieve_org_hours {
3082 my($self, $conn, $auth, $org_id) = @_;
3083 my $e = new_editor(authtoken => $auth);
3084 return $e->die_event unless $e->checkauth;
3085 $org_id ||= $e->requestor->ws_ou;
3086 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3090 __PACKAGE__->register_method (
3091 method => 'verify_user_password',
3092 api_name => 'open-ils.actor.verify_user_password',
3094 Given a barcode or username and the MD5 encoded password,
3095 returns 1 if the password is correct. Returns 0 otherwise.
3099 sub verify_user_password {
3100 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3101 my $e = new_editor(authtoken => $auth);
3102 return $e->die_event unless $e->checkauth;
3104 my $user_by_barcode;
3105 my $user_by_username;
3107 my $card = $e->search_actor_card([
3108 {barcode => $barcode},
3109 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3110 $user_by_barcode = $card->usr;
3111 $user = $user_by_barcode;
3114 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3115 $user = $user_by_username;
3117 return 0 if (!$user);
3118 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3119 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3120 return 1 if $user->passwd eq $password;
3124 __PACKAGE__->register_method (
3125 method => 'retrieve_usr_id_via_barcode_or_usrname',
3126 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3128 Given a barcode or username returns the id for the user or
3133 sub retrieve_usr_id_via_barcode_or_usrname {
3134 my($self, $conn, $auth, $barcode, $username) = @_;
3135 my $e = new_editor(authtoken => $auth);
3136 return $e->die_event unless $e->checkauth;
3137 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3139 my $user_by_barcode;
3140 my $user_by_username;
3141 $logger->info("$id_as_barcode is the ID as BARCODE");
3143 my $card = $e->search_actor_card([
3144 {barcode => $barcode},
3145 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3146 if ($id_as_barcode =~ /^t/i) {
3148 $user = $e->retrieve_actor_user($barcode);
3149 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3151 $user_by_barcode = $card->usr;
3152 $user = $user_by_barcode;
3155 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3156 $user_by_barcode = $card->usr;
3157 $user = $user_by_barcode;
3162 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3164 $user = $user_by_username;
3166 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3167 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3168 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3173 __PACKAGE__->register_method (
3174 method => 'merge_users',
3175 api_name => 'open-ils.actor.user.merge',
3178 Given a list of source users and destination user, transfer all data from the source
3179 to the dest user and delete the source user. All user related data is
3180 transferred, including circulations, holds, bookbags, etc.
3186 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3187 my $e = new_editor(xact => 1, authtoken => $auth);
3188 return $e->die_event unless $e->checkauth;
3190 # disallow the merge if any subordinate accounts are in collections
3191 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3192 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3194 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3195 my $del_addrs = ($U->ou_ancestor_setting_value(
3196 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3197 my $del_cards = ($U->ou_ancestor_setting_value(
3198 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3199 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3200 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3202 for my $src_id (@$user_ids) {
3203 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3205 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3206 if($src_user->home_ou ne $master_user->home_ou) {
3207 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3210 return $e->die_event unless
3211 $e->json_query({from => [
3226 __PACKAGE__->register_method (
3227 method => 'approve_user_address',
3228 api_name => 'open-ils.actor.user.pending_address.approve',
3235 sub approve_user_address {
3236 my($self, $conn, $auth, $addr) = @_;
3237 my $e = new_editor(xact => 1, authtoken => $auth);
3238 return $e->die_event unless $e->checkauth;
3240 # if the caller passes an address object, assume they want to
3241 # update it first before approving it
3242 $e->update_actor_user_address($addr) or return $e->die_event;
3244 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3246 my $user = $e->retrieve_actor_user($addr->usr);
3247 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3248 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3249 or return $e->die_event;
3251 return [values %$result]->[0];
3255 __PACKAGE__->register_method (
3256 method => 'retrieve_friends',
3257 api_name => 'open-ils.actor.friends.retrieve',
3260 returns { confirmed: [], pending_out: [], pending_in: []}
3261 pending_out are users I'm requesting friendship with
3262 pending_in are users requesting friendship with me
3267 sub retrieve_friends {
3268 my($self, $conn, $auth, $user_id, $options) = @_;
3269 my $e = new_editor(authtoken => $auth);
3270 return $e->event unless $e->checkauth;
3271 $user_id ||= $e->requestor->id;
3273 if($user_id != $e->requestor->id) {
3274 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3275 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3278 return OpenILS::Application::Actor::Friends->retrieve_friends(
3279 $e, $user_id, $options);
3284 __PACKAGE__->register_method (
3285 method => 'apply_friend_perms',
3286 api_name => 'open-ils.actor.friends.perms.apply',
3292 sub apply_friend_perms {
3293 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3294 my $e = new_editor(authtoken => $auth, xact => 1);
3295 return $e->die_event unless $e->checkauth;
3297 if($user_id != $e->requestor->id) {
3298 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3299 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3302 for my $perm (@perms) {
3304 OpenILS::Application::Actor::Friends->apply_friend_perm(
3305 $e, $user_id, $delegate_id, $perm);
3306 return $evt if $evt;
3314 __PACKAGE__->register_method (
3315 method => 'update_user_pending_address',
3316 api_name => 'open-ils.actor.user.address.pending.cud'
3319 sub update_user_pending_address {
3320 my($self, $conn, $auth, $addr) = @_;
3321 my $e = new_editor(authtoken => $auth, xact => 1);
3322 return $e->die_event unless $e->checkauth;
3324 if($addr->usr != $e->requestor->id) {
3325 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3326 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3330 $e->create_actor_user_address($addr) or return $e->die_event;
3331 } elsif($addr->isdeleted) {
3332 $e->delete_actor_user_address($addr) or return $e->die_event;
3334 $e->update_actor_user_address($addr) or return $e->die_event;
3342 __PACKAGE__->register_method (
3343 method => 'user_events',
3344 api_name => 'open-ils.actor.user.events.circ',
3347 __PACKAGE__->register_method (
3348 method => 'user_events',
3349 api_name => 'open-ils.actor.user.events.ahr',
3354 my($self, $conn, $auth, $user_id, $filters) = @_;
3355 my $e = new_editor(authtoken => $auth);
3356 return $e->event unless $e->checkauth;
3358 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3359 my $user_field = 'usr';
3362 $filters->{target} = {
3363 select => { $obj_type => ['id'] },
3365 where => {usr => $user_id}
3368 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3369 if($e->requestor->id != $user_id) {
3370 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3373 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3374 my $req = $ses->request('open-ils.trigger.events_by_target',
3375 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3377 while(my $resp = $req->recv) {
3378 my $val = $resp->content;
3379 my $tgt = $val->target;
3381 if($obj_type eq 'circ') {
3382 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3384 } elsif($obj_type eq 'ahr') {
3385 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3386 if $tgt->current_copy;
3389 $conn->respond($val) if $val;
3395 __PACKAGE__->register_method (
3396 method => 'copy_events',
3397 api_name => 'open-ils.actor.copy.events.circ',
3400 __PACKAGE__->register_method (
3401 method => 'copy_events',
3402 api_name => 'open-ils.actor.copy.events.ahr',
3407 my($self, $conn, $auth, $copy_id, $filters) = @_;
3408 my $e = new_editor(authtoken => $auth);
3409 return $e->event unless $e->checkauth;
3411 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3413 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3415 my $copy_field = 'target_copy';
3416 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3419 $filters->{target} = {
3420 select => { $obj_type => ['id'] },
3422 where => {$copy_field => $copy_id}
3426 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3427 my $req = $ses->request('open-ils.trigger.events_by_target',
3428 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3430 while(my $resp = $req->recv) {
3431 my $val = $resp->content;
3432 my $tgt = $val->target;
3434 my $user = $e->retrieve_actor_user($tgt->usr);
3435 if($e->requestor->id != $user->id) {
3436 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3439 $tgt->$copy_field($copy);
3442 $conn->respond($val) if $val;
3451 __PACKAGE__->register_method (
3452 method => 'update_events',
3453 api_name => 'open-ils.actor.user.event.cancel.batch',
3456 __PACKAGE__->register_method (
3457 method => 'update_events',
3458 api_name => 'open-ils.actor.user.event.reset.batch',
3463 my($self, $conn, $auth, $event_ids) = @_;
3464 my $e = new_editor(xact => 1, authtoken => $auth);
3465 return $e->die_event unless $e->checkauth;
3468 for my $id (@$event_ids) {
3470 # do a little dance to determine what user we are ultimately affecting
3471 my $event = $e->retrieve_action_trigger_event([
3474 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3476 ]) or return $e->die_event;
3479 if($event->event_def->hook->core_type eq 'circ') {
3480 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3481 } elsif($event->event_def->hook->core_type eq 'ahr') {
3482 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3487 my $user = $e->retrieve_actor_user($user_id);
3488 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3490 if($self->api_name =~ /cancel/) {
3491 $event->state('invalid');
3492 } elsif($self->api_name =~ /reset/) {
3493 $event->clear_start_time;
3494 $event->clear_update_time;
3495 $event->state('pending');
3498 $e->update_action_trigger_event($event) or return $e->die_event;
3499 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3503 return {complete => 1};
3507 __PACKAGE__->register_method (
3508 method => 'really_delete_user',
3509 api_name => 'open-ils.actor.user.delete',
3511 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3512 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3513 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3514 dest_usr_id is only required when deleting a user that performs staff functions.
3518 sub really_delete_user {
3519 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3520 my $e = new_editor(authtoken => $auth, xact => 1);
3521 return $e->die_event unless $e->checkauth;
3522 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3523 # No deleting yourself - UI is supposed to stop you first, though.
3524 return $e->die_event unless $e->requestor->id != $user->id;
3525 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3526 # Check if you are allowed to mess with this patron permission group at all
3527 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
3528 my $evt = group_perm_failed($session, $e->requestor, $user);
3529 return $e->die_event($evt) if $evt;
3530 my $stat = $e->json_query(
3531 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3532 or return $e->die_event;
3539 __PACKAGE__->register_method (
3540 method => 'user_payments',
3541 api_name => 'open-ils.actor.user.payments.retrieve',
3544 Returns all payments for a given user. Default order is newest payments first.
3545 @param auth Authentication token
3546 @param user_id The user ID
3547 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3552 my($self, $conn, $auth, $user_id, $filters) = @_;
3555 my $e = new_editor(authtoken => $auth);
3556 return $e->die_event unless $e->checkauth;
3558 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3559 return $e->event unless
3560 $e->requestor->id == $user_id or
3561 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3563 # Find all payments for all transactions for user $user_id
3565 select => {mp => ['id']},
3570 select => {mbt => ['id']},
3572 where => {usr => $user_id}
3576 order_by => [{ # by default, order newest payments first
3578 field => 'payment_ts',
3583 for (qw/order_by limit offset/) {
3584 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3587 if(defined $filters->{where}) {
3588 foreach (keys %{$filters->{where}}) {
3589 # don't allow the caller to expand the result set to other users
3590 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3594 my $payment_ids = $e->json_query($query);
3595 for my $pid (@$payment_ids) {
3596 my $pay = $e->retrieve_money_payment([
3601 mbt => ['summary', 'circulation', 'grocery'],
3602 circ => ['target_copy'],
3603 acp => ['call_number'],
3611 xact_type => $pay->xact->summary->xact_type,
3612 last_billing_type => $pay->xact->summary->last_billing_type,
3615 if($pay->xact->summary->xact_type eq 'circulation') {
3616 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3617 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3620 $pay->xact($pay->xact->id); # de-flesh
3621 $conn->respond($resp);
3629 __PACKAGE__->register_method (
3630 method => 'negative_balance_users',
3631 api_name => 'open-ils.actor.users.negative_balance',
3634 Returns all users that have an overall negative balance
3635 @param auth Authentication token
3636 @param org_id The context org unit as an ID or list of IDs. This will be the home
3637 library of the user. If no org_unit is specified, no org unit filter is applied
3641 sub negative_balance_users {
3642 my($self, $conn, $auth, $org_id) = @_;
3644 my $e = new_editor(authtoken => $auth);
3645 return $e->die_event unless $e->checkauth;
3646 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3650 mous => ['usr', 'balance_owed'],
3653 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3654 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3671 where => {'+mous' => {balance_owed => {'<' => 0}}}
3674 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3676 my $list = $e->json_query($query, {timeout => 600});
3678 for my $data (@$list) {
3680 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3681 balance_owed => $data->{balance_owed},
3682 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3689 __PACKAGE__->register_method(
3690 method => "request_password_reset",
3691 api_name => "open-ils.actor.patron.password_reset.request",
3693 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3694 "method for changing a user's password. The UUID token is distributed via A/T " .
3695 "templates (i.e. email to the user).",
3697 { desc => 'user_id_type', type => 'string' },
3698 { desc => 'user_id', type => 'string' },
3699 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3701 return => {desc => '1 on success, Event on error'}
3704 sub request_password_reset {
3705 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3707 # Check to see if password reset requests are already being throttled:
3708 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3710 my $e = new_editor(xact => 1);
3713 # Get the user, if any, depending on the input value
3714 if ($user_id_type eq 'username') {
3715 $user = $e->search_actor_user({usrname => $user_id})->[0];
3718 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3720 } elsif ($user_id_type eq 'barcode') {
3721 my $card = $e->search_actor_card([
3722 {barcode => $user_id},
3723 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3726 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3731 # If the user doesn't have an email address, we can't help them
3732 if (!$user->email) {
3734 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3737 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3738 if ($email_must_match) {
3739 if ($user->email ne $email) {
3740 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3744 _reset_password_request($conn, $e, $user);
3747 # Once we have the user, we can issue the password reset request
3748 # XXX Add a wrapper method that accepts barcode + email input
3749 sub _reset_password_request {
3750 my ($conn, $e, $user) = @_;
3752 # 1. Get throttle threshold and time-to-live from OU_settings
3753 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3754 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3756 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3758 # 2. Get time of last request and number of active requests (num_active)
3759 my $active_requests = $e->json_query({
3765 transform => 'COUNT'
3768 column => 'request_time',
3774 has_been_reset => { '=' => 'f' },
3775 request_time => { '>' => $threshold_time }
3779 # Guard against no active requests
3780 if ($active_requests->[0]->{'request_time'}) {
3781 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3782 my $now = DateTime::Format::ISO8601->new();
3784 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3785 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3786 ($last_request->add_duration('1 minute') > $now)) {
3787 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3789 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3793 # TODO Check to see if the user is in a password-reset-restricted group
3795 # Otherwise, go ahead and try to get the user.
3797 # Check the number of active requests for this user
3798 $active_requests = $e->json_query({
3804 transform => 'COUNT'
3809 usr => { '=' => $user->id },
3810 has_been_reset => { '=' => 'f' },
3811 request_time => { '>' => $threshold_time }
3815 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3817 # if less than or equal to per-user threshold, proceed; otherwise, return event
3818 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3819 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3821 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3824 # Create the aupr object and insert into the database
3825 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3826 my $uuid = create_uuid_as_string(UUID_V4);
3827 $reset_request->uuid($uuid);
3828 $reset_request->usr($user->id);
3830 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3833 # Create an event to notify user of the URL to reset their password
3835 # Can we stuff this in the user_data param for trigger autocreate?
3836 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3838 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3839 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3842 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3847 __PACKAGE__->register_method(
3848 method => "commit_password_reset",
3849 api_name => "open-ils.actor.patron.password_reset.commit",
3851 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3852 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3853 "with the supplied password.",
3855 { desc => 'uuid', type => 'string' },
3856 { desc => 'password', type => 'string' },
3858 return => {desc => '1 on success, Event on error'}
3861 sub commit_password_reset {
3862 my($self, $conn, $uuid, $password) = @_;
3864 # Check to see if password reset requests are already being throttled:
3865 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3866 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3867 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3869 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3872 my $e = new_editor(xact => 1);
3874 my $aupr = $e->search_actor_usr_password_reset({
3881 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3883 my $user_id = $aupr->[0]->usr;
3884 my $user = $e->retrieve_actor_user($user_id);
3886 # Ensure we're still within the TTL for the request
3887 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3888 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3889 if ($threshold < DateTime->now(time_zone => 'local')) {
3891 $logger->info("Password reset request needed to be submitted before $threshold");
3892 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3895 # Check complexity of password against OU-defined regex
3896 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3900 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3901 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3902 $is_strong = check_password_strength_custom($password, $pw_regex);
3904 $is_strong = check_password_strength_default($password);
3909 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3912 # All is well; update the password
3913 $user->passwd($password);
3914 $e->update_actor_user($user);
3916 # And flag that this password reset request has been honoured
3917 $aupr->[0]->has_been_reset('t');
3918 $e->update_actor_usr_password_reset($aupr->[0]);
3924 sub check_password_strength_default {
3925 my $password = shift;
3926 # Use the default set of checks
3927 if ( (length($password) < 7) or
3928 ($password !~ m/.*\d+.*/) or
3929 ($password !~ m/.*[A-Za-z]+.*/)
3936 sub check_password_strength_custom {
3937 my ($password, $pw_regex) = @_;
3939 $pw_regex = qr/$pw_regex/;
3940 if ($password !~ /$pw_regex/) {
3948 __PACKAGE__->register_method(
3949 method => "event_def_opt_in_settings",
3950 api_name => "open-ils.actor.event_def.opt_in.settings",
3953 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3955 { desc => 'Authentication token', type => 'string'},
3957 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3962 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3969 sub event_def_opt_in_settings {
3970 my($self, $conn, $auth, $org_id) = @_;
3971 my $e = new_editor(authtoken => $auth);
3972 return $e->event unless $e->checkauth;
3974 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3975 return $e->event unless
3976 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3978 $org_id = $e->requestor->home_ou;
3981 # find all config.user_setting_type's related to event_defs for the requested org unit
3982 my $types = $e->json_query({
3983 select => {cust => ['name']},
3984 from => {atevdef => 'cust'},
3987 owner => $U->get_org_ancestors($org_id), # context org plus parents
3994 $conn->respond($_) for
3995 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4002 __PACKAGE__->register_method(
4003 method => "user_visible_circs",
4004 api_name => "open-ils.actor.history.circ.visible",
4007 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
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/An object with 2 fields: circulation and summary.
4015 circulation is the "circ" object. summary is the related "accs" object/,
4021 __PACKAGE__->register_method(
4022 method => "user_visible_circs",
4023 api_name => "open-ils.actor.history.circ.visible.print",
4026 desc => 'Returns printable output for the set of opt-in visible circulations',
4028 { desc => 'Authentication token', type => 'string'},
4029 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4030 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4033 desc => q/An action_trigger.event object or error event./,
4039 __PACKAGE__->register_method(
4040 method => "user_visible_circs",
4041 api_name => "open-ils.actor.history.circ.visible.email",
4044 desc => 'Emails the set of opt-in visible circulations to the requestor',
4046 { desc => 'Authentication token', type => 'string'},
4047 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4048 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4051 desc => q/undef, or event on error/
4056 __PACKAGE__->register_method(
4057 method => "user_visible_circs",
4058 api_name => "open-ils.actor.history.hold.visible",
4061 desc => 'Returns the set of opt-in visible holds',
4063 { desc => 'Authentication token', type => 'string'},
4064 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4065 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4068 desc => q/An object with 1 field: "hold"/,
4074 __PACKAGE__->register_method(
4075 method => "user_visible_circs",
4076 api_name => "open-ils.actor.history.hold.visible.print",
4079 desc => 'Returns printable output for the set of opt-in visible holds',
4081 { desc => 'Authentication token', type => 'string'},
4082 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4083 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4086 desc => q/An action_trigger.event object or error event./,
4092 __PACKAGE__->register_method(
4093 method => "user_visible_circs",
4094 api_name => "open-ils.actor.history.hold.visible.email",
4097 desc => 'Emails the set of opt-in visible holds to the requestor',
4099 { desc => 'Authentication token', type => 'string'},
4100 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4101 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4104 desc => q/undef, or event on error/
4109 sub user_visible_circs {
4110 my($self, $conn, $auth, $user_id, $options) = @_;
4112 my $is_hold = ($self->api_name =~ /hold/);
4113 my $for_print = ($self->api_name =~ /print/);
4114 my $for_email = ($self->api_name =~ /email/);
4115 my $e = new_editor(authtoken => $auth);
4116 return $e->event unless $e->checkauth;
4118 $user_id ||= $e->requestor->id;
4120 $options->{limit} ||= 50;
4121 $options->{offset} ||= 0;
4123 if($user_id != $e->requestor->id) {
4124 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4125 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4126 return $e->event unless $e->allowed($perm, $user->home_ou);
4129 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4131 my $data = $e->json_query({
4132 from => [$db_func, $user_id],
4133 limit => $$options{limit},
4134 offset => $$options{offset}
4136 # TODO: I only want IDs. code below didn't get me there
4137 # {"select":{"au":[{"column":"id", "result_field":"id",
4138 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4143 return undef unless @$data;
4147 # collect the batch of objects
4151 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4152 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4156 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4157 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4160 } elsif ($for_email) {
4162 $conn->respond_complete(1) if $for_email; # no sense in waiting
4170 my $hold = $e->retrieve_action_hold_request($id);
4171 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4172 # events will be fired from action_trigger_runner
4176 my $circ = $e->retrieve_action_circulation($id);
4177 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4178 # events will be fired from action_trigger_runner
4182 } else { # just give me the data please
4190 my $hold = $e->retrieve_action_hold_request($id);
4191 $conn->respond({hold => $hold});
4195 my $circ = $e->retrieve_action_circulation($id);
4198 summary => $U->create_circ_chain_summary($e, $id)
4207 __PACKAGE__->register_method(
4208 method => "user_saved_search_cud",
4209 api_name => "open-ils.actor.user.saved_search.cud",
4212 desc => 'Create/Update/Delete Access to user saved searches',
4214 { desc => 'Authentication token', type => 'string' },
4215 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4218 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4224 __PACKAGE__->register_method(
4225 method => "user_saved_search_cud",
4226 api_name => "open-ils.actor.user.saved_search.retrieve",
4229 desc => 'Retrieve a saved search object',
4231 { desc => 'Authentication token', type => 'string' },
4232 { desc => 'Saved Search ID', type => 'number' }
4235 desc => q/The saved search object, Event on error/,
4241 sub user_saved_search_cud {
4242 my( $self, $client, $auth, $search ) = @_;
4243 my $e = new_editor( authtoken=>$auth );
4244 return $e->die_event unless $e->checkauth;
4246 my $o_search; # prior version of the object, if any
4247 my $res; # to be returned
4249 # branch on the operation type
4251 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4253 # Get the old version, to check ownership
4254 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4255 or return $e->die_event;
4257 # You can't read somebody else's search
4258 return OpenILS::Event->new('BAD_PARAMS')
4259 unless $o_search->owner == $e->requestor->id;
4265 $e->xact_begin; # start an editor transaction
4267 if( $search->isnew ) { # Create
4269 # You can't create a search for somebody else
4270 return OpenILS::Event->new('BAD_PARAMS')
4271 unless $search->owner == $e->requestor->id;
4273 $e->create_actor_usr_saved_search( $search )
4274 or return $e->die_event;
4278 } elsif( $search->ischanged ) { # Update
4280 # You can't change ownership of a search
4281 return OpenILS::Event->new('BAD_PARAMS')
4282 unless $search->owner == $e->requestor->id;
4284 # Get the old version, to check ownership
4285 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4286 or return $e->die_event;
4288 # You can't update somebody else's search
4289 return OpenILS::Event->new('BAD_PARAMS')
4290 unless $o_search->owner == $e->requestor->id;
4293 $e->update_actor_usr_saved_search( $search )
4294 or return $e->die_event;
4298 } elsif( $search->isdeleted ) { # Delete
4300 # Get the old version, to check ownership
4301 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4302 or return $e->die_event;
4304 # You can't delete somebody else's search
4305 return OpenILS::Event->new('BAD_PARAMS')
4306 unless $o_search->owner == $e->requestor->id;
4309 $e->delete_actor_usr_saved_search( $o_search )
4310 or return $e->die_event;
4321 __PACKAGE__->register_method(
4322 method => "get_barcodes",
4323 api_name => "open-ils.actor.get_barcodes"
4327 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4328 my $e = new_editor(authtoken => $auth);
4329 return $e->event unless $e->checkauth;
4330 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4332 my $db_result = $e->json_query(
4334 'evergreen.get_barcodes',
4335 $org_id, $context, $barcode,
4339 if($context =~ /actor/) {
4340 my $filter_result = ();
4342 foreach my $result (@$db_result) {
4343 if($result->{type} eq 'actor') {
4344 if($e->requestor->id != $result->{id}) {
4345 $patron = $e->retrieve_actor_user($result->{id});
4347 push(@$filter_result, $e->event);
4350 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4351 push(@$filter_result, $result);
4354 push(@$filter_result, $e->event);
4358 push(@$filter_result, $result);
4362 push(@$filter_result, $result);
4365 return $filter_result;
4371 __PACKAGE__->register_method(
4372 method => 'address_alert_test',
4373 api_name => 'open-ils.actor.address_alert.test',
4375 desc => "Tests a set of address fields to determine if they match with an address_alert",
4377 {desc => 'Authentication token', type => 'string'},
4378 {desc => 'Org Unit', type => 'number'},
4379 {desc => 'Fields', type => 'hash'},
4381 return => {desc => 'List of matching address_alerts'}
4385 sub address_alert_test {
4386 my ($self, $client, $auth, $org_unit, $fields) = @_;
4387 return [] unless $fields and grep {$_} values %$fields;
4389 my $e = new_editor(authtoken => $auth);
4390 return $e->event unless $e->checkauth;
4391 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4392 $org_unit ||= $e->requestor->ws_ou;
4394 my $alerts = $e->json_query({
4396 'actor.address_alert_matches',
4404 $$fields{post_code},
4405 $$fields{mailing_address},
4406 $$fields{billing_address}
4410 # map the json_query hashes to real objects
4412 map {$e->retrieve_actor_address_alert($_)}
4413 (map {$_->{id}} @$alerts)