1 # We'll be working with XML, so...
4 use Unicode::Normalize;
6 # ... and this has some handy common methods
7 use OpenILS::Application::AppUtils;
9 use OpenILS::Application::SuperCat::OAI;
11 my $parser = new XML::LibXML;
12 my $U = 'OpenILS::Application::AppUtils';
15 package OpenILS::Application::SuperCat;
19 use OpenILS::Utils::Normalize qw( naco_normalize );
21 # All OpenSRF applications must be based on OpenSRF::Application or
22 # a subclass thereof. Makes sense, eh?
23 use OpenILS::Application;
24 use base qw/OpenILS::Application/;
26 # This is the client class, used for connecting to open-ils.storage
27 use OpenSRF::AppSession;
29 # This is an extension of Error.pm that supplies some error types to throw
30 use OpenSRF::EX qw(:try);
32 # This is a helper class for querying the OpenSRF Settings application ...
33 use OpenSRF::Utils::SettingsClient;
35 # ... and here we have the built in logging helper ...
36 use OpenSRF::Utils::Logger qw($logger);
38 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
39 use OpenILS::Utils::Fieldmapper;
41 use OpenILS::Utils::CStoreEditor q/:funcs/;
42 use OpenILS::Utils::TagURI;
51 %authority_browse_axis_cache,
55 # we need an XML parser
56 $_parser = new XML::LibXML;
59 $_xslt = new XML::LibXSLT;
61 # parse the MODS xslt ...
62 my $mods33_xslt = $_parser->parse_file(
63 OpenSRF::Utils::SettingsClient
65 ->config_value( dirs => 'xsl' ).
66 "/MARC21slim2MODS33.xsl"
68 # and stash a transformer
69 $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
70 $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
71 $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
72 $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
74 # parse the MODS xslt ...
75 my $mods32_xslt = $_parser->parse_file(
76 OpenSRF::Utils::SettingsClient
78 ->config_value( dirs => 'xsl' ).
79 "/MARC21slim2MODS32.xsl"
81 # and stash a transformer
82 $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
83 $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
84 $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
85 $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
87 # parse the MODS xslt ...
88 my $mods3_xslt = $_parser->parse_file(
89 OpenSRF::Utils::SettingsClient
91 ->config_value( dirs => 'xsl' ).
92 "/MARC21slim2MODS3.xsl"
94 # and stash a transformer
95 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
96 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
97 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
98 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
100 # parse the MODS xslt ...
101 my $mods_xslt = $_parser->parse_file(
102 OpenSRF::Utils::SettingsClient
104 ->config_value( dirs => 'xsl' ).
105 "/MARC21slim2MODS.xsl"
107 # and stash a transformer
108 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
109 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
110 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
111 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
113 # parse the ATOM entry xslt ...
114 my $atom_xslt = $_parser->parse_file(
115 OpenSRF::Utils::SettingsClient
117 ->config_value( dirs => 'xsl' ).
118 "/MARC21slim2ATOM.xsl"
120 # and stash a transformer
121 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
122 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
123 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
125 # parse the RDFDC xslt ...
126 my $rdf_dc_xslt = $_parser->parse_file(
127 OpenSRF::Utils::SettingsClient
129 ->config_value( dirs => 'xsl' ).
130 "/MARC21slim2RDFDC.xsl"
132 # and stash a transformer
133 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
134 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
135 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
137 # parse the SRWDC xslt ...
138 my $srw_dc_xslt = $_parser->parse_file(
139 OpenSRF::Utils::SettingsClient
141 ->config_value( dirs => 'xsl' ).
142 "/MARC21slim2SRWDC.xsl"
144 # and stash a transformer
145 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
146 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
147 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
149 # parse the OAIDC xslt ...
150 my $oai_dc_xslt = $_parser->parse_file(
151 OpenSRF::Utils::SettingsClient
153 ->config_value( dirs => 'xsl' ).
154 "/MARC21slim2OAIDC.xsl"
156 # and stash a transformer
157 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
158 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
159 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
161 # parse the RSS xslt ...
162 my $rss_xslt = $_parser->parse_file(
163 OpenSRF::Utils::SettingsClient
165 ->config_value( dirs => 'xsl' ).
166 "/MARC21slim2RSS2.xsl"
168 # and stash a transformer
169 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
171 # parse the FGDC xslt ...
172 my $fgdc_xslt = $_parser->parse_file(
173 OpenSRF::Utils::SettingsClient
175 ->config_value( dirs => 'xsl' ).
176 "/MARC21slim2FGDC.xsl"
178 # and stash a transformer
179 $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
180 $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
181 $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
183 register_record_transforms();
185 register_new_authorities_methods();
187 OpenILS::Application::SuperCat::OAI->child_init();
192 sub register_record_transforms {
193 for my $type ( keys %record_xslt ) {
194 __PACKAGE__->register_method(
195 method => 'retrieve_record_transform',
196 api_name => "open-ils.supercat.record.$type.retrieve",
200 { desc => "Returns the \U$type\E representation ".
201 "of the requested bibliographic record",
205 desc => 'An OpenILS biblio::record_entry id',
209 { desc => "The bib record in \U$type\E",
214 __PACKAGE__->register_method(
215 method => 'retrieve_isbn_transform',
216 api_name => "open-ils.supercat.isbn.$type.retrieve",
220 { desc => "Returns the \U$type\E representation ".
221 "of the requested bibliographic record",
229 { desc => "The bib record in \U$type\E",
236 sub register_new_authorities_methods {
237 my %register_args = (
238 method => "generic_new_authorities_method",
242 desc => q/Generated method/,
245 desc => "An axis, an authority tag number, or a bibliographic tag number, depending on invocation",
248 desc => "A search term",
251 desc => "zero-based page number of results",
253 {name => "page size",
254 desc => "number of results per page",
258 desc => "A list of authority record IDs", type => "array"
263 foreach my $how (qw/axis atag btag/) {
264 foreach my $action (qw/browse_center browse_top
265 search_rank search_heading/) {
267 $register_args{api_name} =
268 "open-ils.supercat.authority.$action.by_$how";
269 __PACKAGE__->register_method(%register_args);
271 $register_args{api_name} =
272 "open-ils.supercat.authority.$action.by_$how.refs";
273 __PACKAGE__->register_method(%register_args);
279 sub generic_new_authorities_method {
283 # We want to be extra careful with these arguments, since the next
284 # thing we're doing with them is passing them to a DB procedure.
287 my $page = int(shift || 0);
288 my $page_size = shift;
289 my $thesauruses = shift;
291 # undef ok, but other non numbers not ok
292 $page_size = int($page_size) if defined $page_size;
294 # Figure out how we were called and what DB procedure we'll call in turn.
295 $self->api_name =~ /\.by_(\w+)($|\.)/;
299 $self->api_name =~ /authority\.(\w+)\./;
302 my $method = "${metaaxis}_$action";
303 $method .= "_refs" if $refs;
305 # Match authority.full_rec normalization
306 # XXX don't know whether we need second arg 'subfield'?
307 $term = naco_normalize($term);
309 my $storage = create OpenSRF::AppSession("open-ils.storage");
310 my $list = $storage->request(
311 "open-ils.storage.authority.in_db.browse_or_search",
312 $method, $what, $term, $page, $page_size, $thesauruses
326 return unless ($tree && ref($tree->$field));
328 my @things = $filter->($tree);
329 for my $v ( @{$tree->$field} ){
330 push @things, $filter->($v);
331 push @things, tree_walker($v, $field, $filter);
336 # find a label_sortkey for a call number with a label which is equal
337 # (or close to) a given label value
338 sub _label_sortkey_from_label {
339 my ($label, $_storage, $ou_ids, $cp_filter) = @_;
341 my $closest_cn = $_storage->request(
342 "open-ils.cstore.direct.asset.call_number.search.atomic",
343 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
344 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
349 flesh_fields => { acn => [qw/label_class/] },
351 order_by => { acn => "oils_text_as_bytea(label), id" }
355 if ($closest_cn->[0]->label eq $label) {
356 # we found an exact match stop here
357 return $closest_cn->[0]->label_sortkey;
359 # we got as close as we could by label alone, let's try to
360 # normalize and get closer
361 $closest_cn = $_storage->request(
362 "open-ils.cstore.direct.asset.call_number.search.atomic",
364 => { ">=" => [$closest_cn->[0]->label_class->normalizer, $label] },
365 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
370 order_by => { acn => "label_sortkey, id" }
374 return $closest_cn->[0]->label_sortkey;
379 return '~~~'; #fallback to high ascii value, we are at the end of the range
388 my $page_size = shift || 9;
389 my $page = shift || 0;
390 my $statuses = shift || [];
391 my $copy_locations = shift || [];
393 my ($before_limit,$after_limit) = (0,0);
394 my ($before_offset,$after_offset) = (0,0);
397 $before_limit = $after_limit = int($page_size / 2);
398 $after_limit += 1 if ($page_size % 2);
400 $before_offset = $after_offset = int($page_size / 2);
401 $before_offset += 1 if ($page_size % 2);
402 $before_limit = $after_limit = $page_size;
405 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
408 # if OU is not specified, or if '-' is specified, do not enter the block
409 unless (!$ou || $ou eq '-') {
410 # check if the shortname of the top org_unit is specified
411 my $top_org = $_storage->request(
412 "open-ils.cstore.direct.actor.org_unit.search",
413 { parent_ou => undef }
416 if ($ou eq $top_org->shortname) {
417 $logger->debug("Searching for CNs at top org $ou");
419 my $o_search = { shortname => $ou };
421 my $orgs = $_storage->request(
422 "open-ils.cstore.direct.actor.org_unit.search",
425 flesh_fields => { aou => [qw/children/] }
429 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
431 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
438 if (@$statuses || @$copy_locations) {
444 call_number => { '=' => { '+acn' => 'id' } },
446 ((@$statuses) ? ( status => $statuses) : ()),
447 ((@$copy_locations) ? ( location => $copy_locations) : ())
453 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
456 my $before = $_storage->request(
457 "open-ils.cstore.direct.asset.call_number.search.atomic",
458 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
459 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
464 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
465 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
466 limit => $before_limit,
467 offset => abs($page) * $page_size - $before_offset,
470 push @list, reverse(@$before);
474 my $after = $_storage->request(
475 "open-ils.cstore.direct.asset.call_number.search.atomic",
476 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
477 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
482 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
483 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
484 limit => $after_limit,
485 offset => abs($page) * $page_size - $after_offset,
493 __PACKAGE__->register_method(
494 method => 'cn_browse',
495 api_name => 'open-ils.supercat.call_number.browse',
500 Returns the XML representation of the requested bibliographic record's holdings
505 desc => 'The target call number label',
507 { name => 'org_unit',
508 desc => 'The org unit shortname (or "-" or undef for global) to browse',
510 { name => 'page_size',
511 desc => 'Count of call numbers to retrieve, default is 9',
514 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
516 { name => 'statuses',
517 desc => 'Array of statuses to filter copies by, optional and can be undef.',
519 { name => 'locations',
520 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
524 { desc => 'Call numbers with owning_lib and record fleshed',
535 my $limit = shift || 10;
536 my $page = shift || 0;
537 my $statuses = shift || [];
538 my $copy_locations = shift || [];
541 my $offset = abs($page) * $limit;
542 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
544 my $o_search = { shortname => $ou };
545 if (!$ou || $ou eq '-') {
546 $o_search = { parent_ou => undef };
549 my $orgs = $_storage->request(
550 "open-ils.cstore.direct.actor.org_unit.search",
553 flesh_fields => { aou => [qw/children/] }
557 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
559 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
564 if (@$statuses || @$copy_locations) {
570 call_number => { '=' => { '+acn' => 'id' } },
572 ((@$statuses) ? ( status => $statuses) : ()),
573 ((@$copy_locations) ? ( location => $copy_locations) : ())
579 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
582 my $before = $_storage->request(
583 "open-ils.cstore.direct.asset.call_number.search.atomic",
584 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
585 owning_lib => \@ou_ids,
590 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
591 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
596 push @list, reverse(@$before);
600 my $after = $_storage->request(
601 "open-ils.cstore.direct.asset.call_number.search.atomic",
602 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
603 owning_lib => \@ou_ids,
608 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
609 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
619 __PACKAGE__->register_method(
620 method => 'cn_startwith',
621 api_name => 'open-ils.supercat.call_number.startwith',
626 Returns the XML representation of the requested bibliographic record's holdings
631 desc => 'The target call number label',
633 { name => 'org_unit',
634 desc => 'The org unit shortname (or "-" or undef for global) to browse',
636 { name => 'page_size',
637 desc => 'Count of call numbers to retrieve, default is 9',
640 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
642 { name => 'statuses',
643 desc => 'Array of statuses to filter copies by, optional and can be undef.',
645 { name => 'locations',
646 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
650 { desc => 'Call numbers with owning_lib and record fleshed',
656 sub new_books_by_item {
661 my $page_size = shift || 10;
662 my $page = shift || 1;
663 my $statuses = shift || [];
664 my $copy_locations = shift || [];
666 my $offset = $page_size * ($page - 1);
668 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
671 if ($ou && $ou ne '-') {
672 my $orgs = $_storage->request(
673 "open-ils.cstore.direct.actor.org_unit.search",
674 { shortname => $ou },
676 flesh_fields => { aou => [qw/children/] }
679 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
682 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
683 my $cns = $_storage->request(
684 "open-ils.cstore.json_query.atomic",
685 { select => { acn => ['record'],
686 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
688 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
692 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
693 ((@$statuses) ? ( status => $statuses) : ()),
694 ((@$copy_locations) ? ( location => $copy_locations) : ())
696 '+acn' => { record => { '>' => 0 } },
698 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
704 return [ map { $_->{record} } @$cns ];
706 __PACKAGE__->register_method(
707 method => 'new_books_by_item',
708 api_name => 'open-ils.supercat.new_book_list',
713 Returns the XML representation of the requested bibliographic record's holdings
717 { name => 'org_unit',
718 desc => 'The org unit shortname (or "-" or undef for global) to list',
720 { name => 'page_size',
721 desc => 'Count of records to retrieve, default is 10',
724 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
726 { name => 'statuses',
727 desc => 'Array of statuses to filter copies by, optional and can be undef.',
729 { name => 'locations',
730 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
734 { desc => 'Record IDs',
744 my $format = shift || 'xml';
746 $u2 = OpenILS::Utils::TagURI->new($u2);
747 return '' unless $u2;
749 # Use pathinfo on acp as a lookup type specifier.
750 if ($u2->classname eq 'acp' and $u2->pathinfo =~ /\bbarcode\b/) {
751 my( $copy, $evt ) = $U->fetch_copy_by_barcode( $u2->id );
752 $u2->id( $copy->id );
755 if ($u2->classname eq 'biblio_record_entry_feed') {
756 $u2->id( '{' . $u2->id . '}' );
759 'unapi.' . $u2->classname,
763 push @$args, $u2->classname unless $u2->classname eq 'biblio_record_entry_feed';
764 push @$args, '{' . ( $u2->includes ? join( ',', keys %{ $u2->includes } ) : '' ) . '}';
765 push @$args, ($u2->location || undef);
766 push @$args, ($u2->depth || undef);
768 return OpenSRF::AppSession->create('open-ils.cstore')->request(
769 "open-ils.cstore.json_query.atomic",
771 )->gather(1)->[0]{'unapi.'. $u2->classname};
773 __PACKAGE__->register_method(
775 api_name => 'open-ils.supercat.u2',
780 Returns the XML representation of the requested object
785 desc => 'The U2 Tag URI (OpenILS::Utils::TagURI)',
788 desc => 'For bre and bre feeds, the xml transform format',
792 { desc => 'XML (or transformed) object data',
801 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
803 __PACKAGE__->register_method(
804 method => 'general_browse',
805 api_name => 'open-ils.supercat.title.browse',
806 tag => 'tnf', subfield => 'a',
810 { desc => "Returns a list of the requested org-scoped record IDs held",
812 [ { name => 'value', desc => 'The target title', type => 'string' },
813 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
814 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
815 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
816 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
817 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
818 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
821 __PACKAGE__->register_method(
822 method => 'general_browse',
823 api_name => 'open-ils.supercat.author.browse',
824 tag => [qw/100 110 111/], subfield => 'a',
828 { desc => "Returns a list of the requested org-scoped record IDs held",
830 [ { name => 'value', desc => 'The target author', type => 'string' },
831 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
832 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
833 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
834 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
835 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
836 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
839 __PACKAGE__->register_method(
840 method => 'general_browse',
841 api_name => 'open-ils.supercat.subject.browse',
842 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
846 { desc => "Returns a list of the requested org-scoped record IDs held",
848 [ { name => 'value', desc => 'The target subject', type => 'string' },
849 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
850 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
851 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
852 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
853 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
854 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
857 __PACKAGE__->register_method(
858 method => 'general_browse',
859 api_name => 'open-ils.supercat.topic.browse',
860 tag => [qw/650 690/], subfield => 'a',
864 { desc => "Returns a list of the requested org-scoped record IDs held",
866 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
867 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
868 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
869 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
870 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
871 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
872 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
875 __PACKAGE__->register_method(
876 method => 'general_browse',
877 api_name => 'open-ils.supercat.series.browse',
878 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
882 { desc => "Returns a list of the requested org-scoped record IDs held",
884 [ { name => 'value', desc => 'The target series', type => 'string' },
885 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
886 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
887 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
888 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
889 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
890 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
900 my $subfield = shift;
903 my $page_size = shift || 9;
904 my $page = shift || 0;
905 my $statuses = shift || [];
906 my $copy_locations = shift || [];
908 my ($before_limit,$after_limit) = (0,0);
909 my ($before_offset,$after_offset) = (0,0);
912 $before_limit = $after_limit = int($page_size / 2);
913 $after_limit += 1 if ($page_size % 2);
915 $before_offset = $after_offset = int($page_size / 2);
916 $before_offset += 1 if ($page_size % 2);
917 $before_limit = $after_limit = $page_size;
920 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
923 if ($ou && $ou ne '-') {
924 my $orgs = $_storage->request(
925 "open-ils.cstore.direct.actor.org_unit.search",
926 { shortname => $ou },
928 flesh_fields => { aou => [qw/children/] }
931 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
934 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
939 my $before = $_storage->request(
940 "open-ils.cstore.json_query.atomic",
941 { select => { mfr => [qw/record value/] },
946 subfield => $subfield,
947 value => { '<' => lc($value) }
951 { select=> { acp => [ 'id' ] },
952 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
954 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
957 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
958 ((@$statuses) ? ( status => $statuses) : ()),
959 ((@$copy_locations) ? ( location => $copy_locations) : ())
966 { select=> { auri => [ 'id' ] },
967 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
969 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
970 '+auri' => { active => 't' }
977 order_by => { mfr => { value => 'desc' } },
978 limit => $before_limit,
979 offset => abs($page) * $page_size - $before_offset,
982 push @list, map { $_->{record} } reverse(@$before);
986 my $after = $_storage->request(
987 "open-ils.cstore.json_query.atomic",
988 { select => { mfr => [qw/record value/] },
993 subfield => $subfield,
994 value => { '>=' => lc($value) }
998 { select=> { acp => [ 'id' ] },
999 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1001 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1004 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1005 ((@$statuses) ? ( status => $statuses) : ()),
1006 ((@$copy_locations) ? ( location => $copy_locations) : ())
1013 { select=> { auri => [ 'id' ] },
1014 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1016 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1017 '+auri' => { active => 't' }
1024 order_by => { mfr => { value => 'asc' } },
1025 limit => $after_limit,
1026 offset => abs($page) * $page_size - $after_offset,
1029 push @list, map { $_->{record} } @$after;
1034 __PACKAGE__->register_method(
1035 method => 'tag_sf_browse',
1036 api_name => 'open-ils.supercat.tag.browse',
1040 { desc => <<" DESC",
1041 Returns a list of the requested org-scoped record IDs held
1046 desc => 'The target MARC tag',
1048 { name => 'subfield',
1049 desc => 'The target MARC subfield',
1052 desc => 'The target string',
1054 { name => 'org_unit',
1055 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1057 { name => 'page_size',
1058 desc => 'Count of call numbers to retrieve, default is 9',
1061 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1063 { name => 'statuses',
1064 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1066 { name => 'locations',
1067 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1071 { desc => 'Record IDs that have copies at the relevant org units',
1076 sub grab_authority_browse_axes {
1077 my ($self, $client, $full) = @_;
1079 unless(scalar(keys(%authority_browse_axis_cache))) {
1080 my $axes = new_editor->search_authority_browse_axis([
1081 { code => { '<>' => undef } },
1082 { flesh => 2, flesh_fields => { aba => ['fields'], acsaf => ['bib_fields','sub_entries'] } }
1084 $authority_browse_axis_cache{$_->code} = $_ for (@$axes);
1089 map { $authority_browse_axis_cache{$_} } sort keys %authority_browse_axis_cache
1092 return [keys %authority_browse_axis_cache];
1095 __PACKAGE__->register_method(
1096 method => 'grab_authority_browse_axes',
1097 api_name => 'open-ils.supercat.authority.browse_axis_list',
1101 { desc => "Returns a list of valid authority browse/startswith axes",
1103 { name => 'full', desc => 'Optional. If true, return array containing the full object for each axis, sorted by code. Otherwise just return an array of the codes.', type => 'number' }
1105 'return' => { desc => 'Axis codes or whole axes, see "full" param', type => 'array' }
1109 sub axis_authority_browse {
1114 $axis =~ s/^authority\.//;
1115 $axis =~ s/(\.refs)$//;
1118 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1121 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1122 push @tags, $f->tag;
1124 push @tags, $_->tag for @{$f->sub_entries};
1128 return authority_tag_sf_browse($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1130 __PACKAGE__->register_method(
1131 method => 'axis_authority_browse',
1132 api_name => 'open-ils.supercat.authority.browse.by_axis',
1136 { desc => "Returns a list of the requested authority record IDs held",
1138 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1139 { name => 'value', desc => 'The target value', type => 'string' },
1140 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1141 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1142 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1148 sub general_authority_browse {
1151 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
1153 __PACKAGE__->register_method(
1154 method => 'general_authority_browse',
1155 api_name => 'open-ils.supercat.authority.title.browse',
1156 tag => ['130'], subfield => 'a',
1160 { desc => "Returns a list of the requested authority record IDs held",
1162 [ { name => 'value', desc => 'The target title', type => 'string' },
1163 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1164 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1165 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1168 __PACKAGE__->register_method(
1169 method => 'general_authority_browse',
1170 api_name => 'open-ils.supercat.authority.author.browse',
1171 tag => [qw/100 110 111/], subfield => 'a',
1175 { desc => "Returns a list of the requested authority record IDs held",
1177 [ { name => 'value', desc => 'The target author', type => 'string' },
1178 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1179 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1180 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1183 __PACKAGE__->register_method(
1184 method => 'general_authority_browse',
1185 api_name => 'open-ils.supercat.authority.subject.browse',
1186 tag => [qw/148 150 151 155/], subfield => 'a',
1190 { desc => "Returns a list of the requested authority record IDs held",
1192 [ { name => 'value', desc => 'The target subject', type => 'string' },
1193 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1194 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1195 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1198 __PACKAGE__->register_method(
1199 method => 'general_authority_browse',
1200 api_name => 'open-ils.supercat.authority.topic.browse',
1201 tag => ['150'], subfield => 'a',
1205 { desc => "Returns a list of the requested authority record IDs held",
1207 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1208 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1209 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1210 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1213 __PACKAGE__->register_method(
1214 method => 'general_authority_browse',
1215 api_name => 'open-ils.supercat.authority.title.refs.browse',
1216 tag => ['130'], subfield => 'a',
1220 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1222 [ { name => 'value', desc => 'The target title', type => 'string' },
1223 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1224 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1225 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1228 __PACKAGE__->register_method(
1229 method => 'general_authority_browse',
1230 api_name => 'open-ils.supercat.authority.author.refs.browse',
1231 tag => [qw/100 110 111/], subfield => 'a',
1235 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1237 [ { name => 'value', desc => 'The target author', type => 'string' },
1238 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1239 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1240 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1243 __PACKAGE__->register_method(
1244 method => 'general_authority_browse',
1245 api_name => 'open-ils.supercat.authority.subject.refs.browse',
1246 tag => [qw/148 150 151 155/], subfield => 'a',
1250 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1252 [ { name => 'value', desc => 'The target subject', type => 'string' },
1253 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1254 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1255 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1258 __PACKAGE__->register_method(
1259 method => 'general_authority_browse',
1260 api_name => 'open-ils.supercat.authority.topic.refs.browse',
1261 tag => ['150'], subfield => 'a',
1265 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1267 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1268 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1269 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1270 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1276 sub authority_tag_sf_browse {
1281 my $subfield = shift;
1283 my $page_size = shift || 9;
1284 my $page = shift || 0;
1286 # Match authority.full_rec normalization
1287 $value = naco_normalize($value, $subfield);
1289 my ($before_limit,$after_limit) = (0,0);
1290 my ($before_offset,$after_offset) = (0,0);
1293 $before_limit = $after_limit = int($page_size / 2);
1294 $after_limit += 1 if ($page_size % 2);
1296 $before_offset = $after_offset = int($page_size / 2);
1297 $before_offset += 1 if ($page_size % 2);
1298 $before_limit = $after_limit = $page_size;
1301 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1303 # .refs variant includes 4xx and 5xx variants for see / see also
1305 foreach my $tagname (@$tag) {
1306 push(@ref_tags, $tagname);
1307 if ($self->api_name =~ /\.refs\./) {
1308 push(@ref_tags, '4' . substr($tagname, 1, 2));
1309 push(@ref_tags, '5' . substr($tagname, 1, 2));
1315 my $before = $_storage->request(
1316 "open-ils.cstore.json_query.atomic",
1317 { select => { afr => [qw/record value/] },
1319 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1320 order_by => { afr => { value => 'desc' } },
1321 limit => $before_limit,
1322 offset => abs($page) * $page_size - $before_offset,
1325 push @list, map { $_->{record} } reverse(@$before);
1329 my $after = $_storage->request(
1330 "open-ils.cstore.json_query.atomic",
1331 { select => { afr => [qw/record value/] },
1333 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1334 order_by => { afr => { value => 'asc' } },
1335 limit => $after_limit,
1336 offset => abs($page) * $page_size - $after_offset,
1339 push @list, map { $_->{record} } @$after;
1342 # If we're not pulling in see/see also references, just return the raw list
1343 if ($self->api_name !~ /\.refs\./) {
1347 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1350 foreach my $record (@list) {
1351 next if exists $seen{$record};
1352 push @retlist, int($record);
1358 __PACKAGE__->register_method(
1359 method => 'authority_tag_sf_browse',
1360 api_name => 'open-ils.supercat.authority.tag.browse',
1364 { desc => <<" DESC",
1365 Returns a list of the requested authority record IDs held
1370 desc => 'The target Authority MARC tag',
1372 { name => 'subfield',
1373 desc => 'The target Authority MARC subfield',
1376 desc => 'The target string',
1378 { name => 'page_size',
1379 desc => 'Count of call numbers to retrieve, default is 9',
1382 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1386 { desc => 'Authority Record IDs that are near the target string',
1391 sub general_startwith {
1394 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1396 __PACKAGE__->register_method(
1397 method => 'general_startwith',
1398 api_name => 'open-ils.supercat.title.startwith',
1399 tag => 'tnf', subfield => 'a',
1403 { desc => "Returns a list of the requested org-scoped record IDs held",
1405 [ { name => 'value', desc => 'The target title', type => 'string' },
1406 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1407 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1408 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1409 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1410 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1411 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1414 __PACKAGE__->register_method(
1415 method => 'general_startwith',
1416 api_name => 'open-ils.supercat.author.startwith',
1417 tag => [qw/100 110 111/], subfield => 'a',
1421 { desc => "Returns a list of the requested org-scoped record IDs held",
1423 [ { name => 'value', desc => 'The target author', type => 'string' },
1424 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1425 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1426 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1427 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1428 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1429 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1432 __PACKAGE__->register_method(
1433 method => 'general_startwith',
1434 api_name => 'open-ils.supercat.subject.startwith',
1435 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1439 { desc => "Returns a list of the requested org-scoped record IDs held",
1441 [ { name => 'value', desc => 'The target subject', type => 'string' },
1442 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1443 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1444 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1445 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1446 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1447 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1450 __PACKAGE__->register_method(
1451 method => 'general_startwith',
1452 api_name => 'open-ils.supercat.topic.startwith',
1453 tag => [qw/650 690/], subfield => 'a',
1457 { desc => "Returns a list of the requested org-scoped record IDs held",
1459 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1460 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1461 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1462 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1463 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1464 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1465 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1468 __PACKAGE__->register_method(
1469 method => 'general_startwith',
1470 api_name => 'open-ils.supercat.series.startwith',
1471 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1475 { desc => "Returns a list of the requested org-scoped record IDs held",
1477 [ { name => 'value', desc => 'The target series', type => 'string' },
1478 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1479 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1480 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1481 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1482 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1483 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1488 sub tag_sf_startwith {
1493 my $subfield = shift;
1496 my $limit = shift || 10;
1497 my $page = shift || 0;
1498 my $statuses = shift || [];
1499 my $copy_locations = shift || [];
1501 my $offset = $limit * abs($page);
1502 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1505 if ($ou && $ou ne '-') {
1506 my $orgs = $_storage->request(
1507 "open-ils.cstore.direct.actor.org_unit.search",
1508 { shortname => $ou },
1510 flesh_fields => { aou => [qw/children/] }
1513 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1516 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1521 my $before = $_storage->request(
1522 "open-ils.cstore.json_query.atomic",
1523 { select => { mfr => [qw/record value/] },
1528 subfield => $subfield,
1529 value => { '<' => lc($value) }
1533 { select=> { acp => [ 'id' ] },
1534 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1536 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1539 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1540 ((@$statuses) ? ( status => $statuses) : ()),
1541 ((@$copy_locations) ? ( location => $copy_locations) : ())
1548 { select=> { auri => [ 'id' ] },
1549 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1551 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1552 '+auri' => { active => 't' }
1559 order_by => { mfr => { value => 'desc' } },
1564 push @list, map { $_->{record} } reverse(@$before);
1568 my $after = $_storage->request(
1569 "open-ils.cstore.json_query.atomic",
1570 { select => { mfr => [qw/record value/] },
1575 subfield => $subfield,
1576 value => { '>=' => lc($value) }
1580 { select=> { acp => [ 'id' ] },
1581 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1583 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1586 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1587 ((@$statuses) ? ( status => $statuses) : ()),
1588 ((@$copy_locations) ? ( location => $copy_locations) : ())
1595 { select=> { auri => [ 'id' ] },
1596 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1598 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1599 '+auri' => { active => 't' }
1606 order_by => { mfr => { value => 'asc' } },
1611 push @list, map { $_->{record} } @$after;
1616 __PACKAGE__->register_method(
1617 method => 'tag_sf_startwith',
1618 api_name => 'open-ils.supercat.tag.startwith',
1622 { desc => <<" DESC",
1623 Returns a list of the requested org-scoped record IDs held
1628 desc => 'The target MARC tag',
1630 { name => 'subfield',
1631 desc => 'The target MARC subfield',
1634 desc => 'The target string',
1636 { name => 'org_unit',
1637 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1639 { name => 'page_size',
1640 desc => 'Count of call numbers to retrieve, default is 9',
1643 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1645 { name => 'statuses',
1646 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1648 { name => 'locations',
1649 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1653 { desc => 'Record IDs that have copies at the relevant org units',
1658 sub axis_authority_startwith {
1663 $axis =~ s/^authority\.//;
1664 $axis =~ s/(\.refs)$//;
1667 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1670 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1671 push @tags, $f->tag;
1673 push @tags, $_->tag for @{$f->sub_entries};
1677 return authority_tag_sf_startwith($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1679 __PACKAGE__->register_method(
1680 method => 'axis_authority_startwith',
1681 api_name => 'open-ils.supercat.authority.startwith.by_axis',
1685 { desc => "Returns a list of the requested authority record IDs held",
1687 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1688 { name => 'value', desc => 'The target value', type => 'string' },
1689 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1690 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1691 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1697 sub general_authority_startwith {
1700 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1702 __PACKAGE__->register_method(
1703 method => 'general_authority_startwith',
1704 api_name => 'open-ils.supercat.authority.title.startwith',
1705 tag => ['130'], subfield => 'a',
1709 { desc => "Returns a list of the requested authority record IDs held",
1711 [ { name => 'value', desc => 'The target title', type => 'string' },
1712 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1713 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1714 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1717 __PACKAGE__->register_method(
1718 method => 'general_authority_startwith',
1719 api_name => 'open-ils.supercat.authority.author.startwith',
1720 tag => [qw/100 110 111/], subfield => 'a',
1724 { desc => "Returns a list of the requested authority record IDs held",
1726 [ { name => 'value', desc => 'The target author', type => 'string' },
1727 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1728 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1729 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1732 __PACKAGE__->register_method(
1733 method => 'general_authority_startwith',
1734 api_name => 'open-ils.supercat.authority.subject.startwith',
1735 tag => [qw/148 150 151 155/], subfield => 'a',
1739 { desc => "Returns a list of the requested authority record IDs held",
1741 [ { name => 'value', desc => 'The target subject', type => 'string' },
1742 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1743 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1744 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1747 __PACKAGE__->register_method(
1748 method => 'general_authority_startwith',
1749 api_name => 'open-ils.supercat.authority.topic.startwith',
1750 tag => ['150'], subfield => 'a',
1754 { desc => "Returns a list of the requested authority record IDs held",
1756 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1757 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1758 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1759 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1762 __PACKAGE__->register_method(
1763 method => 'general_authority_startwith',
1764 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1765 tag => ['130'], subfield => 'a',
1769 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1771 [ { name => 'value', desc => 'The target title', type => 'string' },
1772 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1773 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1774 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1777 __PACKAGE__->register_method(
1778 method => 'general_authority_startwith',
1779 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1780 tag => [qw/100 110 111/], subfield => 'a',
1784 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1786 [ { name => 'value', desc => 'The target author', type => 'string' },
1787 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1788 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1789 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1792 __PACKAGE__->register_method(
1793 method => 'general_authority_startwith',
1794 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1795 tag => [qw/148 150 151 155/], subfield => 'a',
1799 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1801 [ { name => 'value', desc => 'The target subject', type => 'string' },
1802 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1803 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1804 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1807 __PACKAGE__->register_method(
1808 method => 'general_authority_startwith',
1809 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1810 tag => ['150'], subfield => 'a',
1814 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1816 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1817 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1818 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1819 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1825 sub authority_tag_sf_startwith {
1830 my $subfield = shift;
1833 my $limit = shift || 10;
1834 my $page = shift || 0;
1836 # Match authority.full_rec normalization
1837 $value = naco_normalize($value, $subfield);
1839 my $ref_limit = $limit;
1840 my $offset = $limit * abs($page);
1841 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1844 # .refs variant includes 4xx and 5xx variants for see / see also
1845 foreach my $tagname (@$tag) {
1846 push(@ref_tags, $tagname);
1847 if ($self->api_name =~ /\.refs\./) {
1848 push(@ref_tags, '4' . substr($tagname, 1, 2));
1849 push(@ref_tags, '5' . substr($tagname, 1, 2));
1856 # Don't skip the first actual page of results in descending order
1857 $offset = $offset - $limit;
1859 my $before = $_storage->request(
1860 "open-ils.cstore.json_query.atomic",
1861 { select => { afr => [qw/record value/] },
1863 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1864 order_by => { afr => { value => 'desc' } },
1865 limit => $ref_limit,
1869 push @list, map { $_->{record} } reverse(@$before);
1873 my $after = $_storage->request(
1874 "open-ils.cstore.json_query.atomic",
1875 { select => { afr => [qw/record value/] },
1877 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1878 order_by => { afr => { value => 'asc' } },
1879 limit => $ref_limit,
1883 push @list, map { $_->{record} } @$after;
1886 # If we're not pulling in see/see also references, just return the raw list
1887 if ($self->api_name !~ /\.refs\./) {
1891 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1894 foreach my $record (@list) {
1895 next if exists $seen{$record};
1896 push @retlist, int($record);
1902 __PACKAGE__->register_method(
1903 method => 'authority_tag_sf_startwith',
1904 api_name => 'open-ils.supercat.authority.tag.startwith',
1908 { desc => <<" DESC",
1909 Returns a list of the requested authority record IDs held
1914 desc => 'The target Authority MARC tag',
1916 { name => 'subfield',
1917 desc => 'The target Authority MARC subfield',
1920 desc => 'The target string',
1922 { name => 'page_size',
1923 desc => 'Count of call numbers to retrieve, default is 10',
1926 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1930 { desc => 'Authority Record IDs that are near the target string',
1936 sub holding_data_formats {
1939 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1940 docs => 'http://www.loc.gov/marcxml/',
1941 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1945 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1946 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1947 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1950 __PACKAGE__->register_method(
1951 method => 'retrieve_uri',
1952 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1956 { desc => <<" DESC",
1957 Returns a fleshed call number object
1962 desc => 'An OpenILS asset::uri id',
1966 { desc => 'fleshed uri',
1974 my $args = shift || {};
1976 return OpenILS::Application::SuperCat::unAPI
1977 ->new(OpenSRF::AppSession
1978 ->create( 'open-ils.cstore' )
1980 "open-ils.cstore.direct.asset.uri.retrieve",
1984 auri => [qw/call_number_maps/],
1985 auricnm => [qw/call_number/],
1986 acn => [qw/owning_lib record prefix suffix/],
1993 __PACKAGE__->register_method(
1994 method => 'retrieve_copy',
1995 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1999 { desc => <<" DESC",
2000 Returns a fleshed call number object
2005 desc => 'An OpenILS asset::copy id',
2009 { desc => 'fleshed copy',
2017 my $args = shift || {};
2019 return OpenILS::Application::SuperCat::unAPI
2020 ->new(OpenSRF::AppSession
2021 ->create( 'open-ils.cstore' )
2023 "open-ils.cstore.direct.asset.copy.retrieve",
2027 acn => [qw/owning_lib record prefix suffix/],
2028 acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
2029 asce => [qw/stat_cat/],
2036 __PACKAGE__->register_method(
2037 method => 'retrieve_callnumber',
2038 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
2043 { desc => <<" DESC",
2044 Returns a fleshed call number object
2049 desc => 'An OpenILS asset::call_number id',
2053 { desc => 'call number with copies',
2057 sub retrieve_callnumber {
2061 my $args = shift || {};
2063 return OpenILS::Application::SuperCat::unAPI
2064 ->new(OpenSRF::AppSession
2065 ->create( 'open-ils.cstore' )
2067 "open-ils.cstore.direct.asset.call_number.retrieve",
2071 acn => [qw/owning_lib record copies uri_maps prefix suffix/],
2072 auricnm => [qw/uri/],
2073 acp => [qw/location status circ_lib stat_cat_entries notes parts/],
2074 asce => [qw/stat_cat/],
2082 __PACKAGE__->register_method(
2083 method => 'basic_record_holdings',
2084 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
2089 { desc => <<" DESC",
2090 Returns a basic hash representation of the requested bibliographic record's holdings
2095 desc => 'An OpenILS biblio::record_entry id',
2099 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
2103 sub basic_record_holdings {
2109 # holdings hold an array of call numbers, which hold an array of copies
2110 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
2113 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2115 my $tree = $_storage->request(
2116 "open-ils.cstore.direct.biblio.record_entry.retrieve",
2120 bre => [qw/call_numbers/],
2121 acn => [qw/copies owning_lib prefix suffix/],
2122 acp => [qw/location status circ_lib parts/],
2127 my $o_search = { shortname => uc($ou) };
2128 if (!$ou || $ou eq '-') {
2129 $o_search = { parent_ou => undef };
2132 my $orgs = $_storage->request(
2133 "open-ils.cstore.direct.actor.org_unit.search",
2136 flesh_fields => { aou => [qw/children/] }
2140 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
2142 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2144 for my $cn (@{$tree->call_numbers}) {
2145 next unless ( $cn->deleted eq 'f' || !$cn->deleted );
2148 for my $c (@{$cn->copies}) {
2149 next unless grep {$c->circ_lib->id == $_} @ou_ids;
2150 next unless _cp_is_visible($cn, $c);
2156 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
2158 for my $cp (@{$cn->copies}) {
2160 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
2161 next unless _cp_is_visible($cn, $cp);
2163 push @{$holdings{$cn->label}{'copies'}}, {
2164 barcode => $cp->barcode,
2165 status => $cp->status->name,
2166 location => $cp->location->name,
2167 circlib => $cp->circ_lib->shortname
2176 sub _cp_is_visible {
2181 if ( ($cp->deleted eq 'f' || !$cp->deleted) &&
2182 $cp->location->opac_visible eq 't' &&
2183 $cp->status->opac_visible eq 't' &&
2184 $cp->opac_visible eq 't' &&
2185 $cp->circ_lib->opac_visible eq 't' &&
2186 $cn->owning_lib->opac_visible eq 't'
2194 #__PACKAGE__->register_method(
2195 # method => 'new_record_holdings',
2196 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2201 # { desc => <<" DESC",
2202 #Returns the XML representation of the requested bibliographic record's holdings
2206 # { name => 'bibId',
2207 # desc => 'An OpenILS biblio::record_entry id',
2208 # type => 'number' },
2211 # { desc => 'Stream of bib record holdings hierarchy in XML',
2212 # type => 'string' }
2217 sub new_record_holdings {
2226 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
2227 my $limit = $$paging[0];
2228 my $offset = $$paging[1] || 0;
2230 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2231 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
2233 my $o_search = { shortname => uc($ou) };
2234 if (!$ou || $ou eq '-') {
2235 $o_search = { parent_ou => undef };
2238 my $one_org = $_storage->request(
2239 "open-ils.cstore.direct.actor.org_unit.search",
2243 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
2244 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
2246 my $orgs = $_storage->request(
2247 'open-ils.cstore.json_query.atomic',
2248 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
2252 my @ou_ids = map { $_->{id} } @$orgs;
2254 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2256 my %subselect = ( '-or' => [
2257 { owning_lib => \@ou_ids },
2261 call_number => { '=' => {'+acn'=>'id'} },
2263 circ_lib => \@ou_ids
2270 # we are dealing with -full or -uris, so we need to flesh things out
2273 # either way we're going to need uris
2274 # get all the uris up the tree (see also ba47ecc6196)
2276 my $uri_orgs = $_storage->request(
2277 'open-ils.cstore.json_query.atomic',
2278 { from => [ 'actor.org_unit_ancestors', $one_org->id ] }
2281 my @uri_ou_ids = map { $_->{id} } @$uri_orgs;
2283 # we have a -uris, just get the uris
2286 owning_lib => \@uri_ou_ids,
2289 from => { auricnm => 'auri' },
2291 call_number => { '=' => {'+acn'=>'id'} },
2292 '+auri' => { active => 't' }
2296 # we have a -full, get all the things
2297 } elsif ($flesh == 1) {
2298 %subselect = ( '-or' => [
2299 { owning_lib => \@ou_ids },
2303 call_number => { '=' => {'+acn'=>'id'} },
2305 circ_lib => \@ou_ids
2311 { owning_lib => \@uri_ou_ids },
2313 from => { auricnm => 'auri' },
2315 call_number => { '=' => {'+acn'=>'id'} },
2316 '+auri' => { active => 't' }
2325 my $cns = $_storage->request(
2326 "open-ils.cstore.direct.asset.call_number.search.atomic",
2333 acn => [qw/copies owning_lib uri_maps prefix suffix/],
2334 auricnm => [qw/uri/],
2335 acp => [qw/circ_lib location status stat_cat_entries notes parts/],
2336 asce => [qw/stat_cat/],
2338 ( $limit > -1 ? ( limit => $limit ) : () ),
2339 ( $offset ? ( offset => $offset ) : () ),
2340 order_by => { acn => { label_sortkey => {} } }
2344 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2348 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
2350 my $copy_counts = $count_req->gather(1);
2351 my $staff_copy_counts = $staff_count_req->gather(1);
2353 for my $c (@$copy_counts) {
2354 $$c{transcendant} ||= 0;
2355 my $out = "<count type='public'";
2356 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2357 $client->respond("$out/>\n")
2360 for my $c (@$staff_copy_counts) {
2361 $$c{transcendant} ||= 0;
2362 my $out = "<count type='staff'";
2363 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2364 $client->respond("$out/>\n")
2367 $client->respond("</counts><volumes>\n");
2369 for my $cn (@$cns) {
2370 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2372 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2373 # In the context of BibTemplate, copies aren't necessary because we pull those
2374 # in a separate call
2376 OpenILS::Application::SuperCat::unAPI::acn
2378 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2382 $client->respond("</volumes><subscriptions>\n");
2384 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2386 %subselect = ( '-or' => [
2387 { owning_lib => \@ou_ids },
2390 where => { holding_lib => \@ou_ids },
2396 my $ssubs = $_storage->request(
2397 "open-ils.cstore.direct.serial.subscription.search.atomic",
2398 { record_entry => $bib,
2403 ssub => [qw/distributions issuances scaps owning_lib/],
2404 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2405 sstr => [qw/items/],
2406 sitem => [qw/notes unit/],
2407 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2408 asce => [qw/stat_cat/],
2409 acn => [qw/owning_lib prefix suffix/],
2411 ( $limit > -1 ? ( limit => $limit ) : () ),
2412 ( $offset ? ( offset => $offset ) : () ),
2424 date_expected => {},
2431 for my $ssub (@$ssubs) {
2432 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2434 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2435 # In the context of BibTemplate, copies aren't necessary because we pull those
2436 # in a separate call
2438 OpenILS::Application::SuperCat::unAPI::ssub
2440 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2445 return "</subscriptions></holdings>\n";
2447 __PACKAGE__->register_method(
2448 method => 'new_record_holdings',
2449 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2454 { desc => <<" DESC",
2455 Returns the XML representation of the requested bibliographic record's holdings
2460 desc => 'An OpenILS biblio::record_entry ID',
2462 { name => 'orgUnit',
2463 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2466 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2468 { name => 'hideCopies',
2469 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2470 type => 'boolean' },
2472 desc => 'Arry of limit and offset for holdings paging',
2476 { desc => 'Stream of bib record holdings hierarchy in XML',
2486 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2488 my $recs = $_storage->request(
2489 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2490 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2493 return undef unless (@$recs);
2495 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2497 __PACKAGE__->register_method(
2498 method => 'isbn_holdings',
2499 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2503 { desc => <<" DESC",
2504 Returns the XML representation of the requested bibliographic record's holdings
2513 { desc => 'The bib record holdings hierarchy in XML',
2521 return '' unless $text;
2522 $text =~ s/&/&/gsom;
2523 $text =~ s/</</gsom;
2524 $text =~ s/>/>/gsom;
2525 $text =~ s/"/"/gsom;
2526 $text =~ s/'/'/gsom;
2530 sub recent_changes {
2533 my $when = shift || '1-01-01';
2536 my $type = 'biblio';
2539 if ($self->api_name =~ /authority/o) {
2540 $type = 'authority';
2544 my $axis = 'create_date';
2545 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2547 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2549 return $_storage->request(
2550 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2551 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2552 { order_by => { $hint => "$axis desc" }, limit => $limit }
2556 for my $t ( qw/biblio authority/ ) {
2557 for my $a ( qw/import edit/ ) {
2559 __PACKAGE__->register_method(
2560 method => 'recent_changes',
2561 api_name => "open-ils.supercat.$t.record.$a.recent",
2565 { desc => "Returns a list of recently ${a}ed $t records",
2569 desc => "Date to start looking for ${a}ed records",
2570 default => '1-01-01',
2574 desc => "Maximum count to retrieve",
2578 { desc => "An id list of $t records",
2586 sub retrieve_authority_marcxml {
2591 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2593 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2594 return $U->entityize( $record->marc ) if ($record);
2598 __PACKAGE__->register_method(
2599 method => 'retrieve_authority_marcxml',
2600 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2604 { desc => <<" DESC",
2605 Returns the MARCXML representation of the requested authority record
2609 { name => 'authorityId',
2610 desc => 'An OpenILS authority::record_entry id',
2614 { desc => 'The authority record in MARCXML',
2619 sub retrieve_record_marcxml {
2624 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2626 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2627 return $U->entityize( $record->marc ) if ($record);
2631 __PACKAGE__->register_method(
2632 method => 'retrieve_record_marcxml',
2633 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2637 { desc => <<" DESC",
2638 Returns the MARCXML representation of the requested bibliographic record
2643 desc => 'An OpenILS biblio::record_entry id',
2647 { desc => 'The bib record in MARCXML',
2652 sub retrieve_isbn_marcxml {
2657 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2659 my $recs = $_storage->request(
2660 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2661 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2664 return undef unless (@$recs);
2666 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2667 return $U->entityize( $record->marc ) if ($record);
2671 __PACKAGE__->register_method(
2672 method => 'retrieve_isbn_marcxml',
2673 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2677 { desc => <<" DESC",
2678 Returns the MARCXML representation of the requested ISBN
2683 desc => 'An ... um ... ISBN',
2687 { desc => 'The bib record in MARCXML',
2692 sub retrieve_record_transform {
2697 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2698 my $xslt = $record_xslt{$transform}{xslt};
2700 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2701 #$_storage->connect;
2703 my $record = $_storage->request(
2704 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2708 return undef unless ($record);
2710 return $U->entityize($xslt->output_as_chars($xslt->transform($_parser->parse_string($record->marc))));
2713 sub retrieve_isbn_transform {
2718 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2720 my $recs = $_storage->request(
2721 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2722 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2725 return undef unless (@$recs);
2727 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2728 my $xslt = $record_xslt{$transform}{xslt};
2730 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2732 return undef unless ($record);
2734 return $U->entityize($xslt->output_as_chars($xslt->transform($_parser->parse_string($record->marc))));
2737 sub retrieve_record_objects {
2742 my $type = 'biblio';
2744 if ($self->api_name =~ /authority/) {
2745 $type = 'authority';
2748 $ids = [$ids] unless (ref $ids);
2749 $ids = [grep {$_} @$ids];
2751 return [] unless (@$ids);
2753 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2754 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2756 __PACKAGE__->register_method(
2757 method => 'retrieve_record_objects',
2758 api_name => 'open-ils.supercat.record.object.retrieve',
2762 { desc => <<" DESC",
2763 Returns the Fieldmapper object representation of the requested bibliographic records
2768 desc => 'OpenILS biblio::record_entry ids',
2772 { desc => 'The bib records',
2777 __PACKAGE__->register_method(
2778 method => 'retrieve_record_objects',
2779 api_name => 'open-ils.supercat.authority.object.retrieve',
2783 { desc => <<" DESC",
2784 Returns the Fieldmapper object representation of the requested authority records
2788 { name => 'authIds',
2789 desc => 'OpenILS authority::record_entry ids',
2793 { desc => 'The authority records',
2798 sub retrieve_isbn_object {
2803 return undef unless ($isbn);
2805 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2806 my $recs = $_storage->request(
2807 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2808 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2811 return undef unless (@$recs);
2813 return $_storage->request(
2814 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2815 { id => $recs->[0]->record }
2818 __PACKAGE__->register_method(
2819 method => 'retrieve_isbn_object',
2820 api_name => 'open-ils.supercat.isbn.object.retrieve',
2824 { desc => <<" DESC",
2825 Returns the Fieldmapper object representation of the requested bibliographic record
2834 { desc => 'The bib record',
2841 sub retrieve_metarecord_mods {
2846 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2848 # Get the metarecord in question
2851 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2854 # Now get the map of all bib records for the metarecord
2857 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2858 {metarecord => $rid}
2861 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2863 # and retrieve the lead (master) record as MODS
2865 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2866 ->run($mr->master_record);
2867 my $master_mods = $_parser->parse_string($master)->documentElement;
2868 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2869 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2871 # ... and a MODS clone to populate, with guts removed.
2872 my $mods = $_parser->parse_string($master)->documentElement;
2873 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2874 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2875 ($mods) = $mods->findnodes('//mods:mods');
2876 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2877 $mods->removeChildNodes;
2878 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2880 # Add the metarecord ID as a (locally defined) info URI
2881 my $recordInfo = $mods
2883 ->createElement("recordInfo");
2885 my $recordIdentifier = $mods
2887 ->createElement("recordIdentifier");
2889 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2894 $recordIdentifier->appendTextNode(
2895 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2898 $recordInfo->appendChild($recordIdentifier);
2899 $mods->appendChild($recordInfo);
2901 # Grab the title, author and ISBN for the master record and populate the metarecord
2902 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2905 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2906 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2907 $title = $mods->ownerDocument->importNode($title);
2908 $mods->appendChild($title);
2911 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2913 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2914 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2915 $author = $mods->ownerDocument->importNode($author);
2916 $mods->appendChild($author);
2919 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2921 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2922 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2923 $isbn = $mods->ownerDocument->importNode($isbn);
2924 $mods->appendChild($isbn);
2927 # ... and loop over the constituent records
2928 for my $map ( @$recs ) {
2932 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2933 ->run($map->source);
2935 my $part_mods = $_parser->parse_string($rec);
2936 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2937 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2938 ($part_mods) = $part_mods->findnodes('//mods:mods');
2940 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2941 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2942 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2943 $node = $mods->ownerDocument->importNode($node);
2944 $mods->appendChild( $node );
2947 my $relatedItem = $mods
2949 ->createElement("relatedItem");
2951 $relatedItem->setAttribute( type => 'constituent' );
2953 my $identifier = $mods
2955 ->createElement("identifier");
2957 $identifier->setAttribute( type => 'uri' );
2959 my $subRecordInfo = $mods
2961 ->createElement("recordInfo");
2963 my $subRecordIdentifier = $mods
2965 ->createElement("recordIdentifier");
2967 my $subid = $map->source;
2968 $subRecordIdentifier->appendTextNode(
2969 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2974 $subRecordInfo->appendChild($subRecordIdentifier);
2976 $relatedItem->appendChild( $subRecordInfo );
2978 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2979 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2980 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2981 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2982 $relatedItem->appendChild($tor) if ($tor);
2984 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2985 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2986 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2987 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2988 $relatedItem->appendChild( $part_isbn );
2991 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2995 $mods->appendChild( $relatedItem );
2999 $_storage->disconnect;
3001 return $U->entityize($mods->toString);
3004 __PACKAGE__->register_method(
3005 method => 'retrieve_metarecord_mods',
3006 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
3010 { desc => <<" DESC",
3011 Returns the MODS representation of the requested metarecord
3015 { name => 'metarecordId',
3016 desc => 'An OpenILS metabib::metarecord id',
3020 { desc => 'The metarecord in MODS',
3025 sub list_metarecord_formats {
3028 { namespace_uri => 'http://www.loc.gov/mods/',
3029 docs => 'http://www.loc.gov/mods/',
3030 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
3035 for my $type ( keys %metarecord_xslt ) {
3038 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
3039 docs => $metarecord_xslt{$type}{docs},
3040 schema_location => $metarecord_xslt{$type}{schema_location},
3047 __PACKAGE__->register_method(
3048 method => 'list_metarecord_formats',
3049 api_name => 'open-ils.supercat.metarecord.formats',
3053 { desc => <<" DESC",
3054 Returns the list of valid metarecord formats that supercat understands.
3057 { desc => 'The format list',
3063 sub list_authority_formats {
3066 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3067 docs => 'http://www.loc.gov/marcxml/',
3068 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3070 marc21 => { docs => 'http://www.loc.gov/marc/' }
3074 # for my $type ( keys %record_xslt ) {
3077 # { namespace_uri => $record_xslt{$type}{namespace_uri},
3078 # docs => $record_xslt{$type}{docs},
3079 # schema_location => $record_xslt{$type}{schema_location},
3086 __PACKAGE__->register_method(
3087 method => 'list_authority_formats',
3088 api_name => 'open-ils.supercat.authority.formats',
3092 { desc => <<" DESC",
3093 Returns the list of valid authority formats that supercat understands.
3096 { desc => 'The format list',
3101 sub list_record_formats {
3104 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3105 docs => 'http://www.loc.gov/marcxml/',
3106 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3109 { marc21 => { docs => 'http://www.loc.gov/marc/' } }
3112 for my $type ( keys %record_xslt ) {
3115 { namespace_uri => $record_xslt{$type}{namespace_uri},
3116 docs => $record_xslt{$type}{docs},
3117 schema_location => $record_xslt{$type}{schema_location},
3124 __PACKAGE__->register_method(
3125 method => 'list_record_formats',
3126 api_name => 'open-ils.supercat.record.formats',
3130 { desc => <<" DESC",
3131 Returns the list of valid record formats that supercat understands.
3134 { desc => 'The format list',
3138 __PACKAGE__->register_method(
3139 method => 'list_record_formats',
3140 api_name => 'open-ils.supercat.isbn.formats',
3144 { desc => <<" DESC",
3145 Returns the list of valid record formats that supercat understands.
3148 { desc => 'The format list',
3161 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
3162 unless (length($isbn) >= 10);
3164 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3166 # Create a storage session, since we'll be making muliple requests.
3169 # Find the record that has that ISBN.
3170 my $bibrec = $_storage->request(
3171 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
3172 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
3175 # Go away if we don't have one.
3176 return {} unless (@$bibrec);
3178 # Find the metarecord for that bib record.
3179 my $mr = $_storage->request(
3180 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3181 {source => $bibrec->[0]->record}
3184 # Find the other records for that metarecord.
3185 my $records = $_storage->request(
3186 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3187 {metarecord => $mr->[0]->metarecord}
3190 # Just to be safe. There's currently no unique constraint on sources...
3191 my %unique_recs = map { ($_->source, 1) } @$records;
3192 my @rec_list = sort keys %unique_recs;
3194 # And now fetch the ISBNs for thos records.
3198 'open-ils.cstore.direct.metabib.full_rec.search',
3199 { tag => '020', subfield => 'a', record => $_ }
3200 )->gather(1) for (@rec_list);
3202 # We're done with the storage server session.
3203 $_storage->disconnect;
3205 # Return the oISBN data structure. This will be XMLized at a higher layer.
3207 { metarecord => $mr->[0]->metarecord,
3208 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
3211 __PACKAGE__->register_method(
3213 api_name => 'open-ils.supercat.oisbn',
3217 { desc => <<" DESC",
3218 Returns the ISBN list for the metarecord of the requested isbn
3223 desc => 'An ISBN. Duh.',
3227 { desc => 'record to isbn map',
3232 sub return_bib_search_aliases {
3235 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3237 my $cmsa = $_storage->request(
3238 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
3239 { alias => { '!=' => undef } }
3243 if ($_->alias =~ /\./) {
3244 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
3245 $aliases{$qualifier}{$name}{'index'} = $_->alias;
3246 # We will add a 'title' property in a subsequent schema
3247 $aliases{$qualifier}{$name}{'title'} = $name;
3249 # au/kw/se/su/ti go into the default 'eg' qualifier
3250 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
3251 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
3258 __PACKAGE__->register_method(
3259 method => 'return_bib_search_aliases',
3260 api_name => 'open-ils.supercat.biblio.search_aliases',
3264 { desc => <<" DESC",
3265 Returns the set of qualified search aliases in the system
3269 { desc => 'Hash of qualified search aliases',
3275 package OpenILS::Application::SuperCat::unAPI;
3276 use base qw/OpenILS::Application::SuperCat/;
3279 die "dummy superclass, use a real class";
3285 return unless ($obj);
3287 $class = ref($class) || $class;
3289 if ($class eq __PACKAGE__) {
3290 return unless (ref($obj));
3291 $class .= '::' . $obj->json_hint;
3294 return bless { obj => $obj } => $class;
3299 return $self->{obj};
3302 package OpenILS::Application::SuperCat::unAPI::auri;
3303 use base qw/OpenILS::Application::SuperCat::unAPI/;
3309 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
3310 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
3311 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
3312 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3313 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
3315 if (!$args->{no_volumes}) {
3316 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
3317 $xml .= " <volumes>\n" . join(
3320 OpenILS::Application::SuperCat::unAPI
3321 ->new( $_->call_number )
3322 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
3323 } @{ $self->obj->call_number_maps }
3324 ) . " </volumes>\n";
3327 $xml .= " <volumes/>\n";
3331 $xml .= " </uri>\n";
3336 package OpenILS::Application::SuperCat::unAPI::acn;
3337 use base qw/OpenILS::Application::SuperCat::unAPI/;
3343 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
3345 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
3346 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
3347 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
3348 $xml .= 'deleted="' . $self->obj->deleted . '" ';
3349 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
3352 if (!$args->{no_copies}) {
3353 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
3354 $xml .= " <copies>\n" . join(
3357 OpenILS::Application::SuperCat::unAPI
3359 ->as_xml({ %$args, no_volume=>1 })
3360 } @{ $self->obj->copies }
3364 $xml .= " <copies/>\n";
3368 if (!$args->{no_uris}) {
3369 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3370 $xml .= " <uris>\n" . join(
3373 OpenILS::Application::SuperCat::unAPI
3375 ->as_xml({ %$args, no_volumes=>1 })
3376 } @{ $self->obj->uri_maps }
3380 $xml .= " <uris/>\n";
3385 $xml .= ' <prefix ';
3386 $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3387 $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3388 $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3389 $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3392 $xml .= ' <suffix ';
3393 $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3394 $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3395 $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3396 $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3399 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3400 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3401 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3402 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3405 unless ($args->{no_record}) {
3406 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3408 my $r_doc = $parser->parse_string($self->obj->record->marc);
3409 $r_doc->documentElement->setAttribute( id => $rec_tag );
3410 $xml .= $U->entityize($r_doc->documentElement->toString);
3413 $xml .= " </volume>\n";
3418 package OpenILS::Application::SuperCat::unAPI::ssub;
3419 use base qw/OpenILS::Application::SuperCat::unAPI/;
3425 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3427 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3428 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3429 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3430 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3433 if (!$args->{no_distributions}) {
3434 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3435 $xml .= " <distributions>\n" . join(
3438 OpenILS::Application::SuperCat::unAPI
3440 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3441 } @{ $self->obj->distributions }
3442 ) . " </distributions>\n";
3445 $xml .= " <distributions/>\n";
3449 if (!$args->{no_captions_and_patterns}) {
3450 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3451 $xml .= " <captions_and_patterns>\n" . join(
3454 OpenILS::Application::SuperCat::unAPI
3456 ->as_xml({ %$args, no_subscription=>1 })
3457 } @{ $self->obj->scaps }
3458 ) . " </captions_and_patterns>\n";
3461 $xml .= " <captions_and_patterns/>\n";
3465 if (!$args->{no_issuances}) {
3466 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3467 $xml .= " <issuances>\n" . join(
3470 OpenILS::Application::SuperCat::unAPI
3472 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3473 } @{ $self->obj->issuances }
3474 ) . " </issuances>\n";
3477 $xml .= " <issuances/>\n";
3482 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3483 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3484 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3485 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3488 unless ($args->{no_record}) {
3489 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3491 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3492 $r_doc->documentElement->setAttribute( id => $rec_tag );
3493 $xml .= $U->entityize($r_doc->documentElement->toString);
3496 $xml .= " </subscription>\n";
3501 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3502 use base qw/OpenILS::Application::SuperCat::unAPI/;
3508 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3510 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3512 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3513 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3514 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3515 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3518 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3520 $xml .= " </serial_summary>\n";
3526 package OpenILS::Application::SuperCat::unAPI::sssum;
3527 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3529 package OpenILS::Application::SuperCat::unAPI::sbsum;
3530 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3532 package OpenILS::Application::SuperCat::unAPI::sisum;
3533 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3535 package OpenILS::Application::SuperCat::unAPI::sdist;
3536 use base qw/OpenILS::Application::SuperCat::unAPI/;
3542 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3544 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3545 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3546 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3547 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3550 if (!$args->{no_distributions}) {
3551 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3552 $xml .= " <streams>\n" . join(
3555 OpenILS::Application::SuperCat::unAPI
3557 ->as_xml({ %$args, no_distribution=>1 })
3558 } @{ $self->obj->streams }
3559 ) . " </streams>\n";
3562 $xml .= " <streams/>\n";
3566 if (!$args->{no_summaries}) {
3567 $xml .= " <summaries>\n";
3571 OpenILS::Application::SuperCat::unAPI
3573 ->as_xml({ %$args, no_distribution=>1 }) : ""
3574 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3577 $xml .= " </summaries>\n";
3581 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3582 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3583 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3584 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3587 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3589 if (!$args->{no_record} && $self->obj->record_entry) {
3590 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3592 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3593 $r_doc->documentElement->setAttribute( id => $rec_tag );
3594 $xml .= $U->entityize($r_doc->documentElement->toString);
3597 $xml .= " </distribution>\n";
3602 package OpenILS::Application::SuperCat::unAPI::sstr;
3603 use base qw/OpenILS::Application::SuperCat::unAPI/;
3609 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3611 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3612 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3615 if (!$args->{no_items}) {
3616 if (ref($self->obj->items) && @{ $self->obj->items }) {
3617 $xml .= " <items>\n" . join(
3620 OpenILS::Application::SuperCat::unAPI
3622 ->as_xml({ %$args, no_stream=>1 })
3623 } @{ $self->obj->items }
3627 $xml .= " <items/>\n";
3631 #XXX routing_list_user's?
3633 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3635 $xml .= " </stream>\n";
3640 package OpenILS::Application::SuperCat::unAPI::sitem;
3641 use base qw/OpenILS::Application::SuperCat::unAPI/;
3647 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3649 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3650 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3651 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3653 if ($args->{no_issuance}) {
3654 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3655 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3660 if (ref($self->obj->notes) && $self->obj->notes) {
3661 $xml .= " <notes>\n";
3662 for my $note ( @{$self->obj->notes} ) {
3663 next unless ( $note->pub eq 't' );
3664 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3667 $xml .= " </notes>\n";
3669 $xml .= " <notes/>\n";
3672 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3673 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3674 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->unit )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if ($self->obj->unit && !$args->{no_unit});
3675 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->uri )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if ($self->obj->uri && !$args->{no_uri});
3677 $xml .= " </serial_item>\n";
3682 package OpenILS::Application::SuperCat::unAPI::sunit;
3683 use base qw/OpenILS::Application::SuperCat::unAPI/;
3689 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3690 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3692 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3693 create_date edit_date copy_number circulate deposit ref holdable deleted
3694 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3695 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3700 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3701 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3702 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3704 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3705 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3706 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3707 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3710 $xml .= " <copy_notes>\n";
3711 if (ref($self->obj->notes) && $self->obj->notes) {
3712 for my $note ( @{$self->obj->notes} ) {
3713 next unless ( $note->pub eq 't' );
3714 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3719 $xml .= " </copy_notes>\n";
3720 $xml .= " <statcats>\n";
3722 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3723 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3724 next unless ( $sce->stat_cat->opac_visible eq 't' );
3725 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3729 $xml .= " </statcats>\n";
3731 unless ($args->{no_volume}) {
3732 if (ref($self->obj->call_number)) {
3733 $xml .= OpenILS::Application::SuperCat::unAPI
3734 ->new( $self->obj->call_number )
3735 ->as_xml({ %$args, no_copies=>1 });
3737 $xml .= " <volume/>\n";
3741 $xml .= " </serial_unit>\n";
3746 package OpenILS::Application::SuperCat::unAPI::scap;
3747 use base qw/OpenILS::Application::SuperCat::unAPI/;
3753 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3754 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3756 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3757 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3758 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3761 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3762 $xml .= " </caption_and_pattern>\n";
3767 package OpenILS::Application::SuperCat::unAPI::siss;
3768 use base qw/OpenILS::Application::SuperCat::unAPI/;
3774 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3775 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3777 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3778 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3782 if (!$args->{no_items}) {
3783 if (ref($self->obj->items) && @{ $self->obj->items }) {
3784 $xml .= " <items>\n" . join(
3787 OpenILS::Application::SuperCat::unAPI
3789 ->as_xml({ %$args, no_stream=>1 })
3790 } @{ $self->obj->items }
3794 $xml .= " <items/>\n";
3798 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3799 $xml .= " </issuance>\n";
3804 package OpenILS::Application::SuperCat::unAPI::acp;
3805 use base qw/OpenILS::Application::SuperCat::unAPI/;
3811 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3812 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3814 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3815 create_date edit_date copy_number circulate deposit ref holdable deleted
3816 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3821 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3822 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3823 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '" opac_visible="'.$self->obj->circ_lib->opac_visible.'">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3825 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3826 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3827 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3828 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3831 $xml .= " <monograph_parts>\n";
3832 if (ref($self->obj->parts) && $self->obj->parts) {
3833 for my $part ( @{$self->obj->parts} ) {
3834 next if $U->is_true($part->deleted);
3835 $xml .= sprintf(' <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3840 $xml .= " </monograph_parts>\n";
3841 $xml .= " <copy_notes>\n";
3842 if (ref($self->obj->notes) && $self->obj->notes) {
3843 for my $note ( @{$self->obj->notes} ) {
3844 next unless ( $note->pub eq 't' );
3845 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3850 $xml .= " </copy_notes>\n";
3851 $xml .= " <statcats>\n";
3853 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3854 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3855 next unless ( $sce->stat_cat->opac_visible eq 't' );
3856 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3860 $xml .= " </statcats>\n";
3862 unless ($args->{no_volume}) {
3863 if (ref($self->obj->call_number)) {
3864 $xml .= OpenILS::Application::SuperCat::unAPI
3865 ->new( $self->obj->call_number )
3866 ->as_xml({ %$args, no_copies=>1 });
3868 $xml .= " <volume/>\n";
3872 $xml .= " </copy>\n";