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;
11 my $log = 'OpenSRF::Utils::Logger';
14 use MARC::File::XML ( BinaryEncoding => 'UTF-8' );
16 #our $_default_subfield_map = {
20 # circulating_lib => $cl,
21 # copy_location => $sl,
22 # copy_number => $num,
25 # create_date => $date,
27 # legacy_item_type => $it,
28 # legacy_item_cat_1 => $ic1,
29 # legacy_item_cat_2 => $ic2,
34 sub import_xml_holdings {
42 my $date_format = shift || 'mm/dd/yyyy';
44 ($record) = biblio::record_entry->search_where($record);
46 return 0 unless ($record);
48 my $r = MARC::Record->new_from_xml($xml);
51 for my $f ( $r->fields( $tag ) ) {
52 next unless ($f->subfield( $map->{owning_lib} ));
58 $org_cache{ $f->subfield( $map->{owning_lib} ) }
59 || actor::org_unit->search( shortname => $f->subfield( $map->{owning_lib} ) )->next->id;
61 $org_cache{ $f->subfield( $map->{owning_lib} ) } = $ol;
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 ).']');
69 $org_cache{ $f->subfield( $map->{circulating_lib} ) }
70 || actor::org_unit->search( shortname => $f->subfield( $map->{circulating_lib} ) )->next->id;
72 $org_cache{ $f->subfield( $map->{circulating_lib} ) } = $cl;
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 ).']');
78 next unless ($ol && $cl);
82 $cn = asset::call_number->find_or_create(
83 { label => $f->subfield( $map->{call_number} ),
85 record => $record->id,
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 ).']');
97 my $create_date = $f->subfield( $map->{create_date} );
100 if ($date_format eq 'mm/dd/yyyy') {
101 ($m,$d,$y) = split '/', $create_date;
103 } elsif ($date_format eq 'dd/mm/yyyy') {
104 ($d,$m,$y) = split '/', $create_date;
106 } elsif ($date_format eq 'mm-dd-yyyy') {
107 ($m,$d,$y) = split '-', $create_date;
109 } elsif ($date_format eq 'dd-mm-yyyy') {
110 ($d,$m,$y) = split '-', $create_date;
112 } elsif ($date_format eq 'yyyy-mm-dd') {
113 ($y,$m,$d) = split '-', $create_date;
115 } elsif ($date_format eq 'yyyy/mm/dd') {
116 ($y,$m,$d) = split '/', $create_date;
120 (undef,undef,undef,$d,$m,$y) = localtime;
125 my $price = $f->subfield( $map->{price} );
126 $price =~ s/[^0-9\.]+//gso;
132 copy_number => $f->subfield( $map->{copy_number} ),
134 barcode => $f->subfield( $map->{barcode} ),
139 create_date => sprintf('%04d-%02d-%02d',$y,$m,$d),
144 $log->debug('Could not create copy ['.$f->subfield( $map->{barcode} ).'] : '. shift(), ERROR);
150 __PACKAGE__->register_method(
151 method => 'import_xml_holdings',
152 api_name => 'open-ils.storage.asset.holdings.import.xml',
158 # see /home/miker/cn_browse-test.sql for page up and down sql ...
161 sub cn_browse_pagedown {
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;
174 my $table = asset::call_number->table;
176 my $descendants = "actor.org_unit_descendants($org)";
177 if (defined $depth) {
178 $descendants = "actor.org_unit_descendants($org,$depth)";
181 my $orgs = join(',', @{ asset::call_number->db_Main->selectcol_arrayref("SELECT DISTINCT id FROM $descendants;") });
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
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]);
208 __PACKAGE__->register_method(
209 method => 'cn_browse_pagedown',
210 api_name => 'open-ils.storage.asset.call_number.browse.page_down',
215 sub cn_browse_pageup {
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;
228 my $table = asset::call_number->table;
230 my $descendants = "actor.org_unit_descendants($org)";
231 if (defined $depth) {
232 $descendants = "actor.org_unit_descendants($org,$depth)";
235 my $orgs = join(',', @{ asset::call_number->db_Main->selectcol_arrayref("SELECT DISTINCT id FROM $descendants;") });
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
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]);
265 __PACKAGE__->register_method(
266 method => 'cn_browse_pageup',
267 api_name => 'open-ils.storage.asset.call_number.browse.page_up',
272 sub cn_browse_target {
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;
286 my $table = asset::call_number->table;
288 my $descendants = "actor.org_unit_descendants($org)";
289 if (defined $depth) {
290 $descendants = "actor.org_unit_descendants($org,$depth)";
293 my $orgs = join(',', @{ asset::call_number->db_Main->selectcol_arrayref("SELECT DISTINCT id FROM $descendants;") });
295 my $top_sql = <<" SQL";
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
314 my $bottom_sql = <<" SQL";
324 and oils_text_as_bytea(label) >= ?
325 and owning_lib in ($orgs)
326 order by oils_text_as_bytea(label),4,2
330 my $sth = asset::call_number->db_Main->prepare($top_sql);
332 while ( my @row = $sth->fetchrow_array ) {
333 $client->respond([@row]);
337 $sth = asset::call_number->db_Main->prepare($bottom_sql);
339 while ( my @row = $sth->fetchrow_array ) {
340 $client->respond([@row]);
346 __PACKAGE__->register_method(
347 method => 'cn_browse_target',
348 api_name => 'open-ils.storage.asset.call_number.browse.target',
359 my $org = shift; # hold pickup lib
362 return unless ($cp && $org);
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 = ?',
371 return $row->{prox} if $row;
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.
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',
390 return $row->{prox} if $row;
393 $cp = asset::copy->retrieve($cp) unless (ref($cp));
396 my $ol = $cp->circ_lib;
398 return (actor::org_unit_proximity->search( from_org => "$ol", to_org => "$org"))[0]->prox;
400 __PACKAGE__->register_method(
401 method => 'copy_proximity',
402 api_name => 'open-ils.storage.asset.copy.proximity',
407 sub asset_copy_location_all {
411 for my $rec ( asset::copy_location->retrieve_all ) {
412 next if $rec->deleted eq "t";
413 $client->respond( $rec->to_fieldmapper );
418 __PACKAGE__->register_method(
419 method => 'asset_copy_location_all',
420 api_name => 'open-ils.storage.direct.asset.copy_location.retrieve.all',
425 # XXX arg, with the descendancy SPs...
426 sub ranged_asset_copy_location {
431 my $ctable = asset::copy_location->table;
433 my $descendants = defined($binds[1]) ?
434 "actor.org_unit_full_path(?, ?)" :
435 "actor.org_unit_full_path(?)" ;
442 ON (d.id = c.owning_lib)
443 WHERE deleted IS FALSE
447 my $sth = asset::copy_location->db_Main->prepare($sql);
448 $sth->execute(@binds);
450 while ( my $rec = $sth->fetchrow_hashref ) {
452 my $cnct = new Fieldmapper::asset::copy_location;
453 map {$cnct->$_($$rec{$_})} keys %$rec;
454 $client->respond( $cnct );
459 __PACKAGE__->register_method(
460 method => 'ranged_asset_copy_location',
461 api_name => 'open-ils.storage.ranged.asset.copy_location.retrieve',
472 return undef unless (@ids);
474 @ids = ($ids[0]) unless ($self->api_name =~ /batch/o);
476 for my $id ( @ids ) {
478 my $cp = asset::copy->retrieve($id);
481 my $cp_fm = $cp->to_fieldmapper;
482 $cp_fm->circ_lib( $cp->circ_lib->to_fieldmapper );
483 $cp_fm->location( $cp->location->to_fieldmapper );
484 $cp_fm->status( $cp->status->to_fieldmapper );
485 $cp_fm->stat_cat_entries( [ map { $_->to_fieldmapper } $cp->stat_cat_entries ] );
487 $client->respond( $cp_fm );
492 __PACKAGE__->register_method(
493 api_name => 'open-ils.storage.fleshed.asset.copy.batch.retrieve',
494 method => 'fleshed_copy',
498 __PACKAGE__->register_method(
499 api_name => 'open-ils.storage.fleshed.asset.copy.retrieve',
500 method => 'fleshed_copy',
504 sub fleshed_copy_by_barcode {
509 my ($cp) = asset::copy->search( { barcode => $bc } );
511 return undef unless ($cp);
513 my $cp_fm = $cp->to_fieldmapper;
514 $cp_fm->circ_lib( $cp->circ_lib->to_fieldmapper );
515 $cp_fm->location( $cp->location->to_fieldmapper );
516 $cp_fm->status( $cp->status->to_fieldmapper );
520 __PACKAGE__->register_method(
521 api_name => 'open-ils.storage.fleshed.asset.copy.search.barcode',
522 method => 'fleshed_copy_by_barcode',
528 #XXX Fix stored proc calls
529 sub fleshed_asset_stat_cat {
534 @list = ($list[0]) unless ($self->api_name =~ /batch/o);
536 my $cat = asset::stat_cat->retrieve($sc);
540 my $sc_fm = $cat->to_fieldmapper;
541 $sc_fm->entries( [ map { $_->to_fieldmapper } $cat->entries ] );
542 $client->respond( $sc_fm );
547 __PACKAGE__->register_method(
548 api_name => 'open-ils.storage.fleshed.asset.stat_cat.retrieve',
550 method => 'fleshed_asset_stat_cat',
553 __PACKAGE__->register_method(
554 api_name => 'open-ils.storage.fleshed.asset.stat_cat.retrieve.batch',
557 method => 'fleshed_asset_stat_cat',
561 #XXX Fix stored proc calls
562 sub ranged_asset_stat_cat {
567 return undef unless ($ou);
568 my $s_table = asset::stat_cat->table;
570 my $select = <<" SQL";
573 JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
578 $fleshed = 1 if ($self->api_name =~ /fleshed/o);
580 my $sth = asset::stat_cat->db_Main->prepare_cached($select);
583 for my $sc ( map { asset::stat_cat->construct($_) } $sth->fetchall_hash ) {
584 my $sc_fm = $sc->to_fieldmapper;
586 [ $self->method_lookup( 'open-ils.storage.ranged.asset.stat_cat_entry.search.stat_cat' )->run($ou,$sc->id) ]
588 $client->respond( $sc_fm );
593 __PACKAGE__->register_method(
594 api_name => 'open-ils.storage.ranged.fleshed.asset.stat_cat.all',
597 method => 'ranged_asset_stat_cat',
600 __PACKAGE__->register_method(
601 api_name => 'open-ils.storage.ranged.asset.stat_cat.all',
604 method => 'ranged_asset_stat_cat',
608 #XXX Fix stored proc calls
609 sub multiranged_asset_stat_cat {
614 return undef unless (defined($ous) and @$ous);
615 my $s_table = asset::stat_cat->table;
617 my $select = <<" SQL";
620 WHERE s.owner IN ( XXX )
624 my $collector = ' INTERSECT ';
625 my $entry_method = 'open-ils.storage.multiranged.intersect.asset.stat_cat_entry.search.stat_cat';
626 if ($self->api_name =~ /union/o) {
627 $collector = ' UNION ';
628 $entry_method = 'open-ils.storage.multiranged.union.asset.stat_cat_entry.search.stat_cat';
631 my $binds = join($collector, map { 'SELECT id FROM actor.org_unit_full_path(?)' } grep {defined} @$ous);
632 $select =~ s/XXX/$binds/so;
635 $fleshed = 1 if ($self->api_name =~ /fleshed/o);
637 my $sth = asset::stat_cat->db_Main->prepare_cached($select);
638 $sth->execute(map { "$_" } grep {defined} @$ous);
640 for my $sc ( map { asset::stat_cat->construct($_) } $sth->fetchall_hash ) {
641 my $sc_fm = $sc->to_fieldmapper;
643 [ $self->method_lookup( $entry_method )->run($ous, $sc->id) ]
645 $client->respond( $sc_fm );
650 __PACKAGE__->register_method(
651 api_name => 'open-ils.storage.multiranged.intersect.fleshed.asset.stat_cat.all',
654 method => 'multiranged_asset_stat_cat',
656 __PACKAGE__->register_method(
657 api_name => 'open-ils.storage.multiranged.union.fleshed.asset.stat_cat.all',
660 method => 'multiranged_asset_stat_cat',
663 #XXX Fix stored proc calls
664 sub ranged_asset_stat_cat_entry {
670 return undef unless ($ou);
671 my $s_table = asset::stat_cat_entry->table;
673 my $select = <<" SQL";
676 JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
681 my $sth = asset::stat_cat->db_Main->prepare_cached($select);
682 $sth->execute($ou,$sc);
684 for my $sce ( map { asset::stat_cat_entry->construct($_) } $sth->fetchall_hash ) {
685 $client->respond( $sce->to_fieldmapper );
690 __PACKAGE__->register_method(
691 api_name => 'open-ils.storage.ranged.asset.stat_cat_entry.search.stat_cat',
694 method => 'ranged_asset_stat_cat_entry',
697 #XXX Fix stored proc calls
698 sub multiranged_asset_stat_cat_entry {
704 return undef unless (defined($ous) and @$ous);
705 my $s_table = asset::stat_cat_entry->table;
707 my $collector = ' INTERSECT ';
708 $collector = ' UNION ' if ($self->api_name =~ /union/o);
710 my $select = <<" SQL";
713 WHERE s.owner IN ( XXX ) and s.stat_cat = ?
717 my $binds = join($collector, map { 'SELECT id FROM actor.org_unit_full_path(?)' } grep {defined} @$ous);
718 $select =~ s/XXX/$binds/so;
720 my $sth = asset::stat_cat->db_Main->prepare_cached($select);
721 $sth->execute(map {"$_"} @$ous,$sc);
723 for my $sce ( map { asset::stat_cat_entry->construct($_) } $sth->fetchall_hash ) {
724 $client->respond( $sce->to_fieldmapper );
729 __PACKAGE__->register_method(
730 api_name => 'open-ils.storage.multiranged.intersect.asset.stat_cat_entry.search.stat_cat',
733 method => 'multiranged_asset_stat_cat_entry',
735 __PACKAGE__->register_method(
736 api_name => 'open-ils.storage.multiranged.union.asset.stat_cat_entry.search.stat_cat',
739 method => 'multiranged_asset_stat_cat_entry',
748 my $depth = shift || 0;
753 ->selectcol_arrayref(
754 'SELECT id FROM actor.org_unit_descendants(?,?)',
760 return undef unless ($ou_list and @$ou_list);
762 $cn = asset::call_number->retrieve( $cn );
763 return undef unless ($cn);
764 return undef if ($cn->deleted);
766 my $call_number = $cn->to_fieldmapper;
767 $call_number->copies([]);
769 $call_number->record( $cn->record->to_fieldmapper );
770 $call_number->record->fixed_fields( $cn->record->record_descriptor->next->to_fieldmapper );
772 for my $cp ( $cn->copies(circ_lib => $ou_list) ) {
773 next if ($cp->deleted);
774 my $copy = $cp->to_fieldmapper;
775 $copy->status( $cp->status->to_fieldmapper );
776 $copy->location( $cp->location->to_fieldmapper );
778 push @{ $call_number->copies }, $copy;
783 __PACKAGE__->register_method(
784 api_name => 'open-ils.storage.asset.call_number.ranged_tree',
785 method => 'cn_ranged_tree',
791 # XXX Since this is all we need in open-ils.storage for serial stuff ATM, just
792 # XXX putting it here instead of creating a whole new file.
793 sub issuance_ranged_tree {
798 my $depth = shift || 0;
803 ->selectcol_arrayref(
804 'SELECT id FROM actor.org_unit_descendants(?,?)',
810 return undef unless ($ou_list and @$ou_list);
812 $iss = serial::issuance->retrieve( $iss );
813 return undef unless ($iss);
815 my $issuance = $iss->to_fieldmapper;
816 $issuance->items([]);
818 # Now, gather issuances on the same bib, with the same label and date_published ...
819 my @subs = map { $_->id } serial::subscription->search( record_entry => $iss->subscription->record_entry->id );
821 my @similar_iss = serial::issuance->search_where(
822 subscription => \@subs,
823 label => $iss->label,
824 date_published => $iss->date_published
827 # ... and add all /their/ items to the target issuance
828 for my $i ( @similar_iss ) {
829 for my $it ( $i->items() ) {
830 next unless $it->unit and not $it->unit->deleted;
831 next unless (grep { $it->unit->circ_lib eq $_ } @$ou_list);
833 my $unit = $it->unit->to_fieldmapper;
834 $unit->status( $it->unit->status->to_fieldmapper );
835 $unit->location( $it->unit->location->to_fieldmapper );
837 my $item = $it->to_fieldmapper;
838 $item->unit( $unit );
840 push @{ $issuance->items }, $item;
846 __PACKAGE__->register_method(
847 api_name => 'open-ils.storage.serial.issuance.ranged_tree',
848 method => 'issuance_ranged_tree',
853 sub merge_record_assets {
860 for my $source ( @sources ) {
861 $count += asset::call_number
863 ->selectcol_arrayref(
864 "SELECT asset.merge_record_assets(?,?);",
873 __PACKAGE__->register_method(
874 api_name => 'open-ils.storage.asset.merge_record_assets',
875 method => 'merge_record_assets',