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