]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/actor.pm
attempt to speed up date overlap calc
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / Publisher / actor.pm
1 package OpenILS::Application::Storage::Publisher::actor;
2 use base qw/OpenILS::Application::Storage/;
3 use OpenILS::Application::Storage::CDBI::actor;
4 use OpenSRF::Utils::Logger qw/:level/;
5 use OpenSRF::Utils qw/:datetime/;
6 use OpenILS::Utils::Fieldmapper;
7
8 use DateTime;           
9 use DateTime::Format::ISO8601;  
10                                                 
11                                                                                                 
12 my $_dt_parser = DateTime::Format::ISO8601->new;    
13
14 my $log = 'OpenSRF::Utils::Logger';
15
16 sub new_usergroup_id {
17         return actor::user->db_Main->selectrow_array("select nextval('actor.usr_usrgroup_seq'::regclass)");
18 }
19 __PACKAGE__->register_method(
20         api_name        => 'open-ils.storage.actor.user.group_id.new',
21         api_level       => 1,
22         method          => 'new_usergroup_id',
23 );
24
25 sub usr_total_owed {
26         my $self = shift;
27         my $client = shift;
28         my $usr = shift;
29
30         my $sql = <<"   SQL";
31                         SELECT  x.usr,
32                                         SUM(COALESCE((SELECT SUM(b.amount) FROM money.billing b WHERE b.voided IS FALSE AND b.xact = x.id),0.0)) -
33                                                 SUM(COALESCE((SELECT SUM(p.amount) FROM money.payment p WHERE p.voided IS FALSE AND p.xact = x.id),0.0))
34                           FROM  money.billable_xact x
35                           WHERE x.usr = ? AND x.xact_finish IS NULL
36                           GROUP BY 1
37         SQL
38
39         my (undef,$val) = actor::user->db_Main->selectrow_array($sql, {}, $usr);
40
41         return $val;
42 }
43 __PACKAGE__->register_method(
44         api_name        => 'open-ils.storage.actor.user.total_owed',
45         api_level       => 1,
46         method          => 'usr_total_owed',
47 );
48
49 sub usr_breakdown_out {
50         my $self = shift;
51         my $client = shift;
52         my $usr = shift;
53
54         my $out_sql = <<"       SQL";
55                         SELECT  id
56                           FROM  action.circulation
57                           WHERE usr = ? AND checkin_time IS NULL AND due_date >= 'today' AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE'))
58         SQL
59
60         my $out = actor::user->db_Main->selectcol_arrayref($out_sql, {}, $usr);
61
62         my $od_sql = <<"        SQL";
63                         SELECT  id
64                           FROM  action.circulation
65                           WHERE usr = ? AND checkin_time IS NULL AND due_date < 'today' AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE'))
66         SQL
67
68         my $od = actor::user->db_Main->selectcol_arrayref($od_sql, {}, $usr);
69
70         my $lost_sql = <<"      SQL";
71                         SELECT  id
72                           FROM  action.circulation
73                           WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'LOST'
74         SQL
75
76         my $lost = actor::user->db_Main->selectcol_arrayref($lost_sql, {}, $usr);
77
78         my $cl_sql = <<"        SQL";
79                         SELECT  id
80                           FROM  action.circulation
81                           WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'CLAIMSRETURNED'
82         SQL
83
84         my $cl = actor::user->db_Main->selectcol_arrayref($cl_sql, {}, $usr);
85
86         my $lo_sql = <<"        SQL";
87                         SELECT  id
88                           FROM  action.circulation
89                           WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'LONGOVERDUE'
90         SQL
91
92         my $lo = actor::user->db_Main->selectcol_arrayref($lo_sql, {}, $usr);
93
94         if ($self->api_name =~/count$/o) {
95                 return {        total   => scalar(@$out) + scalar(@$od) + scalar(@$lost) + scalar(@$cl) + scalar(@$lo),
96                                         out             => scalar(@$out),
97                                         overdue => scalar(@$od),
98                                         lost    => scalar(@$lost),
99                                         claims_returned => scalar(@$cl),
100                                         long_overdue            => scalar(@$lo),
101                 };
102         }
103
104         return {        out             => $out,
105                                 overdue => $od,
106                                 lost    => $lost,
107                                 claims_returned => $cl,
108                                 long_overdue            => $lo,
109         };
110 }
111 __PACKAGE__->register_method(
112         api_name        => 'open-ils.storage.actor.user.checked_out',
113         api_level       => 1,
114         method          => 'usr_breakdown_out',
115 );
116 __PACKAGE__->register_method(
117         api_name        => 'open-ils.storage.actor.user.checked_out.count',
118         api_level       => 1,
119         method          => 'usr_breakdown_out',
120 );
121
122 sub usr_total_out {
123         my $self = shift;
124         my $client = shift;
125         my $usr = shift;
126
127         my $sql = <<"   SQL";
128                         SELECT  count(*)
129                           FROM  action.circulation
130                           WHERE usr = ? AND checkin_time IS NULL
131         SQL
132
133         my ($val) = actor::user->db_Main->selectrow_array($sql, {}, $usr);
134
135         return $val;
136 }
137 __PACKAGE__->register_method(
138         api_name        => 'open-ils.storage.actor.user.total_out',
139         api_level       => 1,
140         method          => 'usr_total_out',
141 );
142
143 sub calc_proximity {
144         my $self = shift;
145         my $client = shift;
146
147         local $OpenILS::Application::Storage::WRITE = 1;
148
149         my $delete_sql = <<"    SQL";
150                 DELETE FROM actor.org_unit_proximity;
151         SQL
152
153         my $insert_sql = <<"    SQL";
154                 INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
155                         SELECT  l.id,
156                                 r.id,
157                                 actor.org_unit_proximity(l.id,r.id)
158                           FROM  actor.org_unit l,
159                                 actor.org_unit r;
160         SQL
161
162         actor::org_unit_proximity->db_Main->do($delete_sql);
163         actor::org_unit_proximity->db_Main->do($insert_sql);
164
165         return 1;
166 }
167 __PACKAGE__->register_method(
168         api_name        => 'open-ils.storage.actor.org_unit.refresh_proximity',
169         api_level       => 1,
170         method          => 'calc_proximity',
171 );
172
173
174 sub org_closed_overlap {
175         my $self = shift;
176         my $client = shift;
177         my $ou = shift;
178         my $date = shift;
179         my $direction = shift || 0;
180         my $no_hoo = shift || 0;
181
182         return undef unless ($date && $ou);
183
184         my $t = actor::org_unit::closed_date->table;
185         my $sql = <<"   SQL";
186                 SELECT  *
187                   FROM  $t
188                   WHERE ? between close_start and close_end
189                         AND org_unit = ?
190                   ORDER BY close_start ASC, close_end DESC
191         SQL
192
193         $date = clense_ISO8601($date);
194
195         my $sth = actor::org_unit::closed_date->db_Main->prepare( $sql );
196         $sth->execute($date, $ou);
197         
198         my ($begin, $end);
199         while (my $closure = $sth->fetchrow_hashref) {
200                 $begin ||= clense_ISO8601($closure->{close_start});
201                 $end = clense_ISO8601($closure->{close_end});
202
203                 if ( $direction <= 0 ) {
204                         $before = $_dt_parser->parse_datetime( $begin );
205                         $before->subtract( minutes => 1 );
206
207                         while ( my $_b = org_closed_overlap($self, $client, $ou, $before->iso8601, -1, 1 ) ) {
208                                 $before = $_dt_parser->parse_datetime( clense_ISO8601($_b->{start}) );
209                         }
210                         $begin = clense_ISO8601($before->iso8601);
211                 }
212
213                 if ( $direction >= 0 ) {
214                         $after = $_dt_parser->parse_datetime( $end );
215                         $after->add( minutes => 1 );
216
217                         while ( my $_a = org_closed_overlap($self, $client, $ou, $after->iso8601, 1, 1 ) ) {
218                                 $after = $_dt_parser->parse_datetime( clense_ISO8601($_a->{end}) );
219                         }
220                         $end = clense_ISO8601($after->iso8601);
221                 }
222
223         }
224
225         $begin ||= $date;
226         $end ||= $date;
227
228
229         if ( !$no_hoo ) {
230                 if ( my $hoo = actor::org_unit::hours_of_operation->retrieve($ou) ) {
231
232                         my $begin_dow = $_dt_parser->parse_datetime( $begin )->day_of_week_0;
233                         my $begin_open_meth = "dow_".$begin_dow."_open";
234                         my $begin_close_meth = "dow_".$begin_dow."_close";
235
236                         my $count = 1;
237                         while ($hoo->$begin_open_meth eq '00:00:00' and $hoo->$begin_close_meth eq '00:00:00') {
238                                 $begin = clense_ISO8601($_dt_parser->parse_datetime( $begin )->subtract( days => 1)->iso8601);
239                                 $begin_dow++;
240                                 $begin_dow %= 7;
241                                 $count++;
242                                 last if ($count > 6);
243                                 $begin_open_meth = "dow_".$begin_dow."_open";
244                                 $begin_close_meth = "dow_".$begin_dow."_close";
245                         }
246         
247                         my $end_dow = $_dt_parser->parse_datetime( $end )->day_of_week_0;
248                         my $end_open_meth = "dow_".$end_dow."_open";
249                         my $end_close_meth = "dow_".$end_dow."_close";
250         
251                         $count = 1;
252                         while ($hoo->$end_open_meth eq '00:00:00' and $hoo->$end_close_meth eq '00:00:00') {
253                                 $end = clense_ISO8601($_dt_parser->parse_datetime( $end )->add( days => 1)->iso8601);
254                                 $end_dow++;
255                                 $end_dow %= 7;
256                                 $count++;
257                                 last if ($count > 6);
258                                 $end_open_meth = "dow_".$end_dow."_open";
259                                 $end_close_meth = "dow_".$end_dow."_close";
260                         }
261                 }
262         }
263
264         if ($begin eq $date && $end eq $date) {
265                 return undef;
266         }
267
268         return { start => $begin, end => $end };
269 }
270 __PACKAGE__->register_method(
271         api_name        => 'open-ils.storage.actor.org_unit.closed_date.overlap',
272         api_level       => 1,
273         method          => 'org_closed_overlap',
274 );
275
276 sub user_by_barcode {
277         my $self = shift;
278         my $client = shift;
279         my @barcodes = shift;
280
281         return undef unless @barcodes;
282
283         for my $card ( actor::card->search( { barcode => @barcodes } ) ) {
284                 next unless $card;
285                 if (@barcodes == 1) {
286                         return $card->usr->to_fieldmapper;
287                 }
288                 $client->respond( $card->usr->to_fieldmapper);
289         }
290         return undef;
291 }
292 __PACKAGE__->register_method(
293         api_name        => 'open-ils.storage.direct.actor.user.search.barcode',
294         api_level       => 1,
295         method          => 'user_by_barcode',
296         stream          => 1,
297         cachable        => 1,
298 );
299
300 sub lost_barcodes {
301         my $self = shift;
302         my $client = shift;
303
304         my $c = actor::card->table;
305         my $p = actor::user->table;
306
307         my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.card <> c.id";
308
309         my $list = actor::user->db_Main->selectcol_arrayref($sql);
310         for my $bc ( @$list ) {
311                 $client->respond($bc);
312         }
313         return undef;
314 }
315 __PACKAGE__->register_method(
316         api_name        => 'open-ils.storage.actor.user.lost_barcodes',
317         api_level       => 1,
318         stream          => 1,
319         method          => 'lost_barcodes',
320         signature       => <<'  NOTE',
321                 Returns an array of barcodes that belong to lost cards.
322                 @return array of barcodes
323         NOTE
324 );
325
326 sub expired_barcodes {
327         my $self = shift;
328         my $client = shift;
329
330         my $c = actor::card->table;
331         my $p = actor::user->table;
332
333         my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.expire_date < CURRENT_DATE";
334
335         my $list = actor::user->db_Main->selectcol_arrayref($sql);
336         for my $bc ( @$list ) {
337                 $client->respond($bc);
338         }
339         return undef;
340 }
341 __PACKAGE__->register_method(
342         api_name        => 'open-ils.storage.actor.user.expired_barcodes',
343         api_level       => 1,
344         stream          => 1,
345         method          => 'expired_barcodes',
346         signature       => <<'  NOTE',
347                 Returns an array of barcodes that are currently expired.
348                 @return array of barcodes
349         NOTE
350 );
351
352 sub barred_barcodes {
353         my $self = shift;
354         my $client = shift;
355
356         my $c = actor::card->table;
357         my $p = actor::user->table;
358
359         my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.barred IS TRUE";
360
361         my $list = actor::user->db_Main->selectcol_arrayref($sql);
362         for my $bc ( @$list ) {
363                 $client->respond($bc);
364         }
365         return undef;
366 }
367 __PACKAGE__->register_method(
368         api_name        => 'open-ils.storage.actor.user.barred_barcodes',
369         api_level       => 1,
370         stream          => 1,
371         method          => 'barred_barcodes',
372         signature       => <<'  NOTE',
373                 Returns an array of barcodes that are currently barred.
374                 @return array of barcodes
375         NOTE
376 );
377
378 sub penalized_barcodes {
379         my $self = shift;
380         my $client = shift;
381         my @ignore = @_;
382
383         my $c = actor::card->table;
384         my $p = actor::user_standing_penalty->table;
385
386         my $sql = "SELECT c.barcode FROM $c c JOIN $p p USING (usr)";
387
388         if (@ignore) {
389                 $sql .= ' WHERE penalty_type NOT IN ('. join(',', map { '?' } @ignore) . ')';
390         }
391
392         $sql .= ' GROUP BY c.barcode;';
393
394         my $list = actor::user->db_Main->selectcol_arrayref($sql, {}, @ignore);
395         for my $bc ( @$list ) {
396                 $client->respond($bc);
397         }
398         return undef;
399 }
400 __PACKAGE__->register_method(
401         api_name        => 'open-ils.storage.actor.user.penalized_barcodes',
402         api_level       => 1,
403         stream          => 1,
404         method          => 'penalized_barcodes',
405         signature       => <<'  NOTE',
406                 Returns an array of barcodes that have penalties not listed
407                 as a parameter.  Supply a list of any penalty types that should
408                 not stop a patron from checking out materials.
409
410                 @param ignore_list Penalty type to ignore
411                 @return array of barcodes
412         NOTE
413 );
414
415
416 sub patron_search {
417         my $self = shift;
418         my $client = shift;
419         my $search = shift;
420         my $limit = shift || 1000;
421         my $sort = shift;
422         my $inactive = shift;
423         $sort = ['family_name','first_given_name'] unless ($$sort[0]);
424
425         # group 0 = user
426         # group 1 = address
427         # group 2 = phone, ident
428
429         my $usr = join ' AND ', map { "LOWER($_) ~ ?" } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
430         my @usrv = map { "^$$search{$_}{value}" } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
431
432         my $addr = join ' AND ', map { "LOWER($_) ~ ?" } grep { ''.$$search{$_}{group} eq '1' } keys %$search;
433         my @addrv = map { "^$$search{$_}{value}" } grep { ''.$$search{$_}{group} eq '1' } keys %$search;
434
435         my $pv = $$search{phone}{value};
436         my $iv = $$search{ident}{value};
437         my $nv = $$search{name}{value};
438
439         my $phone = '';
440         my @ps;
441         my @phonev;
442         if ($pv) {
443                 for my $p ( qw/day_phone evening_phone other_phone/ ) {
444                         push @ps, "LOWER($p) ~ ?";
445                         push @phonev, "^$pv";
446                 }
447                 $phone = '(' . join(' OR ', @ps) . ')';
448         }
449
450         my $ident = '';
451         my @is;
452         my @identv;
453         if ($iv) {
454                 for my $i ( qw/ident_value ident_value2/ ) {
455                         push @is, "LOWER($i) ~ ?";
456                         push @identv, "^$iv";
457                 }
458                 $ident = '(' . join(' OR ', @is) . ')';
459         }
460
461         my $name = '';
462         my @ns;
463         my @namev;
464         if (0 && $nv) {
465                 for my $n ( qw/first_given_name second_given_name family_name/ ) {
466                         push @ns, "LOWER($i) ~ ?";
467                         push @namev, "^$nv";
468                 }
469                 $name = '(' . join(' OR ', @ns) . ')';
470         }
471
472         my $usr_where = join ' AND ', grep { $_ } ($usr,$phone,$ident,$name);
473         my $addr_where = $addr;
474
475
476         my $u_table = actor::user->table;
477         my $a_table = actor::user_address->table;
478
479         my $u_select = "SELECT id as id FROM $u_table u WHERE $usr_where";
480         my $a_select = "SELECT usr as id FROM $a_table a WHERE $addr_where";
481         my $clone_select = '';
482         $clone_select = "JOIN (SELECT cu.id as id FROM $a_table ca ".
483                            "JOIN $u_table cu ON (cu.mailing_address = ca.id OR cu.billing_address = ca.id) ".
484                            "WHERE $addr_where) AS clone USING (id)" if ($addr_where);
485
486         my $select = '';
487         if ($usr_where) {
488                 if ($addr_where) {
489                         $select = "$u_select INTERSECT $a_select";
490                 } else {
491                         $select = $u_select;
492                 }
493         } elsif ($addr_where) {
494                 $select = "$a_select";
495         } else {
496                 return undef;
497         }
498
499         my $order_by = join ', ', map { 'users.'. $_} @$sort;
500
501         if ($inactive) {
502                 $inactive = '';
503         } else {
504                 $inactive = 'AND users.active = TRUE';
505         }
506
507         $select = <<"   SQL";
508                 SELECT  users.id
509                   FROM  $u_table AS users
510                         JOIN ($select) AS search
511                   USING (id)
512                   $clone_select
513                   WHERE users.deleted = FALSE $inactive
514                   ORDER BY $order_by
515                   LIMIT $limit
516         SQL
517
518         return actor::user->db_Main->selectcol_arrayref($select, {}, map {lc($_)} (@usrv,@phonev,@identv,@namev,@addrv,@addrv));
519 }
520 __PACKAGE__->register_method(
521         api_name        => 'open-ils.storage.actor.user.crazy_search',
522         api_level       => 1,
523         method          => 'patron_search',
524 );
525
526 =comment not gonna use it...
527
528 sub fleshed_search {
529         my $self = shift;
530         my $client = shift;
531         my $searches = shift;
532
533         return undef unless (defined $searches);
534
535         for my $usr ( actor::user->search( $searches ) ) {
536                 next unless $usr;
537                 $client->respond( flesh_user( $usr ) );
538         }
539         return undef;
540 }
541 __PACKAGE__->register_method(
542         api_name        => 'open-ils.storage.fleshed.actor.user.search',
543         api_level       => 1,
544         method          => 'fleshed_search',
545         stream          => 1,
546         cachable        => 1,
547 );
548
549 sub fleshed_search_like {
550         my $self = shift;
551         my $client = shift;
552         my $searches = shift;
553
554         return undef unless (defined $searches);
555
556         for my $usr ( actor::user->search_like( $searches ) ) {
557                 next unless $usr;
558                 $client->respond( flesh_user( $usr ) );
559         }
560         return undef;
561 }
562 __PACKAGE__->register_method(
563         api_name        => 'open-ils.storage.fleshed.actor.user.search_like',
564         api_level       => 1,
565         method          => 'user_by_barcode',
566         stream          => 1,
567         cachable        => 1,
568 );
569
570 sub retrieve_fleshed_user {
571         my $self = shift;
572         my $client = shift;
573         my @ids = shift;
574
575         return undef unless @ids;
576
577         @ids = ($ids[0]) unless ($self->api_name =~ /batch/o); 
578
579         $client->respond( flesh_user( actor::user->retrieve( $_ ) ) ) for ( @ids );
580
581         return undef;
582 }
583 __PACKAGE__->register_method(
584         api_name        => 'open-ils.storage.fleshed.actor.user.retrieve',
585         api_level       => 1,
586         method          => 'retrieve_fleshed_user',
587         cachable        => 1,
588 );
589 __PACKAGE__->register_method(
590         api_name        => 'open-ils.storage.fleshed.actor.user.batch.retrieve',
591         api_level       => 1,
592         method          => 'retrieve_fleshed_user',
593         stream          => 1,
594         cachable        => 1,
595 );
596
597 sub flesh_user {
598         my $usr = shift;
599
600
601         my $standing = $usr->standing;
602         my $profile = $usr->profile;
603         my $ident_type = $usr->ident_type;
604                 
605         my $maddress = $usr->mailing_address;
606         my $baddress = $usr->billing_address;
607         my $card = $usr->card;
608
609         my @addresses = $usr->addresses;
610         my @cards = $usr->cards;
611
612         my $usr_fm = $usr->to_fieldmapper;
613         $usr_fm->standing( $standing->to_fieldmapper );
614         $usr_fm->profile( $profile->to_fieldmapper );
615         $usr_fm->ident_type( $ident_type->to_fieldmapper );
616
617         $usr_fm->card( $card->to_fieldmapper );
618         $usr_fm->mailing_address( $maddress->to_fieldmapper ) if ($maddress);
619         $usr_fm->billing_address( $baddress->to_fieldmapper ) if ($baddress);
620
621         $usr_fm->cards( [ map { $_->to_fieldmapper } @cards ] );
622         $usr_fm->addresses( [ map { $_->to_fieldmapper } @addresses ] );
623
624         return $usr_fm;
625 }
626
627 =cut
628
629 sub org_unit_list {
630         my $self = shift;
631         my $client = shift;
632
633         my $select =<<" SQL";
634         SELECT  *
635           FROM  actor.org_unit
636           ORDER BY CASE WHEN parent_ou IS NULL THEN 0 ELSE 1 END, name;
637         SQL
638
639         my $sth = actor::org_unit->db_Main->prepare_cached($select);
640         $sth->execute;
641
642         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
643
644         return undef;
645 }
646 __PACKAGE__->register_method(
647         api_name        => 'open-ils.storage.direct.actor.org_unit.retrieve.all',
648         api_level       => 1,
649         stream          => 1,
650         method          => 'org_unit_list',
651 );
652
653 sub org_unit_type_list {
654         my $self = shift;
655         my $client = shift;
656
657         my $select =<<" SQL";
658         SELECT  *
659           FROM  actor.org_unit_type
660           ORDER BY depth, name;
661         SQL
662
663         my $sth = actor::org_unit_type->db_Main->prepare_cached($select);
664         $sth->execute;
665
666         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit_type->construct($_) } $sth->fetchall_hash );
667
668         return undef;
669 }
670 __PACKAGE__->register_method(
671         api_name        => 'open-ils.storage.direct.actor.org_unit_type.retrieve.all',
672         api_level       => 1,
673         stream          => 1,
674         method          => 'org_unit_type_list',
675 );
676
677 sub org_unit_full_path {
678         my $self = shift;
679         my $client = shift;
680         my @binds = @_;
681
682         return undef unless (@binds);
683
684         my $func = 'actor.org_unit_full_path(?)';
685         $func = 'actor.org_unit_full_path(?,?)' if (@binds > 1);
686
687         my $sth = actor::org_unit->db_Main->prepare_cached("SELECT * FROM $func");
688         $sth->execute(@binds);
689
690         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
691
692         return undef;
693 }
694 __PACKAGE__->register_method(
695         api_name        => 'open-ils.storage.actor.org_unit.full_path',
696         api_level       => 1,
697         stream          => 1,
698         method          => 'org_unit_full_path',
699 );
700
701 sub org_unit_ancestors {
702         my $self = shift;
703         my $client = shift;
704         my $id = shift;
705
706         return undef unless ($id);
707
708         my $func = 'actor.org_unit_ancestors(?)';
709
710         my $sth = actor::org_unit->db_Main->prepare_cached(<<"  SQL");
711                 SELECT  f.*
712                   FROM  $func f
713                         JOIN actor.org_unit_type t ON (f.ou_type = t.id)
714                   ORDER BY t.depth, f.name;
715         SQL
716         $sth->execute(''.$id);
717
718         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
719
720         return undef;
721 }
722 __PACKAGE__->register_method(
723         api_name        => 'open-ils.storage.actor.org_unit.ancestors',
724         api_level       => 1,
725         stream          => 1,
726         method          => 'org_unit_ancestors',
727 );
728
729 sub org_unit_descendants {
730         my $self = shift;
731         my $client = shift;
732         my $id = shift;
733         my $depth = shift;
734
735         return undef unless ($id);
736
737         my $func = 'actor.org_unit_descendants(?)';
738         if (defined $depth) {
739                 $func = 'actor.org_unit_descendants(?,?)';
740         }
741
742         my $sth = actor::org_unit->db_Main->prepare_cached("SELECT * FROM $func");
743         $sth->execute(''.$id, ''.$depth) if (defined $depth);
744         $sth->execute(''.$id) unless (defined $depth);
745
746         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
747
748         return undef;
749 }
750 __PACKAGE__->register_method(
751         api_name        => 'open-ils.storage.actor.org_unit.descendants',
752         api_level       => 1,
753         stream          => 1,
754         method          => 'org_unit_descendants',
755 );
756
757 sub fleshed_actor_stat_cat {
758         my $self = shift;
759         my $client = shift;
760         my @list = @_;
761         
762         @list = ($list[0]) unless ($self->api_name =~ /batch/o);
763
764         for my $sc (@list) {
765                 my $cat = actor::stat_cat->retrieve($sc);
766                 next unless ($cat);
767
768                 my $sc_fm = $cat->to_fieldmapper;
769                 $sc_fm->entries( [ map { $_->to_fieldmapper } $cat->entries ] );
770
771                 $client->respond( $sc_fm );
772
773         }
774
775         return undef;
776 }
777 __PACKAGE__->register_method(
778         api_name        => 'open-ils.storage.fleshed.actor.stat_cat.retrieve',
779         api_level       => 1,
780         argc            => 1,
781         method          => 'fleshed_actor_stat_cat',
782 );
783
784 __PACKAGE__->register_method(
785         api_name        => 'open-ils.storage.fleshed.actor.stat_cat.retrieve.batch',
786         api_level       => 1,
787         argc            => 1,
788         stream          => 1,
789         method          => 'fleshed_actor_stat_cat',
790 );
791
792 #XXX Fix stored proc calls
793 sub ranged_actor_stat_cat_all {
794         my $self = shift;
795         my $client = shift;
796         my $ou = ''.shift();
797         
798         return undef unless ($ou);
799         my $s_table = actor::stat_cat->table;
800
801         my $select = <<"        SQL";
802                 SELECT  s.*
803                   FROM  $s_table s
804                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
805                   ORDER BY name
806         SQL
807
808         $fleshed = 0;
809         $fleshed = 1 if ($self->api_name =~ /fleshed/o);
810
811         my $sth = actor::stat_cat->db_Main->prepare_cached($select);
812         $sth->execute($ou);
813
814         for my $sc ( map { actor::stat_cat->construct($_) } $sth->fetchall_hash ) {
815                 my $sc_fm = $sc->to_fieldmapper;
816                 $sc_fm->entries(
817                         [ $self->method_lookup( 'open-ils.storage.ranged.actor.stat_cat_entry.search.stat_cat' )->run($ou,$sc->id) ]
818                 ) if ($fleshed);
819                 $client->respond( $sc_fm );
820         }
821
822         return undef;
823 }
824 __PACKAGE__->register_method(
825         api_name        => 'open-ils.storage.ranged.fleshed.actor.stat_cat.all',
826         api_level       => 1,
827         argc            => 1,
828         stream          => 1,
829         method          => 'ranged_actor_stat_cat_all',
830 );
831
832 __PACKAGE__->register_method(
833         api_name        => 'open-ils.storage.ranged.actor.stat_cat.all',
834         api_level       => 1,
835         argc            => 1,
836         stream          => 1,
837         method          => 'ranged_actor_stat_cat_all',
838 );
839
840 #XXX Fix stored proc calls
841 sub ranged_actor_stat_cat_entry {
842         my $self = shift;
843         my $client = shift;
844         my $ou = ''.shift();
845         my $sc = ''.shift();
846         
847         return undef unless ($ou);
848         my $s_table = actor::stat_cat_entry->table;
849
850         my $select = <<"        SQL";
851                 SELECT  s.*
852                   FROM  $s_table s
853                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
854                   WHERE stat_cat = ?
855                   ORDER BY name
856         SQL
857
858         my $sth = actor::stat_cat->db_Main->prepare_cached($select);
859         $sth->execute($ou,$sc);
860
861         for my $sce ( map { actor::stat_cat_entry->construct($_) } $sth->fetchall_hash ) {
862                 $client->respond( $sce->to_fieldmapper );
863         }
864
865         return undef;
866 }
867 __PACKAGE__->register_method(
868         api_name        => 'open-ils.storage.ranged.actor.stat_cat_entry.search.stat_cat',
869         api_level       => 1,
870         stream          => 1,
871         method          => 'ranged_actor_stat_cat_entry',
872 );
873
874
875 1;