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 my $parser = new XML::LibXML;
10 my $U = 'OpenILS::Application::AppUtils';
13 package OpenILS::Application::SuperCat;
17 use OpenILS::Utils::Normalize qw( naco_normalize );
19 # All OpenSRF applications must be based on OpenSRF::Application or
20 # a subclass thereof. Makes sense, eh?
21 use OpenILS::Application;
22 use base qw/OpenILS::Application/;
24 # This is the client class, used for connecting to open-ils.storage
25 use OpenSRF::AppSession;
27 # This is an extension of Error.pm that supplies some error types to throw
28 use OpenSRF::EX qw(:try);
30 # This is a helper class for querying the OpenSRF Settings application ...
31 use OpenSRF::Utils::SettingsClient;
33 # ... and here we have the built in logging helper ...
34 use OpenSRF::Utils::Logger qw($logger);
36 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
37 use OpenILS::Utils::Fieldmapper;
39 use OpenILS::Utils::CStoreEditor q/:funcs/;
40 use OpenILS::Utils::TagURI;
49 %authority_browse_axis_cache,
53 # we need an XML parser
54 $_parser = new XML::LibXML;
57 $_xslt = new XML::LibXSLT;
59 # parse the MODS xslt ...
60 my $mods33_xslt = $_parser->parse_file(
61 OpenSRF::Utils::SettingsClient
63 ->config_value( dirs => 'xsl' ).
64 "/MARC21slim2MODS33.xsl"
66 # and stash a transformer
67 $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
68 $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
69 $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
70 $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
72 # parse the MODS xslt ...
73 my $mods32_xslt = $_parser->parse_file(
74 OpenSRF::Utils::SettingsClient
76 ->config_value( dirs => 'xsl' ).
77 "/MARC21slim2MODS32.xsl"
79 # and stash a transformer
80 $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
81 $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
82 $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
83 $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
85 # parse the MODS xslt ...
86 my $mods3_xslt = $_parser->parse_file(
87 OpenSRF::Utils::SettingsClient
89 ->config_value( dirs => 'xsl' ).
90 "/MARC21slim2MODS3.xsl"
92 # and stash a transformer
93 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
94 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
95 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
96 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
98 # parse the MODS xslt ...
99 my $mods_xslt = $_parser->parse_file(
100 OpenSRF::Utils::SettingsClient
102 ->config_value( dirs => 'xsl' ).
103 "/MARC21slim2MODS.xsl"
105 # and stash a transformer
106 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
107 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
108 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
109 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
111 # parse the ATOM entry xslt ...
112 my $atom_xslt = $_parser->parse_file(
113 OpenSRF::Utils::SettingsClient
115 ->config_value( dirs => 'xsl' ).
116 "/MARC21slim2ATOM.xsl"
118 # and stash a transformer
119 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
120 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
121 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
123 # parse the RDFDC xslt ...
124 my $rdf_dc_xslt = $_parser->parse_file(
125 OpenSRF::Utils::SettingsClient
127 ->config_value( dirs => 'xsl' ).
128 "/MARC21slim2RDFDC.xsl"
130 # and stash a transformer
131 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
132 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
133 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
135 # parse the SRWDC xslt ...
136 my $srw_dc_xslt = $_parser->parse_file(
137 OpenSRF::Utils::SettingsClient
139 ->config_value( dirs => 'xsl' ).
140 "/MARC21slim2SRWDC.xsl"
142 # and stash a transformer
143 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
144 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
145 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
147 # parse the OAIDC xslt ...
148 my $oai_dc_xslt = $_parser->parse_file(
149 OpenSRF::Utils::SettingsClient
151 ->config_value( dirs => 'xsl' ).
152 "/MARC21slim2OAIDC.xsl"
154 # and stash a transformer
155 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
156 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
157 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
159 # parse the RSS xslt ...
160 my $rss_xslt = $_parser->parse_file(
161 OpenSRF::Utils::SettingsClient
163 ->config_value( dirs => 'xsl' ).
164 "/MARC21slim2RSS2.xsl"
166 # and stash a transformer
167 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
169 # parse the FGDC xslt ...
170 my $fgdc_xslt = $_parser->parse_file(
171 OpenSRF::Utils::SettingsClient
173 ->config_value( dirs => 'xsl' ).
174 "/MARC21slim2FGDC.xsl"
176 # and stash a transformer
177 $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
178 $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
179 $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
181 register_record_transforms();
183 register_new_authorities_methods();
188 sub register_record_transforms {
189 for my $type ( keys %record_xslt ) {
190 __PACKAGE__->register_method(
191 method => 'retrieve_record_transform',
192 api_name => "open-ils.supercat.record.$type.retrieve",
196 { desc => "Returns the \U$type\E representation ".
197 "of the requested bibliographic record",
201 desc => 'An OpenILS biblio::record_entry id',
205 { desc => "The bib record in \U$type\E",
210 __PACKAGE__->register_method(
211 method => 'retrieve_isbn_transform',
212 api_name => "open-ils.supercat.isbn.$type.retrieve",
216 { desc => "Returns the \U$type\E representation ".
217 "of the requested bibliographic record",
225 { desc => "The bib record in \U$type\E",
232 sub register_new_authorities_methods {
233 my %register_args = (
234 method => "generic_new_authorities_method",
238 desc => q/Generated method/,
241 desc => "An axis, an authority tag number, or a bibliographic tag number, depending on invocation",
244 desc => "A search term",
247 desc => "zero-based page number of results",
249 {name => "page size",
250 desc => "number of results per page",
254 desc => "A list of authority record IDs", type => "array"
259 foreach my $how (qw/axis atag btag/) {
260 foreach my $action (qw/browse_center browse_top
261 search_rank search_heading/) {
263 $register_args{api_name} =
264 "open-ils.supercat.authority.$action.by_$how";
265 __PACKAGE__->register_method(%register_args);
267 $register_args{api_name} =
268 "open-ils.supercat.authority.$action.by_$how.refs";
269 __PACKAGE__->register_method(%register_args);
275 sub generic_new_authorities_method {
279 # We want to be extra careful with these arguments, since the next
280 # thing we're doing with them is passing them to a DB procedure.
283 my $page = int(shift || 0);
284 my $page_size = shift;
285 my $thesauruses = shift;
287 # undef ok, but other non numbers not ok
288 $page_size = int($page_size) if defined $page_size;
290 # Figure out how we were called and what DB procedure we'll call in turn.
291 $self->api_name =~ /\.by_(\w+)($|\.)/;
295 $self->api_name =~ /authority\.(\w+)\./;
298 my $method = "${metaaxis}_$action";
299 $method .= "_refs" if $refs;
301 # Match authority.full_rec normalization
302 # XXX don't know whether we need second arg 'subfield'?
303 $term = naco_normalize($term);
305 my $storage = create OpenSRF::AppSession("open-ils.storage");
306 my $list = $storage->request(
307 "open-ils.storage.authority.in_db.browse_or_search",
308 $method, $what, $term, $page, $page_size, $thesauruses
322 return unless ($tree && ref($tree->$field));
324 my @things = $filter->($tree);
325 for my $v ( @{$tree->$field} ){
326 push @things, $filter->($v);
327 push @things, tree_walker($v, $field, $filter);
332 # find a label_sortkey for a call number with a label which is equal
333 # (or close to) a given label value
334 sub _label_sortkey_from_label {
335 my ($label, $_storage, $ou_ids, $cp_filter) = @_;
337 my $closest_cn = $_storage->request(
338 "open-ils.cstore.direct.asset.call_number.search.atomic",
339 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
340 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
345 flesh_fields => { acn => [qw/label_class/] },
347 order_by => { acn => "oils_text_as_bytea(label), id" }
351 if ($closest_cn->[0]->label eq $label) {
352 # we found an exact match stop here
353 return $closest_cn->[0]->label_sortkey;
355 # we got as close as we could by label alone, let's try to
356 # normalize and get closer
357 $closest_cn = $_storage->request(
358 "open-ils.cstore.direct.asset.call_number.search.atomic",
360 => { ">=" => [$closest_cn->[0]->label_class->normalizer, $label] },
361 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
366 order_by => { acn => "label_sortkey, id" }
370 return $closest_cn->[0]->label_sortkey;
375 return '~~~'; #fallback to high ascii value, we are at the end of the range
384 my $page_size = shift || 9;
385 my $page = shift || 0;
386 my $statuses = shift || [];
387 my $copy_locations = shift || [];
389 my ($before_limit,$after_limit) = (0,0);
390 my ($before_offset,$after_offset) = (0,0);
393 $before_limit = $after_limit = int($page_size / 2);
394 $after_limit += 1 if ($page_size % 2);
396 $before_offset = $after_offset = int($page_size / 2);
397 $before_offset += 1 if ($page_size % 2);
398 $before_limit = $after_limit = $page_size;
401 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
404 # if OU is not specified, or if '-' is specified, do not enter the block
405 unless (!$ou || $ou eq '-') {
406 # check if the shortname of the top org_unit is specified
407 my $top_org = $_storage->request(
408 "open-ils.cstore.direct.actor.org_unit.search",
409 { parent_ou => undef }
412 if ($ou eq $top_org->shortname) {
413 $logger->debug("Searching for CNs at top org $ou");
415 my $o_search = { shortname => $ou };
417 my $orgs = $_storage->request(
418 "open-ils.cstore.direct.actor.org_unit.search",
421 flesh_fields => { aou => [qw/children/] }
425 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
427 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
434 if (@$statuses || @$copy_locations) {
440 call_number => { '=' => { '+acn' => 'id' } },
442 ((@$statuses) ? ( status => $statuses) : ()),
443 ((@$copy_locations) ? ( location => $copy_locations) : ())
449 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
452 my $before = $_storage->request(
453 "open-ils.cstore.direct.asset.call_number.search.atomic",
454 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
455 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
460 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
461 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
462 limit => $before_limit,
463 offset => abs($page) * $page_size - $before_offset,
466 push @list, reverse(@$before);
470 my $after = $_storage->request(
471 "open-ils.cstore.direct.asset.call_number.search.atomic",
472 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
473 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
478 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
479 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
480 limit => $after_limit,
481 offset => abs($page) * $page_size - $after_offset,
489 __PACKAGE__->register_method(
490 method => 'cn_browse',
491 api_name => 'open-ils.supercat.call_number.browse',
496 Returns the XML representation of the requested bibliographic record's holdings
501 desc => 'The target call number label',
503 { name => 'org_unit',
504 desc => 'The org unit shortname (or "-" or undef for global) to browse',
506 { name => 'page_size',
507 desc => 'Count of call numbers to retrieve, default is 9',
510 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
512 { name => 'statuses',
513 desc => 'Array of statuses to filter copies by, optional and can be undef.',
515 { name => 'locations',
516 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
520 { desc => 'Call numbers with owning_lib and record fleshed',
531 my $limit = shift || 10;
532 my $page = shift || 0;
533 my $statuses = shift || [];
534 my $copy_locations = shift || [];
537 my $offset = abs($page) * $limit;
538 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
540 my $o_search = { shortname => $ou };
541 if (!$ou || $ou eq '-') {
542 $o_search = { parent_ou => undef };
545 my $orgs = $_storage->request(
546 "open-ils.cstore.direct.actor.org_unit.search",
549 flesh_fields => { aou => [qw/children/] }
553 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
555 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
560 if (@$statuses || @$copy_locations) {
566 call_number => { '=' => { '+acn' => 'id' } },
568 ((@$statuses) ? ( status => $statuses) : ()),
569 ((@$copy_locations) ? ( location => $copy_locations) : ())
575 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
578 my $before = $_storage->request(
579 "open-ils.cstore.direct.asset.call_number.search.atomic",
580 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
581 owning_lib => \@ou_ids,
586 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
587 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
592 push @list, reverse(@$before);
596 my $after = $_storage->request(
597 "open-ils.cstore.direct.asset.call_number.search.atomic",
598 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
599 owning_lib => \@ou_ids,
604 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
605 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
615 __PACKAGE__->register_method(
616 method => 'cn_startwith',
617 api_name => 'open-ils.supercat.call_number.startwith',
622 Returns the XML representation of the requested bibliographic record's holdings
627 desc => 'The target call number label',
629 { name => 'org_unit',
630 desc => 'The org unit shortname (or "-" or undef for global) to browse',
632 { name => 'page_size',
633 desc => 'Count of call numbers to retrieve, default is 9',
636 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
638 { name => 'statuses',
639 desc => 'Array of statuses to filter copies by, optional and can be undef.',
641 { name => 'locations',
642 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
646 { desc => 'Call numbers with owning_lib and record fleshed',
652 sub new_books_by_item {
657 my $page_size = shift || 10;
658 my $page = shift || 1;
659 my $statuses = shift || [];
660 my $copy_locations = shift || [];
662 my $offset = $page_size * ($page - 1);
664 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
667 if ($ou && $ou ne '-') {
668 my $orgs = $_storage->request(
669 "open-ils.cstore.direct.actor.org_unit.search",
670 { shortname => $ou },
672 flesh_fields => { aou => [qw/children/] }
675 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
678 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
679 my $cns = $_storage->request(
680 "open-ils.cstore.json_query.atomic",
681 { select => { acn => ['record'],
682 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
684 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
688 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
689 ((@$statuses) ? ( status => $statuses) : ()),
690 ((@$copy_locations) ? ( location => $copy_locations) : ())
692 '+acn' => { record => { '>' => 0 } },
694 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
700 return [ map { $_->{record} } @$cns ];
702 __PACKAGE__->register_method(
703 method => 'new_books_by_item',
704 api_name => 'open-ils.supercat.new_book_list',
709 Returns the XML representation of the requested bibliographic record's holdings
713 { name => 'org_unit',
714 desc => 'The org unit shortname (or "-" or undef for global) to list',
716 { name => 'page_size',
717 desc => 'Count of records to retrieve, default is 10',
720 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
722 { name => 'statuses',
723 desc => 'Array of statuses to filter copies by, optional and can be undef.',
725 { name => 'locations',
726 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
730 { desc => 'Record IDs',
740 my $format = shift || 'xml';
742 $u2 = OpenILS::Utils::TagURI->new($u2);
743 return '' unless $u2;
745 # Use pathinfo on acp as a lookup type specifier.
746 if ($u2->classname eq 'acp' and $u2->pathinfo =~ /\bbarcode\b/) {
747 my( $copy, $evt ) = $U->fetch_copy_by_barcode( $u2->id );
748 $u2->id( $copy->id );
751 if ($u2->classname eq 'biblio_record_entry_feed') {
752 $u2->id( '{' . $u2->id . '}' );
755 'unapi.' . $u2->classname,
759 push @$args, $u2->classname unless $u2->classname eq 'biblio_record_entry_feed';
760 push @$args, '{' . ( $u2->includes ? join( ',', keys %{ $u2->includes } ) : '' ) . '}';
761 push @$args, ($u2->location || undef);
762 push @$args, ($u2->depth || undef);
764 return OpenSRF::AppSession->create('open-ils.cstore')->request(
765 "open-ils.cstore.json_query.atomic",
767 )->gather(1)->[0]{'unapi.'. $u2->classname};
769 __PACKAGE__->register_method(
771 api_name => 'open-ils.supercat.u2',
776 Returns the XML representation of the requested object
781 desc => 'The U2 Tag URI (OpenILS::Utils::TagURI)',
784 desc => 'For bre and bre feeds, the xml transform format',
788 { desc => 'XML (or transformed) object data',
797 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
799 __PACKAGE__->register_method(
800 method => 'general_browse',
801 api_name => 'open-ils.supercat.title.browse',
802 tag => 'tnf', subfield => 'a',
806 { desc => "Returns a list of the requested org-scoped record IDs held",
808 [ { name => 'value', desc => 'The target title', type => 'string' },
809 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
810 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
811 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
812 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
813 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
814 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
817 __PACKAGE__->register_method(
818 method => 'general_browse',
819 api_name => 'open-ils.supercat.author.browse',
820 tag => [qw/100 110 111/], subfield => 'a',
824 { desc => "Returns a list of the requested org-scoped record IDs held",
826 [ { name => 'value', desc => 'The target author', type => 'string' },
827 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
828 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
829 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
830 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
831 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
832 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
835 __PACKAGE__->register_method(
836 method => 'general_browse',
837 api_name => 'open-ils.supercat.subject.browse',
838 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
842 { desc => "Returns a list of the requested org-scoped record IDs held",
844 [ { name => 'value', desc => 'The target subject', type => 'string' },
845 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
846 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
847 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
848 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
849 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
850 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
853 __PACKAGE__->register_method(
854 method => 'general_browse',
855 api_name => 'open-ils.supercat.topic.browse',
856 tag => [qw/650 690/], subfield => 'a',
860 { desc => "Returns a list of the requested org-scoped record IDs held",
862 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
863 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
864 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
865 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
866 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
867 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
868 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
871 __PACKAGE__->register_method(
872 method => 'general_browse',
873 api_name => 'open-ils.supercat.series.browse',
874 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
878 { desc => "Returns a list of the requested org-scoped record IDs held",
880 [ { name => 'value', desc => 'The target series', type => 'string' },
881 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
882 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
883 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
884 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
885 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
886 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
896 my $subfield = shift;
899 my $page_size = shift || 9;
900 my $page = shift || 0;
901 my $statuses = shift || [];
902 my $copy_locations = shift || [];
904 my ($before_limit,$after_limit) = (0,0);
905 my ($before_offset,$after_offset) = (0,0);
908 $before_limit = $after_limit = int($page_size / 2);
909 $after_limit += 1 if ($page_size % 2);
911 $before_offset = $after_offset = int($page_size / 2);
912 $before_offset += 1 if ($page_size % 2);
913 $before_limit = $after_limit = $page_size;
916 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
919 if ($ou && $ou ne '-') {
920 my $orgs = $_storage->request(
921 "open-ils.cstore.direct.actor.org_unit.search",
922 { shortname => $ou },
924 flesh_fields => { aou => [qw/children/] }
927 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
930 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
935 my $before = $_storage->request(
936 "open-ils.cstore.json_query.atomic",
937 { select => { mfr => [qw/record value/] },
942 subfield => $subfield,
943 value => { '<' => lc($value) }
947 { select=> { acp => [ 'id' ] },
948 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
950 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
953 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
954 ((@$statuses) ? ( status => $statuses) : ()),
955 ((@$copy_locations) ? ( location => $copy_locations) : ())
962 { select=> { auri => [ 'id' ] },
963 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
965 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
966 '+auri' => { active => 't' }
973 order_by => { mfr => { value => 'desc' } },
974 limit => $before_limit,
975 offset => abs($page) * $page_size - $before_offset,
978 push @list, map { $_->{record} } reverse(@$before);
982 my $after = $_storage->request(
983 "open-ils.cstore.json_query.atomic",
984 { select => { mfr => [qw/record value/] },
989 subfield => $subfield,
990 value => { '>=' => lc($value) }
994 { select=> { acp => [ 'id' ] },
995 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
997 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1000 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1001 ((@$statuses) ? ( status => $statuses) : ()),
1002 ((@$copy_locations) ? ( location => $copy_locations) : ())
1009 { select=> { auri => [ 'id' ] },
1010 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1012 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1013 '+auri' => { active => 't' }
1020 order_by => { mfr => { value => 'asc' } },
1021 limit => $after_limit,
1022 offset => abs($page) * $page_size - $after_offset,
1025 push @list, map { $_->{record} } @$after;
1030 __PACKAGE__->register_method(
1031 method => 'tag_sf_browse',
1032 api_name => 'open-ils.supercat.tag.browse',
1036 { desc => <<" DESC",
1037 Returns a list of the requested org-scoped record IDs held
1042 desc => 'The target MARC tag',
1044 { name => 'subfield',
1045 desc => 'The target MARC subfield',
1048 desc => 'The target string',
1050 { name => 'org_unit',
1051 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1053 { name => 'page_size',
1054 desc => 'Count of call numbers to retrieve, default is 9',
1057 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1059 { name => 'statuses',
1060 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1062 { name => 'locations',
1063 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1067 { desc => 'Record IDs that have copies at the relevant org units',
1072 sub grab_authority_browse_axes {
1073 my ($self, $client, $full) = @_;
1075 unless(scalar(keys(%authority_browse_axis_cache))) {
1076 my $axes = new_editor->search_authority_browse_axis([
1077 { code => { '<>' => undef } },
1078 { flesh => 2, flesh_fields => { aba => ['fields'], acsaf => ['bib_fields','sub_entries'] } }
1080 $authority_browse_axis_cache{$_->code} = $_ for (@$axes);
1085 map { $authority_browse_axis_cache{$_} } sort keys %authority_browse_axis_cache
1088 return [keys %authority_browse_axis_cache];
1091 __PACKAGE__->register_method(
1092 method => 'grab_authority_browse_axes',
1093 api_name => 'open-ils.supercat.authority.browse_axis_list',
1097 { desc => "Returns a list of valid authority browse/startswith axes",
1099 { 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' }
1101 'return' => { desc => 'Axis codes or whole axes, see "full" param', type => 'array' }
1105 sub axis_authority_browse {
1110 $axis =~ s/^authority\.//;
1111 $axis =~ s/(\.refs)$//;
1114 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1117 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1118 push @tags, $f->tag;
1120 push @tags, $_->tag for @{$f->sub_entries};
1124 return authority_tag_sf_browse($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1126 __PACKAGE__->register_method(
1127 method => 'axis_authority_browse',
1128 api_name => 'open-ils.supercat.authority.browse.by_axis',
1132 { desc => "Returns a list of the requested authority record IDs held",
1134 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1135 { name => 'value', desc => 'The target value', type => 'string' },
1136 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1137 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1138 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1144 sub general_authority_browse {
1147 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
1149 __PACKAGE__->register_method(
1150 method => 'general_authority_browse',
1151 api_name => 'open-ils.supercat.authority.title.browse',
1152 tag => ['130'], subfield => 'a',
1156 { desc => "Returns a list of the requested authority record IDs held",
1158 [ { name => 'value', desc => 'The target title', type => 'string' },
1159 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1160 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1161 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1164 __PACKAGE__->register_method(
1165 method => 'general_authority_browse',
1166 api_name => 'open-ils.supercat.authority.author.browse',
1167 tag => [qw/100 110 111/], subfield => 'a',
1171 { desc => "Returns a list of the requested authority record IDs held",
1173 [ { name => 'value', desc => 'The target author', type => 'string' },
1174 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1175 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1176 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1179 __PACKAGE__->register_method(
1180 method => 'general_authority_browse',
1181 api_name => 'open-ils.supercat.authority.subject.browse',
1182 tag => [qw/148 150 151 155/], subfield => 'a',
1186 { desc => "Returns a list of the requested authority record IDs held",
1188 [ { name => 'value', desc => 'The target subject', type => 'string' },
1189 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1190 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1191 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1194 __PACKAGE__->register_method(
1195 method => 'general_authority_browse',
1196 api_name => 'open-ils.supercat.authority.topic.browse',
1197 tag => ['150'], subfield => 'a',
1201 { desc => "Returns a list of the requested authority record IDs held",
1203 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1204 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1205 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1206 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1209 __PACKAGE__->register_method(
1210 method => 'general_authority_browse',
1211 api_name => 'open-ils.supercat.authority.title.refs.browse',
1212 tag => ['130'], subfield => 'a',
1216 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1218 [ { name => 'value', desc => 'The target title', type => 'string' },
1219 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1220 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1221 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1224 __PACKAGE__->register_method(
1225 method => 'general_authority_browse',
1226 api_name => 'open-ils.supercat.authority.author.refs.browse',
1227 tag => [qw/100 110 111/], subfield => 'a',
1231 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1233 [ { name => 'value', desc => 'The target author', type => 'string' },
1234 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1235 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1236 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1239 __PACKAGE__->register_method(
1240 method => 'general_authority_browse',
1241 api_name => 'open-ils.supercat.authority.subject.refs.browse',
1242 tag => [qw/148 150 151 155/], subfield => 'a',
1246 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1248 [ { name => 'value', desc => 'The target subject', type => 'string' },
1249 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1250 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1251 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1254 __PACKAGE__->register_method(
1255 method => 'general_authority_browse',
1256 api_name => 'open-ils.supercat.authority.topic.refs.browse',
1257 tag => ['150'], subfield => 'a',
1261 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1263 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1264 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1265 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1266 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1272 sub authority_tag_sf_browse {
1277 my $subfield = shift;
1279 my $page_size = shift || 9;
1280 my $page = shift || 0;
1282 # Match authority.full_rec normalization
1283 $value = naco_normalize($value, $subfield);
1285 my ($before_limit,$after_limit) = (0,0);
1286 my ($before_offset,$after_offset) = (0,0);
1289 $before_limit = $after_limit = int($page_size / 2);
1290 $after_limit += 1 if ($page_size % 2);
1292 $before_offset = $after_offset = int($page_size / 2);
1293 $before_offset += 1 if ($page_size % 2);
1294 $before_limit = $after_limit = $page_size;
1297 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1299 # .refs variant includes 4xx and 5xx variants for see / see also
1301 foreach my $tagname (@$tag) {
1302 push(@ref_tags, $tagname);
1303 if ($self->api_name =~ /\.refs\./) {
1304 push(@ref_tags, '4' . substr($tagname, 1, 2));
1305 push(@ref_tags, '5' . substr($tagname, 1, 2));
1311 my $before = $_storage->request(
1312 "open-ils.cstore.json_query.atomic",
1313 { select => { afr => [qw/record value/] },
1315 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1316 order_by => { afr => { value => 'desc' } },
1317 limit => $before_limit,
1318 offset => abs($page) * $page_size - $before_offset,
1321 push @list, map { $_->{record} } reverse(@$before);
1325 my $after = $_storage->request(
1326 "open-ils.cstore.json_query.atomic",
1327 { select => { afr => [qw/record value/] },
1329 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1330 order_by => { afr => { value => 'asc' } },
1331 limit => $after_limit,
1332 offset => abs($page) * $page_size - $after_offset,
1335 push @list, map { $_->{record} } @$after;
1338 # If we're not pulling in see/see also references, just return the raw list
1339 if ($self->api_name !~ /\.refs\./) {
1343 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1346 foreach my $record (@list) {
1347 next if exists $seen{$record};
1348 push @retlist, int($record);
1354 __PACKAGE__->register_method(
1355 method => 'authority_tag_sf_browse',
1356 api_name => 'open-ils.supercat.authority.tag.browse',
1360 { desc => <<" DESC",
1361 Returns a list of the requested authority record IDs held
1366 desc => 'The target Authority MARC tag',
1368 { name => 'subfield',
1369 desc => 'The target Authority MARC subfield',
1372 desc => 'The target string',
1374 { name => 'page_size',
1375 desc => 'Count of call numbers to retrieve, default is 9',
1378 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1382 { desc => 'Authority Record IDs that are near the target string',
1387 sub general_startwith {
1390 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1392 __PACKAGE__->register_method(
1393 method => 'general_startwith',
1394 api_name => 'open-ils.supercat.title.startwith',
1395 tag => 'tnf', subfield => 'a',
1399 { desc => "Returns a list of the requested org-scoped record IDs held",
1401 [ { name => 'value', desc => 'The target title', type => 'string' },
1402 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1403 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1404 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1405 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1406 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1407 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1410 __PACKAGE__->register_method(
1411 method => 'general_startwith',
1412 api_name => 'open-ils.supercat.author.startwith',
1413 tag => [qw/100 110 111/], subfield => 'a',
1417 { desc => "Returns a list of the requested org-scoped record IDs held",
1419 [ { name => 'value', desc => 'The target author', type => 'string' },
1420 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1421 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1422 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1423 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1424 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1425 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1428 __PACKAGE__->register_method(
1429 method => 'general_startwith',
1430 api_name => 'open-ils.supercat.subject.startwith',
1431 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1435 { desc => "Returns a list of the requested org-scoped record IDs held",
1437 [ { name => 'value', desc => 'The target subject', type => 'string' },
1438 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1439 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1440 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1441 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1442 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1443 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1446 __PACKAGE__->register_method(
1447 method => 'general_startwith',
1448 api_name => 'open-ils.supercat.topic.startwith',
1449 tag => [qw/650 690/], subfield => 'a',
1453 { desc => "Returns a list of the requested org-scoped record IDs held",
1455 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1456 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1457 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1458 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1459 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1460 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1461 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1464 __PACKAGE__->register_method(
1465 method => 'general_startwith',
1466 api_name => 'open-ils.supercat.series.startwith',
1467 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1471 { desc => "Returns a list of the requested org-scoped record IDs held",
1473 [ { name => 'value', desc => 'The target series', type => 'string' },
1474 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1475 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1476 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1477 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1478 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1479 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1484 sub tag_sf_startwith {
1489 my $subfield = shift;
1492 my $limit = shift || 10;
1493 my $page = shift || 0;
1494 my $statuses = shift || [];
1495 my $copy_locations = shift || [];
1497 my $offset = $limit * abs($page);
1498 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1501 if ($ou && $ou ne '-') {
1502 my $orgs = $_storage->request(
1503 "open-ils.cstore.direct.actor.org_unit.search",
1504 { shortname => $ou },
1506 flesh_fields => { aou => [qw/children/] }
1509 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1512 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1517 my $before = $_storage->request(
1518 "open-ils.cstore.json_query.atomic",
1519 { select => { mfr => [qw/record value/] },
1524 subfield => $subfield,
1525 value => { '<' => lc($value) }
1529 { select=> { acp => [ 'id' ] },
1530 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1532 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1535 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1536 ((@$statuses) ? ( status => $statuses) : ()),
1537 ((@$copy_locations) ? ( location => $copy_locations) : ())
1544 { select=> { auri => [ 'id' ] },
1545 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1547 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1548 '+auri' => { active => 't' }
1555 order_by => { mfr => { value => 'desc' } },
1560 push @list, map { $_->{record} } reverse(@$before);
1564 my $after = $_storage->request(
1565 "open-ils.cstore.json_query.atomic",
1566 { select => { mfr => [qw/record value/] },
1571 subfield => $subfield,
1572 value => { '>=' => lc($value) }
1576 { select=> { acp => [ 'id' ] },
1577 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1579 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1582 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1583 ((@$statuses) ? ( status => $statuses) : ()),
1584 ((@$copy_locations) ? ( location => $copy_locations) : ())
1591 { select=> { auri => [ 'id' ] },
1592 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1594 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1595 '+auri' => { active => 't' }
1602 order_by => { mfr => { value => 'asc' } },
1607 push @list, map { $_->{record} } @$after;
1612 __PACKAGE__->register_method(
1613 method => 'tag_sf_startwith',
1614 api_name => 'open-ils.supercat.tag.startwith',
1618 { desc => <<" DESC",
1619 Returns a list of the requested org-scoped record IDs held
1624 desc => 'The target MARC tag',
1626 { name => 'subfield',
1627 desc => 'The target MARC subfield',
1630 desc => 'The target string',
1632 { name => 'org_unit',
1633 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1635 { name => 'page_size',
1636 desc => 'Count of call numbers to retrieve, default is 9',
1639 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1641 { name => 'statuses',
1642 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1644 { name => 'locations',
1645 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1649 { desc => 'Record IDs that have copies at the relevant org units',
1654 sub axis_authority_startwith {
1659 $axis =~ s/^authority\.//;
1660 $axis =~ s/(\.refs)$//;
1663 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1666 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1667 push @tags, $f->tag;
1669 push @tags, $_->tag for @{$f->sub_entries};
1673 return authority_tag_sf_startwith($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1675 __PACKAGE__->register_method(
1676 method => 'axis_authority_startwith',
1677 api_name => 'open-ils.supercat.authority.startwith.by_axis',
1681 { desc => "Returns a list of the requested authority record IDs held",
1683 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1684 { name => 'value', desc => 'The target value', type => 'string' },
1685 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1686 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1687 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1693 sub general_authority_startwith {
1696 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1698 __PACKAGE__->register_method(
1699 method => 'general_authority_startwith',
1700 api_name => 'open-ils.supercat.authority.title.startwith',
1701 tag => ['130'], subfield => 'a',
1705 { desc => "Returns a list of the requested authority record IDs held",
1707 [ { name => 'value', desc => 'The target title', type => 'string' },
1708 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1709 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1710 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1713 __PACKAGE__->register_method(
1714 method => 'general_authority_startwith',
1715 api_name => 'open-ils.supercat.authority.author.startwith',
1716 tag => [qw/100 110 111/], subfield => 'a',
1720 { desc => "Returns a list of the requested authority record IDs held",
1722 [ { name => 'value', desc => 'The target author', type => 'string' },
1723 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1724 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1725 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1728 __PACKAGE__->register_method(
1729 method => 'general_authority_startwith',
1730 api_name => 'open-ils.supercat.authority.subject.startwith',
1731 tag => [qw/148 150 151 155/], subfield => 'a',
1735 { desc => "Returns a list of the requested authority record IDs held",
1737 [ { name => 'value', desc => 'The target subject', type => 'string' },
1738 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1739 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1740 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1743 __PACKAGE__->register_method(
1744 method => 'general_authority_startwith',
1745 api_name => 'open-ils.supercat.authority.topic.startwith',
1746 tag => ['150'], subfield => 'a',
1750 { desc => "Returns a list of the requested authority record IDs held",
1752 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1753 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1754 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1755 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1758 __PACKAGE__->register_method(
1759 method => 'general_authority_startwith',
1760 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1761 tag => ['130'], subfield => 'a',
1765 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1767 [ { name => 'value', desc => 'The target title', type => 'string' },
1768 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1769 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1770 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1773 __PACKAGE__->register_method(
1774 method => 'general_authority_startwith',
1775 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1776 tag => [qw/100 110 111/], subfield => 'a',
1780 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1782 [ { name => 'value', desc => 'The target author', type => 'string' },
1783 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1784 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1785 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1788 __PACKAGE__->register_method(
1789 method => 'general_authority_startwith',
1790 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1791 tag => [qw/148 150 151 155/], subfield => 'a',
1795 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1797 [ { name => 'value', desc => 'The target subject', type => 'string' },
1798 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1799 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1800 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1803 __PACKAGE__->register_method(
1804 method => 'general_authority_startwith',
1805 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1806 tag => ['150'], subfield => 'a',
1810 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1812 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1813 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1814 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1815 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1821 sub authority_tag_sf_startwith {
1826 my $subfield = shift;
1829 my $limit = shift || 10;
1830 my $page = shift || 0;
1832 # Match authority.full_rec normalization
1833 $value = naco_normalize($value, $subfield);
1835 my $ref_limit = $limit;
1836 my $offset = $limit * abs($page);
1837 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1840 # .refs variant includes 4xx and 5xx variants for see / see also
1841 foreach my $tagname (@$tag) {
1842 push(@ref_tags, $tagname);
1843 if ($self->api_name =~ /\.refs\./) {
1844 push(@ref_tags, '4' . substr($tagname, 1, 2));
1845 push(@ref_tags, '5' . substr($tagname, 1, 2));
1852 # Don't skip the first actual page of results in descending order
1853 $offset = $offset - $limit;
1855 my $before = $_storage->request(
1856 "open-ils.cstore.json_query.atomic",
1857 { select => { afr => [qw/record value/] },
1859 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1860 order_by => { afr => { value => 'desc' } },
1861 limit => $ref_limit,
1865 push @list, map { $_->{record} } reverse(@$before);
1869 my $after = $_storage->request(
1870 "open-ils.cstore.json_query.atomic",
1871 { select => { afr => [qw/record value/] },
1873 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1874 order_by => { afr => { value => 'asc' } },
1875 limit => $ref_limit,
1879 push @list, map { $_->{record} } @$after;
1882 # If we're not pulling in see/see also references, just return the raw list
1883 if ($self->api_name !~ /\.refs\./) {
1887 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1890 foreach my $record (@list) {
1891 next if exists $seen{$record};
1892 push @retlist, int($record);
1898 __PACKAGE__->register_method(
1899 method => 'authority_tag_sf_startwith',
1900 api_name => 'open-ils.supercat.authority.tag.startwith',
1904 { desc => <<" DESC",
1905 Returns a list of the requested authority record IDs held
1910 desc => 'The target Authority MARC tag',
1912 { name => 'subfield',
1913 desc => 'The target Authority MARC subfield',
1916 desc => 'The target string',
1918 { name => 'page_size',
1919 desc => 'Count of call numbers to retrieve, default is 10',
1922 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1926 { desc => 'Authority Record IDs that are near the target string',
1932 sub holding_data_formats {
1935 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1936 docs => 'http://www.loc.gov/marcxml/',
1937 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1941 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1942 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1943 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1946 __PACKAGE__->register_method(
1947 method => 'retrieve_uri',
1948 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1952 { desc => <<" DESC",
1953 Returns a fleshed call number object
1958 desc => 'An OpenILS asset::uri id',
1962 { desc => 'fleshed uri',
1970 my $args = shift || {};
1972 return OpenILS::Application::SuperCat::unAPI
1973 ->new(OpenSRF::AppSession
1974 ->create( 'open-ils.cstore' )
1976 "open-ils.cstore.direct.asset.uri.retrieve",
1980 auri => [qw/call_number_maps/],
1981 auricnm => [qw/call_number/],
1982 acn => [qw/owning_lib record prefix suffix/],
1989 __PACKAGE__->register_method(
1990 method => 'retrieve_copy',
1991 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1995 { desc => <<" DESC",
1996 Returns a fleshed call number object
2001 desc => 'An OpenILS asset::copy id',
2005 { desc => 'fleshed copy',
2013 my $args = shift || {};
2015 return OpenILS::Application::SuperCat::unAPI
2016 ->new(OpenSRF::AppSession
2017 ->create( 'open-ils.cstore' )
2019 "open-ils.cstore.direct.asset.copy.retrieve",
2023 acn => [qw/owning_lib record prefix suffix/],
2024 acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
2031 __PACKAGE__->register_method(
2032 method => 'retrieve_callnumber',
2033 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
2038 { desc => <<" DESC",
2039 Returns a fleshed call number object
2044 desc => 'An OpenILS asset::call_number id',
2048 { desc => 'call number with copies',
2052 sub retrieve_callnumber {
2056 my $args = shift || {};
2058 return OpenILS::Application::SuperCat::unAPI
2059 ->new(OpenSRF::AppSession
2060 ->create( 'open-ils.cstore' )
2062 "open-ils.cstore.direct.asset.call_number.retrieve",
2066 acn => [qw/owning_lib record copies uri_maps prefix suffix/],
2067 auricnm => [qw/uri/],
2068 acp => [qw/location status circ_lib stat_cat_entries notes parts/],
2076 __PACKAGE__->register_method(
2077 method => 'basic_record_holdings',
2078 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
2083 { desc => <<" DESC",
2084 Returns a basic hash representation of the requested bibliographic record's holdings
2089 desc => 'An OpenILS biblio::record_entry id',
2093 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
2097 sub basic_record_holdings {
2103 # holdings hold an array of call numbers, which hold an array of copies
2104 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
2107 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2109 my $tree = $_storage->request(
2110 "open-ils.cstore.direct.biblio.record_entry.retrieve",
2114 bre => [qw/call_numbers/],
2115 acn => [qw/copies owning_lib prefix suffix/],
2116 acp => [qw/location status circ_lib parts/],
2121 my $o_search = { shortname => uc($ou) };
2122 if (!$ou || $ou eq '-') {
2123 $o_search = { parent_ou => undef };
2126 my $orgs = $_storage->request(
2127 "open-ils.cstore.direct.actor.org_unit.search",
2130 flesh_fields => { aou => [qw/children/] }
2134 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
2136 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2138 for my $cn (@{$tree->call_numbers}) {
2139 next unless ( $cn->deleted eq 'f' || !$cn->deleted );
2142 for my $c (@{$cn->copies}) {
2143 next unless grep {$c->circ_lib->id == $_} @ou_ids;
2144 next unless _cp_is_visible($cn, $c);
2150 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
2152 for my $cp (@{$cn->copies}) {
2154 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
2155 next unless _cp_is_visible($cn, $cp);
2157 push @{$holdings{$cn->label}{'copies'}}, {
2158 barcode => $cp->barcode,
2159 status => $cp->status->name,
2160 location => $cp->location->name,
2161 circlib => $cp->circ_lib->shortname
2170 sub _cp_is_visible {
2175 if ( ($cp->deleted eq 'f' || !$cp->deleted) &&
2176 $cp->location->opac_visible eq 't' &&
2177 $cp->status->opac_visible eq 't' &&
2178 $cp->opac_visible eq 't' &&
2179 $cp->circ_lib->opac_visible eq 't' &&
2180 $cn->owning_lib->opac_visible eq 't'
2188 #__PACKAGE__->register_method(
2189 # method => 'new_record_holdings',
2190 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2195 # { desc => <<" DESC",
2196 #Returns the XML representation of the requested bibliographic record's holdings
2200 # { name => 'bibId',
2201 # desc => 'An OpenILS biblio::record_entry id',
2202 # type => 'number' },
2205 # { desc => 'Stream of bib record holdings hierarchy in XML',
2206 # type => 'string' }
2211 sub new_record_holdings {
2220 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
2221 my $limit = $$paging[0];
2222 my $offset = $$paging[1] || 0;
2224 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2225 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
2227 my $o_search = { shortname => uc($ou) };
2228 if (!$ou || $ou eq '-') {
2229 $o_search = { parent_ou => undef };
2232 my $one_org = $_storage->request(
2233 "open-ils.cstore.direct.actor.org_unit.search",
2237 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
2238 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
2240 my $orgs = $_storage->request(
2241 'open-ils.cstore.json_query.atomic',
2242 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
2246 my @ou_ids = map { $_->{id} } @$orgs;
2248 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2250 my %subselect = ( '-or' => [
2251 { owning_lib => \@ou_ids },
2255 call_number => { '=' => {'+acn'=>'id'} },
2257 circ_lib => \@ou_ids
2264 # we are dealing with -full or -uris, so we need to flesh things out
2267 # either way we're going to need uris
2268 # get all the uris up the tree (see also ba47ecc6196)
2270 my $uri_orgs = $_storage->request(
2271 'open-ils.cstore.json_query.atomic',
2272 { from => [ 'actor.org_unit_ancestors', $one_org->id ] }
2275 my @uri_ou_ids = map { $_->{id} } @$uri_orgs;
2277 # we have a -uris, just get the uris
2280 owning_lib => \@uri_ou_ids,
2283 from => { auricnm => 'auri' },
2285 call_number => { '=' => {'+acn'=>'id'} },
2286 '+auri' => { active => 't' }
2290 # we have a -full, get all the things
2291 } elsif ($flesh == 1) {
2292 %subselect = ( '-or' => [
2293 { owning_lib => \@ou_ids },
2297 call_number => { '=' => {'+acn'=>'id'} },
2299 circ_lib => \@ou_ids
2305 { owning_lib => \@uri_ou_ids },
2307 from => { auricnm => 'auri' },
2309 call_number => { '=' => {'+acn'=>'id'} },
2310 '+auri' => { active => 't' }
2319 my $cns = $_storage->request(
2320 "open-ils.cstore.direct.asset.call_number.search.atomic",
2327 acn => [qw/copies owning_lib uri_maps prefix suffix/],
2328 auricnm => [qw/uri/],
2329 acp => [qw/circ_lib location status stat_cat_entries notes parts/],
2330 asce => [qw/stat_cat/],
2332 ( $limit > -1 ? ( limit => $limit ) : () ),
2333 ( $offset ? ( offset => $offset ) : () ),
2334 order_by => { acn => { label_sortkey => {} } }
2338 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2342 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
2344 my $copy_counts = $count_req->gather(1);
2345 my $staff_copy_counts = $staff_count_req->gather(1);
2347 for my $c (@$copy_counts) {
2348 $$c{transcendant} ||= 0;
2349 my $out = "<count type='public'";
2350 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2351 $client->respond("$out/>\n")
2354 for my $c (@$staff_copy_counts) {
2355 $$c{transcendant} ||= 0;
2356 my $out = "<count type='staff'";
2357 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2358 $client->respond("$out/>\n")
2361 $client->respond("</counts><volumes>\n");
2363 for my $cn (@$cns) {
2364 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2366 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2367 # In the context of BibTemplate, copies aren't necessary because we pull those
2368 # in a separate call
2370 OpenILS::Application::SuperCat::unAPI::acn
2372 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2376 $client->respond("</volumes><subscriptions>\n");
2378 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2380 %subselect = ( '-or' => [
2381 { owning_lib => \@ou_ids },
2384 where => { holding_lib => \@ou_ids },
2390 my $ssubs = $_storage->request(
2391 "open-ils.cstore.direct.serial.subscription.search.atomic",
2392 { record_entry => $bib,
2397 ssub => [qw/distributions issuances scaps owning_lib/],
2398 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2399 sstr => [qw/items/],
2400 sitem => [qw/notes unit/],
2401 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2402 acn => [qw/owning_lib prefix suffix/],
2404 ( $limit > -1 ? ( limit => $limit ) : () ),
2405 ( $offset ? ( offset => $offset ) : () ),
2417 date_expected => {},
2424 for my $ssub (@$ssubs) {
2425 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2427 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2428 # In the context of BibTemplate, copies aren't necessary because we pull those
2429 # in a separate call
2431 OpenILS::Application::SuperCat::unAPI::ssub
2433 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2438 return "</subscriptions></holdings>\n";
2440 __PACKAGE__->register_method(
2441 method => 'new_record_holdings',
2442 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2447 { desc => <<" DESC",
2448 Returns the XML representation of the requested bibliographic record's holdings
2453 desc => 'An OpenILS biblio::record_entry ID',
2455 { name => 'orgUnit',
2456 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2459 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2461 { name => 'hideCopies',
2462 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2463 type => 'boolean' },
2465 desc => 'Arry of limit and offset for holdings paging',
2469 { desc => 'Stream of bib record holdings hierarchy in XML',
2479 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2481 my $recs = $_storage->request(
2482 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2483 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2486 return undef unless (@$recs);
2488 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2490 __PACKAGE__->register_method(
2491 method => 'isbn_holdings',
2492 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2496 { desc => <<" DESC",
2497 Returns the XML representation of the requested bibliographic record's holdings
2506 { desc => 'The bib record holdings hierarchy in XML',
2514 return '' unless $text;
2515 $text =~ s/&/&/gsom;
2516 $text =~ s/</</gsom;
2517 $text =~ s/>/>/gsom;
2518 $text =~ s/"/"/gsom;
2519 $text =~ s/'/'/gsom;
2523 sub recent_changes {
2526 my $when = shift || '1-01-01';
2529 my $type = 'biblio';
2532 if ($self->api_name =~ /authority/o) {
2533 $type = 'authority';
2537 my $axis = 'create_date';
2538 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2540 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2542 return $_storage->request(
2543 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2544 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2545 { order_by => { $hint => "$axis desc" }, limit => $limit }
2549 for my $t ( qw/biblio authority/ ) {
2550 for my $a ( qw/import edit/ ) {
2552 __PACKAGE__->register_method(
2553 method => 'recent_changes',
2554 api_name => "open-ils.supercat.$t.record.$a.recent",
2558 { desc => "Returns a list of recently ${a}ed $t records",
2562 desc => "Date to start looking for ${a}ed records",
2563 default => '1-01-01',
2567 desc => "Maximum count to retrieve",
2571 { desc => "An id list of $t records",
2579 sub retrieve_authority_marcxml {
2584 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2586 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2587 return $U->entityize( $record->marc ) if ($record);
2591 __PACKAGE__->register_method(
2592 method => 'retrieve_authority_marcxml',
2593 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2597 { desc => <<" DESC",
2598 Returns the MARCXML representation of the requested authority record
2602 { name => 'authorityId',
2603 desc => 'An OpenILS authority::record_entry id',
2607 { desc => 'The authority record in MARCXML',
2612 sub retrieve_record_marcxml {
2617 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2619 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2620 return $U->entityize( $record->marc ) if ($record);
2624 __PACKAGE__->register_method(
2625 method => 'retrieve_record_marcxml',
2626 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2630 { desc => <<" DESC",
2631 Returns the MARCXML representation of the requested bibliographic record
2636 desc => 'An OpenILS biblio::record_entry id',
2640 { desc => 'The bib record in MARCXML',
2645 sub retrieve_isbn_marcxml {
2650 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2652 my $recs = $_storage->request(
2653 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2654 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2657 return undef unless (@$recs);
2659 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2660 return $U->entityize( $record->marc ) if ($record);
2664 __PACKAGE__->register_method(
2665 method => 'retrieve_isbn_marcxml',
2666 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2670 { desc => <<" DESC",
2671 Returns the MARCXML representation of the requested ISBN
2676 desc => 'An ... um ... ISBN',
2680 { desc => 'The bib record in MARCXML',
2685 sub retrieve_record_transform {
2690 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2691 my $xslt = $record_xslt{$transform}{xslt};
2693 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2694 #$_storage->connect;
2696 my $record = $_storage->request(
2697 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2701 return undef unless ($record);
2703 return $U->entityize($xslt->output_as_chars($xslt->transform($_parser->parse_string($record->marc))));
2706 sub retrieve_isbn_transform {
2711 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2713 my $recs = $_storage->request(
2714 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2715 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2718 return undef unless (@$recs);
2720 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2721 my $xslt = $record_xslt{$transform}{xslt};
2723 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2725 return undef unless ($record);
2727 return $U->entityize($xslt->output_as_chars($xslt->transform($_parser->parse_string($record->marc))));
2730 sub retrieve_record_objects {
2735 my $type = 'biblio';
2737 if ($self->api_name =~ /authority/) {
2738 $type = 'authority';
2741 $ids = [$ids] unless (ref $ids);
2742 $ids = [grep {$_} @$ids];
2744 return [] unless (@$ids);
2746 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2747 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2749 __PACKAGE__->register_method(
2750 method => 'retrieve_record_objects',
2751 api_name => 'open-ils.supercat.record.object.retrieve',
2755 { desc => <<" DESC",
2756 Returns the Fieldmapper object representation of the requested bibliographic records
2761 desc => 'OpenILS biblio::record_entry ids',
2765 { desc => 'The bib records',
2770 __PACKAGE__->register_method(
2771 method => 'retrieve_record_objects',
2772 api_name => 'open-ils.supercat.authority.object.retrieve',
2776 { desc => <<" DESC",
2777 Returns the Fieldmapper object representation of the requested authority records
2781 { name => 'authIds',
2782 desc => 'OpenILS authority::record_entry ids',
2786 { desc => 'The authority records',
2791 sub retrieve_isbn_object {
2796 return undef unless ($isbn);
2798 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2799 my $recs = $_storage->request(
2800 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2801 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2804 return undef unless (@$recs);
2806 return $_storage->request(
2807 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2808 { id => $recs->[0]->record }
2811 __PACKAGE__->register_method(
2812 method => 'retrieve_isbn_object',
2813 api_name => 'open-ils.supercat.isbn.object.retrieve',
2817 { desc => <<" DESC",
2818 Returns the Fieldmapper object representation of the requested bibliographic record
2827 { desc => 'The bib record',
2834 sub retrieve_metarecord_mods {
2839 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2841 # Get the metarecord in question
2844 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2847 # Now get the map of all bib records for the metarecord
2850 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2851 {metarecord => $rid}
2854 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2856 # and retrieve the lead (master) record as MODS
2858 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2859 ->run($mr->master_record);
2860 my $master_mods = $_parser->parse_string($master)->documentElement;
2861 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2862 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2864 # ... and a MODS clone to populate, with guts removed.
2865 my $mods = $_parser->parse_string($master)->documentElement;
2866 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2867 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2868 ($mods) = $mods->findnodes('//mods:mods');
2869 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2870 $mods->removeChildNodes;
2871 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2873 # Add the metarecord ID as a (locally defined) info URI
2874 my $recordInfo = $mods
2876 ->createElement("recordInfo");
2878 my $recordIdentifier = $mods
2880 ->createElement("recordIdentifier");
2882 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2887 $recordIdentifier->appendTextNode(
2888 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2891 $recordInfo->appendChild($recordIdentifier);
2892 $mods->appendChild($recordInfo);
2894 # Grab the title, author and ISBN for the master record and populate the metarecord
2895 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2898 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2899 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2900 $title = $mods->ownerDocument->importNode($title);
2901 $mods->appendChild($title);
2904 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2906 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2907 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2908 $author = $mods->ownerDocument->importNode($author);
2909 $mods->appendChild($author);
2912 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2914 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2915 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2916 $isbn = $mods->ownerDocument->importNode($isbn);
2917 $mods->appendChild($isbn);
2920 # ... and loop over the constituent records
2921 for my $map ( @$recs ) {
2925 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2926 ->run($map->source);
2928 my $part_mods = $_parser->parse_string($rec);
2929 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2930 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2931 ($part_mods) = $part_mods->findnodes('//mods:mods');
2933 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2934 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2935 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2936 $node = $mods->ownerDocument->importNode($node);
2937 $mods->appendChild( $node );
2940 my $relatedItem = $mods
2942 ->createElement("relatedItem");
2944 $relatedItem->setAttribute( type => 'constituent' );
2946 my $identifier = $mods
2948 ->createElement("identifier");
2950 $identifier->setAttribute( type => 'uri' );
2952 my $subRecordInfo = $mods
2954 ->createElement("recordInfo");
2956 my $subRecordIdentifier = $mods
2958 ->createElement("recordIdentifier");
2960 my $subid = $map->source;
2961 $subRecordIdentifier->appendTextNode(
2962 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2967 $subRecordInfo->appendChild($subRecordIdentifier);
2969 $relatedItem->appendChild( $subRecordInfo );
2971 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2972 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2973 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2974 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2975 $relatedItem->appendChild($tor) if ($tor);
2977 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2978 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2979 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2980 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2981 $relatedItem->appendChild( $part_isbn );
2984 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2988 $mods->appendChild( $relatedItem );
2992 $_storage->disconnect;
2994 return $U->entityize($mods->toString);
2997 __PACKAGE__->register_method(
2998 method => 'retrieve_metarecord_mods',
2999 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
3003 { desc => <<" DESC",
3004 Returns the MODS representation of the requested metarecord
3008 { name => 'metarecordId',
3009 desc => 'An OpenILS metabib::metarecord id',
3013 { desc => 'The metarecord in MODS',
3018 sub list_metarecord_formats {
3021 { namespace_uri => 'http://www.loc.gov/mods/',
3022 docs => 'http://www.loc.gov/mods/',
3023 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
3028 for my $type ( keys %metarecord_xslt ) {
3031 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
3032 docs => $metarecord_xslt{$type}{docs},
3033 schema_location => $metarecord_xslt{$type}{schema_location},
3040 __PACKAGE__->register_method(
3041 method => 'list_metarecord_formats',
3042 api_name => 'open-ils.supercat.metarecord.formats',
3046 { desc => <<" DESC",
3047 Returns the list of valid metarecord formats that supercat understands.
3050 { desc => 'The format list',
3056 sub list_authority_formats {
3059 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3060 docs => 'http://www.loc.gov/marcxml/',
3061 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3063 marc21 => { docs => 'http://www.loc.gov/marc/' }
3067 # for my $type ( keys %record_xslt ) {
3070 # { namespace_uri => $record_xslt{$type}{namespace_uri},
3071 # docs => $record_xslt{$type}{docs},
3072 # schema_location => $record_xslt{$type}{schema_location},
3079 __PACKAGE__->register_method(
3080 method => 'list_authority_formats',
3081 api_name => 'open-ils.supercat.authority.formats',
3085 { desc => <<" DESC",
3086 Returns the list of valid authority formats that supercat understands.
3089 { desc => 'The format list',
3094 sub list_record_formats {
3097 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3098 docs => 'http://www.loc.gov/marcxml/',
3099 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3102 { marc21 => { docs => 'http://www.loc.gov/marc/' } }
3105 for my $type ( keys %record_xslt ) {
3108 { namespace_uri => $record_xslt{$type}{namespace_uri},
3109 docs => $record_xslt{$type}{docs},
3110 schema_location => $record_xslt{$type}{schema_location},
3117 __PACKAGE__->register_method(
3118 method => 'list_record_formats',
3119 api_name => 'open-ils.supercat.record.formats',
3123 { desc => <<" DESC",
3124 Returns the list of valid record formats that supercat understands.
3127 { desc => 'The format list',
3131 __PACKAGE__->register_method(
3132 method => 'list_record_formats',
3133 api_name => 'open-ils.supercat.isbn.formats',
3137 { desc => <<" DESC",
3138 Returns the list of valid record formats that supercat understands.
3141 { desc => 'The format list',
3154 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
3155 unless (length($isbn) >= 10);
3157 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3159 # Create a storage session, since we'll be making muliple requests.
3162 # Find the record that has that ISBN.
3163 my $bibrec = $_storage->request(
3164 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
3165 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
3168 # Go away if we don't have one.
3169 return {} unless (@$bibrec);
3171 # Find the metarecord for that bib record.
3172 my $mr = $_storage->request(
3173 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3174 {source => $bibrec->[0]->record}
3177 # Find the other records for that metarecord.
3178 my $records = $_storage->request(
3179 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3180 {metarecord => $mr->[0]->metarecord}
3183 # Just to be safe. There's currently no unique constraint on sources...
3184 my %unique_recs = map { ($_->source, 1) } @$records;
3185 my @rec_list = sort keys %unique_recs;
3187 # And now fetch the ISBNs for thos records.
3191 'open-ils.cstore.direct.metabib.full_rec.search',
3192 { tag => '020', subfield => 'a', record => $_ }
3193 )->gather(1) for (@rec_list);
3195 # We're done with the storage server session.
3196 $_storage->disconnect;
3198 # Return the oISBN data structure. This will be XMLized at a higher layer.
3200 { metarecord => $mr->[0]->metarecord,
3201 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
3204 __PACKAGE__->register_method(
3206 api_name => 'open-ils.supercat.oisbn',
3210 { desc => <<" DESC",
3211 Returns the ISBN list for the metarecord of the requested isbn
3216 desc => 'An ISBN. Duh.',
3220 { desc => 'record to isbn map',
3225 sub return_bib_search_aliases {
3228 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3230 my $cmsa = $_storage->request(
3231 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
3232 { alias => { '!=' => undef } }
3236 if ($_->alias =~ /\./) {
3237 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
3238 $aliases{$qualifier}{$name}{'index'} = $_->alias;
3239 # We will add a 'title' property in a subsequent schema
3240 $aliases{$qualifier}{$name}{'title'} = $name;
3242 # au/kw/se/su/ti go into the default 'eg' qualifier
3243 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
3244 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
3251 __PACKAGE__->register_method(
3252 method => 'return_bib_search_aliases',
3253 api_name => 'open-ils.supercat.biblio.search_aliases',
3257 { desc => <<" DESC",
3258 Returns the set of qualified search aliases in the system
3262 { desc => 'Hash of qualified search aliases',
3268 package OpenILS::Application::SuperCat::unAPI;
3269 use base qw/OpenILS::Application::SuperCat/;
3272 die "dummy superclass, use a real class";
3278 return unless ($obj);
3280 $class = ref($class) || $class;
3282 if ($class eq __PACKAGE__) {
3283 return unless (ref($obj));
3284 $class .= '::' . $obj->json_hint;
3287 return bless { obj => $obj } => $class;
3292 return $self->{obj};
3295 package OpenILS::Application::SuperCat::unAPI::auri;
3296 use base qw/OpenILS::Application::SuperCat::unAPI/;
3302 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
3303 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
3304 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
3305 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3306 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
3308 if (!$args->{no_volumes}) {
3309 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
3310 $xml .= " <volumes>\n" . join(
3313 OpenILS::Application::SuperCat::unAPI
3314 ->new( $_->call_number )
3315 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
3316 } @{ $self->obj->call_number_maps }
3317 ) . " </volumes>\n";
3320 $xml .= " <volumes/>\n";
3324 $xml .= " </uri>\n";
3329 package OpenILS::Application::SuperCat::unAPI::acn;
3330 use base qw/OpenILS::Application::SuperCat::unAPI/;
3336 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
3338 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
3339 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
3340 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
3341 $xml .= 'deleted="' . $self->obj->deleted . '" ';
3342 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
3345 if (!$args->{no_copies}) {
3346 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
3347 $xml .= " <copies>\n" . join(
3350 OpenILS::Application::SuperCat::unAPI
3352 ->as_xml({ %$args, no_volume=>1 })
3353 } @{ $self->obj->copies }
3357 $xml .= " <copies/>\n";
3361 if (!$args->{no_uris}) {
3362 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3363 $xml .= " <uris>\n" . join(
3366 OpenILS::Application::SuperCat::unAPI
3368 ->as_xml({ %$args, no_volumes=>1 })
3369 } @{ $self->obj->uri_maps }
3373 $xml .= " <uris/>\n";
3378 $xml .= ' <prefix ';
3379 $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3380 $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3381 $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3382 $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3385 $xml .= ' <suffix ';
3386 $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3387 $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3388 $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3389 $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3392 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3393 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3394 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3395 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3398 unless ($args->{no_record}) {
3399 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3401 my $r_doc = $parser->parse_string($self->obj->record->marc);
3402 $r_doc->documentElement->setAttribute( id => $rec_tag );
3403 $xml .= $U->entityize($r_doc->documentElement->toString);
3406 $xml .= " </volume>\n";
3411 package OpenILS::Application::SuperCat::unAPI::ssub;
3412 use base qw/OpenILS::Application::SuperCat::unAPI/;
3418 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3420 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3421 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3422 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3423 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3426 if (!$args->{no_distributions}) {
3427 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3428 $xml .= " <distributions>\n" . join(
3431 OpenILS::Application::SuperCat::unAPI
3433 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3434 } @{ $self->obj->distributions }
3435 ) . " </distributions>\n";
3438 $xml .= " <distributions/>\n";
3442 if (!$args->{no_captions_and_patterns}) {
3443 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3444 $xml .= " <captions_and_patterns>\n" . join(
3447 OpenILS::Application::SuperCat::unAPI
3449 ->as_xml({ %$args, no_subscription=>1 })
3450 } @{ $self->obj->scaps }
3451 ) . " </captions_and_patterns>\n";
3454 $xml .= " <captions_and_patterns/>\n";
3458 if (!$args->{no_issuances}) {
3459 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3460 $xml .= " <issuances>\n" . join(
3463 OpenILS::Application::SuperCat::unAPI
3465 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3466 } @{ $self->obj->issuances }
3467 ) . " </issuances>\n";
3470 $xml .= " <issuances/>\n";
3475 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3476 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3477 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3478 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3481 unless ($args->{no_record}) {
3482 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3484 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3485 $r_doc->documentElement->setAttribute( id => $rec_tag );
3486 $xml .= $U->entityize($r_doc->documentElement->toString);
3489 $xml .= " </subscription>\n";
3494 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3495 use base qw/OpenILS::Application::SuperCat::unAPI/;
3501 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3503 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3505 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3506 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3507 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3508 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3511 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3513 $xml .= " </serial_summary>\n";
3519 package OpenILS::Application::SuperCat::unAPI::sssum;
3520 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3522 package OpenILS::Application::SuperCat::unAPI::sbsum;
3523 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3525 package OpenILS::Application::SuperCat::unAPI::sisum;
3526 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3528 package OpenILS::Application::SuperCat::unAPI::sdist;
3529 use base qw/OpenILS::Application::SuperCat::unAPI/;
3535 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3537 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3538 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3539 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3540 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3543 if (!$args->{no_distributions}) {
3544 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3545 $xml .= " <streams>\n" . join(
3548 OpenILS::Application::SuperCat::unAPI
3550 ->as_xml({ %$args, no_distribution=>1 })
3551 } @{ $self->obj->streams }
3552 ) . " </streams>\n";
3555 $xml .= " <streams/>\n";
3559 if (!$args->{no_summaries}) {
3560 $xml .= " <summaries>\n";
3564 OpenILS::Application::SuperCat::unAPI
3566 ->as_xml({ %$args, no_distribution=>1 }) : ""
3567 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3570 $xml .= " </summaries>\n";
3574 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3575 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3576 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3577 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3580 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3582 if (!$args->{no_record} && $self->obj->record_entry) {
3583 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3585 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3586 $r_doc->documentElement->setAttribute( id => $rec_tag );
3587 $xml .= $U->entityize($r_doc->documentElement->toString);
3590 $xml .= " </distribution>\n";
3595 package OpenILS::Application::SuperCat::unAPI::sstr;
3596 use base qw/OpenILS::Application::SuperCat::unAPI/;
3602 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3604 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3605 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3608 if (!$args->{no_items}) {
3609 if (ref($self->obj->items) && @{ $self->obj->items }) {
3610 $xml .= " <items>\n" . join(
3613 OpenILS::Application::SuperCat::unAPI
3615 ->as_xml({ %$args, no_stream=>1 })
3616 } @{ $self->obj->items }
3620 $xml .= " <items/>\n";
3624 #XXX routing_list_user's?
3626 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3628 $xml .= " </stream>\n";
3633 package OpenILS::Application::SuperCat::unAPI::sitem;
3634 use base qw/OpenILS::Application::SuperCat::unAPI/;
3640 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3642 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3643 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3644 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3646 if ($args->{no_issuance}) {
3647 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3648 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3653 if (ref($self->obj->notes) && $self->obj->notes) {
3654 $xml .= " <notes>\n";
3655 for my $note ( @{$self->obj->notes} ) {
3656 next unless ( $note->pub eq 't' );
3657 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3660 $xml .= " </notes>\n";
3662 $xml .= " <notes/>\n";
3665 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3666 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3667 $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});
3668 $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});
3670 $xml .= " </serial_item>\n";
3675 package OpenILS::Application::SuperCat::unAPI::sunit;
3676 use base qw/OpenILS::Application::SuperCat::unAPI/;
3682 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3683 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3685 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3686 create_date edit_date copy_number circulate deposit ref holdable deleted
3687 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3688 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3693 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3694 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3695 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3697 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3698 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3699 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3700 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3703 $xml .= " <copy_notes>\n";
3704 if (ref($self->obj->notes) && $self->obj->notes) {
3705 for my $note ( @{$self->obj->notes} ) {
3706 next unless ( $note->pub eq 't' );
3707 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3712 $xml .= " </copy_notes>\n";
3713 $xml .= " <statcats>\n";
3715 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3716 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3717 next unless ( $sce->stat_cat->opac_visible eq 't' );
3718 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3722 $xml .= " </statcats>\n";
3724 unless ($args->{no_volume}) {
3725 if (ref($self->obj->call_number)) {
3726 $xml .= OpenILS::Application::SuperCat::unAPI
3727 ->new( $self->obj->call_number )
3728 ->as_xml({ %$args, no_copies=>1 });
3730 $xml .= " <volume/>\n";
3734 $xml .= " </serial_unit>\n";
3739 package OpenILS::Application::SuperCat::unAPI::scap;
3740 use base qw/OpenILS::Application::SuperCat::unAPI/;
3746 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3747 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3749 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3750 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3751 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3754 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3755 $xml .= " </caption_and_pattern>\n";
3760 package OpenILS::Application::SuperCat::unAPI::siss;
3761 use base qw/OpenILS::Application::SuperCat::unAPI/;
3767 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3768 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3770 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3771 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3775 if (!$args->{no_items}) {
3776 if (ref($self->obj->items) && @{ $self->obj->items }) {
3777 $xml .= " <items>\n" . join(
3780 OpenILS::Application::SuperCat::unAPI
3782 ->as_xml({ %$args, no_stream=>1 })
3783 } @{ $self->obj->items }
3787 $xml .= " <items/>\n";
3791 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3792 $xml .= " </issuance>\n";
3797 package OpenILS::Application::SuperCat::unAPI::acp;
3798 use base qw/OpenILS::Application::SuperCat::unAPI/;
3800 use OpenILS::Application::AppUtils;
3801 my $U = "OpenILS::Application::AppUtils";
3807 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3808 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3810 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3811 create_date edit_date copy_number circulate deposit ref holdable deleted
3812 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3817 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3818 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3819 $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";
3821 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3822 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3823 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3824 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3827 $xml .= " <monograph_parts>\n";
3828 if (ref($self->obj->parts) && $self->obj->parts) {
3829 for my $part ( @{$self->obj->parts} ) {
3830 next if $U->is_true($part->deleted);
3831 $xml .= sprintf(' <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3836 $xml .= " </monograph_parts>\n";
3837 $xml .= " <copy_notes>\n";
3838 if (ref($self->obj->notes) && $self->obj->notes) {
3839 for my $note ( @{$self->obj->notes} ) {
3840 next unless ( $note->pub eq 't' );
3841 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3846 $xml .= " </copy_notes>\n";
3847 $xml .= " <statcats>\n";
3849 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3850 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3851 next unless ( $sce->stat_cat->opac_visible eq 't' );
3852 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3856 $xml .= " </statcats>\n";
3858 unless ($args->{no_volume}) {
3859 if (ref($self->obj->call_number)) {
3860 $xml .= OpenILS::Application::SuperCat::unAPI
3861 ->new( $self->obj->call_number )
3862 ->as_xml({ %$args, no_copies=>1 });
3864 $xml .= " <volume/>\n";
3868 $xml .= " </copy>\n";