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