1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
35 use OpenILS::Utils::CStoreEditor qw/:funcs/;
36 use OpenILS::Utils::Penalty;
37 use List::Util qw/max/;
39 use UUID::Tiny qw/:std/;
42 OpenILS::Application::Actor::Container->initialize();
43 OpenILS::Application::Actor::UserGroups->initialize();
44 OpenILS::Application::Actor::ClosedDates->initialize();
47 my $apputils = "OpenILS::Application::AppUtils";
50 sub _d { warn "Patron:\n" . Dumper(shift()); }
53 my $set_user_settings;
57 #__PACKAGE__->register_method(
58 # method => "allowed_test",
59 # api_name => "open-ils.actor.allowed_test",
62 # my($self, $conn, $auth, $orgid, $permcode) = @_;
63 # my $e = new_editor(authtoken => $auth);
64 # return $e->die_event unless $e->checkauth;
68 # permcode => $permcode,
69 # result => $e->allowed($permcode, $orgid)
73 __PACKAGE__->register_method(
74 method => "update_user_setting",
75 api_name => "open-ils.actor.patron.settings.update",
77 sub update_user_setting {
78 my($self, $conn, $auth, $user_id, $settings) = @_;
79 my $e = new_editor(xact => 1, authtoken => $auth);
80 return $e->die_event unless $e->checkauth;
82 $user_id = $e->requestor->id unless defined $user_id;
84 unless($e->requestor->id == $user_id) {
85 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
86 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
89 for my $name (keys %$settings) {
90 my $val = $$settings{$name};
91 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
94 $val = OpenSRF::Utils::JSON->perl2JSON($val);
97 $e->update_actor_user_setting($set) or return $e->die_event;
99 $set = Fieldmapper::actor::user_setting->new;
103 $e->create_actor_user_setting($set) or return $e->die_event;
106 $e->delete_actor_user_setting($set) or return $e->die_event;
115 __PACKAGE__->register_method(
116 method => "set_ou_settings",
117 api_name => "open-ils.actor.org_unit.settings.update",
119 desc => "Updates the value for a given org unit setting. The permission to update " .
120 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
121 "permission specified in the update_perm column of the config.org_unit_setting_type " .
122 "table's row corresponding to the setting being changed." ,
124 {desc => 'Authentication token', type => 'string'},
125 {desc => 'Org unit ID', type => 'number'},
126 {desc => 'Hash of setting name-value pairs', type => 'object'}
128 return => {desc => '1 on success, Event on error'}
132 sub set_ou_settings {
133 my( $self, $client, $auth, $org_id, $settings ) = @_;
135 my $e = new_editor(authtoken => $auth, xact => 1);
136 return $e->die_event unless $e->checkauth;
138 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
140 for my $name (keys %$settings) {
141 my $val = $$settings{$name};
143 my $type = $e->retrieve_config_org_unit_setting_type([
145 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
146 ]) or return $e->die_event;
147 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
149 # If there is no relevant permission, the default assumption will
150 # be, "no, the caller cannot change that value."
151 return $e->die_event unless ($all_allowed ||
152 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
155 $val = OpenSRF::Utils::JSON->perl2JSON($val);
158 $e->update_actor_org_unit_setting($set) or return $e->die_event;
160 $set = Fieldmapper::actor::org_unit_setting->new;
161 $set->org_unit($org_id);
164 $e->create_actor_org_unit_setting($set) or return $e->die_event;
167 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
175 __PACKAGE__->register_method(
176 method => "user_settings",
177 api_name => "open-ils.actor.patron.settings.retrieve",
180 my( $self, $client, $auth, $user_id, $setting ) = @_;
182 my $e = new_editor(authtoken => $auth);
183 return $e->event unless $e->checkauth;
184 $user_id = $e->requestor->id unless defined $user_id;
186 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
187 if($e->requestor->id != $user_id) {
188 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
192 my($e, $user_id, $setting) = @_;
193 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
194 return undef unless $val; # XXX this should really return undef, but needs testing
195 return OpenSRF::Utils::JSON->JSON2perl($val->value);
199 if(ref $setting eq 'ARRAY') {
201 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
204 return get_setting($e, $user_id, $setting);
207 my $s = $e->search_actor_user_setting({usr => $user_id});
208 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
213 __PACKAGE__->register_method(
214 method => "ranged_ou_settings",
215 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
217 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
218 "is implied for retrieving OU settings by the authenticated users' permissions.",
220 {desc => 'Authentication token', type => 'string'},
221 {desc => 'Org unit ID', type => 'number'},
223 return => {desc => 'A hashref of "ranged" settings, event on error'}
226 sub ranged_ou_settings {
227 my( $self, $client, $auth, $org_id ) = @_;
229 my $e = new_editor(authtoken => $auth);
230 return $e->event unless $e->checkauth;
233 my $org_list = $U->get_org_ancestors($org_id);
234 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
235 $org_list = [ reverse @$org_list ];
237 # start at the context org and capture the setting value
238 # without clobbering settings we've already captured
239 for my $this_org_id (@$org_list) {
241 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
243 for my $set (@sets) {
244 my $type = $e->retrieve_config_org_unit_setting_type([
246 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
249 # If there is no relevant permission, the default assumption will
250 # be, "yes, the caller can have that value."
251 if ($type && $type->view_perm) {
252 next if not $e->allowed($type->view_perm->code, $org_id);
255 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
256 unless defined $ranged_settings{$set->name};
260 return \%ranged_settings;
265 __PACKAGE__->register_method(
266 api_name => 'open-ils.actor.ou_setting.ancestor_default',
267 method => 'ou_ancestor_setting',
269 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
270 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
271 'user has permission to view that setting, if there is a permission associated with the setting.' ,
273 { desc => 'Org unit ID', type => 'number' },
274 { desc => 'setting name', type => 'string' },
275 { desc => 'authtoken (optional)', type => 'string' }
277 return => {desc => 'A value for the org unit setting, or undef'}
281 # ------------------------------------------------------------------
282 # Attempts to find the org setting value for a given org. if not
283 # found at the requested org, searches up the org tree until it
284 # finds a parent that has the requested setting.
285 # when found, returns { org => $id, value => $value }
286 # otherwise, returns NULL
287 # ------------------------------------------------------------------
288 sub ou_ancestor_setting {
289 my( $self, $client, $orgid, $name, $auth ) = @_;
290 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
293 __PACKAGE__->register_method(
294 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
295 method => 'ou_ancestor_setting_batch',
297 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
298 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given ' .
299 'user has permission to view that setting, if there is a permission associated with the setting.' ,
301 { desc => 'Org unit ID', type => 'number' },
302 { desc => 'setting name list', type => 'array' },
303 { desc => 'authtoken (optional)', type => 'string' }
305 return => {desc => 'A hash with name => value pairs for the org unit settings'}
308 sub ou_ancestor_setting_batch {
309 my( $self, $client, $orgid, $name_list, $auth ) = @_;
311 $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
317 __PACKAGE__->register_method(
318 method => "update_patron",
319 api_name => "open-ils.actor.patron.update",
322 Update an existing user, or create a new one. Related objects,
323 like cards, addresses, survey responses, and stat cats,
324 can be updated by attaching them to the user object in their
325 respective fields. For examples, the billing address object
326 may be inserted into the 'billing_address' field, etc. For each
327 attached object, indicate if the object should be created,
328 updated, or deleted using the built-in 'isnew', 'ischanged',
329 and 'isdeleted' fields on the object.
332 { desc => 'Authentication token', type => 'string' },
333 { desc => 'Patron data object', type => 'object' }
335 return => {desc => 'A fleshed user object, event on error'}
340 my( $self, $client, $user_session, $patron ) = @_;
342 my $session = $apputils->start_db_session();
344 $logger->info($patron->isnew ? "Creating new patron..." : "Updating Patron: " . $patron->id);
346 my( $user_obj, $evt ) = $U->checkses($user_session);
349 $evt = check_group_perm($session, $user_obj, $patron);
353 # $new_patron is the patron in progress. $patron is the original patron
354 # passed in with the method. new_patron will change as the components
355 # of patron are added/updated.
359 # unflesh the real items on the patron
360 $patron->card( $patron->card->id ) if(ref($patron->card));
361 $patron->billing_address( $patron->billing_address->id )
362 if(ref($patron->billing_address));
363 $patron->mailing_address( $patron->mailing_address->id )
364 if(ref($patron->mailing_address));
366 # create/update the patron first so we can use his id
367 if($patron->isnew()) {
368 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
370 } else { $new_patron = $patron; }
372 ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
375 ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
378 ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
381 # re-update the patron if anything has happened to him during this process
382 if($new_patron->ischanged()) {
383 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
387 ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
390 ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
393 $apputils->commit_db_session($session);
395 $evt = apply_invalid_addr_penalty($patron);
398 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
400 $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
402 $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
405 return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
408 sub apply_invalid_addr_penalty {
410 my $e = new_editor(xact => 1);
412 # grab the invalid address penalty if set
413 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
415 my ($addr_penalty) = grep
416 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
418 # do we enforce invalid address penalty
419 my $enforce = $U->ou_ancestor_setting_value(
420 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
422 my $addrs = $e->search_actor_user_address(
423 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
424 my $addr_count = scalar(@$addrs);
426 if($addr_count == 0 and $addr_penalty) {
428 # regardless of any settings, remove the penalty when the user has no invalid addresses
429 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
432 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
434 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
435 my $depth = $ptype->org_depth;
436 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
437 $ctx_org = $patron->home_ou unless defined $ctx_org;
439 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
440 $penalty->usr($patron->id);
441 $penalty->org_unit($ctx_org);
442 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
444 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
458 return new_flesh_user($id, [
461 "standing_penalties",
465 "stat_cat_entries" ], $e );
473 # clone and clear stuff that would break the database
477 my $new_patron = $patron->clone;
479 $new_patron->clear_billing_address();
480 $new_patron->clear_mailing_address();
481 $new_patron->clear_addresses();
482 $new_patron->clear_card();
483 $new_patron->clear_cards();
484 $new_patron->clear_id();
485 $new_patron->clear_isnew();
486 $new_patron->clear_ischanged();
487 $new_patron->clear_isdeleted();
488 $new_patron->clear_stat_cat_entries();
489 $new_patron->clear_permissions();
490 $new_patron->clear_standing_penalties();
500 my $user_obj = shift;
502 my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
503 return (undef, $evt) if $evt;
505 my $ex = $session->request(
506 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
508 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
511 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
513 my $id = $session->request(
514 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
515 return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
517 $logger->info("Successfully created new user [$id] in DB");
519 return ( $session->request(
520 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
524 sub check_group_perm {
525 my( $session, $requestor, $patron ) = @_;
528 # first let's see if the requestor has
529 # priveleges to update this user in any way
530 if( ! $patron->isnew ) {
531 my $p = $session->request(
532 'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
534 # If we are the requestor (trying to update our own account)
535 # and we are not trying to change our profile, we're good
536 if( $p->id == $requestor->id and
537 $p->profile == $patron->profile ) {
542 $evt = group_perm_failed($session, $requestor, $p);
546 # They are allowed to edit this patron.. can they put the
547 # patron into the group requested?
548 $evt = group_perm_failed($session, $requestor, $patron);
554 sub group_perm_failed {
555 my( $session, $requestor, $patron ) = @_;
559 my $grpid = $patron->profile;
563 $logger->debug("user update looking for group perm for group $grpid");
564 $grp = $session->request(
565 'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
566 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
568 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
570 $logger->info("user update checking perm $perm on user ".
571 $requestor->id." for update/create on user username=".$patron->usrname);
573 my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
581 my( $session, $patron, $user_obj, $noperm) = @_;
583 $logger->info("Updating patron ".$patron->id." in DB");
588 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
589 return (undef, $evt) if $evt;
592 # update the password by itself to avoid the password protection magic
593 if( $patron->passwd ) {
594 my $s = $session->request(
595 'open-ils.storage.direct.actor.user.remote_update',
596 {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
597 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
598 $patron->clear_passwd;
601 if(!$patron->ident_type) {
602 $patron->clear_ident_type;
603 $patron->clear_ident_value;
606 $evt = verify_last_xact($session, $patron);
607 return (undef, $evt) if $evt;
609 my $stat = $session->request(
610 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
611 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
616 sub verify_last_xact {
617 my( $session, $patron ) = @_;
618 return undef unless $patron->id and $patron->id > 0;
619 my $p = $session->request(
620 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
621 my $xact = $p->last_xact_id;
622 return undef unless $xact;
623 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
624 return OpenILS::Event->new('XACT_COLLISION')
625 if $xact != $patron->last_xact_id;
630 sub _check_dup_ident {
631 my( $session, $patron ) = @_;
633 return undef unless $patron->ident_value;
636 ident_type => $patron->ident_type,
637 ident_value => $patron->ident_value,
640 $logger->debug("patron update searching for dup ident values: " .
641 $patron->ident_type . ':' . $patron->ident_value);
643 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
645 my $dups = $session->request(
646 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
649 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
656 sub _add_update_addresses {
660 my $new_patron = shift;
664 my $current_id; # id of the address before creation
666 for my $address (@{$patron->addresses()}) {
668 next unless ref $address;
669 $current_id = $address->id();
671 if( $patron->billing_address() and
672 $patron->billing_address() == $current_id ) {
673 $logger->info("setting billing addr to $current_id");
674 $new_patron->billing_address($address->id());
675 $new_patron->ischanged(1);
678 if( $patron->mailing_address() and
679 $patron->mailing_address() == $current_id ) {
680 $new_patron->mailing_address($address->id());
681 $logger->info("setting mailing addr to $current_id");
682 $new_patron->ischanged(1);
686 if($address->isnew()) {
688 $address->usr($new_patron->id());
690 ($address, $evt) = _add_address($session,$address);
691 return (undef, $evt) if $evt;
693 # we need to get the new id
694 if( $patron->billing_address() and
695 $patron->billing_address() == $current_id ) {
696 $new_patron->billing_address($address->id());
697 $logger->info("setting billing addr to $current_id");
698 $new_patron->ischanged(1);
701 if( $patron->mailing_address() and
702 $patron->mailing_address() == $current_id ) {
703 $new_patron->mailing_address($address->id());
704 $logger->info("setting mailing addr to $current_id");
705 $new_patron->ischanged(1);
708 } elsif($address->ischanged() ) {
710 ($address, $evt) = _update_address($session, $address);
711 return (undef, $evt) if $evt;
713 } elsif($address->isdeleted() ) {
715 if( $address->id() == $new_patron->mailing_address() ) {
716 $new_patron->clear_mailing_address();
717 ($new_patron, $evt) = _update_patron($session, $new_patron);
718 return (undef, $evt) if $evt;
721 if( $address->id() == $new_patron->billing_address() ) {
722 $new_patron->clear_billing_address();
723 ($new_patron, $evt) = _update_patron($session, $new_patron);
724 return (undef, $evt) if $evt;
727 $evt = _delete_address($session, $address);
728 return (undef, $evt) if $evt;
732 return ( $new_patron, undef );
736 # adds an address to the db and returns the address with new id
738 my($session, $address) = @_;
739 $address->clear_id();
741 $logger->info("Creating new address at street ".$address->street1);
743 # put the address into the database
744 my $id = $session->request(
745 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
746 return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
749 return ($address, undef);
753 sub _update_address {
754 my( $session, $address ) = @_;
756 $logger->info("Updating address ".$address->id." in the DB");
758 my $stat = $session->request(
759 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
761 return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
762 return ($address, undef);
767 sub _add_update_cards {
771 my $new_patron = shift;
775 my $virtual_id; #id of the card before creation
776 for my $card (@{$patron->cards()}) {
778 $card->usr($new_patron->id());
780 if(ref($card) and $card->isnew()) {
782 $virtual_id = $card->id();
783 ( $card, $evt ) = _add_card($session,$card);
784 return (undef, $evt) if $evt;
786 #if(ref($patron->card)) { $patron->card($patron->card->id); }
787 if($patron->card() == $virtual_id) {
788 $new_patron->card($card->id());
789 $new_patron->ischanged(1);
792 } elsif( ref($card) and $card->ischanged() ) {
793 $evt = _update_card($session, $card);
794 return (undef, $evt) if $evt;
798 return ( $new_patron, undef );
802 # adds an card to the db and returns the card with new id
804 my( $session, $card ) = @_;
807 $logger->info("Adding new patron card ".$card->barcode);
809 my $id = $session->request(
810 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
811 return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
812 $logger->info("Successfully created patron card $id");
815 return ( $card, undef );
819 # returns event on error. returns undef otherwise
821 my( $session, $card ) = @_;
822 $logger->info("Updating patron card ".$card->id);
824 my $stat = $session->request(
825 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
826 return $U->DB_UPDATE_FAILED($card) unless defined($stat);
833 # returns event on error. returns undef otherwise
834 sub _delete_address {
835 my( $session, $address ) = @_;
837 $logger->info("Deleting address ".$address->id." from DB");
839 my $stat = $session->request(
840 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
842 return $U->DB_UPDATE_FAILED($address) unless defined($stat);
848 sub _add_survey_responses {
849 my ($session, $patron, $new_patron) = @_;
851 $logger->info( "Updating survey responses for patron ".$new_patron->id );
853 my $responses = $patron->survey_responses;
857 $_->usr($new_patron->id) for (@$responses);
859 my $evt = $U->simplereq( "open-ils.circ",
860 "open-ils.circ.survey.submit.user_id", $responses );
862 return (undef, $evt) if defined($U->event_code($evt));
866 return ( $new_patron, undef );
870 sub _create_stat_maps {
872 my($session, $user_session, $patron, $new_patron) = @_;
874 my $maps = $patron->stat_cat_entries();
876 for my $map (@$maps) {
878 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
880 if ($map->isdeleted()) {
881 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
883 } elsif ($map->isnew()) {
884 $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
889 $map->target_usr($new_patron->id);
892 $logger->info("Updating stat entry with method $method and map $map");
894 my $stat = $session->request($method, $map)->gather(1);
895 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
899 return ($new_patron, undef);
902 sub _create_perm_maps {
904 my($session, $user_session, $patron, $new_patron) = @_;
906 my $maps = $patron->permissions;
908 for my $map (@$maps) {
910 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
911 if ($map->isdeleted()) {
912 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
913 } elsif ($map->isnew()) {
914 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
919 $map->usr($new_patron->id);
921 #warn( "Updating permissions with method $method and session $user_session and map $map" );
922 $logger->info( "Updating permissions with method $method and map $map" );
924 my $stat = $session->request($method, $map)->gather(1);
925 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
929 return ($new_patron, undef);
933 __PACKAGE__->register_method(
934 method => "set_user_work_ous",
935 api_name => "open-ils.actor.user.work_ous.update",
938 sub set_user_work_ous {
944 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
947 my $session = $apputils->start_db_session();
949 for my $map (@$maps) {
951 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
952 if ($map->isdeleted()) {
953 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
954 } elsif ($map->isnew()) {
955 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
959 #warn( "Updating permissions with method $method and session $ses and map $map" );
960 $logger->info( "Updating work_ou map with method $method and map $map" );
962 my $stat = $session->request($method, $map)->gather(1);
963 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
967 $apputils->commit_db_session($session);
969 return scalar(@$maps);
973 __PACKAGE__->register_method(
974 method => "set_user_perms",
975 api_name => "open-ils.actor.user.permissions.update",
984 my $session = $apputils->start_db_session();
986 my( $user_obj, $evt ) = $U->checkses($ses);
989 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
992 $all = 1 if ($U->is_true($user_obj->super_user()));
993 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
995 for my $map (@$maps) {
997 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
998 if ($map->isdeleted()) {
999 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1000 } elsif ($map->isnew()) {
1001 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1005 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1006 #warn( "Updating permissions with method $method and session $ses and map $map" );
1007 $logger->info( "Updating permissions with method $method and map $map" );
1009 my $stat = $session->request($method, $map)->gather(1);
1010 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1014 $apputils->commit_db_session($session);
1016 return scalar(@$maps);
1020 __PACKAGE__->register_method(
1021 method => "user_retrieve_by_barcode",
1023 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1025 sub user_retrieve_by_barcode {
1026 my($self, $client, $auth, $barcode) = @_;
1028 my $e = new_editor(authtoken => $auth);
1029 return $e->event unless $e->checkauth;
1031 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1032 or return $e->event;
1034 my $user = flesh_user($card->usr, $e);
1035 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1041 __PACKAGE__->register_method(
1042 method => "get_user_by_id",
1044 api_name => "open-ils.actor.user.retrieve",
1047 sub get_user_by_id {
1048 my ($self, $client, $auth, $id) = @_;
1049 my $e = new_editor(authtoken=>$auth);
1050 return $e->event unless $e->checkauth;
1051 my $user = $e->retrieve_actor_user($id) or return $e->event;
1052 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1057 __PACKAGE__->register_method(
1058 method => "get_org_types",
1059 api_name => "open-ils.actor.org_types.retrieve",
1062 return $U->get_org_types();
1066 __PACKAGE__->register_method(
1067 method => "get_user_ident_types",
1068 api_name => "open-ils.actor.user.ident_types.retrieve",
1071 sub get_user_ident_types {
1072 return $ident_types if $ident_types;
1073 return $ident_types =
1074 new_editor()->retrieve_all_config_identification_type();
1078 __PACKAGE__->register_method(
1079 method => "get_org_unit",
1080 api_name => "open-ils.actor.org_unit.retrieve",
1084 my( $self, $client, $user_session, $org_id ) = @_;
1085 my $e = new_editor(authtoken => $user_session);
1087 return $e->event unless $e->checkauth;
1088 $org_id = $e->requestor->ws_ou;
1090 my $o = $e->retrieve_actor_org_unit($org_id)
1091 or return $e->event;
1095 __PACKAGE__->register_method(
1096 method => "search_org_unit",
1097 api_name => "open-ils.actor.org_unit_list.search",
1100 sub search_org_unit {
1102 my( $self, $client, $field, $value ) = @_;
1104 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1106 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1107 { $field => $value } );
1113 # build the org tree
1115 __PACKAGE__->register_method(
1116 method => "get_org_tree",
1117 api_name => "open-ils.actor.org_tree.retrieve",
1119 note => "Returns the entire org tree structure",
1125 return $U->get_org_tree($client->session->session_locale);
1129 __PACKAGE__->register_method(
1130 method => "get_org_descendants",
1131 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1134 # depth is optional. org_unit is the id
1135 sub get_org_descendants {
1136 my( $self, $client, $org_unit, $depth ) = @_;
1138 if(ref $org_unit eq 'ARRAY') {
1141 for my $i (0..scalar(@$org_unit)-1) {
1142 my $list = $U->simple_scalar_request(
1144 "open-ils.storage.actor.org_unit.descendants.atomic",
1145 $org_unit->[$i], $depth->[$i] );
1146 push(@trees, $U->build_org_tree($list));
1151 my $orglist = $apputils->simple_scalar_request(
1153 "open-ils.storage.actor.org_unit.descendants.atomic",
1154 $org_unit, $depth );
1155 return $U->build_org_tree($orglist);
1160 __PACKAGE__->register_method(
1161 method => "get_org_ancestors",
1162 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1165 # depth is optional. org_unit is the id
1166 sub get_org_ancestors {
1167 my( $self, $client, $org_unit, $depth ) = @_;
1168 my $orglist = $apputils->simple_scalar_request(
1170 "open-ils.storage.actor.org_unit.ancestors.atomic",
1171 $org_unit, $depth );
1172 return $U->build_org_tree($orglist);
1176 __PACKAGE__->register_method(
1177 method => "get_standings",
1178 api_name => "open-ils.actor.standings.retrieve"
1183 return $user_standings if $user_standings;
1184 return $user_standings =
1185 $apputils->simple_scalar_request(
1187 "open-ils.cstore.direct.config.standing.search.atomic",
1188 { id => { "!=" => undef } }
1193 __PACKAGE__->register_method(
1194 method => "get_my_org_path",
1195 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1198 sub get_my_org_path {
1199 my( $self, $client, $auth, $org_id ) = @_;
1200 my $e = new_editor(authtoken=>$auth);
1201 return $e->event unless $e->checkauth;
1202 $org_id = $e->requestor->ws_ou unless defined $org_id;
1204 return $apputils->simple_scalar_request(
1206 "open-ils.storage.actor.org_unit.full_path.atomic",
1211 __PACKAGE__->register_method(
1212 method => "patron_adv_search",
1213 api_name => "open-ils.actor.patron.search.advanced"
1215 sub patron_adv_search {
1216 my( $self, $client, $auth, $search_hash,
1217 $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1219 my $e = new_editor(authtoken=>$auth);
1220 return $e->event unless $e->checkauth;
1221 return $e->event unless $e->allowed('VIEW_USER');
1222 return $U->storagereq(
1223 "open-ils.storage.actor.user.crazy_search", $search_hash,
1224 $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1228 __PACKAGE__->register_method(
1229 method => "update_passwd",
1230 api_name => "open-ils.actor.user.password.update",
1232 desc => "Update the operator's password",
1234 { desc => 'Authentication token', type => 'string' },
1235 { desc => 'New password', type => 'string' },
1236 { desc => 'Current password', type => 'string' }
1238 return => {desc => '1 on success, Event on error or incorrect current password'}
1242 __PACKAGE__->register_method(
1243 method => "update_passwd",
1244 api_name => "open-ils.actor.user.username.update",
1246 desc => "Update the operator's username",
1248 { desc => 'Authentication token', type => 'string' },
1249 { desc => 'New username', type => 'string' }
1251 return => {desc => '1 on success, Event on error'}
1255 __PACKAGE__->register_method(
1256 method => "update_passwd",
1257 api_name => "open-ils.actor.user.email.update",
1259 desc => "Update the operator's email address",
1261 { desc => 'Authentication token', type => 'string' },
1262 { desc => 'New email address', type => 'string' }
1264 return => {desc => '1 on success, Event on error'}
1269 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1270 my $e = new_editor(xact=>1, authtoken=>$auth);
1271 return $e->die_event unless $e->checkauth;
1273 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1274 or return $e->die_event;
1275 my $api = $self->api_name;
1277 if( $api =~ /password/o ) {
1279 # make sure the original password matches the in-database password
1280 return OpenILS::Event->new('INCORRECT_PASSWORD')
1281 if md5_hex($orig_pw) ne $db_user->passwd;
1282 $db_user->passwd($new_val);
1286 # if we don't clear the password, the user will be updated with
1287 # a hashed version of the hashed version of their password
1288 $db_user->clear_passwd;
1290 if( $api =~ /username/o ) {
1292 # make sure no one else has this username
1293 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1294 return OpenILS::Event->new('USERNAME_EXISTS') if @$exist;
1295 $db_user->usrname($new_val);
1297 } elsif( $api =~ /email/o ) {
1298 $db_user->email($new_val);
1302 $e->update_actor_user($db_user) or return $e->die_event;
1309 __PACKAGE__->register_method(
1310 method => "check_user_perms",
1311 api_name => "open-ils.actor.user.perm.check",
1312 notes => <<" NOTES");
1313 Takes a login session, user id, an org id, and an array of perm type strings. For each
1314 perm type, if the user does *not* have the given permission it is added
1315 to a list which is returned from the method. If all permissions
1316 are allowed, an empty list is returned
1317 if the logged in user does not match 'user_id', then the logged in user must
1318 have VIEW_PERMISSION priveleges.
1321 sub check_user_perms {
1322 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1324 my( $staff, $evt ) = $apputils->checkses($login_session);
1325 return $evt if $evt;
1327 if($staff->id ne $user_id) {
1328 if( $evt = $apputils->check_perms(
1329 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1335 for my $perm (@$perm_types) {
1336 if($apputils->check_perms($user_id, $org_id, $perm)) {
1337 push @not_allowed, $perm;
1341 return \@not_allowed
1344 __PACKAGE__->register_method(
1345 method => "check_user_perms2",
1346 api_name => "open-ils.actor.user.perm.check.multi_org",
1348 Checks the permissions on a list of perms and orgs for a user
1349 @param authtoken The login session key
1350 @param user_id The id of the user to check
1351 @param orgs The array of org ids
1352 @param perms The array of permission names
1353 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1354 if the logged in user does not match 'user_id', then the logged in user must
1355 have VIEW_PERMISSION priveleges.
1358 sub check_user_perms2 {
1359 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1361 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1362 $authtoken, $user_id, 'VIEW_PERMISSION' );
1363 return $evt if $evt;
1366 for my $org (@$orgs) {
1367 for my $perm (@$perms) {
1368 if($apputils->check_perms($user_id, $org, $perm)) {
1369 push @not_allowed, [ $org, $perm ];
1374 return \@not_allowed
1378 __PACKAGE__->register_method(
1379 method => 'check_user_perms3',
1380 api_name => 'open-ils.actor.user.perm.highest_org',
1382 Returns the highest org unit id at which a user has a given permission
1383 If the requestor does not match the target user, the requestor must have
1384 'VIEW_PERMISSION' rights at the home org unit of the target user
1385 @param authtoken The login session key
1386 @param userid The id of the user in question
1387 @param perm The permission to check
1388 @return The org unit highest in the org tree within which the user has
1389 the requested permission
1392 sub check_user_perms3 {
1393 my($self, $client, $authtoken, $user_id, $perm) = @_;
1394 my $e = new_editor(authtoken=>$authtoken);
1395 return $e->event unless $e->checkauth;
1397 my $tree = $U->get_org_tree();
1399 unless($e->requestor->id == $user_id) {
1400 my $user = $e->retrieve_actor_user($user_id)
1401 or return $e->event;
1402 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1403 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1406 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1409 __PACKAGE__->register_method(
1410 method => 'user_has_work_perm_at',
1411 api_name => 'open-ils.actor.user.has_work_perm_at',
1415 Returns a set of org unit IDs which represent the highest orgs in
1416 the org tree where the user has the requested permission. The
1417 purpose of this method is to return the smallest set of org units
1418 which represent the full expanse of the user's ability to perform
1419 the requested action. The user whose perms this method should
1420 check is implied by the authtoken. /,
1422 {desc => 'authtoken', type => 'string'},
1423 {desc => 'permission name', type => 'string'},
1424 {desc => q/user id, optional. If present, check perms for
1425 this user instead of the logged in user/, type => 'number'},
1427 return => {desc => 'An array of org IDs'}
1431 sub user_has_work_perm_at {
1432 my($self, $conn, $auth, $perm, $user_id) = @_;
1433 my $e = new_editor(authtoken=>$auth);
1434 return $e->event unless $e->checkauth;
1435 if(defined $user_id) {
1436 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1437 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1439 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1442 __PACKAGE__->register_method(
1443 method => 'user_has_work_perm_at_batch',
1444 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1448 sub user_has_work_perm_at_batch {
1449 my($self, $conn, $auth, $perms, $user_id) = @_;
1450 my $e = new_editor(authtoken=>$auth);
1451 return $e->event unless $e->checkauth;
1452 if(defined $user_id) {
1453 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1454 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1457 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1463 __PACKAGE__->register_method(
1464 method => 'check_user_perms4',
1465 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1467 Returns the highest org unit id at which a user has a given permission
1468 If the requestor does not match the target user, the requestor must have
1469 'VIEW_PERMISSION' rights at the home org unit of the target user
1470 @param authtoken The login session key
1471 @param userid The id of the user in question
1472 @param perms An array of perm names to check
1473 @return An array of orgId's representing the org unit
1474 highest in the org tree within which the user has the requested permission
1475 The arrah of orgId's has matches the order of the perms array
1478 sub check_user_perms4 {
1479 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1481 my( $staff, $target, $org, $evt );
1483 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1484 $authtoken, $userid, 'VIEW_PERMISSION' );
1485 return $evt if $evt;
1488 return [] unless ref($perms);
1489 my $tree = $U->get_org_tree();
1491 for my $p (@$perms) {
1492 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1498 __PACKAGE__->register_method(
1499 method => "user_fines_summary",
1500 api_name => "open-ils.actor.user.fines.summary",
1503 desc => 'Returns a short summary of the users total open fines, ' .
1504 'excluding voided fines Params are login_session, user_id' ,
1506 {desc => 'Authentication token', type => 'string'},
1507 {desc => 'User ID', type => 'string'} # number?
1510 desc => "a 'mous' object, event on error",
1515 sub user_fines_summary {
1516 my( $self, $client, $auth, $user_id ) = @_;
1518 my $e = new_editor(authtoken=>$auth);
1519 return $e->event unless $e->checkauth;
1521 if( $user_id ne $e->requestor->id ) {
1522 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1523 return $e->event unless
1524 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1527 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1531 ##### a small consolidation of related method registrations
1532 my $common_params = [
1533 { desc => 'Authentication token', type => 'string' },
1534 { desc => 'User ID', type => 'string' },
1535 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1536 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1539 'open-ils.actor.user.transactions' => '',
1540 'open-ils.actor.user.transactions.fleshed' => '',
1541 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1542 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1543 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1544 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1547 foreach (keys %methods) {
1549 method => "user_transactions",
1552 desc => 'For a given user, retrieve a list of '
1553 . (/\.fleshed/ ? 'fleshed ' : '')
1554 . 'transactions' . $methods{$_}
1555 . ' optionally limited to transactions of a given type.',
1556 params => $common_params,
1558 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1559 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1563 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1564 __PACKAGE__->register_method(%args);
1567 # Now for the counts
1569 'open-ils.actor.user.transactions.count' => '',
1570 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1571 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1574 foreach (keys %methods) {
1576 method => "user_transactions",
1579 desc => 'For a given user, retrieve a count of open '
1580 . 'transactions' . $methods{$_}
1581 . ' optionally limited to transactions of a given type.',
1582 params => $common_params,
1583 return => { desc => "Integer count of transactions, or event on error" }
1586 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1587 __PACKAGE__->register_method(%args);
1590 __PACKAGE__->register_method(
1591 method => "user_transactions",
1592 api_name => "open-ils.actor.user.transactions.have_balance.total",
1595 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1596 . ' optionally limited to transactions of a given type.',
1597 params => $common_params,
1598 return => { desc => "Decimal balance value, or event on error" }
1603 sub user_transactions {
1604 my( $self, $client, $login_session, $user_id, $type, $options ) = @_;
1607 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1608 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1609 return $evt if $evt;
1611 my $api = $self->api_name();
1613 my $filter = ($api =~ /have_balance/o) ?
1614 { 'balance_owed' => { '<>' => 0 } }:
1615 { 'total_owed' => { '>' => 0 } };
1617 my ($trans) = $self->method_lookup(
1618 'open-ils.actor.user.transactions.history.still_open')
1619 ->run( $login_session, $user_id, $type, $filter, $options );
1621 if($api =~ /total/o) {
1623 for my $t (@$trans) {
1624 $total += $t->balance_owed;
1627 $logger->debug("Total balance owed by user $user_id: $total");
1631 ($api =~ /count/o ) and return scalar @$trans;
1632 ($api !~ /fleshed/o) and return $trans;
1635 for my $t (@$trans) {
1637 if( $t->xact_type ne 'circulation' ) {
1638 push @resp, {transaction => $t};
1642 my $circ = $apputils->simple_scalar_request(
1644 "open-ils.cstore.direct.action.circulation.retrieve",
1649 my $title = $apputils->simple_scalar_request(
1651 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1652 $circ->target_copy );
1656 my $u = OpenILS::Utils::ModsParser->new();
1657 $u->start_mods_batch($title->marc());
1658 my $mods = $u->finish_mods_batch();
1659 $mods->doc_id($title->id) if $mods;
1661 push @resp, {transaction => $t, circ => $circ, record => $mods };
1669 __PACKAGE__->register_method(
1670 method => "user_transaction_retrieve",
1671 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1673 notes => "Returns a fleshed transaction record"
1676 __PACKAGE__->register_method(
1677 method => "user_transaction_retrieve",
1678 api_name => "open-ils.actor.user.transaction.retrieve",
1680 notes => "Returns a transaction record"
1683 sub user_transaction_retrieve {
1684 my( $self, $client, $login_session, $bill_id ) = @_;
1686 # I think I'm deprecated... make sure. phasefx says, "No, I'll use you :)
1688 my $trans = $apputils->simple_scalar_request(
1690 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1694 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1695 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1696 return $evt if $evt;
1698 my $api = $self->api_name();
1699 if($api !~ /fleshed/o) { return $trans; }
1701 if( $trans->xact_type ne 'circulation' ) {
1702 $logger->debug("Returning non-circ transaction");
1703 return {transaction => $trans};
1706 my $circ = $apputils->simple_scalar_request(
1708 "open-ils.cstore.direct.action.circulation.retrieve",
1711 return {transaction => $trans} unless $circ;
1712 $logger->debug("Found the circ transaction");
1714 my $title = $apputils->simple_scalar_request(
1716 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1717 $circ->target_copy );
1719 return {transaction => $trans, circ => $circ } unless $title;
1720 $logger->debug("Found the circ title");
1723 my $copy = $apputils->simple_scalar_request(
1725 "open-ils.cstore.direct.asset.copy.retrieve",
1726 $circ->target_copy );
1729 my $u = OpenILS::Utils::ModsParser->new();
1730 $u->start_mods_batch($title->marc());
1731 $mods = $u->finish_mods_batch();
1733 if ($title->id == OILS_PRECAT_RECORD) {
1734 $mods = new Fieldmapper::metabib::virtual_record;
1735 $mods->doc_id(OILS_PRECAT_RECORD);
1736 $mods->title($copy->dummy_title);
1737 $mods->author($copy->dummy_author);
1741 $logger->debug("MODSized the circ title");
1743 return {transaction => $trans, circ => $circ, record => $mods, copy => $copy };
1747 __PACKAGE__->register_method(
1748 method => "hold_request_count",
1749 api_name => "open-ils.actor.user.hold_requests.count",
1752 notes => 'Returns hold ready/total counts'
1755 sub hold_request_count {
1756 my( $self, $client, $login_session, $userid ) = @_;
1758 my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1759 $login_session, $userid, 'VIEW_HOLD' );
1760 return $evt if $evt;
1763 my $holds = $apputils->simple_scalar_request(
1765 "open-ils.cstore.direct.action.hold_request.search.atomic",
1768 fulfillment_time => {"=" => undef },
1769 cancel_time => undef,
1774 for my $h (@$holds) {
1775 next unless $h->capture_time and $h->current_copy;
1777 my $copy = $apputils->simple_scalar_request(
1779 "open-ils.cstore.direct.asset.copy.retrieve",
1783 if ($copy and $copy->status == 8) {
1788 return { total => scalar(@$holds), ready => scalar(@ready) };
1791 __PACKAGE__->register_method(
1792 method => "checked_out",
1793 api_name => "open-ils.actor.user.checked_out",
1797 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
1798 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
1799 . "(i.e., outstanding balance or some other pending action on the circ). "
1800 . "The .count method also includes a 'total' field which sums all open circs.",
1802 { desc => 'Authentication Token', type => 'string'},
1803 { desc => 'User ID', type => 'string'},
1806 desc => 'Returns event on error, or an object with ID lists, like: '
1807 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
1812 __PACKAGE__->register_method(
1813 method => "checked_out",
1814 api_name => "open-ils.actor.user.checked_out.count",
1817 signature => q/@see open-ils.actor.user.checked_out/
1821 my( $self, $conn, $auth, $userid ) = @_;
1823 my $e = new_editor(authtoken=>$auth);
1824 return $e->event unless $e->checkauth;
1826 if( $userid ne $e->requestor->id ) {
1827 my $user = $e->retrieve_actor_user($userid) or return $e->event;
1828 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1830 # see if there is a friend link allowing circ.view perms
1831 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1832 $e, $userid, $e->requestor->id, 'circ.view');
1833 return $e->event unless $allowed;
1837 my $count = $self->api_name =~ /count/;
1838 return _checked_out( $count, $e, $userid );
1842 my( $iscount, $e, $userid ) = @_;
1843 my $meth = 'open-ils.storage.actor.user.checked_out';
1844 $meth = "$meth.count" if $iscount;
1845 return $U->storagereq($meth, $userid);
1850 __PACKAGE__->register_method(
1851 method => "checked_in_with_fines",
1852 api_name => "open-ils.actor.user.checked_in_with_fines",
1855 signature => q/@see open-ils.actor.user.checked_out/
1858 sub checked_in_with_fines {
1859 my( $self, $conn, $auth, $userid ) = @_;
1861 my $e = new_editor(authtoken=>$auth);
1862 return $e->event unless $e->checkauth;
1864 if( $userid ne $e->requestor->id ) {
1865 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1868 # money is owed on these items and they are checked in
1869 my $open = $e->search_action_circulation(
1872 xact_finish => undef,
1873 checkin_time => { "!=" => undef },
1878 my( @lost, @cr, @lo );
1879 for my $c (@$open) {
1880 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1881 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1882 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1887 claims_returned => \@cr,
1888 long_overdue => \@lo
1894 my ($api, $desc, $auth) = @_;
1895 $desc = $desc ? (" " . $desc) : '';
1896 my $ids = ($api =~ /ids$/) ? 1 : 0;
1899 method => "user_transaction_history",
1900 api_name => "open-ils.actor.user.transactions.$api",
1902 desc => "For a given User ID, returns a list of billable transaction" .
1903 ($ids ? " id" : '') .
1904 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
1905 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
1907 {desc => 'Authentication token', type => 'string'},
1908 {desc => 'User ID', type => 'number'},
1909 {desc => 'Transaction type (optional)', type => 'number'},
1910 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
1913 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
1917 $auth and push @sig, (authoritative => 1);
1921 my %hist_methods = (
1923 'history.have_charge' => 'that have an initial charge',
1924 'history.still_open' => 'that are not finished',
1926 my %auth_hist_methods = (
1927 'history.have_balance' => 'that have a balance',
1928 'history.have_bill' => 'that have billings',
1929 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
1931 foreach (keys %hist_methods) {
1932 __PACKAGE__->register_method(_sigmaker($_, $hist_methods{$_}));
1933 __PACKAGE__->register_method(_sigmaker("$_.ids", $hist_methods{$_}));
1935 foreach (keys %auth_hist_methods) {
1936 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
1937 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
1940 sub user_transaction_history {
1941 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
1945 # run inside of a transaction to prevent replication delays
1946 my $e = new_editor(authtoken=>$auth);
1947 return $e->die_event unless $e->checkauth;
1949 if ($e->requestor->id ne $userid) {
1950 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
1953 my $api = $self->api_name;
1954 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
1956 if(defined($type)) {
1957 $filter->{'xact_type'} = $type;
1960 if($api =~ /have_bill_or_payment/o) {
1962 # transactions that have a non-zero sum across all billings or at least 1 payment
1963 $filter->{'-or'} = {
1964 'balance_owed' => { '<>' => 0 },
1965 'last_payment_ts' => { '<>' => undef }
1968 } elsif( $api =~ /have_balance/o) {
1970 # transactions that have a non-zero overall balance
1971 $filter->{'balance_owed'} = { '<>' => 0 };
1973 } elsif( $api =~ /have_charge/o) {
1975 # transactions that have at least 1 billing, regardless of whether it was voided
1976 $filter->{'last_billing_ts'} = { '<>' => undef };
1978 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
1980 # transactions that have non-zero sum across all billings. This will exclude
1981 # xacts where all billings have been voided
1982 $filter->{'total_owed'} = { '<>' => 0 };
1985 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
1986 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
1987 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
1989 my $mbts = $e->search_money_billable_transaction_summary(
1991 { usr => $userid, @xact_finish, %$filter },
1996 if ($api =~ /\.ids/) {
1997 return [map {$_->id} @$mbts];
2005 __PACKAGE__->register_method(
2006 method => "user_perms",
2007 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2009 notes => "Returns a list of permissions"
2013 my( $self, $client, $authtoken, $user ) = @_;
2015 my( $staff, $evt ) = $apputils->checkses($authtoken);
2016 return $evt if $evt;
2018 $user ||= $staff->id;
2020 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2024 return $apputils->simple_scalar_request(
2026 "open-ils.storage.permission.user_perms.atomic",
2030 __PACKAGE__->register_method(
2031 method => "retrieve_perms",
2032 api_name => "open-ils.actor.permissions.retrieve",
2033 notes => "Returns a list of permissions"
2035 sub retrieve_perms {
2036 my( $self, $client ) = @_;
2037 return $apputils->simple_scalar_request(
2039 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2040 { id => { '!=' => undef } }
2044 __PACKAGE__->register_method(
2045 method => "retrieve_groups",
2046 api_name => "open-ils.actor.groups.retrieve",
2047 notes => "Returns a list of user groups"
2049 sub retrieve_groups {
2050 my( $self, $client ) = @_;
2051 return new_editor()->retrieve_all_permission_grp_tree();
2054 __PACKAGE__->register_method(
2055 method => "retrieve_org_address",
2056 api_name => "open-ils.actor.org_unit.address.retrieve",
2057 notes => <<' NOTES');
2058 Returns an org_unit address by ID
2059 @param An org_address ID
2061 sub retrieve_org_address {
2062 my( $self, $client, $id ) = @_;
2063 return $apputils->simple_scalar_request(
2065 "open-ils.cstore.direct.actor.org_address.retrieve",
2070 __PACKAGE__->register_method(
2071 method => "retrieve_groups_tree",
2072 api_name => "open-ils.actor.groups.tree.retrieve",
2073 notes => "Returns a list of user groups"
2076 sub retrieve_groups_tree {
2077 my( $self, $client ) = @_;
2078 return new_editor()->search_permission_grp_tree(
2083 flesh_fields => { pgt => ["children"] },
2084 order_by => { pgt => 'name'}
2091 __PACKAGE__->register_method(
2092 method => "add_user_to_groups",
2093 api_name => "open-ils.actor.user.set_groups",
2094 notes => "Adds a user to one or more permission groups"
2097 sub add_user_to_groups {
2098 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2100 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2101 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2102 return $evt if $evt;
2104 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2105 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2106 return $evt if $evt;
2108 $apputils->simplereq(
2110 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2112 for my $group (@$groups) {
2113 my $link = Fieldmapper::permission::usr_grp_map->new;
2115 $link->usr($userid);
2117 my $id = $apputils->simplereq(
2119 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2125 __PACKAGE__->register_method(
2126 method => "get_user_perm_groups",
2127 api_name => "open-ils.actor.user.get_groups",
2128 notes => "Retrieve a user's permission groups."
2132 sub get_user_perm_groups {
2133 my( $self, $client, $authtoken, $userid ) = @_;
2135 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2136 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2137 return $evt if $evt;
2139 return $apputils->simplereq(
2141 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2145 __PACKAGE__->register_method(
2146 method => "get_user_work_ous",
2147 api_name => "open-ils.actor.user.get_work_ous",
2148 notes => "Retrieve a user's work org units."
2151 __PACKAGE__->register_method(
2152 method => "get_user_work_ous",
2153 api_name => "open-ils.actor.user.get_work_ous.ids",
2154 notes => "Retrieve a user's work org units."
2157 sub get_user_work_ous {
2158 my( $self, $client, $auth, $userid ) = @_;
2159 my $e = new_editor(authtoken=>$auth);
2160 return $e->event unless $e->checkauth;
2161 $userid ||= $e->requestor->id;
2163 if($e->requestor->id != $userid) {
2164 my $user = $e->retrieve_actor_user($userid)
2165 or return $e->event;
2166 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2169 return $e->search_permission_usr_work_ou_map({usr => $userid})
2170 unless $self->api_name =~ /.ids$/;
2172 # client just wants a list of org IDs
2173 return $U->get_user_work_ou_ids($e, $userid);
2178 __PACKAGE__->register_method(
2179 method => 'register_workstation',
2180 api_name => 'open-ils.actor.workstation.register.override',
2181 signature => q/@see open-ils.actor.workstation.register/
2184 __PACKAGE__->register_method(
2185 method => 'register_workstation',
2186 api_name => 'open-ils.actor.workstation.register',
2188 Registers a new workstion in the system
2189 @param authtoken The login session key
2190 @param name The name of the workstation id
2191 @param owner The org unit that owns this workstation
2192 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2193 if the name is already in use.
2197 sub register_workstation {
2198 my( $self, $conn, $authtoken, $name, $owner ) = @_;
2200 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2201 return $e->die_event unless $e->checkauth;
2202 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2203 my $existing = $e->search_actor_workstation({name => $name})->[0];
2207 if( $self->api_name =~ /override/o ) {
2208 # workstation with the given name exists.
2210 if($owner ne $existing->owning_lib) {
2211 # if necessary, update the owning_lib of the workstation
2213 $logger->info("changing owning lib of workstation ".$existing->id.
2214 " from ".$existing->owning_lib." to $owner");
2215 return $e->die_event unless
2216 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2218 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2220 $existing->owning_lib($owner);
2221 return $e->die_event unless $e->update_actor_workstation($existing);
2227 "attempt to register an existing workstation. returning existing ID");
2230 return $existing->id;
2233 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2237 my $ws = Fieldmapper::actor::workstation->new;
2238 $ws->owning_lib($owner);
2240 $e->create_actor_workstation($ws) or return $e->die_event;
2242 return $ws->id; # note: editor sets the id on the new object for us
2245 __PACKAGE__->register_method(
2246 method => 'workstation_list',
2247 api_name => 'open-ils.actor.workstation.list',
2249 Returns a list of workstations registered at the given location
2250 @param authtoken The login session key
2251 @param ids A list of org_unit.id's for the workstation owners
2255 sub workstation_list {
2256 my( $self, $conn, $authtoken, @orgs ) = @_;
2258 my $e = new_editor(authtoken=>$authtoken);
2259 return $e->event unless $e->checkauth;
2264 unless $e->allowed('REGISTER_WORKSTATION', $o);
2265 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2271 __PACKAGE__->register_method(
2272 method => 'fetch_patron_note',
2273 api_name => 'open-ils.actor.note.retrieve.all',
2276 Returns a list of notes for a given user
2277 Requestor must have VIEW_USER permission if pub==false and
2278 @param authtoken The login session key
2279 @param args Hash of params including
2280 patronid : the patron's id
2281 pub : true if retrieving only public notes
2285 sub fetch_patron_note {
2286 my( $self, $conn, $authtoken, $args ) = @_;
2287 my $patronid = $$args{patronid};
2289 my($reqr, $evt) = $U->checkses($authtoken);
2290 return $evt if $evt;
2293 ($patron, $evt) = $U->fetch_user($patronid);
2294 return $evt if $evt;
2297 if( $patronid ne $reqr->id ) {
2298 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2299 return $evt if $evt;
2301 return $U->cstorereq(
2302 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2303 { usr => $patronid, pub => 't' } );
2306 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2307 return $evt if $evt;
2309 return $U->cstorereq(
2310 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2313 __PACKAGE__->register_method(
2314 method => 'create_user_note',
2315 api_name => 'open-ils.actor.note.create',
2317 Creates a new note for the given user
2318 @param authtoken The login session key
2319 @param note The note object
2322 sub create_user_note {
2323 my( $self, $conn, $authtoken, $note ) = @_;
2324 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2325 return $e->die_event unless $e->checkauth;
2327 my $user = $e->retrieve_actor_user($note->usr)
2328 or return $e->die_event;
2330 return $e->die_event unless
2331 $e->allowed('UPDATE_USER',$user->home_ou);
2333 $note->creator($e->requestor->id);
2334 $e->create_actor_usr_note($note) or return $e->die_event;
2340 __PACKAGE__->register_method(
2341 method => 'delete_user_note',
2342 api_name => 'open-ils.actor.note.delete',
2344 Deletes a note for the given user
2345 @param authtoken The login session key
2346 @param noteid The note id
2349 sub delete_user_note {
2350 my( $self, $conn, $authtoken, $noteid ) = @_;
2352 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2353 return $e->die_event unless $e->checkauth;
2354 my $note = $e->retrieve_actor_usr_note($noteid)
2355 or return $e->die_event;
2356 my $user = $e->retrieve_actor_user($note->usr)
2357 or return $e->die_event;
2358 return $e->die_event unless
2359 $e->allowed('UPDATE_USER', $user->home_ou);
2361 $e->delete_actor_usr_note($note) or return $e->die_event;
2367 __PACKAGE__->register_method(
2368 method => 'update_user_note',
2369 api_name => 'open-ils.actor.note.update',
2371 @param authtoken The login session key
2372 @param note The note
2376 sub update_user_note {
2377 my( $self, $conn, $auth, $note ) = @_;
2378 my $e = new_editor(authtoken=>$auth, xact=>1);
2379 return $e->event unless $e->checkauth;
2380 my $patron = $e->retrieve_actor_user($note->usr)
2381 or return $e->event;
2382 return $e->event unless
2383 $e->allowed('UPDATE_USER', $patron->home_ou);
2384 $e->update_actor_user_note($note)
2385 or return $e->event;
2392 __PACKAGE__->register_method(
2393 method => 'create_closed_date',
2394 api_name => 'open-ils.actor.org_unit.closed_date.create',
2396 Creates a new closing entry for the given org_unit
2397 @param authtoken The login session key
2398 @param note The closed_date object
2401 sub create_closed_date {
2402 my( $self, $conn, $authtoken, $cd ) = @_;
2404 my( $user, $evt ) = $U->checkses($authtoken);
2405 return $evt if $evt;
2407 $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2408 return $evt if $evt;
2410 $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2412 my $id = $U->storagereq(
2413 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2414 return $U->DB_UPDATE_FAILED($cd) unless $id;
2419 __PACKAGE__->register_method(
2420 method => 'delete_closed_date',
2421 api_name => 'open-ils.actor.org_unit.closed_date.delete',
2423 Deletes a closing entry for the given org_unit
2424 @param authtoken The login session key
2425 @param noteid The close_date id
2428 sub delete_closed_date {
2429 my( $self, $conn, $authtoken, $cd ) = @_;
2431 my( $user, $evt ) = $U->checkses($authtoken);
2432 return $evt if $evt;
2435 ($cd_obj, $evt) = fetch_closed_date($cd);
2436 return $evt if $evt;
2438 $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2439 return $evt if $evt;
2441 $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2443 my $stat = $U->storagereq(
2444 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2445 return $U->DB_UPDATE_FAILED($cd) unless $stat;
2450 __PACKAGE__->register_method(
2451 method => 'usrname_exists',
2452 api_name => 'open-ils.actor.username.exists',
2454 desc => 'Check if a username is already taken (by an undeleted patron)',
2456 {desc => 'Authentication token', type => 'string'},
2457 {desc => 'Username', type => 'string'}
2460 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2465 sub usrname_exists {
2466 my( $self, $conn, $auth, $usrname ) = @_;
2467 my $e = new_editor(authtoken=>$auth);
2468 return $e->event unless $e->checkauth;
2469 my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2470 return $$a[0] if $a and @$a;
2474 __PACKAGE__->register_method(
2475 method => 'barcode_exists',
2476 api_name => 'open-ils.actor.barcode.exists',
2478 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2481 sub barcode_exists {
2482 my( $self, $conn, $auth, $barcode ) = @_;
2483 my $e = new_editor(authtoken=>$auth);
2484 return $e->event unless $e->checkauth;
2485 my $card = $e->search_actor_card({barcode => $barcode});
2491 #return undef unless @$card;
2492 #return $card->[0]->usr;
2496 __PACKAGE__->register_method(
2497 method => 'retrieve_net_levels',
2498 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2501 sub retrieve_net_levels {
2502 my( $self, $conn, $auth ) = @_;
2503 my $e = new_editor(authtoken=>$auth);
2504 return $e->event unless $e->checkauth;
2505 return $e->retrieve_all_config_net_access_level();
2508 # Retain the old typo API name just in case
2509 __PACKAGE__->register_method(
2510 method => 'fetch_org_by_shortname',
2511 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2513 __PACKAGE__->register_method(
2514 method => 'fetch_org_by_shortname',
2515 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2517 sub fetch_org_by_shortname {
2518 my( $self, $conn, $sname ) = @_;
2519 my $e = new_editor();
2520 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2521 return $e->event unless $org;
2526 __PACKAGE__->register_method(
2527 method => 'session_home_lib',
2528 api_name => 'open-ils.actor.session.home_lib',
2531 sub session_home_lib {
2532 my( $self, $conn, $auth ) = @_;
2533 my $e = new_editor(authtoken=>$auth);
2534 return undef unless $e->checkauth;
2535 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2536 return $org->shortname;
2539 __PACKAGE__->register_method(
2540 method => 'session_safe_token',
2541 api_name => 'open-ils.actor.session.safe_token',
2543 Returns a hashed session ID that is safe for export to the world.
2544 This safe token will expire after 1 hour of non-use.
2545 @param auth Active authentication token
2549 sub session_safe_token {
2550 my( $self, $conn, $auth ) = @_;
2551 my $e = new_editor(authtoken=>$auth);
2552 return undef unless $e->checkauth;
2554 my $safe_token = md5_hex($auth);
2556 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2558 # Add more like the following if needed...
2560 "safe-token-home_lib-shortname-$safe_token",
2561 $e->retrieve_actor_org_unit(
2562 $e->requestor->home_ou
2571 __PACKAGE__->register_method(
2572 method => 'safe_token_home_lib',
2573 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2575 Returns the home library shortname from the session
2576 asscociated with a safe token from generated by
2577 open-ils.actor.session.safe_token.
2578 @param safe_token Active safe token
2582 sub safe_token_home_lib {
2583 my( $self, $conn, $safe_token ) = @_;
2585 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2586 return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2591 __PACKAGE__->register_method(
2592 method => 'slim_tree',
2593 api_name => "open-ils.actor.org_tree.slim_hash.retrieve",
2596 my $tree = new_editor()->search_actor_org_unit(
2598 {"parent_ou" => undef },
2601 flesh_fields => { aou => ['children'] },
2602 order_by => { aou => 'name'},
2603 select => { aou => ["id","shortname", "name"]},
2608 return trim_tree($tree);
2614 return undef unless $tree;
2616 code => $tree->shortname,
2617 name => $tree->name,
2619 if( $tree->children and @{$tree->children} ) {
2620 $htree->{children} = [];
2621 for my $c (@{$tree->children}) {
2622 push( @{$htree->{children}}, trim_tree($c) );
2630 __PACKAGE__->register_method(
2631 method => "update_penalties",
2632 api_name => "open-ils.actor.user.penalties.update"
2635 sub update_penalties {
2636 my($self, $conn, $auth, $user_id) = @_;
2637 my $e = new_editor(authtoken=>$auth, xact => 1);
2638 return $e->die_event unless $e->checkauth;
2639 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2640 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2641 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2642 return $evt if $evt;
2648 __PACKAGE__->register_method(
2649 method => "apply_penalty",
2650 api_name => "open-ils.actor.user.penalty.apply"
2654 my($self, $conn, $auth, $penalty) = @_;
2656 my $e = new_editor(authtoken=>$auth, xact => 1);
2657 return $e->die_event unless $e->checkauth;
2659 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2660 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2662 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2665 (defined $ptype->org_depth) ?
2666 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2669 $penalty->org_unit($ctx_org);
2670 $penalty->staff($e->requestor->id);
2671 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2674 return $penalty->id;
2677 __PACKAGE__->register_method(
2678 method => "remove_penalty",
2679 api_name => "open-ils.actor.user.penalty.remove"
2682 sub remove_penalty {
2683 my($self, $conn, $auth, $penalty) = @_;
2684 my $e = new_editor(authtoken=>$auth, xact => 1);
2685 return $e->die_event unless $e->checkauth;
2686 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2687 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2689 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2694 __PACKAGE__->register_method(
2695 method => "update_penalty_note",
2696 api_name => "open-ils.actor.user.penalty.note.update"
2699 sub update_penalty_note {
2700 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2701 my $e = new_editor(authtoken=>$auth, xact => 1);
2702 return $e->die_event unless $e->checkauth;
2703 for my $penalty_id (@$penalty_ids) {
2704 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2705 if (! $penalty ) { return $e->die_event; }
2706 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2707 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2709 $penalty->note( $note ); $penalty->ischanged( 1 );
2711 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2717 __PACKAGE__->register_method(
2718 method => "ranged_penalty_thresholds",
2719 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2723 sub ranged_penalty_thresholds {
2724 my($self, $conn, $auth, $context_org) = @_;
2725 my $e = new_editor(authtoken=>$auth);
2726 return $e->event unless $e->checkauth;
2727 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2728 my $list = $e->search_permission_grp_penalty_threshold([
2729 {org_unit => $U->get_org_ancestors($context_org)},
2730 {order_by => {pgpt => 'id'}}
2732 $conn->respond($_) for @$list;
2738 __PACKAGE__->register_method(
2739 method => "user_retrieve_fleshed_by_id",
2741 api_name => "open-ils.actor.user.fleshed.retrieve",
2744 sub user_retrieve_fleshed_by_id {
2745 my( $self, $client, $auth, $user_id, $fields ) = @_;
2746 my $e = new_editor(authtoken => $auth);
2747 return $e->event unless $e->checkauth;
2749 if( $e->requestor->id != $user_id ) {
2750 return $e->event unless $e->allowed('VIEW_USER');
2756 "standing_penalties",
2760 "stat_cat_entries" ];
2761 return new_flesh_user($user_id, $fields, $e);
2765 sub new_flesh_user {
2768 my $fields = shift || [];
2771 my $fetch_penalties = 0;
2772 if(grep {$_ eq 'standing_penalties'} @$fields) {
2773 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
2774 $fetch_penalties = 1;
2777 my $user = $e->retrieve_actor_user(
2782 "flesh_fields" => { "au" => $fields }
2785 ) or return $e->event;
2788 if( grep { $_ eq 'addresses' } @$fields ) {
2790 $user->addresses([]) unless @{$user->addresses};
2791 # don't expose "replaced" addresses by default
2792 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
2794 if( ref $user->billing_address ) {
2795 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2796 push( @{$user->addresses}, $user->billing_address );
2800 if( ref $user->mailing_address ) {
2801 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2802 push( @{$user->addresses}, $user->mailing_address );
2807 if($fetch_penalties) {
2808 # grab the user penalties ranged for this location
2809 $user->standing_penalties(
2810 $e->search_actor_user_standing_penalty([
2813 {stop_date => undef},
2814 {stop_date => {'>' => 'now'}}
2816 org_unit => $U->get_org_ancestors($e->requestor->ws_ou)
2819 flesh_fields => {ausp => ['standing_penalty']}
2826 $user->clear_passwd();
2833 __PACKAGE__->register_method(
2834 method => "user_retrieve_parts",
2835 api_name => "open-ils.actor.user.retrieve.parts",
2838 sub user_retrieve_parts {
2839 my( $self, $client, $auth, $user_id, $fields ) = @_;
2840 my $e = new_editor(authtoken => $auth);
2841 return $e->event unless $e->checkauth;
2842 $user_id ||= $e->requestor->id;
2843 if( $e->requestor->id != $user_id ) {
2844 return $e->event unless $e->allowed('VIEW_USER');
2847 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2848 push(@resp, $user->$_()) for(@$fields);
2854 __PACKAGE__->register_method(
2855 method => 'user_opt_in_enabled',
2856 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2857 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
2860 sub user_opt_in_enabled {
2861 my($self, $conn) = @_;
2862 my $sc = OpenSRF::Utils::SettingsClient->new;
2863 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
2868 __PACKAGE__->register_method(
2869 method => 'user_opt_in_at_org',
2870 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2872 @param $auth The auth token
2873 @param user_id The ID of the user to test
2874 @return 1 if the user has opted in at the specified org,
2875 event on error, and 0 otherwise. /
2877 sub user_opt_in_at_org {
2878 my($self, $conn, $auth, $user_id) = @_;
2880 # see if we even need to enforce the opt-in value
2881 return 1 unless user_opt_in_enabled($self);
2883 my $e = new_editor(authtoken => $auth);
2884 return $e->event unless $e->checkauth;
2885 my $org_id = $e->requestor->ws_ou;
2887 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2888 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2890 # user is automatically opted-in at the home org
2891 return 1 if $user->home_ou eq $org_id;
2893 my $vals = $e->search_actor_usr_org_unit_opt_in(
2894 {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
2900 __PACKAGE__->register_method(
2901 method => 'create_user_opt_in_at_org',
2902 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
2904 @param $auth The auth token
2905 @param user_id The ID of the user to test
2906 @return The ID of the newly created object, event on error./
2909 sub create_user_opt_in_at_org {
2910 my($self, $conn, $auth, $user_id) = @_;
2912 my $e = new_editor(authtoken => $auth, xact=>1);
2913 return $e->die_event unless $e->checkauth;
2914 my $org_id = $e->requestor->ws_ou;
2916 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2917 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2919 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
2921 $opt_in->org_unit($org_id);
2922 $opt_in->usr($user_id);
2923 $opt_in->staff($e->requestor->id);
2924 $opt_in->opt_in_ts('now');
2925 $opt_in->opt_in_ws($e->requestor->wsid);
2927 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
2928 or return $e->die_event;
2936 __PACKAGE__->register_method (
2937 method => 'retrieve_org_hours',
2938 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
2940 Returns the hours of operation for a specified org unit
2941 @param authtoken The login session key
2942 @param org_id The org_unit ID
2946 sub retrieve_org_hours {
2947 my($self, $conn, $auth, $org_id) = @_;
2948 my $e = new_editor(authtoken => $auth);
2949 return $e->die_event unless $e->checkauth;
2950 $org_id ||= $e->requestor->ws_ou;
2951 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
2955 __PACKAGE__->register_method (
2956 method => 'verify_user_password',
2957 api_name => 'open-ils.actor.verify_user_password',
2959 Given a barcode or username and the MD5 encoded password,
2960 returns 1 if the password is correct. Returns 0 otherwise.
2964 sub verify_user_password {
2965 my($self, $conn, $auth, $barcode, $username, $password) = @_;
2966 my $e = new_editor(authtoken => $auth);
2967 return $e->die_event unless $e->checkauth;
2969 my $user_by_barcode;
2970 my $user_by_username;
2972 my $card = $e->search_actor_card([
2973 {barcode => $barcode},
2974 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
2975 $user_by_barcode = $card->usr;
2976 $user = $user_by_barcode;
2979 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
2980 $user = $user_by_username;
2982 return 0 if (!$user);
2983 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
2984 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2985 return 1 if $user->passwd eq $password;
2989 __PACKAGE__->register_method (
2990 method => 'retrieve_usr_id_via_barcode_or_usrname',
2991 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
2993 Given a barcode or username returns the id for the user or
2998 sub retrieve_usr_id_via_barcode_or_usrname {
2999 my($self, $conn, $auth, $barcode, $username) = @_;
3000 my $e = new_editor(authtoken => $auth);
3001 return $e->die_event unless $e->checkauth;
3002 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3004 my $user_by_barcode;
3005 my $user_by_username;
3006 $logger->info("$id_as_barcode is the ID as BARCODE");
3008 my $card = $e->search_actor_card([
3009 {barcode => $barcode},
3010 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3011 if ($id_as_barcode =~ /^t/i) {
3013 $user = $e->retrieve_actor_user($barcode);
3014 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3016 $user_by_barcode = $card->usr;
3017 $user = $user_by_barcode;
3020 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3021 $user_by_barcode = $card->usr;
3022 $user = $user_by_barcode;
3027 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3029 $user = $user_by_username;
3031 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3032 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3033 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3038 __PACKAGE__->register_method (
3039 method => 'merge_users',
3040 api_name => 'open-ils.actor.user.merge',
3043 Given a list of source users and destination user, transfer all data from the source
3044 to the dest user and delete the source user. All user related data is
3045 transferred, including circulations, holds, bookbags, etc.
3051 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3052 my $e = new_editor(xact => 1, authtoken => $auth);
3053 return $e->die_event unless $e->checkauth;
3055 # disallow the merge if any subordinate accounts are in collections
3056 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3057 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3059 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3060 my $del_addrs = ($U->ou_ancestor_setting_value(
3061 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3062 my $del_cards = ($U->ou_ancestor_setting_value(
3063 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3064 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3065 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3067 for my $src_id (@$user_ids) {
3068 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3070 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3071 if($src_user->home_ou ne $master_user->home_ou) {
3072 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3075 return $e->die_event unless
3076 $e->json_query({from => [
3091 __PACKAGE__->register_method (
3092 method => 'approve_user_address',
3093 api_name => 'open-ils.actor.user.pending_address.approve',
3100 sub approve_user_address {
3101 my($self, $conn, $auth, $addr) = @_;
3102 my $e = new_editor(xact => 1, authtoken => $auth);
3103 return $e->die_event unless $e->checkauth;
3105 # if the caller passes an address object, assume they want to
3106 # update it first before approving it
3107 $e->update_actor_user_address($addr) or return $e->die_event;
3109 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3111 my $user = $e->retrieve_actor_user($addr->usr);
3112 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3113 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3114 or return $e->die_event;
3116 return [values %$result]->[0];
3120 __PACKAGE__->register_method (
3121 method => 'retrieve_friends',
3122 api_name => 'open-ils.actor.friends.retrieve',
3125 returns { confirmed: [], pending_out: [], pending_in: []}
3126 pending_out are users I'm requesting friendship with
3127 pending_in are users requesting friendship with me
3132 sub retrieve_friends {
3133 my($self, $conn, $auth, $user_id, $options) = @_;
3134 my $e = new_editor(authtoken => $auth);
3135 return $e->event unless $e->checkauth;
3136 $user_id ||= $e->requestor->id;
3138 if($user_id != $e->requestor->id) {
3139 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3140 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3143 return OpenILS::Application::Actor::Friends->retrieve_friends(
3144 $e, $user_id, $options);
3149 __PACKAGE__->register_method (
3150 method => 'apply_friend_perms',
3151 api_name => 'open-ils.actor.friends.perms.apply',
3157 sub apply_friend_perms {
3158 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3159 my $e = new_editor(authtoken => $auth, xact => 1);
3160 return $e->event unless $e->checkauth;
3162 if($user_id != $e->requestor->id) {
3163 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3164 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3167 for my $perm (@perms) {
3169 OpenILS::Application::Actor::Friends->apply_friend_perm(
3170 $e, $user_id, $delegate_id, $perm);
3171 return $evt if $evt;
3179 __PACKAGE__->register_method (
3180 method => 'update_user_pending_address',
3181 api_name => 'open-ils.actor.user.address.pending.cud'
3184 sub update_user_pending_address {
3185 my($self, $conn, $auth, $addr) = @_;
3186 my $e = new_editor(authtoken => $auth, xact => 1);
3187 return $e->event unless $e->checkauth;
3189 if($addr->usr != $e->requestor->id) {
3190 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3191 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3195 $e->create_actor_user_address($addr) or return $e->die_event;
3196 } elsif($addr->isdeleted) {
3197 $e->delete_actor_user_address($addr) or return $e->die_event;
3199 $e->update_actor_user_address($addr) or return $e->die_event;
3207 __PACKAGE__->register_method (
3208 method => 'user_events',
3209 api_name => 'open-ils.actor.user.events.circ',
3212 __PACKAGE__->register_method (
3213 method => 'user_events',
3214 api_name => 'open-ils.actor.user.events.ahr',
3219 my($self, $conn, $auth, $user_id, $filters) = @_;
3220 my $e = new_editor(authtoken => $auth);
3221 return $e->event unless $e->checkauth;
3223 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3224 my $user_field = 'usr';
3227 $filters->{target} = {
3228 select => { $obj_type => ['id'] },
3230 where => {usr => $user_id}
3233 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3234 if($e->requestor->id != $user_id) {
3235 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3238 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3239 my $req = $ses->request('open-ils.trigger.events_by_target',
3240 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3242 while(my $resp = $req->recv) {
3243 my $val = $resp->content;
3244 my $tgt = $val->target;
3246 if($obj_type eq 'circ') {
3247 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3249 } elsif($obj_type eq 'ahr') {
3250 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3251 if $tgt->current_copy;
3254 $conn->respond($val) if $val;
3260 __PACKAGE__->register_method (
3261 method => 'copy_events',
3262 api_name => 'open-ils.actor.copy.events.circ',
3265 __PACKAGE__->register_method (
3266 method => 'copy_events',
3267 api_name => 'open-ils.actor.copy.events.ahr',
3272 my($self, $conn, $auth, $copy_id, $filters) = @_;
3273 my $e = new_editor(authtoken => $auth);
3274 return $e->event unless $e->checkauth;
3276 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3278 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3280 my $copy_field = 'target_copy';
3281 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3284 $filters->{target} = {
3285 select => { $obj_type => ['id'] },
3287 where => {$copy_field => $copy_id}
3291 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3292 my $req = $ses->request('open-ils.trigger.events_by_target',
3293 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3295 while(my $resp = $req->recv) {
3296 my $val = $resp->content;
3297 my $tgt = $val->target;
3299 my $user = $e->retrieve_actor_user($tgt->usr);
3300 if($e->requestor->id != $user->id) {
3301 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3304 $tgt->$copy_field($copy);
3307 $conn->respond($val) if $val;
3316 __PACKAGE__->register_method (
3317 method => 'update_events',
3318 api_name => 'open-ils.actor.user.event.cancel.batch',
3321 __PACKAGE__->register_method (
3322 method => 'update_events',
3323 api_name => 'open-ils.actor.user.event.reset.batch',
3328 my($self, $conn, $auth, $event_ids) = @_;
3329 my $e = new_editor(xact => 1, authtoken => $auth);
3330 return $e->die_event unless $e->checkauth;
3333 for my $id (@$event_ids) {
3335 # do a little dance to determine what user we are ultimately affecting
3336 my $event = $e->retrieve_action_trigger_event([
3339 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3341 ]) or return $e->die_event;
3344 if($event->event_def->hook->core_type eq 'circ') {
3345 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3346 } elsif($event->event_def->hook->core_type eq 'ahr') {
3347 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3352 my $user = $e->retrieve_actor_user($user_id);
3353 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3355 if($self->api_name =~ /cancel/) {
3356 $event->state('invalid');
3357 } elsif($self->api_name =~ /reset/) {
3358 $event->clear_start_time;
3359 $event->clear_update_time;
3360 $event->state('pending');
3363 $e->update_action_trigger_event($event) or return $e->die_event;
3364 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3368 return {complete => 1};
3372 __PACKAGE__->register_method (
3373 method => 'really_delete_user',
3374 api_name => 'open-ils.actor.user.delete',
3376 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3377 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3378 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3379 dest_usr_id is only required when deleting a user that performs staff functions.
3383 sub really_delete_user {
3384 my($self, $conn, $auth, $user_id, $dest_user_id) = @_;
3385 my $e = new_editor(authtoken => $auth, xact => 1);
3386 return $e->die_event unless $e->checkauth;
3387 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3388 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3389 my $stat = $e->json_query(
3390 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3391 or return $e->die_event;
3398 __PACKAGE__->register_method (
3399 method => 'user_payments',
3400 api_name => 'open-ils.actor.user.payments.retrieve',
3403 Returns all payments for a given user. Default order is newest payments first.
3404 @param auth Authentication token
3405 @param user_id The user ID
3406 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3411 my($self, $conn, $auth, $user_id, $filters) = @_;
3414 my $e = new_editor(authtoken => $auth);
3415 return $e->die_event unless $e->checkauth;
3417 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3418 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3420 # Find all payments for all transactions for user $user_id
3422 select => {mp => ['id']},
3427 select => {mbt => ['id']},
3429 where => {usr => $user_id}
3433 order_by => [{ # by default, order newest payments first
3435 field => 'payment_ts',
3440 for (qw/order_by limit offset/) {
3441 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3444 if(defined $filters->{where}) {
3445 foreach (keys %{$filters->{where}}) {
3446 # don't allow the caller to expand the result set to other users
3447 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3451 my $payment_ids = $e->json_query($query);
3452 for my $pid (@$payment_ids) {
3453 my $pay = $e->retrieve_money_payment([
3458 mbt => ['summary', 'circulation', 'grocery'],
3459 circ => ['target_copy'],
3460 acp => ['call_number'],
3468 xact_type => $pay->xact->summary->xact_type,
3469 last_billing_type => $pay->xact->summary->last_billing_type,
3472 if($pay->xact->summary->xact_type eq 'circulation') {
3473 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3474 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3477 $pay->xact($pay->xact->id); # de-flesh
3478 $conn->respond($resp);
3486 __PACKAGE__->register_method (
3487 method => 'negative_balance_users',
3488 api_name => 'open-ils.actor.users.negative_balance',
3491 Returns all users that have an overall negative balance
3492 @param auth Authentication token
3493 @param org_id The context org unit as an ID or list of IDs. This will be the home
3494 library of the user. If no org_unit is specified, no org unit filter is applied
3498 sub negative_balance_users {
3499 my($self, $conn, $auth, $org_id) = @_;
3501 my $e = new_editor(authtoken => $auth);
3502 return $e->die_event unless $e->checkauth;
3503 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3507 mous => ['usr', 'balance_owed'],
3510 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3511 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3528 where => {'+mous' => {balance_owed => {'<' => 0}}}
3531 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3533 my $list = $e->json_query($query, {timeout => 600});
3535 for my $data (@$list) {
3537 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3538 balance_owed => $data->{balance_owed},
3539 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3546 __PACKAGE__->register_method(
3547 method => "request_password_reset",
3548 api_name => "open-ils.actor.patron.password_reset.request",
3550 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3551 "method for changing a user's password. The UUID token is distributed via A/T " .
3552 "templates (i.e. email to the user).",
3554 { desc => 'user_id_type', type => 'string' },
3555 { desc => 'user_id', type => 'string' },
3556 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3558 return => {desc => '1 on success, Event on error'}
3561 sub request_password_reset {
3562 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3564 # Check to see if password reset requests are already being throttled:
3565 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3567 my $e = new_editor(xact => 1);
3570 # Get the user, if any, depending on the input value
3571 if ($user_id_type eq 'username') {
3572 $user = $e->search_actor_user({usrname => $user_id})->[0];
3575 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
3577 } elsif ($user_id_type eq 'barcode') {
3578 my $card = $e->search_actor_card([
3579 {barcode => $user_id},
3580 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3583 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
3588 # If the user doesn't have an email address, we can't help them
3589 if (!$user->email) {
3591 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
3594 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
3595 if ($email_must_match) {
3596 if ($user->email ne $email) {
3597 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
3601 _reset_password_request($conn, $e, $user);
3604 # Once we have the user, we can issue the password reset request
3605 # XXX Add a wrapper method that accepts barcode + email input
3606 sub _reset_password_request {
3607 my ($conn, $e, $user) = @_;
3609 # 1. Get throttle threshold and time-to-live from OU_settings
3610 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
3611 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3613 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
3615 # 2. Get time of last request and number of active requests (num_active)
3616 my $active_requests = $e->json_query({
3622 transform => 'COUNT'
3625 column => 'request_time',
3631 has_been_reset => { '=' => 'f' },
3632 request_time => { '>' => $threshold_time }
3636 # Guard against no active requests
3637 if ($active_requests->[0]->{'request_time'}) {
3638 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
3639 my $now = DateTime::Format::ISO8601->new();
3641 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
3642 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
3643 ($last_request->add_duration('1 minute') > $now)) {
3644 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
3646 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3650 # TODO Check to see if the user is in a password-reset-restricted group
3652 # Otherwise, go ahead and try to get the user.
3654 # Check the number of active requests for this user
3655 $active_requests = $e->json_query({
3661 transform => 'COUNT'
3666 usr => { '=' => $user->id },
3667 has_been_reset => { '=' => 'f' },
3668 request_time => { '>' => $threshold_time }
3672 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
3674 # if less than or equal to per-user threshold, proceed; otherwise, return event
3675 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
3676 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
3678 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
3681 # Create the aupr object and insert into the database
3682 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
3683 my $uuid = create_uuid_as_string(UUID_V4);
3684 $reset_request->uuid($uuid);
3685 $reset_request->usr($user->id);
3687 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
3690 # Create an event to notify user of the URL to reset their password
3692 # Can we stuff this in the user_data param for trigger autocreate?
3693 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
3695 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3696 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
3699 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
3704 __PACKAGE__->register_method(
3705 method => "commit_password_reset",
3706 api_name => "open-ils.actor.patron.password_reset.commit",
3708 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
3709 "validity, and if valid, uses it as authorization for changing the associated user's password " .
3710 "with the supplied password.",
3712 { desc => 'uuid', type => 'string' },
3713 { desc => 'password', type => 'string' },
3715 return => {desc => '1 on success, Event on error'}
3718 sub commit_password_reset {
3719 my($self, $conn, $uuid, $password) = @_;
3721 # Check to see if password reset requests are already being throttled:
3722 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3723 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
3724 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
3726 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3729 my $e = new_editor(xact => 1);
3731 my $aupr = $e->search_actor_usr_password_reset({
3738 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3740 my $user_id = $aupr->[0]->usr;
3741 my $user = $e->retrieve_actor_user($user_id);
3743 # Ensure we're still within the TTL for the request
3744 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
3745 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
3746 if ($threshold < DateTime->now(time_zone => 'local')) {
3748 $logger->info("Password reset request needed to be submitted before $threshold");
3749 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
3752 # Check complexity of password against OU-defined regex
3753 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
3757 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
3758 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
3759 $is_strong = check_password_strength_custom($password, $pw_regex);
3761 $is_strong = check_password_strength_default($password);
3766 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
3769 # All is well; update the password
3770 $user->passwd($password);
3771 $e->update_actor_user($user);
3773 # And flag that this password reset request has been honoured
3774 $aupr->[0]->has_been_reset('t');
3775 $e->update_actor_usr_password_reset($aupr->[0]);
3781 sub check_password_strength_default {
3782 my $password = shift;
3783 # Use the default set of checks
3784 if ( (length($password) < 7) or
3785 ($password !~ m/.*\d+.*/) or
3786 ($password !~ m/.*[A-Za-z]+.*/)
3793 sub check_password_strength_custom {
3794 my ($password, $pw_regex) = @_;
3796 $pw_regex = qr/$pw_regex/;
3797 if ($password !~ /$pw_regex/) {
3805 __PACKAGE__->register_method(
3806 method => "event_def_opt_in_settings",
3807 api_name => "open-ils.actor.event_def.opt_in.settings",
3810 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
3812 { desc => 'Authentication token', type => 'string'},
3814 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
3819 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
3826 sub event_def_opt_in_settings {
3827 my($self, $conn, $auth, $org_id) = @_;
3828 my $e = new_editor(authtoken => $auth);
3829 return $e->event unless $e->checkauth;
3831 if(defined $org_id and $org_id != $e->requestor->home_ou) {
3832 return $e->event unless
3833 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
3835 $org_id = $e->requestor->home_ou;
3838 # find all config.user_setting_type's related to event_defs for the requested org unit
3839 my $types = $e->json_query({
3840 select => {cust => ['name']},
3841 from => {atevdef => 'cust'},
3844 owner => $U->get_org_ancestors($org_id), # context org plus parents
3851 $conn->respond($_) for
3852 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
3859 __PACKAGE__->register_method(
3860 method => "user_visible_circs",
3861 api_name => "open-ils.actor.history.circ.visible",
3864 desc => 'Returns the set of opt-in visible circulations accompanied by circulation chain summaries',
3866 { desc => 'Authentication token', type => 'string'},
3867 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3868 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3871 desc => q/An object with 2 fields: circulation and summary.
3872 circulation is the "circ" object. summary is the related "accs" object/,
3878 __PACKAGE__->register_method(
3879 method => "user_visible_circs",
3880 api_name => "open-ils.actor.history.circ.visible.print",
3883 desc => 'Returns printable output for the set of opt-in visible circulations',
3885 { desc => 'Authentication token', type => 'string'},
3886 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3887 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3890 desc => q/An action_trigger.event object or error event./,
3896 __PACKAGE__->register_method(
3897 method => "user_visible_circs",
3898 api_name => "open-ils.actor.history.circ.visible.email",
3901 desc => 'Emails the set of opt-in visible circulations to the requestor',
3903 { desc => 'Authentication token', type => 'string'},
3904 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3905 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3908 desc => q/undef, or event on error/
3913 __PACKAGE__->register_method(
3914 method => "user_visible_circs",
3915 api_name => "open-ils.actor.history.hold.visible",
3918 desc => 'Returns the set of opt-in visible holds',
3920 { desc => 'Authentication token', type => 'string'},
3921 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3922 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3925 desc => q/An object with 1 field: "hold"/,
3931 __PACKAGE__->register_method(
3932 method => "user_visible_circs",
3933 api_name => "open-ils.actor.history.hold.visible.print",
3936 desc => 'Returns printable output for the set of opt-in visible holds',
3938 { desc => 'Authentication token', type => 'string'},
3939 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3940 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3943 desc => q/An action_trigger.event object or error event./,
3949 __PACKAGE__->register_method(
3950 method => "user_visible_circs",
3951 api_name => "open-ils.actor.history.hold.visible.email",
3954 desc => 'Emails the set of opt-in visible holds to the requestor',
3956 { desc => 'Authentication token', type => 'string'},
3957 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
3958 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
3961 desc => q/undef, or event on error/
3966 sub user_visible_circs {
3967 my($self, $conn, $auth, $user_id, $options) = @_;
3969 my $is_hold = ($self->api_name =~ /hold/);
3970 my $for_print = ($self->api_name =~ /print/);
3971 my $for_email = ($self->api_name =~ /email/);
3972 my $e = new_editor(authtoken => $auth);
3973 return $e->event unless $e->checkauth;
3975 $user_id ||= $e->requestor->id;
3977 $options->{limit} ||= 50;
3978 $options->{offset} ||= 0;
3980 if($user_id != $e->requestor->id) {
3981 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
3982 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3983 return $e->event unless $e->allowed($perm, $user->home_ou);
3986 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
3988 my $data = $e->json_query({
3989 from => [$db_func, $user_id],
3990 limit => $$options{limit},
3991 offset => $$options{offset}
3993 # TODO: I only want IDs. code below didn't get me there
3994 # {"select":{"au":[{"column":"id", "result_field":"id",
3995 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4000 return undef unless @$data;
4004 # collect the batch of objects
4008 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4009 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4013 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4014 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4017 } elsif ($for_email) {
4019 $conn->respond_complete(1) if $for_email; # no sense in waiting
4027 my $hold = $e->retrieve_action_hold_request($id);
4028 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4029 # events will be fired from action_trigger_runner
4033 my $circ = $e->retrieve_action_circulation($id);
4034 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4035 # events will be fired from action_trigger_runner
4039 } else { # just give me the data please
4047 my $hold = $e->retrieve_action_hold_request($id);
4048 $conn->respond({hold => $hold});
4052 my $circ = $e->retrieve_action_circulation($id);
4055 summary => $U->create_circ_chain_summary($e, $id)