Calculated Proximity Adjustments, a new feature
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Storage / Publisher / asset.pm
1 package OpenILS::Application::Storage::Publisher::asset;
2 use base qw/OpenILS::Application::Storage/;
3 #use OpenILS::Application::Storage::CDBI::asset;
4 #use OpenILS::Utils::Fieldmapper;
5 use OpenSRF::Utils::Logger qw/:level/;
6 use OpenSRF::EX qw/:try/;
7 use OpenSRF::Utils::JSON;
8
9 #
10
11 my $log = 'OpenSRF::Utils::Logger';
12
13 use MARC::Record;
14 use MARC::File::XML ( BinaryEncoding => 'UTF-8' );
15
16 sub circ_count {
17         my $self = shift;
18         my $client = shift;
19         my $copy = shift;
20         my $granularity = shift;
21
22         my $c_table = action::circulation->table;
23
24         if (lc($granularity) eq 'year') {
25                 $granularity = ", to_char(xact_start, 'YYYY') as when";
26         } elsif (lc($granularity) eq 'month') {
27                 $granularity = ", to_char(xact_start, 'YYYY-MM') as when";
28         } elsif (lc($granularity) eq 'day') {
29                 $granularity = ", to_char(xact_start, 'YYYY-MM-DD') as when";
30         } else {
31                 $granularity = ", 'total' as when";
32         }
33
34         my $SQL = <<"   SQL";
35                 SELECT  COUNT(*) as count $granularity
36                   FROM  $c_table
37                   WHERE target_copy = ?
38         SQL
39
40
41         if ($granularity !~ /total/o) {
42                 $SQL .= ' GROUP BY 2 ORDER BY 2';
43         }
44
45         $log->debug("Circ count SQL [$SQL]", DEBUG);
46
47         return action::circulation->db_Main->selectall_hashref($SQL, 'when', {}, $copy);
48 }
49 __PACKAGE__->register_method(
50         method          => 'circ_count',
51         api_name        => 'open-ils.storage.asset.copy.circ_count',
52         argc            => 1,
53 );
54
55
56 #our $_default_subfield_map = {
57 #        call_number     => $cn,
58 #        barcode         => $bc,
59 #        owning_lib      => $ol,
60 #        circulating_lib => $cl,
61 #        copy_location   => $sl,
62 #        copy_number     => $num,
63 #        price           => $pr,
64 #        status          => $loc,
65 #        create_date     => $date,
66 #
67 #        legacy_item_type        => $it,
68 #        legacy_item_cat_1       => $ic1,
69 #        legacy_item_cat_2       => $ic2,
70 #};
71
72 my %org_cache;
73
74 sub import_xml_holdings {
75         my $self = shift;
76         my $client = shift;
77         my $editor = shift;
78         my $record = shift;
79         my $xml = shift;
80         my $tag = shift;
81         my $map = shift;
82         my $date_format = shift || 'mm/dd/yyyy';
83
84         ($record) = biblio::record_entry->search_where($record);
85
86         return 0 unless ($record);
87
88         my $r = MARC::Record->new_from_xml($xml);
89
90         my $count = 0;
91         for my $f ( $r->fields( $tag ) ) {
92                 next unless ($f->subfield( $map->{owning_lib} ));
93
94                 my ($ol,$cl);
95
96                 try {
97                         $ol = 
98                                 $org_cache{ $f->subfield( $map->{owning_lib} ) }
99                                 || actor::org_unit->search( shortname => $f->subfield( $map->{owning_lib} ) )->next->id;
100
101                         $org_cache{ $f->subfield( $map->{owning_lib} ) } = $ol;
102                 } otherwise {
103                         $log->debug('Could not find library with shortname ['.$f->subfield( $map->{owning_lib} ).'] : '. shift(), ERROR);
104                         $log->info('Failed holdings tag: ['.OpenSRF::Utils::JSON->perl2JSON( $f ).']');
105                 };
106                 
107                 try {
108                         $cl =
109                                 $org_cache{ $f->subfield( $map->{circulating_lib} ) }
110                                 || actor::org_unit->search( shortname => $f->subfield( $map->{circulating_lib} ) )->next->id;
111
112                         $org_cache{ $f->subfield( $map->{circulating_lib} ) } = $cl;
113                 } otherwise {
114                         $log->debug('Could not find library with shortname ['.$f->subfield( $map->{circulating_lib} ).'] : '. shift(), ERROR);
115                         $log->info('Failed holdings tag: ['.OpenSRF::Utils::JSON->perl2JSON( $f ).']');
116                 };
117
118                 next unless ($ol && $cl);
119
120                 my $cn;
121                 try {
122                         $cn = asset::call_number->find_or_create(
123                                 { label         => $f->subfield( $map->{call_number} ),
124                                   owning_lib    => $ol,
125                                   record        => $record->id,
126                                   creator       => $editor,
127                                   editor        => $editor,
128                                 }
129                         );
130                 } otherwise {
131                         $log->debug('Could not find or create callnumber ['.$f->subfield( $map->{call_number} )."] on record $record : ". shift(), ERROR);
132                         $log->info('Failed holdings tag: ['.OpenSRF::Utils::JSON->perl2JSON( $f ).']');
133                 };
134
135                 next unless ($cn);
136
137                 my $create_date =  $f->subfield( $map->{create_date} );
138
139                 my ($m,$d,$y);
140                 if ($date_format eq 'mm/dd/yyyy') {
141                         ($m,$d,$y) = split '/', $create_date;
142
143                 } elsif ($date_format eq 'dd/mm/yyyy') {
144                         ($d,$m,$y) = split '/', $create_date;
145
146                 } elsif ($date_format eq 'mm-dd-yyyy') {
147                         ($m,$d,$y) = split '-', $create_date;
148
149                 } elsif ($date_format eq 'dd-mm-yyyy') {
150                         ($d,$m,$y) = split '-', $create_date;
151
152                 } elsif ($date_format eq 'yyyy-mm-dd') {
153                         ($y,$m,$d) = split '-', $create_date;
154
155                 } elsif ($date_format eq 'yyyy/mm/dd') {
156                         ($y,$m,$d) = split '/', $create_date;
157                 }
158
159                 if ($y == 0) {
160                         (undef,undef,undef,$d,$m,$y) = localtime;
161                         $m++;
162                         $y+=1900;
163                 }
164
165                 my $price = $f->subfield( $map->{price} );
166                 $price =~ s/[^0-9\.]+//gso;
167                 $price ||= '0.00';
168
169                 try {
170                         $cn->add_to_copies(
171                                 { circ_lib      => $cl,
172                                   copy_number   => $f->subfield( $map->{copy_number} ),
173                                   price         => $price,
174                                   barcode       => $f->subfield( $map->{barcode} ),
175                                   loan_duration => 2,
176                                   fine_level    => 2,
177                                   creator       => $editor,
178                                   editor        => $editor,
179                                   create_date   => sprintf('%04d-%02d-%02d',$y,$m,$d),
180                                 }
181                         );
182                         $count++;
183                 } otherwise {
184                         $log->debug('Could not create copy ['.$f->subfield( $map->{barcode} ).'] : '. shift(), ERROR);
185                 };
186         }
187
188         return $count;
189 }
190 __PACKAGE__->register_method(
191         method          => 'import_xml_holdings',
192         api_name        => 'open-ils.storage.asset.holdings.import.xml',
193         argc            => 5,
194         stream          => 0,
195 );
196
197 # XXX
198 # see /home/miker/cn_browse-test.sql for page up and down sql ...
199 # XXX
200
201 sub cn_browse_pagedown {
202         my $self = shift;
203         my $client = shift;
204
205         my %args = @_;
206
207         my $cn = uc($args{label});
208         my $org = $args{org_unit};
209         my $depth = $args{depth};
210         my $boundry_id = $args{boundry_id};
211         my $size = $args{page_size} || 20;
212         $size = int($size);
213
214         my $table = asset::call_number->table;
215
216         my $descendants = "actor.org_unit_descendants($org)";
217         if (defined $depth) {
218                 $descendants = "actor.org_unit_descendants($org,$depth)";
219         }
220
221         my $orgs = join(',', @{ asset::call_number->db_Main->selectcol_arrayref("SELECT DISTINCT id FROM $descendants;") });
222         
223         my $sql = <<"   SQL";
224                 select
225                         cn.label,
226                         cn.owning_lib,
227                         cn.record,
228                         cn.id
229                 from
230                         $table cn
231                 where
232                         not deleted
233                         and (oils_text_as_bytea(label) > ? or ( cn.id > ? and oils_text_as_bytea(label) = ? ))
234                         and owning_lib in ($orgs)
235                 order by oils_text_as_bytea(label), 4, 2
236                 limit $size;
237         SQL
238
239         my $sth = asset::call_number->db_Main->prepare($sql);
240         $sth->execute($cn, $boundry_id, $cn);
241         while ( my @row = $sth->fetchrow_array ) {
242                 $client->respond([@row]);
243         }
244         $sth->finish;
245
246         return undef;
247 }
248 __PACKAGE__->register_method(
249         method          => 'cn_browse_pagedown',
250         api_name        => 'open-ils.storage.asset.call_number.browse.page_down',
251         argc            => 4,
252         stream          => 1,
253 );
254
255 sub cn_browse_pageup {
256         my $self = shift;
257         my $client = shift;
258
259         my %args = @_;
260
261         my $cn = uc($args{label});
262         my $org = $args{org_unit};
263         my $depth = $args{depth};
264         my $boundry_id = $args{boundry_id};
265         my $size = $args{page_size} || 20;
266         $size = int($size);
267
268         my $table = asset::call_number->table;
269
270         my $descendants = "actor.org_unit_descendants($org)";
271         if (defined $depth) {
272                 $descendants = "actor.org_unit_descendants($org,$depth)";
273         }
274
275         my $orgs = join(',', @{ asset::call_number->db_Main->selectcol_arrayref("SELECT DISTINCT id FROM $descendants;") });
276
277         my $sql = <<"   SQL";
278                 select * from (
279                         select
280                                 cn.label,
281                                 cn.owning_lib,
282                                 cn.record,
283                                 cn.id
284                         from
285                                 $table cn
286                         where
287                                 not deleted
288                                 and (oils_text_as_bytea(label) < ? or ( cn.id < ? and oils_text_as_bytea(label) = ? ))
289                                 and owning_lib in ($orgs)
290                         order by oils_text_as_bytea(label) desc, 4 desc, 2 desc
291                         limit $size
292                 ) as bar
293                 order by 1,4,2;
294         SQL
295
296         my $sth = asset::call_number->db_Main->prepare($sql);
297         $sth->execute($cn, $boundry_id, $cn);
298         while ( my @row = $sth->fetchrow_array ) {
299                 $client->respond([@row]);
300         }
301         $sth->finish;
302
303         return undef;
304 }
305 __PACKAGE__->register_method(
306         method          => 'cn_browse_pageup',
307         api_name        => 'open-ils.storage.asset.call_number.browse.page_up',
308         argc            => 4,
309         stream          => 1,
310 );
311
312 sub cn_browse_target {
313         my $self = shift;
314         my $client = shift;
315
316         my %args = @_;
317
318         my $cn = uc($args{label});
319         my $org = $args{org_unit};
320         my $depth = $args{depth};
321         my $size = $args{page_size} || 20;
322         my $topsize = $size / 2;
323         $topsize = int($topsize);
324         $bottomsize = $size - $topsize;
325
326         my $table = asset::call_number->table;
327
328         my $descendants = "actor.org_unit_descendants($org)";
329         if (defined $depth) {
330                 $descendants = "actor.org_unit_descendants($org,$depth)";
331         }
332
333         my $orgs = join(',', @{ asset::call_number->db_Main->selectcol_arrayref("SELECT DISTINCT id FROM $descendants;") });
334
335         my $top_sql = <<"       SQL";
336                 select * from (
337                         select
338                                 cn.label,
339                                 cn.owning_lib,
340                                 cn.record,
341                                 cn.id
342                         from
343                                 $table cn
344                         where
345                                 not deleted
346                                 and oils_text_as_bytea(label) < ?
347                                 and owning_lib in ($orgs)
348                         order by oils_text_as_bytea(label) desc, 4 desc, 2 desc
349                         limit $topsize
350                 ) as bar
351                 order by 1,4,2;
352         SQL
353
354         my $bottom_sql = <<"    SQL";
355                 select
356                         cn.label,
357                         cn.owning_lib,
358                         cn.record,
359                         cn.id
360                 from
361                         $table cn
362                 where
363                         not deleted
364                         and oils_text_as_bytea(label) >= ?
365                         and owning_lib in ($orgs)
366                 order by oils_text_as_bytea(label),4,2
367                 limit $bottomsize;
368         SQL
369
370         my $sth = asset::call_number->db_Main->prepare($top_sql);
371         $sth->execute($cn);
372         while ( my @row = $sth->fetchrow_array ) {
373                 $client->respond([@row]);
374         }
375         $sth->finish;
376
377         $sth = asset::call_number->db_Main->prepare($bottom_sql);
378         $sth->execute($cn);
379         while ( my @row = $sth->fetchrow_array ) {
380                 $client->respond([@row]);
381         }
382         $sth->finish;
383
384         return undef;
385 }
386 __PACKAGE__->register_method(
387         method          => 'cn_browse_target',
388         api_name        => 'open-ils.storage.asset.call_number.browse.target',
389         argc            => 4,
390         stream          => 1,
391 );
392
393
394 sub copy_proximity {
395         my $self = shift;
396         my $client = shift;
397
398         my $cp = shift;
399         my $org = shift;        # hold pickup lib
400         my $hold = shift;
401
402         return unless ($cp && $org);
403
404         if ($hold) {
405                 my $row = action::hold_request->db_Main->selectrow_hashref(
406                         'SELECT proximity AS prox FROM action.hold_copy_map WHERE hold = ? and target_copy = ?',
407                         {},
408                         "$hold",
409                         "$cp"
410                 );
411                 return $row->{prox} if $row;
412
413                 # There was a bug here before.
414                 # action.hold_copy_calculated_proximity()  was called with a
415                 # third argument, $org.  Wrong.  a.hccp() interprets its third
416                 # argument as an optional override of copy circ lib.  $org
417                 # here is hold pickup lib.  This had the effect of basically
418                 # measuring the distance between a hold's pickup lib and
419                 # itself, which is always zero, so all proximities landing in
420                 # the hold copy map were zero.
421
422                 $log->debug("Calculating copy proximity with: action.hold_copy_calculated_proximity($hold,$cp)", DEBUG);
423                 $row = action::hold_request->db_Main->selectrow_hashref(
424                         'SELECT action.hold_copy_calculated_proximity(?,?) AS prox',
425                         {},
426                         "$hold",
427                         "$cp"
428                 );
429
430                 return $row->{prox} if $row;
431         }
432
433         $cp = asset::copy->retrieve($cp) unless (ref($cp));
434
435         return unless $cp;
436         my $ol = $cp->circ_lib;
437
438         return (actor::org_unit_proximity->search( from_org => "$ol", to_org => "$org"))[0]->prox;
439 }
440 __PACKAGE__->register_method(
441         method          => 'copy_proximity',
442         api_name        => 'open-ils.storage.asset.copy.proximity',
443         argc            => 2,
444         stream          => 1,
445 );
446
447 sub asset_copy_location_all {
448         my $self = shift;
449         my $client = shift;
450
451         for my $rec ( asset::copy_location->retrieve_all ) {
452                 $client->respond( $rec->to_fieldmapper );
453         }
454
455         return undef;
456 }
457 __PACKAGE__->register_method(
458         method          => 'asset_copy_location_all',
459         api_name        => 'open-ils.storage.direct.asset.copy_location.retrieve.all',
460         argc            => 0,
461         stream          => 1,
462 );
463
464 # XXX arg, with the descendancy SPs...
465 sub ranged_asset_copy_location {
466         my $self = shift;
467         my $client = shift;
468         my @binds = @_;
469         
470         my $ctable = asset::copy_location->table;
471         
472         my $descendants = defined($binds[1]) ?
473                 "actor.org_unit_full_path(?, ?)" :
474                 "actor.org_unit_full_path(?)" ;
475
476         
477         my $sql = <<"   SQL";
478                 SELECT  DISTINCT c.*
479                   FROM  $ctable c
480                         JOIN $descendants d
481                                 ON (d.id = c.owning_lib)
482                  ORDER BY name
483         SQL
484         
485         my $sth = asset::copy_location->db_Main->prepare($sql);
486         $sth->execute(@binds);
487         
488         while ( my $rec = $sth->fetchrow_hashref ) {
489         
490                 my $cnct = new Fieldmapper::asset::copy_location;
491                 map {$cnct->$_($$rec{$_})} keys %$rec;
492                 $client->respond( $cnct );
493         }
494
495         return undef;
496 }
497 __PACKAGE__->register_method(
498         method          => 'ranged_asset_copy_location',
499         api_name        => 'open-ils.storage.ranged.asset.copy_location.retrieve',
500         argc            => 1,
501         stream          => 1,
502 );
503
504
505 sub fleshed_copy {
506         my $self = shift;
507         my $client = shift;
508         my @ids = @_;
509
510         return undef unless (@ids);
511
512         @ids = ($ids[0]) unless ($self->api_name =~ /batch/o);
513
514         for my $id ( @ids ) {
515                 next unless $id;
516                 my $cp = asset::copy->retrieve($id);
517                 next unless $cp;
518
519                 my $cp_fm = $cp->to_fieldmapper;
520                 $cp_fm->circ_lib( $cp->circ_lib->to_fieldmapper );
521                 $cp_fm->location( $cp->location->to_fieldmapper );
522                 $cp_fm->status( $cp->status->to_fieldmapper );
523                 $cp_fm->stat_cat_entries( [ map { $_->to_fieldmapper } $cp->stat_cat_entries ] );
524
525                 $client->respond( $cp_fm );
526         }
527
528         return undef;
529 }
530 __PACKAGE__->register_method(
531         api_name        => 'open-ils.storage.fleshed.asset.copy.batch.retrieve',
532         method          => 'fleshed_copy',
533         argc            => 1,
534         stream          => 1,
535 );
536 __PACKAGE__->register_method(
537         api_name        => 'open-ils.storage.fleshed.asset.copy.retrieve',
538         method          => 'fleshed_copy',
539         argc            => 1,
540 );
541
542 sub fleshed_copy_by_barcode {
543         my $self = shift;
544         my $client = shift;
545         my $bc = ''.shift;
546
547         my ($cp) = asset::copy->search( { barcode => $bc } );
548
549         return undef unless ($cp);
550
551         my $cp_fm = $cp->to_fieldmapper;
552         $cp_fm->circ_lib( $cp->circ_lib->to_fieldmapper );
553         $cp_fm->location( $cp->location->to_fieldmapper );
554         $cp_fm->status( $cp->status->to_fieldmapper );
555
556         return $cp_fm;
557 }       
558 __PACKAGE__->register_method(
559         api_name        => 'open-ils.storage.fleshed.asset.copy.search.barcode',
560         method          => 'fleshed_copy_by_barcode',
561         argc            => 1,
562         stream          => 1,
563 );
564
565
566 #XXX Fix stored proc calls
567 sub fleshed_asset_stat_cat {
568         my $self = shift;
569         my $client = shift;
570         my @list = @_;
571
572         @list = ($list[0]) unless ($self->api_name =~ /batch/o);
573         for my $sc (@list) {
574                 my $cat = asset::stat_cat->retrieve($sc);
575                 
576                 next unless ($cat);
577
578                 my $sc_fm = $cat->to_fieldmapper;
579                 $sc_fm->entries( [ map { $_->to_fieldmapper } $cat->entries ] );
580                 $client->respond( $sc_fm );
581         }
582
583         return undef;
584 }
585 __PACKAGE__->register_method(
586         api_name        => 'open-ils.storage.fleshed.asset.stat_cat.retrieve',
587         api_level       => 1,
588         method          => 'fleshed_asset_stat_cat',
589 );
590
591 __PACKAGE__->register_method(
592         api_name        => 'open-ils.storage.fleshed.asset.stat_cat.retrieve.batch',
593         api_level       => 1,
594         stream          => 1,
595         method          => 'fleshed_asset_stat_cat',
596 );
597
598
599 #XXX Fix stored proc calls
600 sub ranged_asset_stat_cat {
601         my $self = shift;
602         my $client = shift;
603         my $ou = ''.shift();
604
605         return undef unless ($ou);
606         my $s_table = asset::stat_cat->table;
607
608         my $select = <<"        SQL";
609                 SELECT  s.*
610                   FROM  $s_table s
611                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
612                   ORDER BY name
613         SQL
614
615         $fleshed = 0;
616         $fleshed = 1 if ($self->api_name =~ /fleshed/o);
617
618         my $sth = asset::stat_cat->db_Main->prepare_cached($select);
619         $sth->execute($ou);
620
621         for my $sc ( map { asset::stat_cat->construct($_) } $sth->fetchall_hash ) {
622                 my $sc_fm = $sc->to_fieldmapper;
623                 $sc_fm->entries(
624                         [ $self->method_lookup( 'open-ils.storage.ranged.asset.stat_cat_entry.search.stat_cat' )->run($ou,$sc->id) ]
625                 ) if ($fleshed);
626                 $client->respond( $sc_fm );
627         }
628
629         return undef;
630 }
631 __PACKAGE__->register_method(
632         api_name        => 'open-ils.storage.ranged.fleshed.asset.stat_cat.all',
633         api_level       => 1,
634         stream          => 1,
635         method          => 'ranged_asset_stat_cat',
636 );
637
638 __PACKAGE__->register_method(
639         api_name        => 'open-ils.storage.ranged.asset.stat_cat.all',
640         api_level       => 1,
641         stream          => 1,
642         method          => 'ranged_asset_stat_cat',
643 );
644
645
646 #XXX Fix stored proc calls
647 sub multiranged_asset_stat_cat {
648         my $self = shift;
649         my $client = shift;
650         my $ous = shift;
651
652         return undef unless (defined($ous) and @$ous);
653         my $s_table = asset::stat_cat->table;
654
655         my $select = <<"        SQL";
656                 SELECT  s.*
657                   FROM  $s_table s
658                   WHERE s.owner IN ( XXX )
659                   ORDER BY name
660         SQL
661
662         my $collector = ' INTERSECT ';
663         my $entry_method = 'open-ils.storage.multiranged.intersect.asset.stat_cat_entry.search.stat_cat';
664         if ($self->api_name =~ /union/o) {
665                 $collector = ' UNION ';
666                 $entry_method = 'open-ils.storage.multiranged.union.asset.stat_cat_entry.search.stat_cat';
667         }
668
669         my $binds = join($collector, map { 'SELECT id FROM actor.org_unit_full_path(?)' } grep {defined} @$ous);
670         $select =~ s/XXX/$binds/so;
671         
672         $fleshed = 0;
673         $fleshed = 1 if ($self->api_name =~ /fleshed/o);
674
675         my $sth = asset::stat_cat->db_Main->prepare_cached($select);
676         $sth->execute(map { "$_" } grep {defined} @$ous);
677
678         for my $sc ( map { asset::stat_cat->construct($_) } $sth->fetchall_hash ) {
679                 my $sc_fm = $sc->to_fieldmapper;
680                 $sc_fm->entries(
681                         [ $self->method_lookup( $entry_method )->run($ous, $sc->id) ]
682                 ) if ($fleshed);
683                 $client->respond( $sc_fm );
684         }
685
686         return undef;
687 }
688 __PACKAGE__->register_method(
689         api_name        => 'open-ils.storage.multiranged.intersect.fleshed.asset.stat_cat.all',
690         api_level       => 1,
691         stream          => 1,
692         method          => 'multiranged_asset_stat_cat',
693 );
694 __PACKAGE__->register_method(
695         api_name        => 'open-ils.storage.multiranged.union.fleshed.asset.stat_cat.all',
696         api_level       => 1,
697         stream          => 1,
698         method          => 'multiranged_asset_stat_cat',
699 );
700
701 #XXX Fix stored proc calls
702 sub ranged_asset_stat_cat_entry {
703         my $self = shift;
704         my $client = shift;
705         my $ou = ''.shift();
706         my $sc = ''.shift();
707
708         return undef unless ($ou);
709         my $s_table = asset::stat_cat_entry->table;
710
711         my $select = <<"        SQL";
712                 SELECT  s.*
713                   FROM  $s_table s
714                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
715                   WHERE stat_cat = ?
716                   ORDER BY name
717         SQL
718
719         my $sth = asset::stat_cat->db_Main->prepare_cached($select);
720         $sth->execute($ou,$sc);
721
722         for my $sce ( map { asset::stat_cat_entry->construct($_) } $sth->fetchall_hash ) {
723                 $client->respond( $sce->to_fieldmapper );
724         }
725
726         return undef;
727 }
728 __PACKAGE__->register_method(
729         api_name        => 'open-ils.storage.ranged.asset.stat_cat_entry.search.stat_cat',
730         api_level       => 1,
731         stream          => 1,
732         method          => 'ranged_asset_stat_cat_entry',
733 );
734
735 #XXX Fix stored proc calls
736 sub multiranged_asset_stat_cat_entry {
737         my $self = shift;
738         my $client = shift;
739         my $ous = shift;
740         my $sc = ''.shift();
741
742         return undef unless (defined($ous) and @$ous);
743         my $s_table = asset::stat_cat_entry->table;
744
745         my $collector = ' INTERSECT ';
746         $collector = ' UNION ' if ($self->api_name =~ /union/o);
747
748         my $select = <<"        SQL";
749                 SELECT  s.*
750                   FROM  $s_table s
751                   WHERE s.owner IN ( XXX ) and s.stat_cat = ?
752                   ORDER BY value
753         SQL
754
755         my $binds = join($collector, map { 'SELECT id FROM actor.org_unit_full_path(?)' } grep {defined} @$ous);
756         $select =~ s/XXX/$binds/so;
757         
758         my $sth = asset::stat_cat->db_Main->prepare_cached($select);
759         $sth->execute(map {"$_"} @$ous,$sc);
760
761         for my $sce ( map { asset::stat_cat_entry->construct($_) } $sth->fetchall_hash ) {
762                 $client->respond( $sce->to_fieldmapper );
763         }
764
765         return undef;
766 }
767 __PACKAGE__->register_method(
768         api_name        => 'open-ils.storage.multiranged.intersect.asset.stat_cat_entry.search.stat_cat',
769         api_level       => 1,
770         stream          => 1,
771         method          => 'multiranged_asset_stat_cat_entry',
772 );
773 __PACKAGE__->register_method(
774         api_name        => 'open-ils.storage.multiranged.union.asset.stat_cat_entry.search.stat_cat',
775         api_level       => 1,
776         stream          => 1,
777         method          => 'multiranged_asset_stat_cat_entry',
778 );
779
780
781 sub cn_ranged_tree {
782         my $self = shift;
783         my $client = shift;
784         my $cn = shift;
785         my $ou = shift;
786         my $depth = shift || 0;
787
788         my $ou_list =
789                 actor::org_unit
790                         ->db_Main
791                         ->selectcol_arrayref(
792                                 'SELECT id FROM actor.org_unit_descendants(?,?)',
793                                 {},
794                                 $ou,
795                                 $depth
796                         );
797
798         return undef unless ($ou_list and @$ou_list);
799
800         $cn = asset::call_number->retrieve( $cn );
801         return undef unless ($cn);
802         return undef if ($cn->deleted);
803
804         my $call_number = $cn->to_fieldmapper;
805         $call_number->copies([]);
806
807         $call_number->record( $cn->record->to_fieldmapper );
808         $call_number->record->fixed_fields( $cn->record->record_descriptor->next->to_fieldmapper );
809
810         for my $cp ( $cn->copies(circ_lib => $ou_list) ) {
811                 next if ($cp->deleted);
812                 my $copy = $cp->to_fieldmapper;
813                 $copy->status( $cp->status->to_fieldmapper );
814                 $copy->location( $cp->location->to_fieldmapper );
815
816                 push @{ $call_number->copies }, $copy;
817         }
818
819         return $call_number;
820 }
821 __PACKAGE__->register_method(
822         api_name        => 'open-ils.storage.asset.call_number.ranged_tree',
823         method          => 'cn_ranged_tree',
824         argc            => 1,
825         api_level       => 1,
826 );
827
828
829 # XXX Since this is all we need in open-ils.storage for serial stuff ATM, just
830 # XXX putting it here instead of creating a whole new file.
831 sub issuance_ranged_tree {
832     my $self = shift;
833     my $client = shift;
834     my $iss = shift;
835     my $ou = shift;
836     my $depth = shift || 0;
837
838     my $ou_list =
839         actor::org_unit
840             ->db_Main
841             ->selectcol_arrayref(
842                 'SELECT id FROM actor.org_unit_descendants(?,?)',
843                 {},
844                 $ou,
845                 $depth
846             );
847
848     return undef unless ($ou_list and @$ou_list);
849
850     $iss = serial::issuance->retrieve( $iss );
851     return undef unless ($iss);
852
853     my $issuance = $iss->to_fieldmapper;
854     $issuance->items([]);
855
856     # Now, gather issuances on the same bib, with the same label and date_published ...
857     my @subs = map { $_->id } serial::subscription->search( record_entry => $iss->subscription->record_entry->id );
858
859     my @similar_iss = serial::issuance->search_where(
860         subscription => \@subs,
861         label => $iss->label,
862         date_published => $iss->date_published
863     );
864
865     # ... and add all /their/ items to the target issuance
866     for my $i ( @similar_iss ) {
867         for my $it ( $i->items() ) {
868             next unless $it->unit and not $it->unit->deleted;
869             next unless (grep { $it->unit->circ_lib eq $_ } @$ou_list);
870     
871             my $unit = $it->unit->to_fieldmapper;
872             $unit->status( $it->unit->status->to_fieldmapper );
873             $unit->location( $it->unit->location->to_fieldmapper );
874
875             my $item = $it->to_fieldmapper;
876             $item->unit( $unit );
877     
878             push @{ $issuance->items }, $item;
879         }
880     }
881
882     return $issuance;
883 }
884 __PACKAGE__->register_method(
885     api_name    => 'open-ils.storage.serial.issuance.ranged_tree',
886     method      => 'issuance_ranged_tree',
887     argc        => 1,
888     api_level   => 1,
889 );
890
891 sub merge_record_assets {
892         my $self = shift;
893         my $client = shift;
894         my $target = shift;
895         my @sources = @_;
896
897         my $count = 0;
898         for my $source ( @sources ) {
899                 $count += asset::call_number
900                                 ->db_Main
901                                 ->selectcol_arrayref(
902                                         "SELECT asset.merge_record_assets(?,?);",
903                                         {},
904                                         $target,
905                                         $source
906                                 )->[0];
907         }
908
909         return $count;
910 }
911 __PACKAGE__->register_method(
912         api_name        => 'open-ils.storage.asset.merge_record_assets',
913         method          => 'merge_record_assets',
914         argc            => 2,
915         api_level       => 1,
916 );
917
918 1;