]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
updated some session handling code
[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, $auth, $org_id ) = @_;
1057         my $e = new_editor(authtoken=>$auth);
1058         return $e->event unless $e->checkauth;
1059         $org_id = $e->requestor->ws_ou unless defined $org_id;
1060
1061         return $apputils->simple_scalar_request(
1062                 "open-ils.storage",
1063                 "open-ils.storage.actor.org_unit.full_path.atomic",
1064                 $org_id );
1065 }
1066
1067
1068 __PACKAGE__->register_method(
1069         method  => "patron_adv_search",
1070         api_name        => "open-ils.actor.patron.search.advanced" );
1071 sub patron_adv_search {
1072         my( $self, $client, $auth, $search_hash, $search_limit, $search_sort, $include_inactive ) = @_;
1073         my $e = new_editor(authtoken=>$auth);
1074         return $e->event unless $e->checkauth;
1075         return $e->event unless $e->allowed('VIEW_USER');
1076         return $U->storagereq(
1077                 "open-ils.storage.actor.user.crazy_search", 
1078                 $search_hash, $search_limit, $search_sort, $include_inactive);
1079 }
1080
1081
1082
1083 sub _verify_password {
1084         my($user_session, $password) = @_;
1085         my $user_obj = $apputils->check_user_session($user_session); 
1086
1087         #grab the user with password
1088         $user_obj = $apputils->simple_scalar_request(
1089                 "open-ils.cstore", 
1090                 "open-ils.cstore.direct.actor.user.retrieve",
1091                 $user_obj->id );
1092
1093         if($user_obj->passwd eq $password) {
1094                 return 1;
1095         }
1096
1097         return 0;
1098 }
1099
1100
1101 __PACKAGE__->register_method(
1102         method  => "update_password",
1103         api_name        => "open-ils.actor.user.password.update");
1104
1105 __PACKAGE__->register_method(
1106         method  => "update_password",
1107         api_name        => "open-ils.actor.user.username.update");
1108
1109 __PACKAGE__->register_method(
1110         method  => "update_password",
1111         api_name        => "open-ils.actor.user.email.update");
1112
1113 sub update_password {
1114         my( $self, $client, $user_session, $new_value, $current_password ) = @_;
1115
1116         my $evt;
1117
1118         my $user_obj = $apputils->check_user_session($user_session); 
1119
1120         if($self->api_name =~ /password/o) {
1121
1122                 #make sure they know the current password
1123                 if(!_verify_password($user_session, md5_hex($current_password))) {
1124                         return OpenILS::Event->new('INCORRECT_PASSWORD');
1125                 }
1126
1127                 $logger->debug("update_password setting new password $new_value");
1128                 $user_obj->passwd($new_value);
1129
1130         } elsif($self->api_name =~ /username/o) {
1131                 my $users = search_username(undef, undef, $new_value); 
1132                 if( $users and $users->[0] ) {
1133                         return OpenILS::Event->new('USERNAME_EXISTS');
1134                 }
1135                 $user_obj->usrname($new_value);
1136
1137         } elsif($self->api_name =~ /email/o) {
1138                 #warn "Updating email to $new_value\n";
1139                 $user_obj->email($new_value);
1140         }
1141
1142         my $session = $apputils->start_db_session();
1143
1144         ( $user_obj, $evt ) = _update_patron($session, $user_obj, $user_obj, 1);
1145         return $evt if $evt;
1146
1147         $apputils->commit_db_session($session);
1148
1149         if($user_obj) { return 1; }
1150         return undef;
1151 }
1152
1153
1154 __PACKAGE__->register_method(
1155         method  => "check_user_perms",
1156         api_name        => "open-ils.actor.user.perm.check",
1157         notes           => <<"  NOTES");
1158         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1159         perm type, if the user does *not* have the given permission it is added
1160         to a list which is returned from the method.  If all permissions
1161         are allowed, an empty list is returned
1162         if the logged in user does not match 'user_id', then the logged in user must
1163         have VIEW_PERMISSION priveleges.
1164         NOTES
1165
1166 sub check_user_perms {
1167         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1168
1169         my( $staff, $evt ) = $apputils->checkses($login_session);
1170         return $evt if $evt;
1171
1172         if($staff->id ne $user_id) {
1173                 if( $evt = $apputils->check_perms(
1174                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1175                         return $evt;
1176                 }
1177         }
1178
1179         my @not_allowed;
1180         for my $perm (@$perm_types) {
1181                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1182                         push @not_allowed, $perm;
1183                 }
1184         }
1185
1186         return \@not_allowed
1187 }
1188
1189 __PACKAGE__->register_method(
1190         method  => "check_user_perms2",
1191         api_name        => "open-ils.actor.user.perm.check.multi_org",
1192         notes           => q/
1193                 Checks the permissions on a list of perms and orgs for a user
1194                 @param authtoken The login session key
1195                 @param user_id The id of the user to check
1196                 @param orgs The array of org ids
1197                 @param perms The array of permission names
1198                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1199                 if the logged in user does not match 'user_id', then the logged in user must
1200                 have VIEW_PERMISSION priveleges.
1201         /);
1202
1203 sub check_user_perms2 {
1204         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1205
1206         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1207                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1208         return $evt if $evt;
1209
1210         my @not_allowed;
1211         for my $org (@$orgs) {
1212                 for my $perm (@$perms) {
1213                         if($apputils->check_perms($user_id, $org, $perm)) {
1214                                 push @not_allowed, [ $org, $perm ];
1215                         }
1216                 }
1217         }
1218
1219         return \@not_allowed
1220 }
1221
1222
1223 __PACKAGE__->register_method(
1224         method => 'check_user_perms3',
1225         api_name        => 'open-ils.actor.user.perm.highest_org',
1226         notes           => q/
1227                 Returns the highest org unit id at which a user has a given permission
1228                 If the requestor does not match the target user, the requestor must have
1229                 'VIEW_PERMISSION' rights at the home org unit of the target user
1230                 @param authtoken The login session key
1231                 @param userid The id of the user in question
1232                 @param perm The permission to check
1233                 @return The org unit highest in the org tree within which the user has
1234                 the requested permission
1235         /);
1236
1237 sub check_user_perms3 {
1238         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1239
1240         my( $staff, $target, $org, $evt );
1241
1242         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1243                 $authtoken, $userid, 'VIEW_PERMISSION' );
1244         return $evt if $evt;
1245
1246         my $tree = $self->get_org_tree();
1247         return _find_highest_perm_org( $perm, $userid, $target->ws_ou, $tree );
1248 }
1249
1250
1251 sub _find_highest_perm_org {
1252         my ( $perm, $userid, $start_org, $org_tree ) = @_;
1253         my $org = $apputils->find_org($org_tree, $start_org );
1254
1255         my $lastid = undef;
1256         while( $org ) {
1257                 last if ($apputils->check_perms( $userid, $org->id, $perm )); # perm failed
1258                 $lastid = $org->id;
1259                 $org = $apputils->find_org( $org_tree, $org->parent_ou() );
1260         }
1261
1262         return $lastid;
1263 }
1264
1265 __PACKAGE__->register_method(
1266         method => 'check_user_perms4',
1267         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1268         notes           => q/
1269                 Returns the highest org unit id at which a user has a given permission
1270                 If the requestor does not match the target user, the requestor must have
1271                 'VIEW_PERMISSION' rights at the home org unit of the target user
1272                 @param authtoken The login session key
1273                 @param userid The id of the user in question
1274                 @param perms An array of perm names to check 
1275                 @return An array of orgId's  representing the org unit 
1276                 highest in the org tree within which the user has the requested permission
1277                 The arrah of orgId's has matches the order of the perms array
1278         /);
1279
1280 sub check_user_perms4 {
1281         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1282         
1283         my( $staff, $target, $org, $evt );
1284
1285         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1286                 $authtoken, $userid, 'VIEW_PERMISSION' );
1287         return $evt if $evt;
1288
1289         my @arr;
1290         return [] unless ref($perms);
1291         my $tree = $self->get_org_tree();
1292
1293         for my $p (@$perms) {
1294                 push( @arr, _find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1295         }
1296         return \@arr;
1297 }
1298
1299
1300
1301
1302 __PACKAGE__->register_method(
1303         method  => "user_fines_summary",
1304         api_name        => "open-ils.actor.user.fines.summary",
1305         notes           => <<"  NOTES");
1306         Returns a short summary of the users total open fines, excluding voided fines
1307         Params are login_session, user_id
1308         Returns a 'mous' object.
1309         NOTES
1310
1311 sub user_fines_summary {
1312         my( $self, $client, $auth, $user_id ) = @_;
1313         my $e = new_editor(authtoken=>$auth);
1314         return $e->event unless $e->checkauth;
1315         my $user = $e->retrieve_actor_user($user_id)
1316                 or return $e->event;
1317
1318         if( $user_id ne $e->requestor->id ) {
1319                 return $e->event unless 
1320                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1321         }
1322         
1323         # run this inside a transaction to prevent replication delay errors
1324         my $ses = $U->start_db_session();
1325         my $s = $ses->request(
1326                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1327         $U->rollback_db_session($ses);
1328         return $s;
1329 }
1330
1331
1332
1333
1334 __PACKAGE__->register_method(
1335         method  => "user_transactions",
1336         api_name        => "open-ils.actor.user.transactions",
1337         notes           => <<"  NOTES");
1338         Returns a list of open user transactions (mbts objects);
1339         Params are login_session, user_id
1340         Optional third parameter is the transactions type.  defaults to all
1341         NOTES
1342
1343 __PACKAGE__->register_method(
1344         method  => "user_transactions",
1345         api_name        => "open-ils.actor.user.transactions.have_charge",
1346         notes           => <<"  NOTES");
1347         Returns a list of all open user transactions (mbts objects) that have an initial charge
1348         Params are login_session, user_id
1349         Optional third parameter is the transactions type.  defaults to all
1350         NOTES
1351
1352 __PACKAGE__->register_method(
1353         method  => "user_transactions",
1354         api_name        => "open-ils.actor.user.transactions.have_balance",
1355         notes           => <<"  NOTES");
1356         Returns a list of all open user transactions (mbts objects) that have a balance
1357         Params are login_session, user_id
1358         Optional third parameter is the transactions type.  defaults to all
1359         NOTES
1360
1361 __PACKAGE__->register_method(
1362         method  => "user_transactions",
1363         api_name        => "open-ils.actor.user.transactions.fleshed",
1364         notes           => <<"  NOTES");
1365         Returns an object/hash of transaction, circ, title where transaction = an open 
1366         user transactions (mbts objects), circ is the attached circluation, and title
1367         is the title the circ points to
1368         Params are login_session, user_id
1369         Optional third parameter is the transactions type.  defaults to all
1370         NOTES
1371
1372 __PACKAGE__->register_method(
1373         method  => "user_transactions",
1374         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1375         notes           => <<"  NOTES");
1376         Returns an object/hash of transaction, circ, title where transaction = an open 
1377         user transactions that has an initial charge (mbts objects), circ is the 
1378         attached circluation, and title is the title the circ points to
1379         Params are login_session, user_id
1380         Optional third parameter is the transactions type.  defaults to all
1381         NOTES
1382
1383 __PACKAGE__->register_method(
1384         method  => "user_transactions",
1385         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1386         notes           => <<"  NOTES");
1387         Returns an object/hash of transaction, circ, title where transaction = an open 
1388         user transaction that has a balance (mbts objects), circ is the attached 
1389         circluation, and title is the title the circ points to
1390         Params are login_session, user_id
1391         Optional third parameter is the transaction type.  defaults to all
1392         NOTES
1393
1394 __PACKAGE__->register_method(
1395         method  => "user_transactions",
1396         api_name        => "open-ils.actor.user.transactions.count",
1397         notes           => <<"  NOTES");
1398         Returns an object/hash of transaction, circ, title where transaction = an open 
1399         user transactions (mbts objects), circ is the attached circluation, and title
1400         is the title the circ points to
1401         Params are login_session, user_id
1402         Optional third parameter is the transactions type.  defaults to all
1403         NOTES
1404
1405 __PACKAGE__->register_method(
1406         method  => "user_transactions",
1407         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1408         notes           => <<"  NOTES");
1409         Returns an object/hash of transaction, circ, title where transaction = an open 
1410         user transactions that has an initial charge (mbts objects), circ is the 
1411         attached circluation, and title is the title the circ points to
1412         Params are login_session, user_id
1413         Optional third parameter is the transactions type.  defaults to all
1414         NOTES
1415
1416 __PACKAGE__->register_method(
1417         method  => "user_transactions",
1418         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1419         notes           => <<"  NOTES");
1420         Returns an object/hash of transaction, circ, title where transaction = an open 
1421         user transaction that has a balance (mbts objects), circ is the attached 
1422         circluation, and title is the title the circ points to
1423         Params are login_session, user_id
1424         Optional third parameter is the transaction type.  defaults to all
1425         NOTES
1426
1427 __PACKAGE__->register_method(
1428         method  => "user_transactions",
1429         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1430         notes           => <<"  NOTES");
1431         Returns an object/hash of transaction, circ, title where transaction = an open 
1432         user transaction that has a balance (mbts objects), circ is the attached 
1433         circluation, and title is the title the circ points to
1434         Params are login_session, user_id
1435         Optional third parameter is the transaction type.  defaults to all
1436         NOTES
1437
1438
1439
1440 sub user_transactions {
1441         my( $self, $client, $login_session, $user_id, $type ) = @_;
1442
1443         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1444                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1445         return $evt if $evt;
1446
1447         my $api = $self->api_name();
1448         my $trans;
1449         my @xact;
1450
1451         if(defined($type)) { @xact = (xact_type =>  $type); 
1452
1453         } else { @xact = (); }
1454
1455         ($trans) = $self
1456                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1457                 ->run($login_session => $user_id => $type);
1458
1459         if($api =~ /have_charge/o) {
1460
1461                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1462
1463         } elsif($api =~ /have_balance/o) {
1464
1465                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1466         } else {
1467
1468                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1469
1470         }
1471
1472         if($api =~ /total/o) { 
1473                 my $total = 0.0;
1474                 for my $t (@$trans) {
1475                         $total += $t->balance_owed;
1476                 }
1477
1478                 $logger->debug("Total balance owed by user $user_id: $total");
1479                 return $total;
1480         }
1481
1482         if($api =~ /count/o) { return scalar @$trans; }
1483         if($api !~ /fleshed/o) { return $trans; }
1484
1485         my @resp;
1486         for my $t (@$trans) {
1487                         
1488                 if( $t->xact_type ne 'circulation' ) {
1489                         push @resp, {transaction => $t};
1490                         next;
1491                 }
1492
1493                 my $circ = $apputils->simple_scalar_request(
1494                                 "open-ils.cstore",
1495                                 "open-ils.cstore.direct.action.circulation.retrieve",
1496                                 $t->id );
1497
1498                 next unless $circ;
1499
1500                 my $title = $apputils->simple_scalar_request(
1501                         "open-ils.storage", 
1502                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1503                         $circ->target_copy );
1504
1505                 next unless $title;
1506
1507                 my $u = OpenILS::Utils::ModsParser->new();
1508                 $u->start_mods_batch($title->marc());
1509                 my $mods = $u->finish_mods_batch();
1510                 $mods->doc_id($title->id) if $mods;
1511
1512                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1513
1514         }
1515
1516         return \@resp; 
1517
1518
1519
1520 __PACKAGE__->register_method(
1521         method  => "user_transaction_retrieve",
1522         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1523         argc            => 1,
1524         notes           => <<"  NOTES");
1525         Returns a fleshedtransaction record
1526         NOTES
1527 __PACKAGE__->register_method(
1528         method  => "user_transaction_retrieve",
1529         api_name        => "open-ils.actor.user.transaction.retrieve",
1530         argc            => 1,
1531         notes           => <<"  NOTES");
1532         Returns a transaction record
1533         NOTES
1534 sub user_transaction_retrieve {
1535         my( $self, $client, $login_session, $bill_id ) = @_;
1536
1537         # XXX I think I'm deprecated... make sure
1538
1539         my $trans = $apputils->simple_scalar_request( 
1540                 "open-ils.cstore",
1541                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1542                 $bill_id
1543         );
1544
1545         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1546                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1547         return $evt if $evt;
1548         
1549         my $api = $self->api_name();
1550         if($api !~ /fleshed/o) { return $trans; }
1551
1552         if( $trans->xact_type ne 'circulation' ) {
1553                 $logger->debug("Returning non-circ transaction");
1554                 return {transaction => $trans};
1555         }
1556
1557         my $circ = $apputils->simple_scalar_request(
1558                         "open-ils.cstore",
1559                         "open-ils..direct.action.circulation.retrieve",
1560                         $trans->id );
1561
1562         return {transaction => $trans} unless $circ;
1563         $logger->debug("Found the circ transaction");
1564
1565         my $title = $apputils->simple_scalar_request(
1566                 "open-ils.storage", 
1567                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1568                 $circ->target_copy );
1569
1570         return {transaction => $trans, circ => $circ } unless $title;
1571         $logger->debug("Found the circ title");
1572
1573         my $mods;
1574         try {
1575                 my $u = OpenILS::Utils::ModsParser->new();
1576                 $u->start_mods_batch($title->marc());
1577                 $mods = $u->finish_mods_batch();
1578         } otherwise {
1579                 if ($title->id == OILS_PRECAT_RECORD) {
1580                         my $copy = $apputils->simple_scalar_request(
1581                                 "open-ils.cstore",
1582                                 "open-ils.cstore.direct.asset.copy.retrieve",
1583                                 $circ->target_copy );
1584
1585                         $mods = new Fieldmapper::metabib::virtual_record;
1586                         $mods->doc_id(OILS_PRECAT_RECORD);
1587                         $mods->title($copy->dummy_title);
1588                         $mods->author($copy->dummy_author);
1589                 }
1590         };
1591
1592         $logger->debug("MODSized the circ title");
1593
1594         return {transaction => $trans, circ => $circ, record => $mods };
1595 }
1596
1597
1598 __PACKAGE__->register_method(
1599         method  => "hold_request_count",
1600         api_name        => "open-ils.actor.user.hold_requests.count",
1601         argc            => 1,
1602         notes           => <<"  NOTES");
1603         Returns hold ready/total counts
1604         NOTES
1605 sub hold_request_count {
1606         my( $self, $client, $login_session, $userid ) = @_;
1607
1608         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1609                 $login_session, $userid, 'VIEW_HOLD' );
1610         return $evt if $evt;
1611         
1612
1613         my $holds = $apputils->simple_scalar_request(
1614                         "open-ils.cstore",
1615                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1616                         { 
1617                                 usr => $userid,
1618                                 fulfillment_time => {"=" => undef },
1619                                 cancel_time => undef,
1620                         }
1621         );
1622
1623         my @ready;
1624         for my $h (@$holds) {
1625                 next unless $h->capture_time and $h->current_copy;
1626
1627                 my $copy = $apputils->simple_scalar_request(
1628                         "open-ils.cstore",
1629                         "open-ils.cstore.direct.asset.copy.retrieve",
1630                         $h->current_copy
1631                 );
1632
1633                 if ($copy and $copy->status == 8) {
1634                         push @ready, $h;
1635                 }
1636         }
1637
1638         return { total => scalar(@$holds), ready => scalar(@ready) };
1639 }
1640
1641
1642 __PACKAGE__->register_method(
1643         method  => "checkedout_count",
1644         api_name        => "open-ils.actor.user.checked_out.count__",
1645         argc            => 1,
1646         notes           => <<"  NOTES");
1647         Returns a transaction record
1648         NOTES
1649
1650 # XXX Deprecate Me
1651 sub checkedout_count {
1652         my( $self, $client, $login_session, $userid ) = @_;
1653
1654         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1655                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1656         return $evt if $evt;
1657         
1658         my $circs = $apputils->simple_scalar_request(
1659                         "open-ils.cstore",
1660                         "open-ils.cstore.direct.action.circulation.search.atomic",
1661                         { usr => $userid, stop_fines => undef }
1662                         #{ usr => $userid, checkin_time => {"=" => undef } }
1663         );
1664
1665         my $parser = DateTime::Format::ISO8601->new;
1666
1667         my (@out,@overdue);
1668         for my $c (@$circs) {
1669                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1670                 my $due = $due_dt->epoch;
1671
1672                 if ($due < DateTime->today->epoch) {
1673                         push @overdue, $c;
1674                 }
1675         }
1676
1677         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1678 }
1679
1680
1681 __PACKAGE__->register_method(
1682         method          => "checked_out",
1683         api_name                => "open-ils.actor.user.checked_out",
1684         argc                    => 2,
1685         signature       => q/
1686                 Returns a structure of circulations objects sorted by
1687                 out, overdue, lost, claims_returned, long_overdue.
1688                 A list of IDs are returned of each type.
1689                 lost, long_overdue, and claims_returned circ will not
1690                 be "finished" (there is an outstanding balance or some 
1691                 other pending action on the circ). 
1692
1693                 The .count method also includes a 'total' field which 
1694                 sums all "open" circs
1695         /
1696 );
1697
1698 __PACKAGE__->register_method(
1699         method          => "checked_out",
1700         api_name                => "open-ils.actor.user.checked_out.count",
1701         argc                    => 2,
1702         signature       => q/@see open-ils.actor.user.checked_out/
1703 );
1704
1705 sub checked_out {
1706         my( $self, $conn, $auth, $userid ) = @_;
1707
1708         my $e = new_editor(authtoken=>$auth);
1709         return $e->event unless $e->checkauth;
1710
1711         if( $userid ne $e->requestor->id ) {
1712                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1713         }
1714
1715         my $count = $self->api_name =~ /count/;
1716         return _checked_out( $count, $e, $userid );
1717 }
1718
1719 sub _checked_out {
1720         my( $iscount, $e, $userid ) = @_;
1721
1722
1723         my $meth = 'open-ils.storage.actor.user.checked_out';
1724         $meth = "$meth.count" if $iscount;
1725         return $U->storagereq($meth, $userid);
1726
1727 # XXX Old code - moved to storage
1728 #------------------------------------------------------------------------------
1729 #------------------------------------------------------------------------------
1730         my $circs = $e->search_action_circulation( 
1731                 { usr => $userid, checkin_time => undef });
1732
1733         my $parser = DateTime::Format::ISO8601->new;
1734
1735         # split the circs up into overdue and not-overdue circs
1736         my (@out,@overdue);
1737         for my $c (@$circs) {
1738                 if( $c->due_date ) {
1739                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1740                         my $due = $due_dt->epoch;
1741                         if ($due < DateTime->today->epoch) {
1742                                 push @overdue, $c;
1743                         } else {
1744                                 push @out, $c;
1745                         }
1746                 } else {
1747                         push @out, $c;
1748                 }
1749         }
1750
1751         my( @open, @od, @lost, @cr, @lo );
1752
1753         while (my $c = shift(@out)) {
1754                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1755                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1756                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1757                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1758         }
1759
1760         while (my $c = shift(@overdue)) {
1761                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1762                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1763                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1764                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1765         }
1766
1767         if( $iscount ) {
1768                 return {
1769                         total           => @open + @od + @lost + @cr + @lo,
1770                         out             => scalar(@open),
1771                         overdue => scalar(@od),
1772                         lost            => scalar(@lost),
1773                         claims_returned => scalar(@cr),
1774                         long_overdue            => scalar(@lo)
1775                 };
1776         }
1777
1778         return {
1779                 out             => \@open,
1780                 overdue => \@od,
1781                 lost            => \@lost,
1782                 claims_returned => \@cr,
1783                 long_overdue            => \@lo
1784         };
1785 }
1786
1787
1788 sub _checked_out_WHAT {
1789         my( $iscount, $e, $userid ) = @_;
1790
1791         my $circs = $e->search_action_circulation( 
1792                 { usr => $userid, stop_fines => undef });
1793
1794         my $mcircs = $e->search_action_circulation( 
1795                 { 
1796                         usr => $userid, 
1797                         checkin_time => undef, 
1798                         xact_finish => undef, 
1799                 });
1800
1801         
1802         push( @$circs, @$mcircs );
1803
1804         my $parser = DateTime::Format::ISO8601->new;
1805
1806         # split the circs up into overdue and not-overdue circs
1807         my (@out,@overdue);
1808         for my $c (@$circs) {
1809                 if( $c->due_date ) {
1810                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1811                         my $due = $due_dt->epoch;
1812                         if ($due < DateTime->today->epoch) {
1813                                 push @overdue, $c->id;
1814                         } else {
1815                                 push @out, $c->id;
1816                         }
1817                 } else {
1818                         push @out, $c->id;
1819                 }
1820         }
1821
1822         # grab all of the lost, claims-returned, and longoverdue circs
1823         #my $open = $e->search_action_circulation(
1824         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1825
1826
1827         # these items have stop_fines, but no xact_finish, so money
1828         # is owed on them and they have not been checked in
1829         my $open = $e->search_action_circulation(
1830                 {
1831                         usr                             => $userid, 
1832                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1833                         xact_finish             => undef,
1834                         checkin_time    => undef,
1835                 }
1836         );
1837
1838
1839         my( @lost, @cr, @lo );
1840         for my $c (@$open) {
1841                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1842                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1843                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1844         }
1845
1846
1847         if( $iscount ) {
1848                 return {
1849                         total           => @$circs + @lost + @cr + @lo,
1850                         out             => scalar(@out),
1851                         overdue => scalar(@overdue),
1852                         lost            => scalar(@lost),
1853                         claims_returned => scalar(@cr),
1854                         long_overdue            => scalar(@lo)
1855                 };
1856         }
1857
1858         return {
1859                 out             => \@out,
1860                 overdue => \@overdue,
1861                 lost            => \@lost,
1862                 claims_returned => \@cr,
1863                 long_overdue            => \@lo
1864         };
1865 }
1866
1867
1868
1869 __PACKAGE__->register_method(
1870         method          => "checked_in_with_fines",
1871         api_name                => "open-ils.actor.user.checked_in_with_fines",
1872         argc                    => 2,
1873         signature       => q/@see open-ils.actor.user.checked_out/
1874 );
1875 sub checked_in_with_fines {
1876         my( $self, $conn, $auth, $userid ) = @_;
1877
1878         my $e = new_editor(authtoken=>$auth);
1879         return $e->event unless $e->checkauth;
1880
1881         if( $userid ne $e->requestor->id ) {
1882                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1883         }
1884
1885         # money is owed on these items and they are checked in
1886         my $open = $e->search_action_circulation(
1887                 {
1888                         usr                             => $userid, 
1889                         xact_finish             => undef,
1890                         checkin_time    => { "!=" => undef },
1891                 }
1892         );
1893
1894
1895         my( @lost, @cr, @lo );
1896         for my $c (@$open) {
1897                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1898                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1899                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1900         }
1901
1902         return {
1903                 lost            => \@lost,
1904                 claims_returned => \@cr,
1905                 long_overdue            => \@lo
1906         };
1907 }
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917 __PACKAGE__->register_method(
1918         method  => "user_transaction_history",
1919         api_name        => "open-ils.actor.user.transactions.history",
1920         argc            => 1,
1921         notes           => <<"  NOTES");
1922         Returns a list of billable transaction ids for a user, optionally by type
1923         NOTES
1924 __PACKAGE__->register_method(
1925         method  => "user_transaction_history",
1926         api_name        => "open-ils.actor.user.transactions.history.have_charge",
1927         argc            => 1,
1928         notes           => <<"  NOTES");
1929         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
1930         NOTES
1931 __PACKAGE__->register_method(
1932         method  => "user_transaction_history",
1933         api_name        => "open-ils.actor.user.transactions.history.have_balance",
1934         argc            => 1,
1935         notes           => <<"  NOTES");
1936         Returns a list of billable transaction ids for a user that have a balance, optionally by type
1937         NOTES
1938 __PACKAGE__->register_method(
1939         method  => "user_transaction_history",
1940         api_name        => "open-ils.actor.user.transactions.history.still_open",
1941         argc            => 1,
1942         notes           => <<"  NOTES");
1943         Returns a list of billable transaction ids for a user that are not finished
1944         NOTES
1945 __PACKAGE__->register_method(
1946         method  => "user_transaction_history",
1947         api_name        => "open-ils.actor.user.transactions.history.have_bill",
1948         argc            => 1,
1949         notes           => <<"  NOTES");
1950         Returns a list of billable transaction ids for a user that has billings
1951         NOTES
1952
1953
1954
1955 =head old
1956 sub _user_transaction_history {
1957         my( $self, $client, $login_session, $user_id, $type ) = @_;
1958
1959         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1960                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1961         return $evt if $evt;
1962
1963         my $api = $self->api_name();
1964         my @xact;
1965         my @charge;
1966         my @balance;
1967
1968         @xact = (xact_type =>  $type) if(defined($type));
1969         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
1970         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
1971
1972         $logger->debug("searching for transaction history: @xact : @balance, @charge");
1973
1974         my $trans = $apputils->simple_scalar_request( 
1975                 "open-ils.cstore",
1976                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
1977                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
1978
1979         return [ map { $_->id } @$trans ];
1980 }
1981 =cut
1982
1983 =head SEE APPUTILS.PM
1984 sub _make_mbts {
1985         my @xacts = @_;
1986
1987         my @mbts;
1988         for my $x (@xacts) {
1989                 my $s = new Fieldmapper::money::billable_transaction_summary;
1990                 $s->id( $x->id );
1991                 $s->usr( $x->usr );
1992                 $s->xact_start( $x->xact_start );
1993                 $s->xact_finish( $x->xact_finish );
1994
1995                 my $to = 0;
1996                 my $lb = undef;
1997                 for my $b (@{ $x->billings }) {
1998                         next if ($U->is_true($b->voided));
1999                         $to += ($b->amount * 100);
2000                         $lb ||= $b->billing_ts;
2001                         if ($b->billing_ts ge $lb) {
2002                                 $lb = $b->billing_ts;
2003                                 $s->last_billing_note($b->note);
2004                                 $s->last_billing_ts($b->billing_ts);
2005                                 $s->last_billing_type($b->billing_type);
2006                         }
2007                 }
2008
2009                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2010
2011                 my $tp = 0;
2012                 my $lp = undef;
2013                 for my $p (@{ $x->payments }) {
2014                         next if ($U->is_true($p->voided));
2015                         $tp += ($p->amount * 100);
2016                         $lp ||= $p->payment_ts;
2017                         if ($p->payment_ts ge $lp) {
2018                                 $lp = $p->payment_ts;
2019                                 $s->last_payment_note($p->note);
2020                                 $s->last_payment_ts($p->payment_ts);
2021                                 $s->last_payment_type($p->payment_type);
2022                         }
2023                 }
2024                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2025
2026                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2027
2028                 $s->xact_type( 'grocery' ) if ($x->grocery);
2029                 $s->xact_type( 'circulation' ) if ($x->circulation);
2030
2031                 push @mbts, $s;
2032         }
2033
2034         return @mbts;
2035 }
2036 =cut
2037
2038 sub user_transaction_history {
2039         my( $self, $conn, $auth, $userid, $type ) = @_;
2040
2041         # run inside of a transaction to prevent replication delays
2042         my $e = new_editor(xact=>1, authtoken=>$auth);
2043         return $e->die_event unless $e->checkauth;
2044
2045         if( $e->requestor->id ne $userid ) {
2046                 return $e->die_event 
2047                         unless $e->allowed('VIEW_USER_TRANSACTIONS');
2048         }
2049
2050         my $api = $self->api_name;
2051         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2052
2053         my @xacts = @{ $e->search_money_billable_transaction(
2054                 [       { usr => $userid, @xact_finish },
2055                         { flesh => 1,
2056                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2057                           order_by => { mbt => 'xact_start DESC' },
2058                         }
2059                 ]
2060         ) };
2061
2062         $e->rollback;
2063
2064         #my @mbts = _make_mbts( @xacts );
2065         my @mbts = $U->make_mbts( @xacts );
2066
2067         if(defined($type)) {
2068                 @mbts = grep { $_->xact_type eq $type } @mbts;
2069         }
2070
2071         if($api =~ /have_balance/o) {
2072                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2073         }
2074
2075         if($api =~ /have_charge/o) {
2076                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2077         }
2078
2079         if($api =~ /have_bill/o) {
2080                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2081         }
2082
2083         return [@mbts];
2084 }
2085
2086
2087
2088 __PACKAGE__->register_method(
2089         method  => "user_perms",
2090         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2091         argc            => 1,
2092         notes           => <<"  NOTES");
2093         Returns a list of permissions
2094         NOTES
2095 sub user_perms {
2096         my( $self, $client, $authtoken, $user ) = @_;
2097
2098         my( $staff, $evt ) = $apputils->checkses($authtoken);
2099         return $evt if $evt;
2100
2101         $user ||= $staff->id;
2102
2103         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2104                 return $evt;
2105         }
2106
2107         return $apputils->simple_scalar_request(
2108                 "open-ils.storage",
2109                 "open-ils.storage.permission.user_perms.atomic",
2110                 $user);
2111 }
2112
2113 __PACKAGE__->register_method(
2114         method  => "retrieve_perms",
2115         api_name        => "open-ils.actor.permissions.retrieve",
2116         notes           => <<"  NOTES");
2117         Returns a list of permissions
2118         NOTES
2119 sub retrieve_perms {
2120         my( $self, $client ) = @_;
2121         return $apputils->simple_scalar_request(
2122                 "open-ils.cstore",
2123                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2124                 { id => { '!=' => undef } }
2125         );
2126 }
2127
2128 __PACKAGE__->register_method(
2129         method  => "retrieve_groups",
2130         api_name        => "open-ils.actor.groups.retrieve",
2131         notes           => <<"  NOTES");
2132         Returns a list of user groupss
2133         NOTES
2134 sub retrieve_groups {
2135         my( $self, $client ) = @_;
2136         return new_editor()->retrieve_all_permission_grp_tree();
2137 }
2138
2139 __PACKAGE__->register_method(
2140         method  => "retrieve_org_address",
2141         api_name        => "open-ils.actor.org_unit.address.retrieve",
2142         notes           => <<'  NOTES');
2143         Returns an org_unit address by ID
2144         @param An org_address ID
2145         NOTES
2146 sub retrieve_org_address {
2147         my( $self, $client, $id ) = @_;
2148         return $apputils->simple_scalar_request(
2149                 "open-ils.cstore",
2150                 "open-ils.cstore.direct.actor.org_address.retrieve",
2151                 $id
2152         );
2153 }
2154
2155 __PACKAGE__->register_method(
2156         method  => "retrieve_groups_tree",
2157         api_name        => "open-ils.actor.groups.tree.retrieve",
2158         notes           => <<"  NOTES");
2159         Returns a list of user groups
2160         NOTES
2161 sub retrieve_groups_tree {
2162         my( $self, $client ) = @_;
2163         return new_editor()->search_permission_grp_tree(
2164                 [
2165                         { parent => undef},
2166                         {       
2167                                 flesh                           => 10, 
2168                                 flesh_fields    => { pgt => ["children"] }, 
2169                                 order_by                        => { pgt => 'name'}
2170                         }
2171                 ]
2172         )->[0];
2173 }
2174
2175
2176 # turns an org list into an org tree
2177 =head old code
2178 sub build_group_tree {
2179
2180         my( $self, $grplist) = @_;
2181
2182         return $grplist unless ( 
2183                         ref($grplist) and @$grplist > 1 );
2184
2185         my @list = sort { $a->name cmp $b->name } @$grplist;
2186
2187         my $root;
2188         for my $grp (@list) {
2189
2190                 if ($grp and !defined($grp->parent)) {
2191                         $root = $grp;
2192                         next;
2193                 }
2194                 my ($parent) = grep { $_->id == $grp->parent} @list;
2195
2196                 $parent->children([]) unless defined($parent->children); 
2197                 push( @{$parent->children}, $grp );
2198         }
2199
2200         return $root;
2201 }
2202 =cut
2203
2204
2205 __PACKAGE__->register_method(
2206         method  => "add_user_to_groups",
2207         api_name        => "open-ils.actor.user.set_groups",
2208         notes           => <<"  NOTES");
2209         Adds a user to one or more permission groups
2210         NOTES
2211
2212 sub add_user_to_groups {
2213         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2214
2215         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2216                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2217         return $evt if $evt;
2218
2219         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2220                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2221         return $evt if $evt;
2222
2223         $apputils->simplereq(
2224                 'open-ils.storage',
2225                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2226                 
2227         for my $group (@$groups) {
2228                 my $link = Fieldmapper::permission::usr_grp_map->new;
2229                 $link->grp($group);
2230                 $link->usr($userid);
2231
2232                 my $id = $apputils->simplereq(
2233                         'open-ils.storage',
2234                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2235         }
2236
2237         return 1;
2238 }
2239
2240 __PACKAGE__->register_method(
2241         method  => "get_user_perm_groups",
2242         api_name        => "open-ils.actor.user.get_groups",
2243         notes           => <<"  NOTES");
2244         Retrieve a user's permission groups.
2245         NOTES
2246
2247
2248 sub get_user_perm_groups {
2249         my( $self, $client, $authtoken, $userid ) = @_;
2250
2251         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2252                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2253         return $evt if $evt;
2254
2255         return $apputils->simplereq(
2256                 'open-ils.cstore',
2257                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2258 }       
2259
2260
2261
2262 __PACKAGE__->register_method (
2263         method          => 'register_workstation',
2264         api_name                => 'open-ils.actor.workstation.register.override',
2265         signature       => q/@see open-ils.actor.workstation.register/);
2266
2267 __PACKAGE__->register_method (
2268         method          => 'register_workstation',
2269         api_name                => 'open-ils.actor.workstation.register',
2270         signature       => q/
2271                 Registers a new workstion in the system
2272                 @param authtoken The login session key
2273                 @param name The name of the workstation id
2274                 @param owner The org unit that owns this workstation
2275                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2276                 if the name is already in use.
2277         /);
2278
2279 sub _register_workstation {
2280         my( $self, $connection, $authtoken, $name, $owner ) = @_;
2281         my( $requestor, $evt ) = $U->checkses($authtoken);
2282         return $evt if $evt;
2283         $evt = $U->check_perms($requestor->id, $owner, 'REGISTER_WORKSTATION');
2284         return $evt if $evt;
2285
2286         my $ws = $U->cstorereq(
2287                 'open-ils.cstore.direct.actor.workstation.search', { name => $name } );
2288         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS') if $ws;
2289
2290         $ws = Fieldmapper::actor::workstation->new;
2291         $ws->owning_lib($owner);
2292         $ws->name($name);
2293
2294         my $id = $U->storagereq(
2295                 'open-ils.storage.direct.actor.workstation.create', $ws );
2296         return $U->DB_UPDATE_FAILED($ws) unless $id;
2297
2298         $ws->id($id);
2299         return $ws->id();
2300 }
2301
2302 sub register_workstation {
2303         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2304
2305         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2306         return $e->event unless $e->checkauth;
2307         return $e->event unless $e->allowed('REGISTER_WORKSTATION'); # XXX rely on editor perms
2308         my $existing = $e->search_actor_workstation({name => $name});
2309
2310         if( @$existing ) {
2311                 if( $self->api_name =~ /override/o ) {
2312                         return $e->event unless $e->allowed('DELETE_WORKSTATION'); # XXX rely on editor perms
2313                         return $e->event unless $e->delete_actor_workstation($$existing[0]);
2314                 } else {
2315                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2316                 }
2317         }
2318
2319         my $ws = Fieldmapper::actor::workstation->new;
2320         $ws->owning_lib($owner);
2321         $ws->name($name);
2322         $e->create_actor_workstation($ws) or return $e->event;
2323         $e->commit;
2324         return $ws->id; # note: editor sets the id on the new object for us
2325 }
2326
2327
2328 __PACKAGE__->register_method (
2329         method          => 'fetch_patron_note',
2330         api_name                => 'open-ils.actor.note.retrieve.all',
2331         signature       => q/
2332                 Returns a list of notes for a given user
2333                 Requestor must have VIEW_USER permission if pub==false and
2334                 @param authtoken The login session key
2335                 @param args Hash of params including
2336                         patronid : the patron's id
2337                         pub : true if retrieving only public notes
2338         /
2339 );
2340
2341 sub fetch_patron_note {
2342         my( $self, $conn, $authtoken, $args ) = @_;
2343         my $patronid = $$args{patronid};
2344
2345         my($reqr, $evt) = $U->checkses($authtoken);
2346         return $evt if $evt;
2347
2348         my $patron;
2349         ($patron, $evt) = $U->fetch_user($patronid);
2350         return $evt if $evt;
2351
2352         if($$args{pub}) {
2353                 if( $patronid ne $reqr->id ) {
2354                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2355                         return $evt if $evt;
2356                 }
2357                 return $U->cstorereq(
2358                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2359                         { usr => $patronid, pub => 't' } );
2360         }
2361
2362         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2363         return $evt if $evt;
2364
2365         return $U->cstorereq(
2366                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2367 }
2368
2369 __PACKAGE__->register_method (
2370         method          => 'create_user_note',
2371         api_name                => 'open-ils.actor.note.create',
2372         signature       => q/
2373                 Creates a new note for the given user
2374                 @param authtoken The login session key
2375                 @param note The note object
2376         /
2377 );
2378 sub create_user_note {
2379         my( $self, $conn, $authtoken, $note ) = @_;
2380         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2381         return $e->die_event unless $e->checkauth;
2382
2383         my $user = $e->retrieve_actor_user($note->usr)
2384                 or return $e->die_event;
2385
2386         return $e->die_event unless 
2387                 $e->allowed('UPDATE_USER',$user->home_ou);
2388
2389         $note->creator($e->requestor->id);
2390         $e->create_actor_usr_note($note) or return $e->die_event;
2391         $e->commit;
2392         return $note->id;
2393 }
2394
2395
2396 __PACKAGE__->register_method (
2397         method          => 'delete_user_note',
2398         api_name                => 'open-ils.actor.note.delete',
2399         signature       => q/
2400                 Deletes a note for the given user
2401                 @param authtoken The login session key
2402                 @param noteid The note id
2403         /
2404 );
2405 sub delete_user_note {
2406         my( $self, $conn, $authtoken, $noteid ) = @_;
2407
2408         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2409         return $e->die_event unless $e->checkauth;
2410         my $note = $e->retrieve_actor_usr_note($noteid)
2411                 or return $e->die_event;
2412         my $user = $e->retrieve_actor_user($note->usr)
2413                 or return $e->die_event;
2414         return $e->die_event unless 
2415                 $e->allowed('UPDATE_USER', $user->home_ou);
2416         
2417         $e->delete_actor_usr_note($note) or return $e->die_event;
2418         $e->commit;
2419         return 1;
2420 }
2421
2422
2423 __PACKAGE__->register_method (
2424         method          => 'update_user_note',
2425         api_name                => 'open-ils.actor.note.update',
2426         signature       => q/
2427                 @param authtoken The login session key
2428                 @param note The note
2429         /
2430 );
2431
2432 sub update_user_note {
2433         my( $self, $conn, $auth, $note ) = @_;
2434         my $e = new_editor(authtoken=>$auth, xact=>1);
2435         return $e->event unless $e->checkauth;
2436         my $patron = $e->retrieve_actor_user($note->usr)
2437                 or return $e->event;
2438         return $e->event unless 
2439                 $e->allowed('UPDATE_USER', $patron->home_ou);
2440         $e->update_actor_user_note($note)
2441                 or return $e->event;
2442         $e->commit;
2443         return 1;
2444 }
2445
2446
2447
2448
2449 __PACKAGE__->register_method (
2450         method          => 'create_closed_date',
2451         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2452         signature       => q/
2453                 Creates a new closing entry for the given org_unit
2454                 @param authtoken The login session key
2455                 @param note The closed_date object
2456         /
2457 );
2458 sub create_closed_date {
2459         my( $self, $conn, $authtoken, $cd ) = @_;
2460
2461         my( $user, $evt ) = $U->checkses($authtoken);
2462         return $evt if $evt;
2463
2464         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2465         return $evt if $evt;
2466
2467         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2468
2469         my $id = $U->storagereq(
2470                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2471         return $U->DB_UPDATE_FAILED($cd) unless $id;
2472         return $id;
2473 }
2474
2475
2476 __PACKAGE__->register_method (
2477         method          => 'delete_closed_date',
2478         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2479         signature       => q/
2480                 Deletes a closing entry for the given org_unit
2481                 @param authtoken The login session key
2482                 @param noteid The close_date id
2483         /
2484 );
2485 sub delete_closed_date {
2486         my( $self, $conn, $authtoken, $cd ) = @_;
2487
2488         my( $user, $evt ) = $U->checkses($authtoken);
2489         return $evt if $evt;
2490
2491         my $cd_obj;
2492         ($cd_obj, $evt) = fetch_closed_date($cd);
2493         return $evt if $evt;
2494
2495         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2496         return $evt if $evt;
2497
2498         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2499
2500         my $stat = $U->storagereq(
2501                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2502         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2503         return $stat;
2504 }
2505
2506
2507 __PACKAGE__->register_method(
2508         method => 'usrname_exists',
2509         api_name        => 'open-ils.actor.username.exists',
2510         signature => q/
2511                 Returns 1 if the requested username exists, returns 0 otherwise
2512         /
2513 );
2514
2515 sub usrname_exists {
2516         my( $self, $conn, $auth, $usrname ) = @_;
2517         my $e = new_editor(authtoken=>$auth);
2518         return $e->event unless $e->checkauth;
2519         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2520         return $$a[0] if $a and @$a;
2521         return 0;
2522 }
2523
2524 __PACKAGE__->register_method(
2525         method => 'barcode_exists',
2526         api_name        => 'open-ils.actor.barcode.exists',
2527         signature => q/
2528                 Returns 1 if the requested barcode exists, returns 0 otherwise
2529         /
2530 );
2531
2532 sub barcode_exists {
2533         my( $self, $conn, $auth, $barcode ) = @_;
2534         my $e = new_editor(authtoken=>$auth);
2535         return $e->event unless $e->checkauth;
2536         my $a = $e->search_actor_card({barcode => $barcode}, {idlist=>1});
2537         return $$a[0] if $a and @$a;
2538         return 0;
2539 }
2540
2541
2542 __PACKAGE__->register_method(
2543         method => 'retrieve_net_levels',
2544         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2545 );
2546
2547 sub retrieve_net_levels {
2548         my( $self, $conn, $auth ) = @_;
2549         my $e = new_editor(authtoken=>$auth);
2550         return $e->event unless $e->checkauth;
2551         return $e->retrieve_all_config_net_access_level();
2552 }
2553
2554
2555 __PACKAGE__->register_method(
2556         method => 'fetch_org_by_shortname',
2557         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2558 );
2559 sub fetch_org_by_shortname {
2560         my( $self, $conn, $sname ) = @_;
2561         my $e = new_editor();
2562         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2563         return $e->event unless $org;
2564         return $org;
2565 }
2566
2567
2568 __PACKAGE__->register_method(
2569         method => 'session_home_lib',
2570         api_name => 'open-ils.actor.session.home_lib',
2571 );
2572
2573 sub session_home_lib {
2574         my( $self, $conn, $auth ) = @_;
2575         my $e = new_editor(authtoken=>$auth);
2576         return undef unless $e->checkauth;
2577         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2578         return $org->shortname;
2579 }
2580
2581
2582
2583 __PACKAGE__->register_method(
2584         method => 'slim_tree',
2585         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2586 );
2587 sub slim_tree {
2588         my $tree = new_editor()->search_actor_org_unit( 
2589                 [
2590                         {"parent_ou" => undef },
2591                         {
2592                                 flesh                           => 2,
2593                                 flesh_fields    => { aou =>  ['children'] },
2594                                 order_by                        => { aou => 'name'},
2595                                 select                  => { aou => ["id","shortname", "name"]},
2596                         }
2597                 ]
2598         )->[0];
2599
2600         return trim_tree($tree);
2601 }
2602
2603
2604 sub trim_tree {
2605         my $tree = shift;
2606         return undef unless $tree;
2607         my $htree = {
2608                 code => $tree->shortname,
2609                 name => $tree->name,
2610         };
2611         if( $tree->children and @{$tree->children} ) {
2612                 $htree->{children} = [];
2613                 for my $c (@{$tree->children}) {
2614                         push( @{$htree->{children}}, trim_tree($c) );
2615                 }
2616         }
2617
2618         return $htree;
2619 }
2620
2621
2622 __PACKAGE__->register_method(
2623         method  => "update_penalties",
2624         api_name        => "open-ils.actor.user.penalties.update");
2625 sub update_penalties {
2626         my( $self, $conn, $auth, $userid ) = @_;
2627         my $e = new_editor(authtoken=>$auth);
2628         return $e->event unless $e->checkauth;
2629         $U->update_patron_penalties( 
2630                 authtoken => $auth,
2631                 patronid  => $userid,
2632         );
2633         return 1;
2634 }
2635
2636
2637
2638 __PACKAGE__->register_method(
2639         method  => "user_retrieve_fleshed_by_id",
2640         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2641
2642 sub user_retrieve_fleshed_by_id {
2643         my( $self, $client, $auth, $user_id, $fields ) = @_;
2644         my $e = new_editor(authtoken => $auth);
2645         return $e->event unless $e->checkauth;
2646
2647         if( $e->requestor->id != $user_id ) {
2648                 return $e->event unless $e->allowed('VIEW_USER');
2649         }
2650
2651         $fields ||= [
2652                 "cards",
2653                 "card",
2654                 "standing_penalties",
2655                 "addresses",
2656                 "billing_address",
2657                 "mailing_address",
2658                 "stat_cat_entries" ];
2659         return new_flesh_user($user_id, $fields, $e);
2660 }
2661
2662
2663 sub new_flesh_user {
2664
2665         my $id = shift;
2666         my $fields = shift || [];
2667         my $e   = shift || new_editor(xact=>1);
2668
2669         my $user = $e->retrieve_actor_user(
2670         [
2671         $id,
2672         {
2673                 "flesh"                         => 1,
2674                 "flesh_fields" =>  { "au" => $fields }
2675         }
2676         ]
2677         ) or return $e->event;
2678
2679
2680         if( grep { $_ eq 'addresses' } @$fields ) {
2681
2682                 $user->addresses([]) unless @{$user->addresses};
2683         
2684                 if( ref $user->billing_address ) {
2685                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2686                                 push( @{$user->addresses}, $user->billing_address );
2687                         }
2688                 }
2689         
2690                 if( ref $user->mailing_address ) {
2691                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2692                                 push( @{$user->addresses}, $user->mailing_address );
2693                         }
2694                 }
2695         }
2696
2697         $e->rollback;
2698         $user->clear_passwd();
2699         return $user;
2700 }
2701
2702
2703
2704
2705 __PACKAGE__->register_method(
2706         method  => "user_retrieve_parts",
2707         api_name        => "open-ils.actor.user.retrieve.parts",);
2708
2709 sub user_retrieve_parts {
2710         my( $self, $client, $auth, $user_id, $fields ) = @_;
2711         my $e = new_editor(authtoken => $auth);
2712         return $e->event unless $e->checkauth;
2713         if( $e->requestor->id != $user_id ) {
2714                 return $e->event unless $e->allowed('VIEW_USER');
2715         }
2716         my @resp;
2717         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2718         push(@resp, $user->$_()) for(@$fields);
2719         return \@resp;
2720 }
2721
2722
2723
2724
2725
2726
2727 1;
2728