]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
moved mbts builder to apputils to share, circ code now uses the mbts instead of open_...
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Actor.pm
1 package OpenILS::Application::Actor;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
4 use Data::Dumper;
5 $Data::Dumper::Indent = 0;
6 use OpenILS::Event;
7
8 use Digest::MD5 qw(md5_hex);
9
10 use OpenSRF::EX qw(:try);
11 use OpenILS::Perm;
12
13 use OpenILS::Application::AppUtils;
14
15 use OpenILS::Utils::Fieldmapper;
16 use OpenILS::Utils::ModsParser;
17 use OpenSRF::Utils::Logger qw/$logger/;
18 use OpenSRF::Utils qw/:datetime/;
19
20 use OpenSRF::Utils::Cache;
21
22 use JSON;
23 use DateTime;
24 use DateTime::Format::ISO8601;
25 use OpenILS::Const qw/:const/;
26
27 use OpenILS::Application::Actor::Container;
28 use OpenILS::Application::Actor::ClosedDates;
29
30 use OpenILS::Utils::CStoreEditor qw/:funcs/;
31
32 use OpenILS::Application::Actor::UserGroups;
33 sub initialize {
34         OpenILS::Application::Actor::Container->initialize();
35         OpenILS::Application::Actor::UserGroups->initialize();
36         OpenILS::Application::Actor::ClosedDates->initialize();
37 }
38
39 my $apputils = "OpenILS::Application::AppUtils";
40 my $U = $apputils;
41
42 sub _d { warn "Patron:\n" . Dumper(shift()); }
43
44 my $cache;
45
46
47 my $set_user_settings;
48 my $set_ou_settings;
49
50 __PACKAGE__->register_method(
51         method  => "set_user_settings",
52         api_name        => "open-ils.actor.patron.settings.update",
53 );
54 sub set_user_settings {
55         my( $self, $client, $user_session, $uid, $settings ) = @_;
56         
57         $logger->debug("Setting user settings: $user_session, $uid, " . Dumper($settings));
58
59         my( $staff, $user, $evt ) = 
60                 $apputils->checkses_requestor( $user_session, $uid, 'UPDATE_USER' );    
61         return $evt if $evt;
62         
63         my @params = map { 
64                 [{ usr => $user->id, name => $_}, {value => $$settings{$_}}] } keys %$settings;
65                 
66         $_->[1]->{value} = JSON->perl2JSON($_->[1]->{value}) for @params;
67
68         $logger->activity("User " . $staff->id . " updating user $uid settings with: " . Dumper(\@params));
69
70         my $ses = $U->start_db_session();
71         my $stat = $ses->request(
72                 'open-ils.storage.direct.actor.user_setting.batch.merge', @params )->gather(1);
73         $U->commit_db_session($ses);
74
75         return $stat;
76 }
77
78
79
80 __PACKAGE__->register_method(
81         method  => "set_ou_settings",
82         api_name        => "open-ils.actor.org_unit.settings.update",
83 );
84 sub set_ou_settings {
85         my( $self, $client, $user_session, $ouid, $settings ) = @_;
86         
87         my( $staff, $evt ) = $apputils->checkses( $user_session );
88         return $evt if $evt;
89         $evt = $apputils->check_perms( $staff->id, $ouid, 'UPDATE_ORG_SETTING' );
90         return $evt if $evt;
91
92         my @params;
93         for my $set (keys %$settings) {
94
95                 my $json = JSON->perl2JSON($$settings{$set});
96                 $logger->activity("updating org_unit.setting: $ouid : $set : $json");
97
98                 push( @params, 
99                         { org_unit => $ouid, name => $set }, 
100                         { value => $json } );
101         }
102
103         my $ses = $U->start_db_session();
104         my $stat = $ses->request(
105                 'open-ils.storage.direct.actor.org_unit_setting.merge', @params )->gather(1);
106         $U->commit_db_session($ses);
107
108         return $stat;
109 }
110
111
112 my $fetch_user_settings;
113 my $fetch_ou_settings;
114
115 __PACKAGE__->register_method(
116         method  => "user_settings",
117         api_name        => "open-ils.actor.patron.settings.retrieve",
118 );
119 sub user_settings {
120         my( $self, $client, $user_session, $uid ) = @_;
121         
122         my( $staff, $user, $evt ) = 
123                 $apputils->checkses_requestor( $user_session, $uid, 'VIEW_USER' );
124         return $evt if $evt;
125
126         $logger->debug("User " . $staff->id . " fetching user $uid\n");
127         my $s = $apputils->simplereq(
128                 'open-ils.cstore',
129                 'open-ils.cstore.direct.actor.user_setting.search.atomic', { usr => $uid } );
130
131         return { map { ( $_->name => JSON->JSON2perl($_->value) ) } @$s };
132 }
133
134
135
136 __PACKAGE__->register_method(
137         method  => "ou_settings",
138         api_name        => "open-ils.actor.org_unit.settings.retrieve",
139 );
140 sub ou_settings {
141         my( $self, $client, $ouid ) = @_;
142         
143         $logger->info("Fetching org unit settings for org $ouid");
144
145         my $s = $apputils->simplereq(
146                 'open-ils.cstore',
147                 'open-ils.cstore.direct.actor.org_unit_setting.search.atomic', {org_unit => $ouid});
148
149         return { map { ( $_->name => JSON->JSON2perl($_->value) ) } @$s };
150 }
151
152 __PACKAGE__->register_method (
153         method          => "ou_setting_delete",
154         api_name                => 'open-ils.actor.org_setting.delete',
155         signature       => q/
156                 Deletes a specific org unit setting for a specific location
157                 @param authtoken The login session key
158                 @param orgid The org unit whose setting we're changing
159                 @param setting The name of the setting to delete
160                 @return True value on success.
161         /
162 );
163
164 sub ou_setting_delete {
165         my( $self, $conn, $authtoken, $orgid, $setting ) = @_;
166         my( $reqr, $evt) = $U->checkses($authtoken);
167         return $evt if $evt;
168         $evt = $U->check_perms($reqr->id, $orgid, 'UPDATE_ORG_SETTING');
169         return $evt if $evt;
170
171         my $id = $U->cstorereq(
172                 'open-ils.cstore.direct.actor.org_unit_setting.id_list', 
173                 { name => $setting, org_unit => $orgid } );
174
175         $logger->debug("Retrieved setting $id in org unit setting delete");
176
177         my $s = $U->cstorereq(
178                 'open-ils.cstore.direct.actor.org_unit_setting.delete', $id );
179
180         $logger->activity("User ".$reqr->id." deleted org unit setting $id") if $s;
181         return $s;
182 }
183
184
185
186 __PACKAGE__->register_method(
187         method  => "update_patron",
188         api_name        => "open-ils.actor.patron.update",);
189
190 sub update_patron {
191         my( $self, $client, $user_session, $patron ) = @_;
192
193         my $session = $apputils->start_db_session();
194         my $err = undef;
195
196
197         $logger->info("Creating new patron...") if $patron->isnew; 
198         $logger->info("Updating Patron: " . $patron->id) unless $patron->isnew;
199
200         my( $user_obj, $evt ) = $U->checkses($user_session);
201         return $evt if $evt;
202
203         $evt = check_group_perm($session, $user_obj, $patron);
204         return $evt if $evt;
205
206
207         # XXX does this user have permission to add/create users.  Granularity?
208         # $new_patron is the patron in progress.  $patron is the original patron
209         # passed in with the method.  new_patron will change as the components
210         # of patron are added/updated.
211
212         my $new_patron;
213
214         # unflesh the real items on the patron
215         $patron->card( $patron->card->id ) if(ref($patron->card));
216         $patron->billing_address( $patron->billing_address->id ) 
217                 if(ref($patron->billing_address));
218         $patron->mailing_address( $patron->mailing_address->id ) 
219                 if(ref($patron->mailing_address));
220
221         # create/update the patron first so we can use his id
222         if($patron->isnew()) {
223                 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
224                 return $evt if $evt;
225         } else { $new_patron = $patron; }
226
227         ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
228         return $evt if $evt;
229
230         ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
231         return $evt if $evt;
232
233         ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
234         return $evt if $evt;
235
236         # re-update the patron if anything has happened to him during this process
237         if($new_patron->ischanged()) {
238                 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
239                 return $evt if $evt;
240         }
241
242         ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
243         return $evt if $evt;
244
245         ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
246         return $evt if $evt;
247
248         ($new_patron, $evt) = _create_standing_penalties($session, $user_session, $patron, $new_patron, $user_obj);
249         return $evt if $evt;
250
251         $logger->activity("user ".$user_obj->id." updating/creating  user ".$new_patron->id);
252
253         my $opatron;
254         if(!$patron->isnew) {
255                 $opatron = new_editor()->retrieve_actor_user($new_patron->id);
256         }
257
258         $apputils->commit_db_session($session);
259         my $fuser =  flesh_user($new_patron->id());
260
261         if( $opatron ) {
262                 # Log the new and old patron for investigation
263                 $logger->info("$user_session updating patron object. orig patron object = ".
264                         JSON->perl2JSON($opatron). " |||| new patron = ".JSON->perl2JSON($fuser));
265         }
266
267
268         return $fuser;
269 }
270
271
272 sub flesh_user {
273         my $id = shift;
274         return new_flesh_user($id, [
275                 "cards",
276                 "card",
277                 "standing_penalties",
278                 "addresses",
279                 "billing_address",
280                 "mailing_address",
281                 "stat_cat_entries" ] );
282 }
283
284
285
286
287
288
289 # clone and clear stuff that would break the database
290 sub _clone_patron {
291         my $patron = shift;
292
293         my $new_patron = $patron->clone;
294         # clear these
295         $new_patron->clear_billing_address();
296         $new_patron->clear_mailing_address();
297         $new_patron->clear_addresses();
298         $new_patron->clear_card();
299         $new_patron->clear_cards();
300         $new_patron->clear_id();
301         $new_patron->clear_isnew();
302         $new_patron->clear_ischanged();
303         $new_patron->clear_isdeleted();
304         $new_patron->clear_stat_cat_entries();
305         $new_patron->clear_permissions();
306         $new_patron->clear_standing_penalties();
307
308         return $new_patron;
309 }
310
311
312 sub _add_patron {
313
314         my $session             = shift;
315         my $patron              = shift;
316         my $user_obj    = shift;
317
318         my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
319         return (undef, $evt) if $evt;
320
321         my $ex = $session->request(
322                 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
323         if( $ex and @$ex ) {
324                 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
325         }
326
327         $logger->info("Creating new user in the DB with username: ".$patron->usrname());
328
329         my $id = $session->request(
330                 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
331         return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
332
333         $logger->info("Successfully created new user [$id] in DB");
334
335         return ( $session->request( 
336                 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
337 }
338
339
340 sub check_group_perm {
341         my( $session, $requestor, $patron ) = @_;
342         my $evt;
343
344         # first let's see if the requestor has 
345         # priveleges to update this user in any way
346         if( ! $patron->isnew ) {
347                 my $p = $session->request(
348                         'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
349
350                 # If we are the requestor (trying to update our own account)
351                 # and we are not trying to change our profile, we're good
352                 if( $p->id == $requestor->id and 
353                                 $p->profile == $patron->profile ) {
354                         return undef;
355                 }
356
357
358                 $evt = group_perm_failed($session, $requestor, $p);
359                 return $evt if $evt;
360         }
361
362         # They are allowed to edit this patron.. can they put the 
363         # patron into the group requested?
364         $evt = group_perm_failed($session, $requestor, $patron);
365         return $evt if $evt;
366         return undef;
367 }
368
369
370 sub group_perm_failed {
371         my( $session, $requestor, $patron ) = @_;
372
373         my $perm;
374         my $grp;
375         my $grpid = $patron->profile;
376
377         do {
378
379                 $logger->debug("user update looking for group perm for group $grpid");
380                 $grp = $session->request(
381                         'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
382                 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
383
384         } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
385
386         $logger->info("user update checking perm $perm on user ".
387                 $requestor->id." for update/create on user username=".$patron->usrname);
388
389         my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
390         return $evt if $evt;
391         return undef;
392 }
393
394
395
396 sub _update_patron {
397         my( $session, $patron, $user_obj, $noperm) = @_;
398
399         $logger->info("Updating patron ".$patron->id." in DB");
400
401         my $evt;
402
403         if(!$noperm) {
404                 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
405                 return (undef, $evt) if $evt;
406         }
407
408         # update the password by itself to avoid the password protection magic
409         if( $patron->passwd ) {
410                 my $s = $session->request(
411                         'open-ils.storage.direct.actor.user.remote_update',
412                         {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
413                 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
414                 $patron->clear_passwd;
415         }
416
417         if(!$patron->ident_type) {
418                 $patron->clear_ident_type;
419                 $patron->clear_ident_value;
420         }
421
422         my $stat = $session->request(
423                 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
424         return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
425
426         return ($patron);
427 }
428
429 sub _check_dup_ident {
430         my( $session, $patron ) = @_;
431
432         return undef unless $patron->ident_value;
433
434         my $search = {
435                 ident_type      => $patron->ident_type, 
436                 ident_value => $patron->ident_value,
437         };
438
439         $logger->debug("patron update searching for dup ident values: " . 
440                 $patron->ident_type . ':' . $patron->ident_value);
441
442         $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
443
444         my $dups = $session->request(
445                 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
446
447
448         return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
449                 if $dups and @$dups;
450
451         return undef;
452 }
453
454
455 sub _add_update_addresses {
456
457         my $session = shift;
458         my $patron = shift;
459         my $new_patron = shift;
460
461         my $evt;
462
463         my $current_id; # id of the address before creation
464
465         for my $address (@{$patron->addresses()}) {
466
467                 next unless ref $address;
468                 $current_id = $address->id();
469
470                 if( $patron->billing_address() and
471                         $patron->billing_address() == $current_id ) {
472                         $logger->info("setting billing addr to $current_id");
473                         $new_patron->billing_address($address->id());
474                         $new_patron->ischanged(1);
475                 }
476         
477                 if( $patron->mailing_address() and
478                         $patron->mailing_address() == $current_id ) {
479                         $new_patron->mailing_address($address->id());
480                         $logger->info("setting mailing addr to $current_id");
481                         $new_patron->ischanged(1);
482                 }
483
484
485                 if($address->isnew()) {
486
487                         $address->usr($new_patron->id());
488
489                         ($address, $evt) = _add_address($session,$address);
490                         return (undef, $evt) if $evt;
491
492                         # we need to get the new id
493                         if( $patron->billing_address() and 
494                                         $patron->billing_address() == $current_id ) {
495                                 $new_patron->billing_address($address->id());
496                                 $logger->info("setting billing addr to $current_id");
497                                 $new_patron->ischanged(1);
498                         }
499
500                         if( $patron->mailing_address() and
501                                         $patron->mailing_address() == $current_id ) {
502                                 $new_patron->mailing_address($address->id());
503                                 $logger->info("setting mailing addr to $current_id");
504                                 $new_patron->ischanged(1);
505                         }
506
507                 } elsif($address->ischanged() ) {
508
509                         ($address, $evt) = _update_address($session, $address);
510                         return (undef, $evt) if $evt;
511
512                 } elsif($address->isdeleted() ) {
513
514                         if( $address->id() == $new_patron->mailing_address() ) {
515                                 $new_patron->clear_mailing_address();
516                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
517                                 return (undef, $evt) if $evt;
518                         }
519
520                         if( $address->id() == $new_patron->billing_address() ) {
521                                 $new_patron->clear_billing_address();
522                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
523                                 return (undef, $evt) if $evt;
524                         }
525
526                         $evt = _delete_address($session, $address);
527                         return (undef, $evt) if $evt;
528                 } 
529         }
530
531         return ( $new_patron, undef );
532 }
533
534
535 # adds an address to the db and returns the address with new id
536 sub _add_address {
537         my($session, $address) = @_;
538         $address->clear_id();
539
540         $logger->info("Creating new address at street ".$address->street1);
541
542         # put the address into the database
543         my $id = $session->request(
544                 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
545         return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
546
547         $address->id( $id );
548         return ($address, undef);
549 }
550
551
552 sub _update_address {
553         my( $session, $address ) = @_;
554
555         $logger->info("Updating address ".$address->id." in the DB");
556
557         my $stat = $session->request(
558                 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
559
560         return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
561         return ($address, undef);
562 }
563
564
565
566 sub _add_update_cards {
567
568         my $session = shift;
569         my $patron = shift;
570         my $new_patron = shift;
571
572         my $evt;
573
574         my $virtual_id; #id of the card before creation
575         for my $card (@{$patron->cards()}) {
576
577                 $card->usr($new_patron->id());
578
579                 if(ref($card) and $card->isnew()) {
580
581                         $virtual_id = $card->id();
582                         ( $card, $evt ) = _add_card($session,$card);
583                         return (undef, $evt) if $evt;
584
585                         #if(ref($patron->card)) { $patron->card($patron->card->id); }
586                         if($patron->card() == $virtual_id) {
587                                 $new_patron->card($card->id());
588                                 $new_patron->ischanged(1);
589                         }
590
591                 } elsif( ref($card) and $card->ischanged() ) {
592                         $evt = _update_card($session, $card);
593                         return (undef, $evt) if $evt;
594                 }
595         }
596
597         return ( $new_patron, undef );
598 }
599
600
601 # adds an card to the db and returns the card with new id
602 sub _add_card {
603         my( $session, $card ) = @_;
604         $card->clear_id();
605
606         $logger->info("Adding new patron card ".$card->barcode);
607
608         my $id = $session->request(
609                 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
610         return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
611         $logger->info("Successfully created patron card $id");
612
613         $card->id($id);
614         return ( $card, undef );
615 }
616
617
618 # returns event on error.  returns undef otherwise
619 sub _update_card {
620         my( $session, $card ) = @_;
621         $logger->info("Updating patron card ".$card->id);
622
623         my $stat = $session->request(
624                 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
625         return $U->DB_UPDATE_FAILED($card) unless defined($stat);
626         return undef;
627 }
628
629
630
631
632 # returns event on error.  returns undef otherwise
633 sub _delete_address {
634         my( $session, $address ) = @_;
635
636         $logger->info("Deleting address ".$address->id." from DB");
637
638         my $stat = $session->request(
639                 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
640
641         return $U->DB_UPDATE_FAILED($address) unless defined($stat);
642         return undef;
643 }
644
645
646
647 sub _add_survey_responses {
648         my ($session, $patron, $new_patron) = @_;
649
650         $logger->info( "Updating survey responses for patron ".$new_patron->id );
651
652         my $responses = $patron->survey_responses;
653
654         if($responses) {
655
656                 $_->usr($new_patron->id) for (@$responses);
657
658                 my $evt = $U->simplereq( "open-ils.circ", 
659                         "open-ils.circ.survey.submit.user_id", $responses );
660
661                 return (undef, $evt) if defined($U->event_code($evt));
662
663         }
664
665         return ( $new_patron, undef );
666 }
667
668
669 sub _create_stat_maps {
670
671         my($session, $user_session, $patron, $new_patron) = @_;
672
673         my $maps = $patron->stat_cat_entries();
674
675         for my $map (@$maps) {
676
677                 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
678
679                 if ($map->isdeleted()) {
680                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
681
682                 } elsif ($map->isnew()) {
683                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
684                         $map->clear_id;
685                 }
686
687
688                 $map->target_usr($new_patron->id);
689
690                 #warn "
691                 $logger->info("Updating stat entry with method $method and map $map");
692
693                 my $stat = $session->request($method, $map)->gather(1);
694                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
695
696         }
697
698         return ($new_patron, undef);
699 }
700
701 sub _create_perm_maps {
702
703         my($session, $user_session, $patron, $new_patron) = @_;
704
705         my $maps = $patron->permissions;
706
707         for my $map (@$maps) {
708
709                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
710                 if ($map->isdeleted()) {
711                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
712                 } elsif ($map->isnew()) {
713                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
714                         $map->clear_id;
715                 }
716
717
718                 $map->usr($new_patron->id);
719
720                 #warn( "Updating permissions with method $method and session $user_session and map $map" );
721                 $logger->info( "Updating permissions with method $method and map $map" );
722
723                 my $stat = $session->request($method, $map)->gather(1);
724                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
725
726         }
727
728         return ($new_patron, undef);
729 }
730
731
732 __PACKAGE__->register_method(
733         method  => "set_user_perms",
734         api_name        => "open-ils.actor.user.permissions.update",
735 );
736
737 sub set_user_perms {
738         my $self = shift;
739         my $client = shift;
740         my $ses = shift;
741         my $maps = shift;
742
743         my $session = $apputils->start_db_session();
744
745         my( $user_obj, $evt ) = $U->checkses($ses);
746         return $evt if $evt;
747
748         my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
749
750         my $all = undef;
751         $all = 1 if ($user_obj->super_user());
752     $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
753
754         for my $map (@$maps) {
755
756                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
757                 if ($map->isdeleted()) {
758                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
759                 } elsif ($map->isnew()) {
760                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
761                         $map->clear_id;
762                 }
763
764                 next if (!$all || !grep { $_->perm eq $map->perm and $_->grantable == 1 and $_->depth <= $map->depth } @$perms);
765
766                 #warn( "Updating permissions with method $method and session $ses and map $map" );
767                 $logger->info( "Updating permissions with method $method and map $map" );
768
769                 my $stat = $session->request($method, $map)->gather(1);
770                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
771
772         }
773
774         $apputils->commit_db_session($session);
775
776         return scalar(@$maps);
777 }
778
779
780 sub _create_standing_penalties {
781
782         my($session, $user_session, $patron, $new_patron) = @_;
783
784         my $maps = $patron->standing_penalties;
785         my $method;
786
787         for my $map (@$maps) {
788
789                 if ($map->isdeleted()) {
790                         $method = "open-ils.storage.direct.actor.user_standing_penalty.delete";
791                 } elsif ($map->isnew()) {
792                         $method = "open-ils.storage.direct.actor.user_standing_penalty.create";
793                         $map->clear_id;
794                 } else {
795                         next;
796                 }
797
798                 $map->usr($new_patron->id);
799
800                 $logger->debug( "Updating standing penalty with method $method and session $user_session and map $map" );
801
802                 my $stat = $session->request($method, $map)->gather(1);
803                 return (undef, $U->DB_UPDATE_FAILED($map)) unless $stat;
804         }
805
806         return ($new_patron, undef);
807 }
808
809
810
811 __PACKAGE__->register_method(
812         method  => "search_username",
813         api_name        => "open-ils.actor.user.search.username",
814 );
815
816 sub search_username {
817         my($self, $client, $username) = @_;
818         my $users = OpenILS::Application::AppUtils->simple_scalar_request(
819                         "open-ils.cstore", 
820                         "open-ils.cstore.direct.actor.user.search.atomic",
821                         { usrname => $username }
822         );
823         return $users;
824 }
825
826
827
828
829 __PACKAGE__->register_method(
830         method  => "user_retrieve_by_barcode",
831         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
832
833 sub user_retrieve_by_barcode {
834         my($self, $client, $user_session, $barcode) = @_;
835
836         $logger->debug("Searching for user with barcode $barcode");
837         my ($user_obj, $evt) = $apputils->checkses($user_session);
838         return $evt if $evt;
839
840         my $card = OpenILS::Application::AppUtils->simple_scalar_request(
841                         "open-ils.cstore", 
842                         "open-ils.cstore.direct.actor.card.search.atomic",
843                         { barcode => $barcode }
844         );
845
846         if(!$card || !$card->[0]) {
847                 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
848         }
849
850         $card = $card->[0];
851         my $user = flesh_user($card->usr());
852
853         $evt = $U->check_perms($user_obj->id, $user->home_ou, 'VIEW_USER');
854         return $evt if $evt;
855
856         if(!$user) { return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ); }
857         return $user;
858
859 }
860
861
862
863 __PACKAGE__->register_method(
864         method  => "get_user_by_id",
865         api_name        => "open-ils.actor.user.retrieve",);
866
867 sub get_user_by_id {
868         my ($self, $client, $auth, $id) = @_;
869         my $e = new_editor(authtoken=>$auth);
870         return $e->event unless $e->checkauth;
871         my $user = $e->retrieve_actor_user($id)
872                 or return $e->event;
873         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);       
874         return $user;
875 }
876
877
878
879 __PACKAGE__->register_method(
880         method  => "get_org_types",
881         api_name        => "open-ils.actor.org_types.retrieve",);
882
883 my $org_types;
884 sub get_org_types {
885         my($self, $client) = @_;
886         return $org_types if $org_types;
887         return $org_types = new_editor()->retrieve_all_actor_org_unit_type();
888 }
889
890
891
892 __PACKAGE__->register_method(
893         method  => "get_user_ident_types",
894         api_name        => "open-ils.actor.user.ident_types.retrieve",
895 );
896 my $ident_types;
897 sub get_user_ident_types {
898         return $ident_types if $ident_types;
899         return $ident_types = 
900                 new_editor()->retrieve_all_config_identification_type();
901 }
902
903
904
905
906 __PACKAGE__->register_method(
907         method  => "get_org_unit",
908         api_name        => "open-ils.actor.org_unit.retrieve",
909 );
910
911 sub get_org_unit {
912         my( $self, $client, $user_session, $org_id ) = @_;
913         my $e = new_editor(authtoken => $user_session);
914         if(!$org_id) {
915                 return $e->event unless $e->checkauth;
916                 $org_id = $e->requestor->ws_ou;
917         }
918         my $o = $e->retrieve_actor_org_unit($org_id)
919                 or return $e->event;
920         return $o;
921 }
922
923 __PACKAGE__->register_method(
924         method  => "search_org_unit",
925         api_name        => "open-ils.actor.org_unit_list.search",
926 );
927
928 sub search_org_unit {
929
930         my( $self, $client, $field, $value ) = @_;
931
932         my $list = OpenILS::Application::AppUtils->simple_scalar_request(
933                 "open-ils.cstore",
934                 "open-ils.cstore.direct.actor.org_unit.search.atomic", 
935                 { $field => $value } );
936
937         return $list;
938 }
939
940
941 # build the org tree
942
943 __PACKAGE__->register_method(
944         method  => "get_org_tree",
945         api_name        => "open-ils.actor.org_tree.retrieve",
946         argc            => 0, 
947         note            => "Returns the entire org tree structure",
948 );
949
950 sub get_org_tree {
951         my( $self, $client) = @_;
952
953         $cache  = OpenSRF::Utils::Cache->new("global", 0) unless $cache;
954         my $tree = $cache->get_cache('orgtree');
955         return $tree if $tree;
956
957         $tree = new_editor()->search_actor_org_unit( 
958                 [
959                         {"parent_ou" => undef },
960                         {
961                                 flesh                           => 2,
962                                 flesh_fields    => { aou =>  ['children'] },
963                                 order_by                        => { aou => 'name'}
964                         }
965                 ]
966         )->[0];
967
968         $cache->put_cache('orgtree', $tree);
969         return $tree;
970 }
971
972
973 # turns an org list into an org tree
974 sub build_org_tree {
975
976         my( $self, $orglist) = @_;
977
978         return $orglist unless ( 
979                         ref($orglist) and @$orglist > 1 );
980
981         my @list = sort { 
982                 $a->ou_type <=> $b->ou_type ||
983                 $a->name cmp $b->name } @$orglist;
984
985         for my $org (@list) {
986
987                 next unless ($org and defined($org->parent_ou));
988                 my ($parent) = grep { $_->id == $org->parent_ou } @list;
989                 next unless $parent;
990
991                 $parent->children([]) unless defined($parent->children); 
992                 push( @{$parent->children}, $org );
993         }
994
995         return $list[0];
996
997 }
998
999
1000 __PACKAGE__->register_method(
1001         method  => "get_org_descendants",
1002         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
1003 );
1004
1005 # depth is optional.  org_unit is the id
1006 sub get_org_descendants {
1007         my( $self, $client, $org_unit, $depth ) = @_;
1008         my $orglist = $apputils->simple_scalar_request(
1009                         "open-ils.storage", 
1010                         "open-ils.storage.actor.org_unit.descendants.atomic",
1011                         $org_unit, $depth );
1012         return $self->build_org_tree($orglist);
1013 }
1014
1015
1016 __PACKAGE__->register_method(
1017         method  => "get_org_ancestors",
1018         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
1019 );
1020
1021 # depth is optional.  org_unit is the id
1022 sub get_org_ancestors {
1023         my( $self, $client, $org_unit, $depth ) = @_;
1024         my $orglist = $apputils->simple_scalar_request(
1025                         "open-ils.storage", 
1026                         "open-ils.storage.actor.org_unit.ancestors.atomic",
1027                         $org_unit, $depth );
1028         return $self->build_org_tree($orglist);
1029 }
1030
1031
1032 __PACKAGE__->register_method(
1033         method  => "get_standings",
1034         api_name        => "open-ils.actor.standings.retrieve"
1035 );
1036
1037 my $user_standings;
1038 sub get_standings {
1039         return $user_standings if $user_standings;
1040         return $user_standings = 
1041                 $apputils->simple_scalar_request(
1042                         "open-ils.cstore",
1043                         "open-ils.cstore.direct.config.standing.search.atomic",
1044                         { id => { "!=" => undef } }
1045                 );
1046 }
1047
1048
1049
1050 __PACKAGE__->register_method(
1051         method  => "get_my_org_path",
1052         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
1053 );
1054
1055 sub get_my_org_path {
1056         my( $self, $client, $user_session, $org_id ) = @_;
1057         my $user_obj = $apputils->check_user_session($user_session); 
1058         if(!defined($org_id)) { $org_id = $user_obj->home_ou; }
1059
1060         return $apputils->simple_scalar_request(
1061                 "open-ils.storage",
1062                 "open-ils.storage.actor.org_unit.full_path.atomic",
1063                 $org_id );
1064 }
1065
1066
1067 __PACKAGE__->register_method(
1068         method  => "patron_adv_search",
1069         api_name        => "open-ils.actor.patron.search.advanced" );
1070 sub patron_adv_search {
1071         my( $self, $client, $auth, $search_hash, $search_limit, $search_sort, $include_inactive ) = @_;
1072         my $e = new_editor(authtoken=>$auth);
1073         return $e->event unless $e->checkauth;
1074         return $e->event unless $e->allowed('VIEW_USER');
1075         return $U->storagereq(
1076                 "open-ils.storage.actor.user.crazy_search", 
1077                 $search_hash, $search_limit, $search_sort, $include_inactive);
1078 }
1079
1080
1081
1082 sub _verify_password {
1083         my($user_session, $password) = @_;
1084         my $user_obj = $apputils->check_user_session($user_session); 
1085
1086         #grab the user with password
1087         $user_obj = $apputils->simple_scalar_request(
1088                 "open-ils.cstore", 
1089                 "open-ils.cstore.direct.actor.user.retrieve",
1090                 $user_obj->id );
1091
1092         if($user_obj->passwd eq $password) {
1093                 return 1;
1094         }
1095
1096         return 0;
1097 }
1098
1099
1100 __PACKAGE__->register_method(
1101         method  => "update_password",
1102         api_name        => "open-ils.actor.user.password.update");
1103
1104 __PACKAGE__->register_method(
1105         method  => "update_password",
1106         api_name        => "open-ils.actor.user.username.update");
1107
1108 __PACKAGE__->register_method(
1109         method  => "update_password",
1110         api_name        => "open-ils.actor.user.email.update");
1111
1112 sub update_password {
1113         my( $self, $client, $user_session, $new_value, $current_password ) = @_;
1114
1115         my $evt;
1116
1117         my $user_obj = $apputils->check_user_session($user_session); 
1118
1119         if($self->api_name =~ /password/o) {
1120
1121                 #make sure they know the current password
1122                 if(!_verify_password($user_session, md5_hex($current_password))) {
1123                         return OpenILS::Event->new('INCORRECT_PASSWORD');
1124                 }
1125
1126                 $logger->debug("update_password setting new password $new_value");
1127                 $user_obj->passwd($new_value);
1128
1129         } elsif($self->api_name =~ /username/o) {
1130                 my $users = search_username(undef, undef, $new_value); 
1131                 if( $users and $users->[0] ) {
1132                         return OpenILS::Event->new('USERNAME_EXISTS');
1133                 }
1134                 $user_obj->usrname($new_value);
1135
1136         } elsif($self->api_name =~ /email/o) {
1137                 #warn "Updating email to $new_value\n";
1138                 $user_obj->email($new_value);
1139         }
1140
1141         my $session = $apputils->start_db_session();
1142
1143         ( $user_obj, $evt ) = _update_patron($session, $user_obj, $user_obj, 1);
1144         return $evt if $evt;
1145
1146         $apputils->commit_db_session($session);
1147
1148         if($user_obj) { return 1; }
1149         return undef;
1150 }
1151
1152
1153 __PACKAGE__->register_method(
1154         method  => "check_user_perms",
1155         api_name        => "open-ils.actor.user.perm.check",
1156         notes           => <<"  NOTES");
1157         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1158         perm type, if the user does *not* have the given permission it is added
1159         to a list which is returned from the method.  If all permissions
1160         are allowed, an empty list is returned
1161         if the logged in user does not match 'user_id', then the logged in user must
1162         have VIEW_PERMISSION priveleges.
1163         NOTES
1164
1165 sub check_user_perms {
1166         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1167
1168         my( $staff, $evt ) = $apputils->checkses($login_session);
1169         return $evt if $evt;
1170
1171         if($staff->id ne $user_id) {
1172                 if( $evt = $apputils->check_perms(
1173                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1174                         return $evt;
1175                 }
1176         }
1177
1178         my @not_allowed;
1179         for my $perm (@$perm_types) {
1180                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1181                         push @not_allowed, $perm;
1182                 }
1183         }
1184
1185         return \@not_allowed
1186 }
1187
1188 __PACKAGE__->register_method(
1189         method  => "check_user_perms2",
1190         api_name        => "open-ils.actor.user.perm.check.multi_org",
1191         notes           => q/
1192                 Checks the permissions on a list of perms and orgs for a user
1193                 @param authtoken The login session key
1194                 @param user_id The id of the user to check
1195                 @param orgs The array of org ids
1196                 @param perms The array of permission names
1197                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1198                 if the logged in user does not match 'user_id', then the logged in user must
1199                 have VIEW_PERMISSION priveleges.
1200         /);
1201
1202 sub check_user_perms2 {
1203         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1204
1205         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1206                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1207         return $evt if $evt;
1208
1209         my @not_allowed;
1210         for my $org (@$orgs) {
1211                 for my $perm (@$perms) {
1212                         if($apputils->check_perms($user_id, $org, $perm)) {
1213                                 push @not_allowed, [ $org, $perm ];
1214                         }
1215                 }
1216         }
1217
1218         return \@not_allowed
1219 }
1220
1221
1222 __PACKAGE__->register_method(
1223         method => 'check_user_perms3',
1224         api_name        => 'open-ils.actor.user.perm.highest_org',
1225         notes           => q/
1226                 Returns the highest org unit id at which a user has a given permission
1227                 If the requestor does not match the target user, the requestor must have
1228                 'VIEW_PERMISSION' rights at the home org unit of the target user
1229                 @param authtoken The login session key
1230                 @param userid The id of the user in question
1231                 @param perm The permission to check
1232                 @return The org unit highest in the org tree within which the user has
1233                 the requested permission
1234         /);
1235
1236 sub check_user_perms3 {
1237         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1238
1239         my( $staff, $target, $org, $evt );
1240
1241         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1242                 $authtoken, $userid, 'VIEW_PERMISSION' );
1243         return $evt if $evt;
1244
1245         my $tree = $self->get_org_tree();
1246         return _find_highest_perm_org( $perm, $userid, $target->ws_ou, $tree );
1247 }
1248
1249
1250 sub _find_highest_perm_org {
1251         my ( $perm, $userid, $start_org, $org_tree ) = @_;
1252         my $org = $apputils->find_org($org_tree, $start_org );
1253
1254         my $lastid = undef;
1255         while( $org ) {
1256                 last if ($apputils->check_perms( $userid, $org->id, $perm )); # perm failed
1257                 $lastid = $org->id;
1258                 $org = $apputils->find_org( $org_tree, $org->parent_ou() );
1259         }
1260
1261         return $lastid;
1262 }
1263
1264 __PACKAGE__->register_method(
1265         method => 'check_user_perms4',
1266         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1267         notes           => q/
1268                 Returns the highest org unit id at which a user has a given permission
1269                 If the requestor does not match the target user, the requestor must have
1270                 'VIEW_PERMISSION' rights at the home org unit of the target user
1271                 @param authtoken The login session key
1272                 @param userid The id of the user in question
1273                 @param perms An array of perm names to check 
1274                 @return An array of orgId's  representing the org unit 
1275                 highest in the org tree within which the user has the requested permission
1276                 The arrah of orgId's has matches the order of the perms array
1277         /);
1278
1279 sub check_user_perms4 {
1280         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1281         
1282         my( $staff, $target, $org, $evt );
1283
1284         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1285                 $authtoken, $userid, 'VIEW_PERMISSION' );
1286         return $evt if $evt;
1287
1288         my @arr;
1289         return [] unless ref($perms);
1290         my $tree = $self->get_org_tree();
1291
1292         for my $p (@$perms) {
1293                 push( @arr, _find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1294         }
1295         return \@arr;
1296 }
1297
1298
1299
1300
1301 __PACKAGE__->register_method(
1302         method  => "user_fines_summary",
1303         api_name        => "open-ils.actor.user.fines.summary",
1304         notes           => <<"  NOTES");
1305         Returns a short summary of the users total open fines, excluding voided fines
1306         Params are login_session, user_id
1307         Returns a 'mous' object.
1308         NOTES
1309
1310 sub user_fines_summary {
1311         my( $self, $client, $auth, $user_id ) = @_;
1312         my $e = new_editor(authtoken=>$auth);
1313         return $e->event unless $e->checkauth;
1314         my $user = $e->retrieve_actor_user($user_id)
1315                 or return $e->event;
1316
1317         if( $user_id ne $e->requestor->id ) {
1318                 return $e->event unless 
1319                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1320         }
1321         
1322         # run this inside a transaction to prevent replication delay errors
1323         my $ses = $U->start_db_session();
1324         my $s = $ses->request(
1325                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1326         $U->rollback_db_session($ses);
1327         return $s;
1328 }
1329
1330
1331
1332
1333 __PACKAGE__->register_method(
1334         method  => "user_transactions",
1335         api_name        => "open-ils.actor.user.transactions",
1336         notes           => <<"  NOTES");
1337         Returns a list of open user transactions (mbts objects);
1338         Params are login_session, user_id
1339         Optional third parameter is the transactions type.  defaults to all
1340         NOTES
1341
1342 __PACKAGE__->register_method(
1343         method  => "user_transactions",
1344         api_name        => "open-ils.actor.user.transactions.have_charge",
1345         notes           => <<"  NOTES");
1346         Returns a list of all open user transactions (mbts objects) that have an initial charge
1347         Params are login_session, user_id
1348         Optional third parameter is the transactions type.  defaults to all
1349         NOTES
1350
1351 __PACKAGE__->register_method(
1352         method  => "user_transactions",
1353         api_name        => "open-ils.actor.user.transactions.have_balance",
1354         notes           => <<"  NOTES");
1355         Returns a list of all open user transactions (mbts objects) that have a balance
1356         Params are login_session, user_id
1357         Optional third parameter is the transactions type.  defaults to all
1358         NOTES
1359
1360 __PACKAGE__->register_method(
1361         method  => "user_transactions",
1362         api_name        => "open-ils.actor.user.transactions.fleshed",
1363         notes           => <<"  NOTES");
1364         Returns an object/hash of transaction, circ, title where transaction = an open 
1365         user transactions (mbts objects), circ is the attached circluation, and title
1366         is the title the circ points to
1367         Params are login_session, user_id
1368         Optional third parameter is the transactions type.  defaults to all
1369         NOTES
1370
1371 __PACKAGE__->register_method(
1372         method  => "user_transactions",
1373         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1374         notes           => <<"  NOTES");
1375         Returns an object/hash of transaction, circ, title where transaction = an open 
1376         user transactions that has an initial charge (mbts objects), circ is the 
1377         attached circluation, and title is the title the circ points to
1378         Params are login_session, user_id
1379         Optional third parameter is the transactions type.  defaults to all
1380         NOTES
1381
1382 __PACKAGE__->register_method(
1383         method  => "user_transactions",
1384         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1385         notes           => <<"  NOTES");
1386         Returns an object/hash of transaction, circ, title where transaction = an open 
1387         user transaction that has a balance (mbts objects), circ is the attached 
1388         circluation, and title is the title the circ points to
1389         Params are login_session, user_id
1390         Optional third parameter is the transaction type.  defaults to all
1391         NOTES
1392
1393 __PACKAGE__->register_method(
1394         method  => "user_transactions",
1395         api_name        => "open-ils.actor.user.transactions.count",
1396         notes           => <<"  NOTES");
1397         Returns an object/hash of transaction, circ, title where transaction = an open 
1398         user transactions (mbts objects), circ is the attached circluation, and title
1399         is the title the circ points to
1400         Params are login_session, user_id
1401         Optional third parameter is the transactions type.  defaults to all
1402         NOTES
1403
1404 __PACKAGE__->register_method(
1405         method  => "user_transactions",
1406         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1407         notes           => <<"  NOTES");
1408         Returns an object/hash of transaction, circ, title where transaction = an open 
1409         user transactions that has an initial charge (mbts objects), circ is the 
1410         attached circluation, and title is the title the circ points to
1411         Params are login_session, user_id
1412         Optional third parameter is the transactions type.  defaults to all
1413         NOTES
1414
1415 __PACKAGE__->register_method(
1416         method  => "user_transactions",
1417         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1418         notes           => <<"  NOTES");
1419         Returns an object/hash of transaction, circ, title where transaction = an open 
1420         user transaction that has a balance (mbts objects), circ is the attached 
1421         circluation, and title is the title the circ points to
1422         Params are login_session, user_id
1423         Optional third parameter is the transaction type.  defaults to all
1424         NOTES
1425
1426 __PACKAGE__->register_method(
1427         method  => "user_transactions",
1428         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1429         notes           => <<"  NOTES");
1430         Returns an object/hash of transaction, circ, title where transaction = an open 
1431         user transaction that has a balance (mbts objects), circ is the attached 
1432         circluation, and title is the title the circ points to
1433         Params are login_session, user_id
1434         Optional third parameter is the transaction type.  defaults to all
1435         NOTES
1436
1437
1438
1439 sub user_transactions {
1440         my( $self, $client, $login_session, $user_id, $type ) = @_;
1441
1442         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1443                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1444         return $evt if $evt;
1445
1446         my $api = $self->api_name();
1447         my $trans;
1448         my @xact;
1449
1450         if(defined($type)) { @xact = (xact_type =>  $type); 
1451
1452         } else { @xact = (); }
1453
1454         ($trans) = $self
1455                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1456                 ->run($login_session => $user_id => $type);
1457
1458         if($api =~ /have_charge/o) {
1459
1460                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1461
1462         } elsif($api =~ /have_balance/o) {
1463
1464                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1465         } else {
1466
1467                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1468
1469         }
1470
1471         if($api =~ /total/o) { 
1472                 my $total = 0.0;
1473                 for my $t (@$trans) {
1474                         $total += $t->balance_owed;
1475                 }
1476
1477                 $logger->debug("Total balance owed by user $user_id: $total");
1478                 return $total;
1479         }
1480
1481         if($api =~ /count/o) { return scalar @$trans; }
1482         if($api !~ /fleshed/o) { return $trans; }
1483
1484         my @resp;
1485         for my $t (@$trans) {
1486                         
1487                 if( $t->xact_type ne 'circulation' ) {
1488                         push @resp, {transaction => $t};
1489                         next;
1490                 }
1491
1492                 my $circ = $apputils->simple_scalar_request(
1493                                 "open-ils.cstore",
1494                                 "open-ils.cstore.direct.action.circulation.retrieve",
1495                                 $t->id );
1496
1497                 next unless $circ;
1498
1499                 my $title = $apputils->simple_scalar_request(
1500                         "open-ils.storage", 
1501                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1502                         $circ->target_copy );
1503
1504                 next unless $title;
1505
1506                 my $u = OpenILS::Utils::ModsParser->new();
1507                 $u->start_mods_batch($title->marc());
1508                 my $mods = $u->finish_mods_batch();
1509                 $mods->doc_id($title->id) if $mods;
1510
1511                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1512
1513         }
1514
1515         return \@resp; 
1516
1517
1518
1519 __PACKAGE__->register_method(
1520         method  => "user_transaction_retrieve",
1521         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1522         argc            => 1,
1523         notes           => <<"  NOTES");
1524         Returns a fleshedtransaction record
1525         NOTES
1526 __PACKAGE__->register_method(
1527         method  => "user_transaction_retrieve",
1528         api_name        => "open-ils.actor.user.transaction.retrieve",
1529         argc            => 1,
1530         notes           => <<"  NOTES");
1531         Returns a transaction record
1532         NOTES
1533 sub user_transaction_retrieve {
1534         my( $self, $client, $login_session, $bill_id ) = @_;
1535
1536         my $trans = $apputils->simple_scalar_request( 
1537                 "open-ils.cstore",
1538                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1539                 $bill_id
1540         );
1541
1542         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1543                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1544         return $evt if $evt;
1545         
1546         my $api = $self->api_name();
1547         if($api !~ /fleshed/o) { return $trans; }
1548
1549         if( $trans->xact_type ne 'circulation' ) {
1550                 $logger->debug("Returning non-circ transaction");
1551                 return {transaction => $trans};
1552         }
1553
1554         my $circ = $apputils->simple_scalar_request(
1555                         "open-ils.cstore",
1556                         "open-ils..direct.action.circulation.retrieve",
1557                         $trans->id );
1558
1559         return {transaction => $trans} unless $circ;
1560         $logger->debug("Found the circ transaction");
1561
1562         my $title = $apputils->simple_scalar_request(
1563                 "open-ils.storage", 
1564                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1565                 $circ->target_copy );
1566
1567         return {transaction => $trans, circ => $circ } unless $title;
1568         $logger->debug("Found the circ title");
1569
1570         my $mods;
1571         try {
1572                 my $u = OpenILS::Utils::ModsParser->new();
1573                 $u->start_mods_batch($title->marc());
1574                 $mods = $u->finish_mods_batch();
1575         } otherwise {
1576                 if ($title->id == OILS_PRECAT_RECORD) {
1577                         my $copy = $apputils->simple_scalar_request(
1578                                 "open-ils.cstore",
1579                                 "open-ils.cstore.direct.asset.copy.retrieve",
1580                                 $circ->target_copy );
1581
1582                         $mods = new Fieldmapper::metabib::virtual_record;
1583                         $mods->doc_id(OILS_PRECAT_RECORD);
1584                         $mods->title($copy->dummy_title);
1585                         $mods->author($copy->dummy_author);
1586                 }
1587         };
1588
1589         $logger->debug("MODSized the circ title");
1590
1591         return {transaction => $trans, circ => $circ, record => $mods };
1592 }
1593
1594
1595 __PACKAGE__->register_method(
1596         method  => "hold_request_count",
1597         api_name        => "open-ils.actor.user.hold_requests.count",
1598         argc            => 1,
1599         notes           => <<"  NOTES");
1600         Returns hold ready/total counts
1601         NOTES
1602 sub hold_request_count {
1603         my( $self, $client, $login_session, $userid ) = @_;
1604
1605         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1606                 $login_session, $userid, 'VIEW_HOLD' );
1607         return $evt if $evt;
1608         
1609
1610         my $holds = $apputils->simple_scalar_request(
1611                         "open-ils.cstore",
1612                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1613                         { 
1614                                 usr => $userid,
1615                                 fulfillment_time => {"=" => undef },
1616                                 cancel_time => undef,
1617                         }
1618         );
1619
1620         my @ready;
1621         for my $h (@$holds) {
1622                 next unless $h->capture_time and $h->current_copy;
1623
1624                 my $copy = $apputils->simple_scalar_request(
1625                         "open-ils.cstore",
1626                         "open-ils.cstore.direct.asset.copy.retrieve",
1627                         $h->current_copy
1628                 );
1629
1630                 if ($copy and $copy->status == 8) {
1631                         push @ready, $h;
1632                 }
1633         }
1634
1635         return { total => scalar(@$holds), ready => scalar(@ready) };
1636 }
1637
1638
1639 __PACKAGE__->register_method(
1640         method  => "checkedout_count",
1641         api_name        => "open-ils.actor.user.checked_out.count__",
1642         argc            => 1,
1643         notes           => <<"  NOTES");
1644         Returns a transaction record
1645         NOTES
1646
1647 # XXX Deprecate Me
1648 sub checkedout_count {
1649         my( $self, $client, $login_session, $userid ) = @_;
1650
1651         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1652                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1653         return $evt if $evt;
1654         
1655         my $circs = $apputils->simple_scalar_request(
1656                         "open-ils.cstore",
1657                         "open-ils.cstore.direct.action.circulation.search.atomic",
1658                         { usr => $userid, stop_fines => undef }
1659                         #{ usr => $userid, checkin_time => {"=" => undef } }
1660         );
1661
1662         my $parser = DateTime::Format::ISO8601->new;
1663
1664         my (@out,@overdue);
1665         for my $c (@$circs) {
1666                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1667                 my $due = $due_dt->epoch;
1668
1669                 if ($due < DateTime->today->epoch) {
1670                         push @overdue, $c;
1671                 }
1672         }
1673
1674         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1675 }
1676
1677
1678 __PACKAGE__->register_method(
1679         method          => "checked_out",
1680         api_name                => "open-ils.actor.user.checked_out",
1681         argc                    => 2,
1682         signature       => q/
1683                 Returns a structure of circulations objects sorted by
1684                 out, overdue, lost, claims_returned, long_overdue.
1685                 A list of IDs are returned of each type.
1686                 lost, long_overdue, and claims_returned circ will not
1687                 be "finished" (there is an outstanding balance or some 
1688                 other pending action on the circ). 
1689
1690                 The .count method also includes a 'total' field which 
1691                 sums all "open" circs
1692         /
1693 );
1694
1695 __PACKAGE__->register_method(
1696         method          => "checked_out",
1697         api_name                => "open-ils.actor.user.checked_out.count",
1698         argc                    => 2,
1699         signature       => q/@see open-ils.actor.user.checked_out/
1700 );
1701
1702 sub checked_out {
1703         my( $self, $conn, $auth, $userid ) = @_;
1704
1705         my $e = new_editor(authtoken=>$auth);
1706         return $e->event unless $e->checkauth;
1707
1708         if( $userid ne $e->requestor->id ) {
1709                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1710         }
1711
1712         my $count = $self->api_name =~ /count/;
1713         return _checked_out( $count, $e, $userid );
1714 }
1715
1716 sub _checked_out {
1717         my( $iscount, $e, $userid ) = @_;
1718
1719
1720         my $meth = 'open-ils.storage.actor.user.checked_out';
1721         $meth = "$meth.count" if $iscount;
1722         return $U->storagereq($meth, $userid);
1723
1724 # XXX Old code - moved to storage
1725 #------------------------------------------------------------------------------
1726 #------------------------------------------------------------------------------
1727         my $circs = $e->search_action_circulation( 
1728                 { usr => $userid, checkin_time => undef });
1729
1730         my $parser = DateTime::Format::ISO8601->new;
1731
1732         # split the circs up into overdue and not-overdue circs
1733         my (@out,@overdue);
1734         for my $c (@$circs) {
1735                 if( $c->due_date ) {
1736                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1737                         my $due = $due_dt->epoch;
1738                         if ($due < DateTime->today->epoch) {
1739                                 push @overdue, $c;
1740                         } else {
1741                                 push @out, $c;
1742                         }
1743                 } else {
1744                         push @out, $c;
1745                 }
1746         }
1747
1748         my( @open, @od, @lost, @cr, @lo );
1749
1750         while (my $c = shift(@out)) {
1751                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1752                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1753                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1754                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1755         }
1756
1757         while (my $c = shift(@overdue)) {
1758                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1759                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1760                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1761                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1762         }
1763
1764         if( $iscount ) {
1765                 return {
1766                         total           => @open + @od + @lost + @cr + @lo,
1767                         out             => scalar(@open),
1768                         overdue => scalar(@od),
1769                         lost            => scalar(@lost),
1770                         claims_returned => scalar(@cr),
1771                         long_overdue            => scalar(@lo)
1772                 };
1773         }
1774
1775         return {
1776                 out             => \@open,
1777                 overdue => \@od,
1778                 lost            => \@lost,
1779                 claims_returned => \@cr,
1780                 long_overdue            => \@lo
1781         };
1782 }
1783
1784
1785 sub _checked_out_WHAT {
1786         my( $iscount, $e, $userid ) = @_;
1787
1788         my $circs = $e->search_action_circulation( 
1789                 { usr => $userid, stop_fines => undef });
1790
1791         my $mcircs = $e->search_action_circulation( 
1792                 { 
1793                         usr => $userid, 
1794                         checkin_time => undef, 
1795                         xact_finish => undef, 
1796                 });
1797
1798         
1799         push( @$circs, @$mcircs );
1800
1801         my $parser = DateTime::Format::ISO8601->new;
1802
1803         # split the circs up into overdue and not-overdue circs
1804         my (@out,@overdue);
1805         for my $c (@$circs) {
1806                 if( $c->due_date ) {
1807                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1808                         my $due = $due_dt->epoch;
1809                         if ($due < DateTime->today->epoch) {
1810                                 push @overdue, $c->id;
1811                         } else {
1812                                 push @out, $c->id;
1813                         }
1814                 } else {
1815                         push @out, $c->id;
1816                 }
1817         }
1818
1819         # grab all of the lost, claims-returned, and longoverdue circs
1820         #my $open = $e->search_action_circulation(
1821         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1822
1823
1824         # these items have stop_fines, but no xact_finish, so money
1825         # is owed on them and they have not been checked in
1826         my $open = $e->search_action_circulation(
1827                 {
1828                         usr                             => $userid, 
1829                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1830                         xact_finish             => undef,
1831                         checkin_time    => undef,
1832                 }
1833         );
1834
1835
1836         my( @lost, @cr, @lo );
1837         for my $c (@$open) {
1838                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1839                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1840                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1841         }
1842
1843
1844         if( $iscount ) {
1845                 return {
1846                         total           => @$circs + @lost + @cr + @lo,
1847                         out             => scalar(@out),
1848                         overdue => scalar(@overdue),
1849                         lost            => scalar(@lost),
1850                         claims_returned => scalar(@cr),
1851                         long_overdue            => scalar(@lo)
1852                 };
1853         }
1854
1855         return {
1856                 out             => \@out,
1857                 overdue => \@overdue,
1858                 lost            => \@lost,
1859                 claims_returned => \@cr,
1860                 long_overdue            => \@lo
1861         };
1862 }
1863
1864
1865
1866 __PACKAGE__->register_method(
1867         method          => "checked_in_with_fines",
1868         api_name                => "open-ils.actor.user.checked_in_with_fines",
1869         argc                    => 2,
1870         signature       => q/@see open-ils.actor.user.checked_out/
1871 );
1872 sub checked_in_with_fines {
1873         my( $self, $conn, $auth, $userid ) = @_;
1874
1875         my $e = new_editor(authtoken=>$auth);
1876         return $e->event unless $e->checkauth;
1877
1878         if( $userid ne $e->requestor->id ) {
1879                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1880         }
1881
1882         # money is owed on these items and they are checked in
1883         my $open = $e->search_action_circulation(
1884                 {
1885                         usr                             => $userid, 
1886                         xact_finish             => undef,
1887                         checkin_time    => { "!=" => undef },
1888                 }
1889         );
1890
1891
1892         my( @lost, @cr, @lo );
1893         for my $c (@$open) {
1894                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1895                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1896                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1897         }
1898
1899         return {
1900                 lost            => \@lost,
1901                 claims_returned => \@cr,
1902                 long_overdue            => \@lo
1903         };
1904 }
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914 __PACKAGE__->register_method(
1915         method  => "user_transaction_history",
1916         api_name        => "open-ils.actor.user.transactions.history",
1917         argc            => 1,
1918         notes           => <<"  NOTES");
1919         Returns a list of billable transaction ids for a user, optionally by type
1920         NOTES
1921 __PACKAGE__->register_method(
1922         method  => "user_transaction_history",
1923         api_name        => "open-ils.actor.user.transactions.history.have_charge",
1924         argc            => 1,
1925         notes           => <<"  NOTES");
1926         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
1927         NOTES
1928 __PACKAGE__->register_method(
1929         method  => "user_transaction_history",
1930         api_name        => "open-ils.actor.user.transactions.history.have_balance",
1931         argc            => 1,
1932         notes           => <<"  NOTES");
1933         Returns a list of billable transaction ids for a user that have a balance, optionally by type
1934         NOTES
1935 __PACKAGE__->register_method(
1936         method  => "user_transaction_history",
1937         api_name        => "open-ils.actor.user.transactions.history.still_open",
1938         argc            => 1,
1939         notes           => <<"  NOTES");
1940         Returns a list of billable transaction ids for a user that are not finished
1941         NOTES
1942 __PACKAGE__->register_method(
1943         method  => "user_transaction_history",
1944         api_name        => "open-ils.actor.user.transactions.history.have_bill",
1945         argc            => 1,
1946         notes           => <<"  NOTES");
1947         Returns a list of billable transaction ids for a user that has billings
1948         NOTES
1949
1950
1951
1952 =head old
1953 sub _user_transaction_history {
1954         my( $self, $client, $login_session, $user_id, $type ) = @_;
1955
1956         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1957                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1958         return $evt if $evt;
1959
1960         my $api = $self->api_name();
1961         my @xact;
1962         my @charge;
1963         my @balance;
1964
1965         @xact = (xact_type =>  $type) if(defined($type));
1966         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
1967         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
1968
1969         $logger->debug("searching for transaction history: @xact : @balance, @charge");
1970
1971         my $trans = $apputils->simple_scalar_request( 
1972                 "open-ils.cstore",
1973                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
1974                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
1975
1976         return [ map { $_->id } @$trans ];
1977 }
1978 =cut
1979
1980 =head SEE APPUTILS.PM
1981 sub _make_mbts {
1982         my @xacts = @_;
1983
1984         my @mbts;
1985         for my $x (@xacts) {
1986                 my $s = new Fieldmapper::money::billable_transaction_summary;
1987                 $s->id( $x->id );
1988                 $s->usr( $x->usr );
1989                 $s->xact_start( $x->xact_start );
1990                 $s->xact_finish( $x->xact_finish );
1991
1992                 my $to = 0;
1993                 my $lb = undef;
1994                 for my $b (@{ $x->billings }) {
1995                         next if ($U->is_true($b->voided));
1996                         $to += ($b->amount * 100);
1997                         $lb ||= $b->billing_ts;
1998                         if ($b->billing_ts ge $lb) {
1999                                 $lb = $b->billing_ts;
2000                                 $s->last_billing_note($b->note);
2001                                 $s->last_billing_ts($b->billing_ts);
2002                                 $s->last_billing_type($b->billing_type);
2003                         }
2004                 }
2005
2006                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2007
2008                 my $tp = 0;
2009                 my $lp = undef;
2010                 for my $p (@{ $x->payments }) {
2011                         next if ($U->is_true($p->voided));
2012                         $tp += ($p->amount * 100);
2013                         $lp ||= $p->payment_ts;
2014                         if ($p->payment_ts ge $lp) {
2015                                 $lp = $p->payment_ts;
2016                                 $s->last_payment_note($p->note);
2017                                 $s->last_payment_ts($p->payment_ts);
2018                                 $s->last_payment_type($p->payment_type);
2019                         }
2020                 }
2021                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2022
2023                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2024
2025                 $s->xact_type( 'grocery' ) if ($x->grocery);
2026                 $s->xact_type( 'circulation' ) if ($x->circulation);
2027
2028                 push @mbts, $s;
2029         }
2030
2031         return @mbts;
2032 }
2033 =cut
2034
2035 sub user_transaction_history {
2036         my( $self, $conn, $auth, $userid, $type ) = @_;
2037
2038         # run inside of a transaction to prevent replication delays
2039         my $e = new_editor(xact=>1, authtoken=>$auth);
2040         return $e->die_event unless $e->checkauth;
2041         return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2042
2043         my $api = $self->api_name;
2044         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2045
2046         my @xacts = @{ $e->search_money_billable_transaction(
2047                 [       { usr => $userid, @xact_finish },
2048                         { flesh => 1,
2049                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2050                           order_by => { mbt => 'xact_start DESC' },
2051                         }
2052                 ]
2053         ) };
2054
2055         $e->rollback;
2056
2057         #my @mbts = _make_mbts( @xacts );
2058         my @mbts = $U->make_mbts( @xacts );
2059
2060         if(defined($type)) {
2061                 @mbts = grep { $_->xact_type eq $type } @mbts;
2062         }
2063
2064         if($api =~ /have_balance/o) {
2065                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2066         }
2067
2068         if($api =~ /have_charge/o) {
2069                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2070         }
2071
2072         if($api =~ /have_bill/o) {
2073                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2074         }
2075
2076         return [@mbts];
2077 }
2078
2079
2080
2081 __PACKAGE__->register_method(
2082         method  => "user_perms",
2083         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2084         argc            => 1,
2085         notes           => <<"  NOTES");
2086         Returns a list of permissions
2087         NOTES
2088 sub user_perms {
2089         my( $self, $client, $authtoken, $user ) = @_;
2090
2091         my( $staff, $evt ) = $apputils->checkses($authtoken);
2092         return $evt if $evt;
2093
2094         $user ||= $staff->id;
2095
2096         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2097                 return $evt;
2098         }
2099
2100         return $apputils->simple_scalar_request(
2101                 "open-ils.storage",
2102                 "open-ils.storage.permission.user_perms.atomic",
2103                 $user);
2104 }
2105
2106 __PACKAGE__->register_method(
2107         method  => "retrieve_perms",
2108         api_name        => "open-ils.actor.permissions.retrieve",
2109         notes           => <<"  NOTES");
2110         Returns a list of permissions
2111         NOTES
2112 sub retrieve_perms {
2113         my( $self, $client ) = @_;
2114         return $apputils->simple_scalar_request(
2115                 "open-ils.cstore",
2116                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2117                 { id => { '!=' => undef } }
2118         );
2119 }
2120
2121 __PACKAGE__->register_method(
2122         method  => "retrieve_groups",
2123         api_name        => "open-ils.actor.groups.retrieve",
2124         notes           => <<"  NOTES");
2125         Returns a list of user groupss
2126         NOTES
2127 sub retrieve_groups {
2128         my( $self, $client ) = @_;
2129         return new_editor()->retrieve_all_permission_grp_tree();
2130 }
2131
2132 __PACKAGE__->register_method(
2133         method  => "retrieve_org_address",
2134         api_name        => "open-ils.actor.org_unit.address.retrieve",
2135         notes           => <<'  NOTES');
2136         Returns an org_unit address by ID
2137         @param An org_address ID
2138         NOTES
2139 sub retrieve_org_address {
2140         my( $self, $client, $id ) = @_;
2141         return $apputils->simple_scalar_request(
2142                 "open-ils.cstore",
2143                 "open-ils.cstore.direct.actor.org_address.retrieve",
2144                 $id
2145         );
2146 }
2147
2148 __PACKAGE__->register_method(
2149         method  => "retrieve_groups_tree",
2150         api_name        => "open-ils.actor.groups.tree.retrieve",
2151         notes           => <<"  NOTES");
2152         Returns a list of user groups
2153         NOTES
2154 sub retrieve_groups_tree {
2155         my( $self, $client ) = @_;
2156         return new_editor()->search_permission_grp_tree(
2157                 [
2158                         { parent => undef},
2159                         {       
2160                                 flesh                           => 10, 
2161                                 flesh_fields    => { pgt => ["children"] }, 
2162                                 order_by                        => { pgt => 'name'}
2163                         }
2164                 ]
2165         )->[0];
2166 }
2167
2168
2169 # turns an org list into an org tree
2170 =head old code
2171 sub build_group_tree {
2172
2173         my( $self, $grplist) = @_;
2174
2175         return $grplist unless ( 
2176                         ref($grplist) and @$grplist > 1 );
2177
2178         my @list = sort { $a->name cmp $b->name } @$grplist;
2179
2180         my $root;
2181         for my $grp (@list) {
2182
2183                 if ($grp and !defined($grp->parent)) {
2184                         $root = $grp;
2185                         next;
2186                 }
2187                 my ($parent) = grep { $_->id == $grp->parent} @list;
2188
2189                 $parent->children([]) unless defined($parent->children); 
2190                 push( @{$parent->children}, $grp );
2191         }
2192
2193         return $root;
2194 }
2195 =cut
2196
2197
2198 __PACKAGE__->register_method(
2199         method  => "add_user_to_groups",
2200         api_name        => "open-ils.actor.user.set_groups",
2201         notes           => <<"  NOTES");
2202         Adds a user to one or more permission groups
2203         NOTES
2204
2205 sub add_user_to_groups {
2206         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2207
2208         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2209                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2210         return $evt if $evt;
2211
2212         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2213                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2214         return $evt if $evt;
2215
2216         $apputils->simplereq(
2217                 'open-ils.storage',
2218                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2219                 
2220         for my $group (@$groups) {
2221                 my $link = Fieldmapper::permission::usr_grp_map->new;
2222                 $link->grp($group);
2223                 $link->usr($userid);
2224
2225                 my $id = $apputils->simplereq(
2226                         'open-ils.storage',
2227                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2228         }
2229
2230         return 1;
2231 }
2232
2233 __PACKAGE__->register_method(
2234         method  => "get_user_perm_groups",
2235         api_name        => "open-ils.actor.user.get_groups",
2236         notes           => <<"  NOTES");
2237         Retrieve a user's permission groups.
2238         NOTES
2239
2240
2241 sub get_user_perm_groups {
2242         my( $self, $client, $authtoken, $userid ) = @_;
2243
2244         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2245                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2246         return $evt if $evt;
2247
2248         return $apputils->simplereq(
2249                 'open-ils.cstore',
2250                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2251 }       
2252
2253
2254
2255 __PACKAGE__->register_method (
2256         method          => 'register_workstation',
2257         api_name                => 'open-ils.actor.workstation.register.override',
2258         signature       => q/@see open-ils.actor.workstation.register/);
2259
2260 __PACKAGE__->register_method (
2261         method          => 'register_workstation',
2262         api_name                => 'open-ils.actor.workstation.register',
2263         signature       => q/
2264                 Registers a new workstion in the system
2265                 @param authtoken The login session key
2266                 @param name The name of the workstation id
2267                 @param owner The org unit that owns this workstation
2268                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2269                 if the name is already in use.
2270         /);
2271
2272 sub _register_workstation {
2273         my( $self, $connection, $authtoken, $name, $owner ) = @_;
2274         my( $requestor, $evt ) = $U->checkses($authtoken);
2275         return $evt if $evt;
2276         $evt = $U->check_perms($requestor->id, $owner, 'REGISTER_WORKSTATION');
2277         return $evt if $evt;
2278
2279         my $ws = $U->cstorereq(
2280                 'open-ils.cstore.direct.actor.workstation.search', { name => $name } );
2281         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS') if $ws;
2282
2283         $ws = Fieldmapper::actor::workstation->new;
2284         $ws->owning_lib($owner);
2285         $ws->name($name);
2286
2287         my $id = $U->storagereq(
2288                 'open-ils.storage.direct.actor.workstation.create', $ws );
2289         return $U->DB_UPDATE_FAILED($ws) unless $id;
2290
2291         $ws->id($id);
2292         return $ws->id();
2293 }
2294
2295 sub register_workstation {
2296         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2297
2298         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2299         return $e->event unless $e->checkauth;
2300         return $e->event unless $e->allowed('REGISTER_WORKSTATION'); # XXX rely on editor perms
2301         my $existing = $e->search_actor_workstation({name => $name});
2302
2303         if( @$existing ) {
2304                 if( $self->api_name =~ /override/o ) {
2305                         return $e->event unless $e->allowed('DELETE_WORKSTATION'); # XXX rely on editor perms
2306                         return $e->event unless $e->delete_actor_workstation($$existing[0]);
2307                 } else {
2308                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2309                 }
2310         }
2311
2312         my $ws = Fieldmapper::actor::workstation->new;
2313         $ws->owning_lib($owner);
2314         $ws->name($name);
2315         $e->create_actor_workstation($ws) or return $e->event;
2316         $e->commit;
2317         return $ws->id; # note: editor sets the id on the new object for us
2318 }
2319
2320
2321 __PACKAGE__->register_method (
2322         method          => 'fetch_patron_note',
2323         api_name                => 'open-ils.actor.note.retrieve.all',
2324         signature       => q/
2325                 Returns a list of notes for a given user
2326                 Requestor must have VIEW_USER permission if pub==false and
2327                 @param authtoken The login session key
2328                 @param args Hash of params including
2329                         patronid : the patron's id
2330                         pub : true if retrieving only public notes
2331         /
2332 );
2333
2334 sub fetch_patron_note {
2335         my( $self, $conn, $authtoken, $args ) = @_;
2336         my $patronid = $$args{patronid};
2337
2338         my($reqr, $evt) = $U->checkses($authtoken);
2339         return $evt if $evt;
2340
2341         my $patron;
2342         ($patron, $evt) = $U->fetch_user($patronid);
2343         return $evt if $evt;
2344
2345         if($$args{pub}) {
2346                 if( $patronid ne $reqr->id ) {
2347                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2348                         return $evt if $evt;
2349                 }
2350                 return $U->cstorereq(
2351                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2352                         { usr => $patronid, pub => 't' } );
2353         }
2354
2355         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2356         return $evt if $evt;
2357
2358         return $U->cstorereq(
2359                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2360 }
2361
2362 __PACKAGE__->register_method (
2363         method          => 'create_user_note',
2364         api_name                => 'open-ils.actor.note.create',
2365         signature       => q/
2366                 Creates a new note for the given user
2367                 @param authtoken The login session key
2368                 @param note The note object
2369         /
2370 );
2371 sub create_user_note {
2372         my( $self, $conn, $authtoken, $note ) = @_;
2373         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2374         return $e->die_event unless $e->checkauth;
2375
2376         my $user = $e->retrieve_actor_user($note->usr)
2377                 or return $e->die_event;
2378
2379         return $e->die_event unless 
2380                 $e->allowed('UPDATE_USER',$user->home_ou);
2381
2382         $note->creator($e->requestor->id);
2383         $e->create_actor_usr_note($note) or return $e->die_event;
2384         $e->commit;
2385         return $note->id;
2386 }
2387
2388
2389 __PACKAGE__->register_method (
2390         method          => 'delete_user_note',
2391         api_name                => 'open-ils.actor.note.delete',
2392         signature       => q/
2393                 Deletes a note for the given user
2394                 @param authtoken The login session key
2395                 @param noteid The note id
2396         /
2397 );
2398 sub delete_user_note {
2399         my( $self, $conn, $authtoken, $noteid ) = @_;
2400
2401         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2402         return $e->die_event unless $e->checkauth;
2403         my $note = $e->retrieve_actor_usr_note($noteid)
2404                 or return $e->die_event;
2405         my $user = $e->retrieve_actor_user($note->usr)
2406                 or return $e->die_event;
2407         return $e->die_event unless 
2408                 $e->allowed('UPDATE_USER', $user->home_ou);
2409         
2410         $e->delete_actor_usr_note($note) or return $e->die_event;
2411         $e->commit;
2412         return 1;
2413 }
2414
2415
2416 __PACKAGE__->register_method (
2417         method          => 'update_user_note',
2418         api_name                => 'open-ils.actor.note.update',
2419         signature       => q/
2420                 @param authtoken The login session key
2421                 @param note The note
2422         /
2423 );
2424
2425 sub update_user_note {
2426         my( $self, $conn, $auth, $note ) = @_;
2427         my $e = new_editor(authtoken=>$auth, xact=>1);
2428         return $e->event unless $e->checkauth;
2429         my $patron = $e->retrieve_actor_user($note->usr)
2430                 or return $e->event;
2431         return $e->event unless 
2432                 $e->allowed('UPDATE_USER', $patron->home_ou);
2433         $e->update_actor_user_note($note)
2434                 or return $e->event;
2435         $e->commit;
2436         return 1;
2437 }
2438
2439
2440
2441
2442 __PACKAGE__->register_method (
2443         method          => 'create_closed_date',
2444         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2445         signature       => q/
2446                 Creates a new closing entry for the given org_unit
2447                 @param authtoken The login session key
2448                 @param note The closed_date object
2449         /
2450 );
2451 sub create_closed_date {
2452         my( $self, $conn, $authtoken, $cd ) = @_;
2453
2454         my( $user, $evt ) = $U->checkses($authtoken);
2455         return $evt if $evt;
2456
2457         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2458         return $evt if $evt;
2459
2460         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2461
2462         my $id = $U->storagereq(
2463                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2464         return $U->DB_UPDATE_FAILED($cd) unless $id;
2465         return $id;
2466 }
2467
2468
2469 __PACKAGE__->register_method (
2470         method          => 'delete_closed_date',
2471         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2472         signature       => q/
2473                 Deletes a closing entry for the given org_unit
2474                 @param authtoken The login session key
2475                 @param noteid The close_date id
2476         /
2477 );
2478 sub delete_closed_date {
2479         my( $self, $conn, $authtoken, $cd ) = @_;
2480
2481         my( $user, $evt ) = $U->checkses($authtoken);
2482         return $evt if $evt;
2483
2484         my $cd_obj;
2485         ($cd_obj, $evt) = fetch_closed_date($cd);
2486         return $evt if $evt;
2487
2488         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2489         return $evt if $evt;
2490
2491         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2492
2493         my $stat = $U->storagereq(
2494                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2495         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2496         return $stat;
2497 }
2498
2499
2500 __PACKAGE__->register_method(
2501         method => 'usrname_exists',
2502         api_name        => 'open-ils.actor.username.exists',
2503         signature => q/
2504                 Returns 1 if the requested username exists, returns 0 otherwise
2505         /
2506 );
2507
2508 sub usrname_exists {
2509         my( $self, $conn, $auth, $usrname ) = @_;
2510         my $e = new_editor(authtoken=>$auth);
2511         return $e->event unless $e->checkauth;
2512         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2513         return $$a[0] if $a and @$a;
2514         return 0;
2515 }
2516
2517 __PACKAGE__->register_method(
2518         method => 'barcode_exists',
2519         api_name        => 'open-ils.actor.barcode.exists',
2520         signature => q/
2521                 Returns 1 if the requested barcode exists, returns 0 otherwise
2522         /
2523 );
2524
2525 sub barcode_exists {
2526         my( $self, $conn, $auth, $barcode ) = @_;
2527         my $e = new_editor(authtoken=>$auth);
2528         return $e->event unless $e->checkauth;
2529         my $a = $e->search_actor_card({barcode => $barcode}, {idlist=>1});
2530         return $$a[0] if $a and @$a;
2531         return 0;
2532 }
2533
2534
2535 __PACKAGE__->register_method(
2536         method => 'retrieve_net_levels',
2537         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2538 );
2539
2540 sub retrieve_net_levels {
2541         my( $self, $conn, $auth ) = @_;
2542         my $e = new_editor(authtoken=>$auth);
2543         return $e->event unless $e->checkauth;
2544         return $e->retrieve_all_config_net_access_level();
2545 }
2546
2547
2548 __PACKAGE__->register_method(
2549         method => 'fetch_org_by_shortname',
2550         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2551 );
2552 sub fetch_org_by_shortname {
2553         my( $self, $conn, $sname ) = @_;
2554         my $e = new_editor();
2555         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2556         return $e->event unless $org;
2557         return $org;
2558 }
2559
2560
2561 __PACKAGE__->register_method(
2562         method => 'session_home_lib',
2563         api_name => 'open-ils.actor.session.home_lib',
2564 );
2565
2566 sub session_home_lib {
2567         my( $self, $conn, $auth ) = @_;
2568         my $e = new_editor(authtoken=>$auth);
2569         return undef unless $e->checkauth;
2570         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2571         return $org->shortname;
2572 }
2573
2574
2575
2576 __PACKAGE__->register_method(
2577         method => 'slim_tree',
2578         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2579 );
2580 sub slim_tree {
2581         my $tree = new_editor()->search_actor_org_unit( 
2582                 [
2583                         {"parent_ou" => undef },
2584                         {
2585                                 flesh                           => 2,
2586                                 flesh_fields    => { aou =>  ['children'] },
2587                                 order_by                        => { aou => 'name'},
2588                                 select                  => { aou => ["id","shortname", "name"]},
2589                         }
2590                 ]
2591         )->[0];
2592
2593         return trim_tree($tree);
2594 }
2595
2596
2597 sub trim_tree {
2598         my $tree = shift;
2599         return undef unless $tree;
2600         my $htree = {
2601                 code => $tree->shortname,
2602                 name => $tree->name,
2603         };
2604         if( $tree->children and @{$tree->children} ) {
2605                 $htree->{children} = [];
2606                 for my $c (@{$tree->children}) {
2607                         push( @{$htree->{children}}, trim_tree($c) );
2608                 }
2609         }
2610
2611         return $htree;
2612 }
2613
2614
2615 __PACKAGE__->register_method(
2616         method  => "update_penalties",
2617         api_name        => "open-ils.actor.user.penalties.update");
2618 sub update_penalties {
2619         my( $self, $conn, $auth, $userid ) = @_;
2620         my $e = new_editor(authtoken=>$auth);
2621         return $e->event unless $e->checkauth;
2622         $U->update_patron_penalties( 
2623                 authtoken => $auth,
2624                 patronid  => $userid,
2625         );
2626         return 1;
2627 }
2628
2629
2630
2631 __PACKAGE__->register_method(
2632         method  => "user_retrieve_fleshed_by_id",
2633         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2634
2635 sub user_retrieve_fleshed_by_id {
2636         my( $self, $client, $auth, $user_id, $fields ) = @_;
2637         my $e = new_editor(authtoken => $auth);
2638         return $e->event unless $e->checkauth;
2639
2640         if( $e->requestor->id != $user_id ) {
2641                 return $e->event unless $e->allowed('VIEW_USER');
2642         }
2643
2644         $fields ||= [
2645                 "cards",
2646                 "card",
2647                 "standing_penalties",
2648                 "addresses",
2649                 "billing_address",
2650                 "mailing_address",
2651                 "stat_cat_entries" ];
2652         return new_flesh_user($user_id, $fields, $e);
2653 }
2654
2655
2656 sub new_flesh_user {
2657
2658         my $id = shift;
2659         my $fields = shift || [];
2660         my $e   = shift || new_editor(xact=>1);
2661
2662         my $user = $e->retrieve_actor_user(
2663         [
2664         $id,
2665         {
2666                 "flesh"                         => 1,
2667                 "flesh_fields" =>  { "au" => $fields }
2668         }
2669         ]
2670         ) or return $e->event;
2671
2672
2673         if( grep { $_ eq 'addresses' } @$fields ) {
2674
2675                 $user->addresses([]) unless @{$user->addresses};
2676         
2677                 if( ref $user->billing_address ) {
2678                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2679                                 push( @{$user->addresses}, $user->billing_address );
2680                         }
2681                 }
2682         
2683                 if( ref $user->mailing_address ) {
2684                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2685                                 push( @{$user->addresses}, $user->mailing_address );
2686                         }
2687                 }
2688         }
2689
2690         $e->rollback;
2691         $user->clear_passwd();
2692         return $user;
2693 }
2694
2695
2696
2697
2698 __PACKAGE__->register_method(
2699         method  => "user_retrieve_parts",
2700         api_name        => "open-ils.actor.user.retrieve.parts",);
2701
2702 sub user_retrieve_parts {
2703         my( $self, $client, $auth, $user_id, $fields ) = @_;
2704         my $e = new_editor(authtoken => $auth);
2705         return $e->event unless $e->checkauth;
2706         if( $e->requestor->id != $user_id ) {
2707                 return $e->event unless $e->allowed('VIEW_USER');
2708         }
2709         my @resp;
2710         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2711         push(@resp, $user->$_()) for(@$fields);
2712         return \@resp;
2713 }
2714
2715
2716
2717
2718
2719
2720 1;
2721