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/;
48 %authority_browse_axis_cache,
52 # we need an XML parser
53 $_parser = new XML::LibXML;
56 $_xslt = new XML::LibXSLT;
58 # parse the MODS xslt ...
59 my $mods33_xslt = $_parser->parse_file(
60 OpenSRF::Utils::SettingsClient
62 ->config_value( dirs => 'xsl' ).
63 "/MARC21slim2MODS33.xsl"
65 # and stash a transformer
66 $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
67 $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
68 $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
69 $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
71 # parse the MODS xslt ...
72 my $mods32_xslt = $_parser->parse_file(
73 OpenSRF::Utils::SettingsClient
75 ->config_value( dirs => 'xsl' ).
76 "/MARC21slim2MODS32.xsl"
78 # and stash a transformer
79 $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
80 $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
81 $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
82 $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
84 # parse the MODS xslt ...
85 my $mods3_xslt = $_parser->parse_file(
86 OpenSRF::Utils::SettingsClient
88 ->config_value( dirs => 'xsl' ).
89 "/MARC21slim2MODS3.xsl"
91 # and stash a transformer
92 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
93 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
94 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
95 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
97 # parse the MODS xslt ...
98 my $mods_xslt = $_parser->parse_file(
99 OpenSRF::Utils::SettingsClient
101 ->config_value( dirs => 'xsl' ).
102 "/MARC21slim2MODS.xsl"
104 # and stash a transformer
105 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
106 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
107 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
108 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
110 # parse the ATOM entry xslt ...
111 my $atom_xslt = $_parser->parse_file(
112 OpenSRF::Utils::SettingsClient
114 ->config_value( dirs => 'xsl' ).
115 "/MARC21slim2ATOM.xsl"
117 # and stash a transformer
118 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
119 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
120 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
122 # parse the RDFDC xslt ...
123 my $rdf_dc_xslt = $_parser->parse_file(
124 OpenSRF::Utils::SettingsClient
126 ->config_value( dirs => 'xsl' ).
127 "/MARC21slim2RDFDC.xsl"
129 # and stash a transformer
130 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
131 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
132 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
134 # parse the SRWDC xslt ...
135 my $srw_dc_xslt = $_parser->parse_file(
136 OpenSRF::Utils::SettingsClient
138 ->config_value( dirs => 'xsl' ).
139 "/MARC21slim2SRWDC.xsl"
141 # and stash a transformer
142 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
143 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
144 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
146 # parse the OAIDC xslt ...
147 my $oai_dc_xslt = $_parser->parse_file(
148 OpenSRF::Utils::SettingsClient
150 ->config_value( dirs => 'xsl' ).
151 "/MARC21slim2OAIDC.xsl"
153 # and stash a transformer
154 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
155 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
156 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
158 # parse the RSS xslt ...
159 my $rss_xslt = $_parser->parse_file(
160 OpenSRF::Utils::SettingsClient
162 ->config_value( dirs => 'xsl' ).
163 "/MARC21slim2RSS2.xsl"
165 # and stash a transformer
166 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
168 # parse the FGDC xslt ...
169 my $fgdc_xslt = $_parser->parse_file(
170 OpenSRF::Utils::SettingsClient
172 ->config_value( dirs => 'xsl' ).
173 "/MARC21slim2FGDC.xsl"
175 # and stash a transformer
176 $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
177 $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
178 $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
180 register_record_transforms();
182 register_new_authorities_methods();
187 sub register_record_transforms {
188 for my $type ( keys %record_xslt ) {
189 __PACKAGE__->register_method(
190 method => 'retrieve_record_transform',
191 api_name => "open-ils.supercat.record.$type.retrieve",
195 { desc => "Returns the \U$type\E representation ".
196 "of the requested bibliographic record",
200 desc => 'An OpenILS biblio::record_entry id',
204 { desc => "The bib record in \U$type\E",
209 __PACKAGE__->register_method(
210 method => 'retrieve_isbn_transform',
211 api_name => "open-ils.supercat.isbn.$type.retrieve",
215 { desc => "Returns the \U$type\E representation ".
216 "of the requested bibliographic record",
224 { desc => "The bib record in \U$type\E",
231 sub register_new_authorities_methods {
232 my %register_args = (
233 method => "generic_new_authorities_method",
237 desc => q/Generated method/,
240 desc => "An axis, an authority tag number, or a bibliographic tag number, depending on invocation",
243 desc => "A search term",
246 desc => "zero-based page number of results",
248 {name => "page size",
249 desc => "number of results per page",
253 desc => "A list of authority record IDs", type => "array"
258 foreach my $how (qw/axis atag btag/) {
259 foreach my $action (qw/browse_center browse_top
260 search_rank search_heading/) {
262 $register_args{api_name} =
263 "open-ils.supercat.authority.$action.by_$how";
264 __PACKAGE__->register_method(%register_args);
266 $register_args{api_name} =
267 "open-ils.supercat.authority.$action.by_$how.refs";
268 __PACKAGE__->register_method(%register_args);
274 sub generic_new_authorities_method {
278 # We want to be extra careful with these arguments, since the next
279 # thing we're doing with them is passing them to a DB procedure.
282 my $page = int(shift || 0);
283 my $page_size = shift;
284 my $thesauruses = shift;
286 # undef ok, but other non numbers not ok
287 $page_size = int($page_size) if defined $page_size;
289 # Figure out how we were called and what DB procedure we'll call in turn.
290 $self->api_name =~ /\.by_(\w+)($|\.)/;
294 $self->api_name =~ /authority\.(\w+)\./;
297 my $method = "${metaaxis}_$action";
298 $method .= "_refs" if $refs;
300 # Match authority.full_rec normalization
301 # XXX don't know whether we need second arg 'subfield'?
302 $term = naco_normalize($term);
304 my $storage = create OpenSRF::AppSession("open-ils.storage");
305 my $list = $storage->request(
306 "open-ils.storage.authority.in_db.browse_or_search",
307 $method, $what, $term, $page, $page_size, $thesauruses
321 return unless ($tree && ref($tree->$field));
323 my @things = $filter->($tree);
324 for my $v ( @{$tree->$field} ){
325 push @things, $filter->($v);
326 push @things, tree_walker($v, $field, $filter);
331 # find a label_sortkey for a call number with a label which is equal
332 # (or close to) a given label value
333 sub _label_sortkey_from_label {
334 my ($label, $_storage, $ou_ids, $cp_filter) = @_;
336 my $closest_cn = $_storage->request(
337 "open-ils.cstore.direct.asset.call_number.search.atomic",
338 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
339 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
344 flesh_fields => { acn => [qw/label_class/] },
346 order_by => { acn => "oils_text_as_bytea(label), id" }
350 if ($closest_cn->[0]->label eq $label) {
351 # we found an exact match stop here
352 return $closest_cn->[0]->label_sortkey;
354 # we got as close as we could by label alone, let's try to
355 # normalize and get closer
356 $closest_cn = $_storage->request(
357 "open-ils.cstore.direct.asset.call_number.search.atomic",
359 => { ">=" => [$closest_cn->[0]->label_class->normalizer, $label] },
360 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
365 order_by => { acn => "label_sortkey, id" }
369 return $closest_cn->[0]->label_sortkey;
374 return '~~~'; #fallback to high ascii value, we are at the end of the range
383 my $page_size = shift || 9;
384 my $page = shift || 0;
385 my $statuses = shift || [];
386 my $copy_locations = shift || [];
388 my ($before_limit,$after_limit) = (0,0);
389 my ($before_offset,$after_offset) = (0,0);
392 $before_limit = $after_limit = int($page_size / 2);
393 $after_limit += 1 if ($page_size % 2);
395 $before_offset = $after_offset = int($page_size / 2);
396 $before_offset += 1 if ($page_size % 2);
397 $before_limit = $after_limit = $page_size;
400 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
403 # if OU is not specified, or if '-' is specified, do not enter the block
404 unless (!$ou || $ou eq '-') {
405 # check if the shortname of the top org_unit is specified
406 my $top_org = $_storage->request(
407 "open-ils.cstore.direct.actor.org_unit.search",
408 { parent_ou => undef }
411 if ($ou eq $top_org->shortname) {
412 $logger->debug("Searching for CNs at top org $ou");
414 my $o_search = { shortname => $ou };
416 my $orgs = $_storage->request(
417 "open-ils.cstore.direct.actor.org_unit.search",
420 flesh_fields => { aou => [qw/children/] }
424 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
426 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
433 if (@$statuses || @$copy_locations) {
439 call_number => { '=' => { '+acn' => 'id' } },
441 ((@$statuses) ? ( status => $statuses) : ()),
442 ((@$copy_locations) ? ( location => $copy_locations) : ())
448 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
451 my $before = $_storage->request(
452 "open-ils.cstore.direct.asset.call_number.search.atomic",
453 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
454 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
459 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
460 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
461 limit => $before_limit,
462 offset => abs($page) * $page_size - $before_offset,
465 push @list, reverse(@$before);
469 my $after = $_storage->request(
470 "open-ils.cstore.direct.asset.call_number.search.atomic",
471 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
472 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
477 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
478 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
479 limit => $after_limit,
480 offset => abs($page) * $page_size - $after_offset,
488 __PACKAGE__->register_method(
489 method => 'cn_browse',
490 api_name => 'open-ils.supercat.call_number.browse',
495 Returns the XML representation of the requested bibliographic record's holdings
500 desc => 'The target call number label',
502 { name => 'org_unit',
503 desc => 'The org unit shortname (or "-" or undef for global) to browse',
505 { name => 'page_size',
506 desc => 'Count of call numbers to retrieve, default is 9',
509 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
511 { name => 'statuses',
512 desc => 'Array of statuses to filter copies by, optional and can be undef.',
514 { name => 'locations',
515 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
519 { desc => 'Call numbers with owning_lib and record fleshed',
530 my $limit = shift || 10;
531 my $page = shift || 0;
532 my $statuses = shift || [];
533 my $copy_locations = shift || [];
536 my $offset = abs($page) * $limit;
537 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
539 my $o_search = { shortname => $ou };
540 if (!$ou || $ou eq '-') {
541 $o_search = { parent_ou => undef };
544 my $orgs = $_storage->request(
545 "open-ils.cstore.direct.actor.org_unit.search",
548 flesh_fields => { aou => [qw/children/] }
552 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
554 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
559 if (@$statuses || @$copy_locations) {
565 call_number => { '=' => { '+acn' => 'id' } },
567 ((@$statuses) ? ( status => $statuses) : ()),
568 ((@$copy_locations) ? ( location => $copy_locations) : ())
574 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
577 my $before = $_storage->request(
578 "open-ils.cstore.direct.asset.call_number.search.atomic",
579 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
580 owning_lib => \@ou_ids,
585 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
586 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
591 push @list, reverse(@$before);
595 my $after = $_storage->request(
596 "open-ils.cstore.direct.asset.call_number.search.atomic",
597 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
598 owning_lib => \@ou_ids,
603 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
604 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
614 __PACKAGE__->register_method(
615 method => 'cn_startwith',
616 api_name => 'open-ils.supercat.call_number.startwith',
621 Returns the XML representation of the requested bibliographic record's holdings
626 desc => 'The target call number label',
628 { name => 'org_unit',
629 desc => 'The org unit shortname (or "-" or undef for global) to browse',
631 { name => 'page_size',
632 desc => 'Count of call numbers to retrieve, default is 9',
635 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
637 { name => 'statuses',
638 desc => 'Array of statuses to filter copies by, optional and can be undef.',
640 { name => 'locations',
641 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
645 { desc => 'Call numbers with owning_lib and record fleshed',
651 sub new_books_by_item {
656 my $page_size = shift || 10;
657 my $page = shift || 1;
658 my $statuses = shift || [];
659 my $copy_locations = shift || [];
661 my $offset = $page_size * ($page - 1);
663 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
666 if ($ou && $ou ne '-') {
667 my $orgs = $_storage->request(
668 "open-ils.cstore.direct.actor.org_unit.search",
669 { shortname => $ou },
671 flesh_fields => { aou => [qw/children/] }
674 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
677 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
678 my $cns = $_storage->request(
679 "open-ils.cstore.json_query.atomic",
680 { select => { acn => ['record'],
681 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
683 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
687 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
688 ((@$statuses) ? ( status => $statuses) : ()),
689 ((@$copy_locations) ? ( location => $copy_locations) : ())
691 '+acn' => { record => { '>' => 0 } },
693 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
699 return [ map { $_->{record} } @$cns ];
701 __PACKAGE__->register_method(
702 method => 'new_books_by_item',
703 api_name => 'open-ils.supercat.new_book_list',
708 Returns the XML representation of the requested bibliographic record's holdings
712 { name => 'org_unit',
713 desc => 'The org unit shortname (or "-" or undef for global) to list',
715 { name => 'page_size',
716 desc => 'Count of records to retrieve, default is 10',
719 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
721 { name => 'statuses',
722 desc => 'Array of statuses to filter copies by, optional and can be undef.',
724 { name => 'locations',
725 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
729 { desc => 'Record IDs',
738 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
740 __PACKAGE__->register_method(
741 method => 'general_browse',
742 api_name => 'open-ils.supercat.title.browse',
743 tag => 'tnf', subfield => 'a',
747 { desc => "Returns a list of the requested org-scoped record IDs held",
749 [ { name => 'value', desc => 'The target title', type => 'string' },
750 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
751 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
752 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
753 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
754 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
755 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
758 __PACKAGE__->register_method(
759 method => 'general_browse',
760 api_name => 'open-ils.supercat.author.browse',
761 tag => [qw/100 110 111/], subfield => 'a',
765 { desc => "Returns a list of the requested org-scoped record IDs held",
767 [ { name => 'value', desc => 'The target author', type => 'string' },
768 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
769 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
770 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
771 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
772 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
773 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
776 __PACKAGE__->register_method(
777 method => 'general_browse',
778 api_name => 'open-ils.supercat.subject.browse',
779 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
783 { desc => "Returns a list of the requested org-scoped record IDs held",
785 [ { name => 'value', desc => 'The target subject', type => 'string' },
786 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
787 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
788 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
789 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
790 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
791 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
794 __PACKAGE__->register_method(
795 method => 'general_browse',
796 api_name => 'open-ils.supercat.topic.browse',
797 tag => [qw/650 690/], subfield => 'a',
801 { desc => "Returns a list of the requested org-scoped record IDs held",
803 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
804 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
805 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
806 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
807 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
808 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
809 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
812 __PACKAGE__->register_method(
813 method => 'general_browse',
814 api_name => 'open-ils.supercat.series.browse',
815 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
819 { desc => "Returns a list of the requested org-scoped record IDs held",
821 [ { name => 'value', desc => 'The target series', type => 'string' },
822 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
823 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
824 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
825 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
826 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
827 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
837 my $subfield = shift;
840 my $page_size = shift || 9;
841 my $page = shift || 0;
842 my $statuses = shift || [];
843 my $copy_locations = shift || [];
845 my ($before_limit,$after_limit) = (0,0);
846 my ($before_offset,$after_offset) = (0,0);
849 $before_limit = $after_limit = int($page_size / 2);
850 $after_limit += 1 if ($page_size % 2);
852 $before_offset = $after_offset = int($page_size / 2);
853 $before_offset += 1 if ($page_size % 2);
854 $before_limit = $after_limit = $page_size;
857 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
860 if ($ou && $ou ne '-') {
861 my $orgs = $_storage->request(
862 "open-ils.cstore.direct.actor.org_unit.search",
863 { shortname => $ou },
865 flesh_fields => { aou => [qw/children/] }
868 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
871 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
876 my $before = $_storage->request(
877 "open-ils.cstore.json_query.atomic",
878 { select => { mfr => [qw/record value/] },
883 subfield => $subfield,
884 value => { '<' => lc($value) }
888 { select=> { acp => [ 'id' ] },
889 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
891 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
894 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
895 ((@$statuses) ? ( status => $statuses) : ()),
896 ((@$copy_locations) ? ( location => $copy_locations) : ())
903 { select=> { auri => [ 'id' ] },
904 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
906 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
907 '+auri' => { active => 't' }
914 order_by => { mfr => { value => 'desc' } },
915 limit => $before_limit,
916 offset => abs($page) * $page_size - $before_offset,
919 push @list, map { $_->{record} } reverse(@$before);
923 my $after = $_storage->request(
924 "open-ils.cstore.json_query.atomic",
925 { select => { mfr => [qw/record value/] },
930 subfield => $subfield,
931 value => { '>=' => lc($value) }
935 { select=> { acp => [ 'id' ] },
936 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
938 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
941 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
942 ((@$statuses) ? ( status => $statuses) : ()),
943 ((@$copy_locations) ? ( location => $copy_locations) : ())
950 { select=> { auri => [ 'id' ] },
951 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
953 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
954 '+auri' => { active => 't' }
961 order_by => { mfr => { value => 'asc' } },
962 limit => $after_limit,
963 offset => abs($page) * $page_size - $after_offset,
966 push @list, map { $_->{record} } @$after;
971 __PACKAGE__->register_method(
972 method => 'tag_sf_browse',
973 api_name => 'open-ils.supercat.tag.browse',
978 Returns a list of the requested org-scoped record IDs held
983 desc => 'The target MARC tag',
985 { name => 'subfield',
986 desc => 'The target MARC subfield',
989 desc => 'The target string',
991 { name => 'org_unit',
992 desc => 'The org unit shortname (or "-" or undef for global) to browse',
994 { name => 'page_size',
995 desc => 'Count of call numbers to retrieve, default is 9',
998 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1000 { name => 'statuses',
1001 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1003 { name => 'locations',
1004 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1008 { desc => 'Record IDs that have copies at the relevant org units',
1013 sub grab_authority_browse_axes {
1014 my ($self, $client, $full) = @_;
1016 unless(scalar(keys(%authority_browse_axis_cache))) {
1017 my $axes = new_editor->search_authority_browse_axis([
1018 { code => { '<>' => undef } },
1019 { flesh => 2, flesh_fields => { aba => ['fields'], acsaf => ['bib_fields','sub_entries'] } }
1021 $authority_browse_axis_cache{$_->code} = $_ for (@$axes);
1026 map { $authority_browse_axis_cache{$_} } sort keys %authority_browse_axis_cache
1029 return [keys %authority_browse_axis_cache];
1032 __PACKAGE__->register_method(
1033 method => 'grab_authority_browse_axes',
1034 api_name => 'open-ils.supercat.authority.browse_axis_list',
1038 { desc => "Returns a list of valid authority browse/startswith axes",
1040 { 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' }
1042 'return' => { desc => 'Axis codes or whole axes, see "full" param', type => 'array' }
1046 sub axis_authority_browse {
1051 $axis =~ s/^authority\.//;
1052 $axis =~ s/(\.refs)$//;
1055 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1058 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1059 push @tags, $f->tag;
1061 push @tags, $_->tag for @{$f->sub_entries};
1065 return authority_tag_sf_browse($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1067 __PACKAGE__->register_method(
1068 method => 'axis_authority_browse',
1069 api_name => 'open-ils.supercat.authority.browse.by_axis',
1073 { desc => "Returns a list of the requested authority record IDs held",
1075 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1076 { name => 'value', desc => 'The target value', type => 'string' },
1077 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1078 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1079 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1085 sub general_authority_browse {
1088 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
1090 __PACKAGE__->register_method(
1091 method => 'general_authority_browse',
1092 api_name => 'open-ils.supercat.authority.title.browse',
1093 tag => ['130'], subfield => 'a',
1097 { desc => "Returns a list of the requested authority record IDs held",
1099 [ { name => 'value', desc => 'The target title', type => 'string' },
1100 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1101 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1102 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1105 __PACKAGE__->register_method(
1106 method => 'general_authority_browse',
1107 api_name => 'open-ils.supercat.authority.author.browse',
1108 tag => [qw/100 110 111/], subfield => 'a',
1112 { desc => "Returns a list of the requested authority record IDs held",
1114 [ { name => 'value', desc => 'The target author', type => 'string' },
1115 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1116 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1117 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1120 __PACKAGE__->register_method(
1121 method => 'general_authority_browse',
1122 api_name => 'open-ils.supercat.authority.subject.browse',
1123 tag => [qw/148 150 151 155/], subfield => 'a',
1127 { desc => "Returns a list of the requested authority record IDs held",
1129 [ { name => 'value', desc => 'The target subject', type => 'string' },
1130 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1131 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1132 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1135 __PACKAGE__->register_method(
1136 method => 'general_authority_browse',
1137 api_name => 'open-ils.supercat.authority.topic.browse',
1138 tag => ['150'], subfield => 'a',
1142 { desc => "Returns a list of the requested authority record IDs held",
1144 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1145 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1146 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1147 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1150 __PACKAGE__->register_method(
1151 method => 'general_authority_browse',
1152 api_name => 'open-ils.supercat.authority.title.refs.browse',
1153 tag => ['130'], subfield => 'a',
1157 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1159 [ { name => 'value', desc => 'The target title', type => 'string' },
1160 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1161 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1162 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1165 __PACKAGE__->register_method(
1166 method => 'general_authority_browse',
1167 api_name => 'open-ils.supercat.authority.author.refs.browse',
1168 tag => [qw/100 110 111/], subfield => 'a',
1172 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1174 [ { name => 'value', desc => 'The target author', type => 'string' },
1175 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1176 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1177 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1180 __PACKAGE__->register_method(
1181 method => 'general_authority_browse',
1182 api_name => 'open-ils.supercat.authority.subject.refs.browse',
1183 tag => [qw/148 150 151 155/], subfield => 'a',
1187 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1189 [ { name => 'value', desc => 'The target subject', type => 'string' },
1190 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1191 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1192 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1195 __PACKAGE__->register_method(
1196 method => 'general_authority_browse',
1197 api_name => 'open-ils.supercat.authority.topic.refs.browse',
1198 tag => ['150'], subfield => 'a',
1202 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1204 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1205 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1206 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1207 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1213 sub authority_tag_sf_browse {
1218 my $subfield = shift;
1220 my $page_size = shift || 9;
1221 my $page = shift || 0;
1223 # Match authority.full_rec normalization
1224 $value = naco_normalize($value, $subfield);
1226 my ($before_limit,$after_limit) = (0,0);
1227 my ($before_offset,$after_offset) = (0,0);
1230 $before_limit = $after_limit = int($page_size / 2);
1231 $after_limit += 1 if ($page_size % 2);
1233 $before_offset = $after_offset = int($page_size / 2);
1234 $before_offset += 1 if ($page_size % 2);
1235 $before_limit = $after_limit = $page_size;
1238 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1240 # .refs variant includes 4xx and 5xx variants for see / see also
1242 foreach my $tagname (@$tag) {
1243 push(@ref_tags, $tagname);
1244 if ($self->api_name =~ /\.refs\./) {
1245 push(@ref_tags, '4' . substr($tagname, 1, 2));
1246 push(@ref_tags, '5' . substr($tagname, 1, 2));
1252 my $before = $_storage->request(
1253 "open-ils.cstore.json_query.atomic",
1254 { select => { afr => [qw/record value/] },
1256 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1257 order_by => { afr => { value => 'desc' } },
1258 limit => $before_limit,
1259 offset => abs($page) * $page_size - $before_offset,
1262 push @list, map { $_->{record} } reverse(@$before);
1266 my $after = $_storage->request(
1267 "open-ils.cstore.json_query.atomic",
1268 { select => { afr => [qw/record value/] },
1270 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1271 order_by => { afr => { value => 'asc' } },
1272 limit => $after_limit,
1273 offset => abs($page) * $page_size - $after_offset,
1276 push @list, map { $_->{record} } @$after;
1279 # If we're not pulling in see/see also references, just return the raw list
1280 if ($self->api_name !~ /\.refs\./) {
1284 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1287 foreach my $record (@list) {
1288 next if exists $seen{$record};
1289 push @retlist, int($record);
1295 __PACKAGE__->register_method(
1296 method => 'authority_tag_sf_browse',
1297 api_name => 'open-ils.supercat.authority.tag.browse',
1301 { desc => <<" DESC",
1302 Returns a list of the requested authority record IDs held
1307 desc => 'The target Authority MARC tag',
1309 { name => 'subfield',
1310 desc => 'The target Authority MARC subfield',
1313 desc => 'The target string',
1315 { name => 'page_size',
1316 desc => 'Count of call numbers to retrieve, default is 9',
1319 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1323 { desc => 'Authority Record IDs that are near the target string',
1328 sub general_startwith {
1331 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1333 __PACKAGE__->register_method(
1334 method => 'general_startwith',
1335 api_name => 'open-ils.supercat.title.startwith',
1336 tag => 'tnf', subfield => 'a',
1340 { desc => "Returns a list of the requested org-scoped record IDs held",
1342 [ { name => 'value', desc => 'The target title', type => 'string' },
1343 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1344 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1345 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1346 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1347 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1348 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1351 __PACKAGE__->register_method(
1352 method => 'general_startwith',
1353 api_name => 'open-ils.supercat.author.startwith',
1354 tag => [qw/100 110 111/], subfield => 'a',
1358 { desc => "Returns a list of the requested org-scoped record IDs held",
1360 [ { name => 'value', desc => 'The target author', type => 'string' },
1361 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1362 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1363 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1364 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1365 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1366 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1369 __PACKAGE__->register_method(
1370 method => 'general_startwith',
1371 api_name => 'open-ils.supercat.subject.startwith',
1372 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1376 { desc => "Returns a list of the requested org-scoped record IDs held",
1378 [ { name => 'value', desc => 'The target subject', type => 'string' },
1379 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1380 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1381 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1382 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1383 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1384 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1387 __PACKAGE__->register_method(
1388 method => 'general_startwith',
1389 api_name => 'open-ils.supercat.topic.startwith',
1390 tag => [qw/650 690/], subfield => 'a',
1394 { desc => "Returns a list of the requested org-scoped record IDs held",
1396 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1397 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1398 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1399 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1400 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1401 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1402 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1405 __PACKAGE__->register_method(
1406 method => 'general_startwith',
1407 api_name => 'open-ils.supercat.series.startwith',
1408 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1412 { desc => "Returns a list of the requested org-scoped record IDs held",
1414 [ { name => 'value', desc => 'The target series', type => 'string' },
1415 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1416 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1417 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1418 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1419 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1420 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1425 sub tag_sf_startwith {
1430 my $subfield = shift;
1433 my $limit = shift || 10;
1434 my $page = shift || 0;
1435 my $statuses = shift || [];
1436 my $copy_locations = shift || [];
1438 my $offset = $limit * abs($page);
1439 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1442 if ($ou && $ou ne '-') {
1443 my $orgs = $_storage->request(
1444 "open-ils.cstore.direct.actor.org_unit.search",
1445 { shortname => $ou },
1447 flesh_fields => { aou => [qw/children/] }
1450 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1453 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1458 my $before = $_storage->request(
1459 "open-ils.cstore.json_query.atomic",
1460 { select => { mfr => [qw/record value/] },
1465 subfield => $subfield,
1466 value => { '<' => lc($value) }
1470 { select=> { acp => [ 'id' ] },
1471 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1473 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1476 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1477 ((@$statuses) ? ( status => $statuses) : ()),
1478 ((@$copy_locations) ? ( location => $copy_locations) : ())
1485 { select=> { auri => [ 'id' ] },
1486 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1488 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1489 '+auri' => { active => 't' }
1496 order_by => { mfr => { value => 'desc' } },
1501 push @list, map { $_->{record} } reverse(@$before);
1505 my $after = $_storage->request(
1506 "open-ils.cstore.json_query.atomic",
1507 { select => { mfr => [qw/record value/] },
1512 subfield => $subfield,
1513 value => { '>=' => lc($value) }
1517 { select=> { acp => [ 'id' ] },
1518 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1520 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1523 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1524 ((@$statuses) ? ( status => $statuses) : ()),
1525 ((@$copy_locations) ? ( location => $copy_locations) : ())
1532 { select=> { auri => [ 'id' ] },
1533 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1535 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1536 '+auri' => { active => 't' }
1543 order_by => { mfr => { value => 'asc' } },
1548 push @list, map { $_->{record} } @$after;
1553 __PACKAGE__->register_method(
1554 method => 'tag_sf_startwith',
1555 api_name => 'open-ils.supercat.tag.startwith',
1559 { desc => <<" DESC",
1560 Returns a list of the requested org-scoped record IDs held
1565 desc => 'The target MARC tag',
1567 { name => 'subfield',
1568 desc => 'The target MARC subfield',
1571 desc => 'The target string',
1573 { name => 'org_unit',
1574 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1576 { name => 'page_size',
1577 desc => 'Count of call numbers to retrieve, default is 9',
1580 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1582 { name => 'statuses',
1583 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1585 { name => 'locations',
1586 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1590 { desc => 'Record IDs that have copies at the relevant org units',
1595 sub axis_authority_startwith {
1600 $axis =~ s/^authority\.//;
1601 $axis =~ s/(\.refs)$//;
1604 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1607 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1608 push @tags, $f->tag;
1610 push @tags, $_->tag for @{$f->sub_entries};
1614 return authority_tag_sf_startwith($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1616 __PACKAGE__->register_method(
1617 method => 'axis_authority_startwith',
1618 api_name => 'open-ils.supercat.authority.startwith.by_axis',
1622 { desc => "Returns a list of the requested authority record IDs held",
1624 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1625 { name => 'value', desc => 'The target value', type => 'string' },
1626 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1627 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1628 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1634 sub general_authority_startwith {
1637 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1639 __PACKAGE__->register_method(
1640 method => 'general_authority_startwith',
1641 api_name => 'open-ils.supercat.authority.title.startwith',
1642 tag => ['130'], subfield => 'a',
1646 { desc => "Returns a list of the requested authority record IDs held",
1648 [ { name => 'value', desc => 'The target title', type => 'string' },
1649 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1650 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1651 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1654 __PACKAGE__->register_method(
1655 method => 'general_authority_startwith',
1656 api_name => 'open-ils.supercat.authority.author.startwith',
1657 tag => [qw/100 110 111/], subfield => 'a',
1661 { desc => "Returns a list of the requested authority record IDs held",
1663 [ { name => 'value', desc => 'The target author', type => 'string' },
1664 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1665 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1666 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1669 __PACKAGE__->register_method(
1670 method => 'general_authority_startwith',
1671 api_name => 'open-ils.supercat.authority.subject.startwith',
1672 tag => [qw/148 150 151 155/], subfield => 'a',
1676 { desc => "Returns a list of the requested authority record IDs held",
1678 [ { name => 'value', desc => 'The target subject', type => 'string' },
1679 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1680 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1681 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1684 __PACKAGE__->register_method(
1685 method => 'general_authority_startwith',
1686 api_name => 'open-ils.supercat.authority.topic.startwith',
1687 tag => ['150'], subfield => 'a',
1691 { desc => "Returns a list of the requested authority record IDs held",
1693 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1694 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1695 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1696 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1699 __PACKAGE__->register_method(
1700 method => 'general_authority_startwith',
1701 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1702 tag => ['130'], subfield => 'a',
1706 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1708 [ { name => 'value', desc => 'The target title', type => 'string' },
1709 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1710 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1711 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1714 __PACKAGE__->register_method(
1715 method => 'general_authority_startwith',
1716 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1717 tag => [qw/100 110 111/], subfield => 'a',
1721 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1723 [ { name => 'value', desc => 'The target author', type => 'string' },
1724 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1725 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1726 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1729 __PACKAGE__->register_method(
1730 method => 'general_authority_startwith',
1731 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1732 tag => [qw/148 150 151 155/], subfield => 'a',
1736 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1738 [ { name => 'value', desc => 'The target subject', type => 'string' },
1739 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1740 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1741 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1744 __PACKAGE__->register_method(
1745 method => 'general_authority_startwith',
1746 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1747 tag => ['150'], subfield => 'a',
1751 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1753 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1754 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1755 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1756 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1762 sub authority_tag_sf_startwith {
1767 my $subfield = shift;
1770 my $limit = shift || 10;
1771 my $page = shift || 0;
1773 # Match authority.full_rec normalization
1774 $value = naco_normalize($value, $subfield);
1776 my $ref_limit = $limit;
1777 my $offset = $limit * abs($page);
1778 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1781 # .refs variant includes 4xx and 5xx variants for see / see also
1782 foreach my $tagname (@$tag) {
1783 push(@ref_tags, $tagname);
1784 if ($self->api_name =~ /\.refs\./) {
1785 push(@ref_tags, '4' . substr($tagname, 1, 2));
1786 push(@ref_tags, '5' . substr($tagname, 1, 2));
1793 # Don't skip the first actual page of results in descending order
1794 $offset = $offset - $limit;
1796 my $before = $_storage->request(
1797 "open-ils.cstore.json_query.atomic",
1798 { select => { afr => [qw/record value/] },
1800 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1801 order_by => { afr => { value => 'desc' } },
1802 limit => $ref_limit,
1806 push @list, map { $_->{record} } reverse(@$before);
1810 my $after = $_storage->request(
1811 "open-ils.cstore.json_query.atomic",
1812 { select => { afr => [qw/record value/] },
1814 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1815 order_by => { afr => { value => 'asc' } },
1816 limit => $ref_limit,
1820 push @list, map { $_->{record} } @$after;
1823 # If we're not pulling in see/see also references, just return the raw list
1824 if ($self->api_name !~ /\.refs\./) {
1828 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1831 foreach my $record (@list) {
1832 next if exists $seen{$record};
1833 push @retlist, int($record);
1839 __PACKAGE__->register_method(
1840 method => 'authority_tag_sf_startwith',
1841 api_name => 'open-ils.supercat.authority.tag.startwith',
1845 { desc => <<" DESC",
1846 Returns a list of the requested authority record IDs held
1851 desc => 'The target Authority MARC tag',
1853 { name => 'subfield',
1854 desc => 'The target Authority MARC subfield',
1857 desc => 'The target string',
1859 { name => 'page_size',
1860 desc => 'Count of call numbers to retrieve, default is 10',
1863 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1867 { desc => 'Authority Record IDs that are near the target string',
1873 sub holding_data_formats {
1876 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1877 docs => 'http://www.loc.gov/marcxml/',
1878 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1882 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1883 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1884 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1887 __PACKAGE__->register_method(
1888 method => 'retrieve_uri',
1889 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1893 { desc => <<" DESC",
1894 Returns a fleshed call number object
1899 desc => 'An OpenILS asset::uri id',
1903 { desc => 'fleshed uri',
1911 my $args = shift || {};
1913 return OpenILS::Application::SuperCat::unAPI
1914 ->new(OpenSRF::AppSession
1915 ->create( 'open-ils.cstore' )
1917 "open-ils.cstore.direct.asset.uri.retrieve",
1921 auri => [qw/call_number_maps/],
1922 auricnm => [qw/call_number/],
1923 acn => [qw/owning_lib record prefix suffix/],
1930 __PACKAGE__->register_method(
1931 method => 'retrieve_copy',
1932 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1936 { desc => <<" DESC",
1937 Returns a fleshed call number object
1942 desc => 'An OpenILS asset::copy id',
1946 { desc => 'fleshed copy',
1954 my $args = shift || {};
1956 return OpenILS::Application::SuperCat::unAPI
1957 ->new(OpenSRF::AppSession
1958 ->create( 'open-ils.cstore' )
1960 "open-ils.cstore.direct.asset.copy.retrieve",
1964 acn => [qw/owning_lib record prefix suffix/],
1965 acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
1972 __PACKAGE__->register_method(
1973 method => 'retrieve_callnumber',
1974 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
1979 { desc => <<" DESC",
1980 Returns a fleshed call number object
1985 desc => 'An OpenILS asset::call_number id',
1989 { desc => 'call number with copies',
1993 sub retrieve_callnumber {
1997 my $args = shift || {};
1999 return OpenILS::Application::SuperCat::unAPI
2000 ->new(OpenSRF::AppSession
2001 ->create( 'open-ils.cstore' )
2003 "open-ils.cstore.direct.asset.call_number.retrieve",
2007 acn => [qw/owning_lib record copies uri_maps prefix suffix/],
2008 auricnm => [qw/uri/],
2009 acp => [qw/location status circ_lib stat_cat_entries notes parts/],
2017 __PACKAGE__->register_method(
2018 method => 'basic_record_holdings',
2019 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
2024 { desc => <<" DESC",
2025 Returns a basic hash representation of the requested bibliographic record's holdings
2030 desc => 'An OpenILS biblio::record_entry id',
2034 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
2038 sub basic_record_holdings {
2044 # holdings hold an array of call numbers, which hold an array of copies
2045 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
2048 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2050 my $tree = $_storage->request(
2051 "open-ils.cstore.direct.biblio.record_entry.retrieve",
2055 bre => [qw/call_numbers/],
2056 acn => [qw/copies owning_lib prefix suffix/],
2057 acp => [qw/location status circ_lib parts/],
2062 my $o_search = { shortname => uc($ou) };
2063 if (!$ou || $ou eq '-') {
2064 $o_search = { parent_ou => undef };
2067 my $orgs = $_storage->request(
2068 "open-ils.cstore.direct.actor.org_unit.search",
2071 flesh_fields => { aou => [qw/children/] }
2075 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
2077 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2079 for my $cn (@{$tree->call_numbers}) {
2080 next unless ( $cn->deleted eq 'f' || !$cn->deleted );
2083 for my $c (@{$cn->copies}) {
2084 next unless grep {$c->circ_lib->id == $_} @ou_ids;
2085 next unless _cp_is_visible($cn, $c);
2091 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
2093 for my $cp (@{$cn->copies}) {
2095 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
2096 next unless _cp_is_visible($cn, $cp);
2098 push @{$holdings{$cn->label}{'copies'}}, {
2099 barcode => $cp->barcode,
2100 status => $cp->status->name,
2101 location => $cp->location->name,
2102 circlib => $cp->circ_lib->shortname
2111 sub _cp_is_visible {
2116 if ( ($cp->deleted eq 'f' || !$cp->deleted) &&
2117 $cp->location->opac_visible eq 't' &&
2118 $cp->status->opac_visible eq 't' &&
2119 $cp->opac_visible eq 't' &&
2120 $cp->circ_lib->opac_visible eq 't' &&
2121 $cn->owning_lib->opac_visible eq 't'
2129 #__PACKAGE__->register_method(
2130 # method => 'new_record_holdings',
2131 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2136 # { desc => <<" DESC",
2137 #Returns the XML representation of the requested bibliographic record's holdings
2141 # { name => 'bibId',
2142 # desc => 'An OpenILS biblio::record_entry id',
2143 # type => 'number' },
2146 # { desc => 'Stream of bib record holdings hierarchy in XML',
2147 # type => 'string' }
2152 sub new_record_holdings {
2161 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
2162 my $limit = $$paging[0];
2163 my $offset = $$paging[1] || 0;
2165 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2166 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
2168 my $o_search = { shortname => uc($ou) };
2169 if (!$ou || $ou eq '-') {
2170 $o_search = { parent_ou => undef };
2173 my $one_org = $_storage->request(
2174 "open-ils.cstore.direct.actor.org_unit.search",
2178 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
2179 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
2181 my $orgs = $_storage->request(
2182 'open-ils.cstore.json_query.atomic',
2183 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
2187 my @ou_ids = map { $_->{id} } @$orgs;
2189 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2191 my %subselect = ( '-or' => [
2192 { owning_lib => \@ou_ids },
2196 call_number => { '=' => {'+acn'=>'id'} },
2198 circ_lib => \@ou_ids
2205 # we are dealing with -full or -uris, so we need to flesh things out
2208 # either way we're going to need uris
2209 # get all the uris up the tree (see also ba47ecc6196)
2211 my $uri_orgs = $_storage->request(
2212 'open-ils.cstore.json_query.atomic',
2213 { from => [ 'actor.org_unit_ancestors', $one_org->id ] }
2216 my @uri_ou_ids = map { $_->{id} } @$uri_orgs;
2218 # we have a -uris, just get the uris
2221 owning_lib => \@uri_ou_ids,
2224 from => { auricnm => 'auri' },
2226 call_number => { '=' => {'+acn'=>'id'} },
2227 '+auri' => { active => 't' }
2231 # we have a -full, get all the things
2232 } elsif ($flesh == 1) {
2233 %subselect = ( '-or' => [
2234 { owning_lib => \@ou_ids },
2238 call_number => { '=' => {'+acn'=>'id'} },
2240 circ_lib => \@ou_ids
2246 { owning_lib => \@uri_ou_ids },
2248 from => { auricnm => 'auri' },
2250 call_number => { '=' => {'+acn'=>'id'} },
2251 '+auri' => { active => 't' }
2260 my $cns = $_storage->request(
2261 "open-ils.cstore.direct.asset.call_number.search.atomic",
2268 acn => [qw/copies owning_lib uri_maps prefix suffix/],
2269 auricnm => [qw/uri/],
2270 acp => [qw/circ_lib location status stat_cat_entries notes parts/],
2271 asce => [qw/stat_cat/],
2273 ( $limit > -1 ? ( limit => $limit ) : () ),
2274 ( $offset ? ( offset => $offset ) : () ),
2275 order_by => { acn => { label_sortkey => {} } }
2279 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2283 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
2285 my $copy_counts = $count_req->gather(1);
2286 my $staff_copy_counts = $staff_count_req->gather(1);
2288 for my $c (@$copy_counts) {
2289 $$c{transcendant} ||= 0;
2290 my $out = "<count type='public'";
2291 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2292 $client->respond("$out/>\n")
2295 for my $c (@$staff_copy_counts) {
2296 $$c{transcendant} ||= 0;
2297 my $out = "<count type='staff'";
2298 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2299 $client->respond("$out/>\n")
2302 $client->respond("</counts><volumes>\n");
2304 for my $cn (@$cns) {
2305 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2307 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2308 # In the context of BibTemplate, copies aren't necessary because we pull those
2309 # in a separate call
2311 OpenILS::Application::SuperCat::unAPI::acn
2313 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2317 $client->respond("</volumes><subscriptions>\n");
2319 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2321 %subselect = ( '-or' => [
2322 { owning_lib => \@ou_ids },
2325 where => { holding_lib => \@ou_ids },
2331 my $ssubs = $_storage->request(
2332 "open-ils.cstore.direct.serial.subscription.search.atomic",
2333 { record_entry => $bib,
2338 ssub => [qw/distributions issuances scaps owning_lib/],
2339 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2340 sstr => [qw/items/],
2341 sitem => [qw/notes unit/],
2342 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2343 acn => [qw/owning_lib prefix suffix/],
2345 ( $limit > -1 ? ( limit => $limit ) : () ),
2346 ( $offset ? ( offset => $offset ) : () ),
2358 date_expected => {},
2365 for my $ssub (@$ssubs) {
2366 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2368 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2369 # In the context of BibTemplate, copies aren't necessary because we pull those
2370 # in a separate call
2372 OpenILS::Application::SuperCat::unAPI::ssub
2374 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2379 return "</subscriptions></holdings>\n";
2381 __PACKAGE__->register_method(
2382 method => 'new_record_holdings',
2383 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2388 { desc => <<" DESC",
2389 Returns the XML representation of the requested bibliographic record's holdings
2394 desc => 'An OpenILS biblio::record_entry ID',
2396 { name => 'orgUnit',
2397 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2400 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2402 { name => 'hideCopies',
2403 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2404 type => 'boolean' },
2406 desc => 'Arry of limit and offset for holdings paging',
2410 { desc => 'Stream of bib record holdings hierarchy in XML',
2420 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2422 my $recs = $_storage->request(
2423 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2424 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2427 return undef unless (@$recs);
2429 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2431 __PACKAGE__->register_method(
2432 method => 'isbn_holdings',
2433 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2437 { desc => <<" DESC",
2438 Returns the XML representation of the requested bibliographic record's holdings
2447 { desc => 'The bib record holdings hierarchy in XML',
2455 return '' unless $text;
2456 $text =~ s/&/&/gsom;
2457 $text =~ s/</</gsom;
2458 $text =~ s/>/>/gsom;
2459 $text =~ s/"/"/gsom;
2460 $text =~ s/'/'/gsom;
2464 sub recent_changes {
2467 my $when = shift || '1-01-01';
2470 my $type = 'biblio';
2473 if ($self->api_name =~ /authority/o) {
2474 $type = 'authority';
2478 my $axis = 'create_date';
2479 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2481 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2483 return $_storage->request(
2484 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2485 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2486 { order_by => { $hint => "$axis desc" }, limit => $limit }
2490 for my $t ( qw/biblio authority/ ) {
2491 for my $a ( qw/import edit/ ) {
2493 __PACKAGE__->register_method(
2494 method => 'recent_changes',
2495 api_name => "open-ils.supercat.$t.record.$a.recent",
2499 { desc => "Returns a list of recently ${a}ed $t records",
2503 desc => "Date to start looking for ${a}ed records",
2504 default => '1-01-01',
2508 desc => "Maximum count to retrieve",
2512 { desc => "An id list of $t records",
2520 sub retrieve_authority_marcxml {
2525 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2527 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2528 return $U->entityize( $record->marc ) if ($record);
2532 __PACKAGE__->register_method(
2533 method => 'retrieve_authority_marcxml',
2534 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2538 { desc => <<" DESC",
2539 Returns the MARCXML representation of the requested authority record
2543 { name => 'authorityId',
2544 desc => 'An OpenILS authority::record_entry id',
2548 { desc => 'The authority record in MARCXML',
2553 sub retrieve_record_marcxml {
2558 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2560 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2561 return $U->entityize( $record->marc ) if ($record);
2565 __PACKAGE__->register_method(
2566 method => 'retrieve_record_marcxml',
2567 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2571 { desc => <<" DESC",
2572 Returns the MARCXML representation of the requested bibliographic record
2577 desc => 'An OpenILS biblio::record_entry id',
2581 { desc => 'The bib record in MARCXML',
2586 sub retrieve_isbn_marcxml {
2591 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2593 my $recs = $_storage->request(
2594 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2595 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2598 return undef unless (@$recs);
2600 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2601 return $U->entityize( $record->marc ) if ($record);
2605 __PACKAGE__->register_method(
2606 method => 'retrieve_isbn_marcxml',
2607 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2611 { desc => <<" DESC",
2612 Returns the MARCXML representation of the requested ISBN
2617 desc => 'An ... um ... ISBN',
2621 { desc => 'The bib record in MARCXML',
2626 sub retrieve_record_transform {
2631 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2633 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2634 #$_storage->connect;
2636 my $record = $_storage->request(
2637 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2641 return undef unless ($record);
2643 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2646 sub retrieve_isbn_transform {
2651 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2653 my $recs = $_storage->request(
2654 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2655 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2658 return undef unless (@$recs);
2660 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2662 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2664 return undef unless ($record);
2666 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2669 sub retrieve_record_objects {
2674 my $type = 'biblio';
2676 if ($self->api_name =~ /authority/) {
2677 $type = 'authority';
2680 $ids = [$ids] unless (ref $ids);
2681 $ids = [grep {$_} @$ids];
2683 return [] unless (@$ids);
2685 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2686 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2688 __PACKAGE__->register_method(
2689 method => 'retrieve_record_objects',
2690 api_name => 'open-ils.supercat.record.object.retrieve',
2694 { desc => <<" DESC",
2695 Returns the Fieldmapper object representation of the requested bibliographic records
2700 desc => 'OpenILS biblio::record_entry ids',
2704 { desc => 'The bib records',
2709 __PACKAGE__->register_method(
2710 method => 'retrieve_record_objects',
2711 api_name => 'open-ils.supercat.authority.object.retrieve',
2715 { desc => <<" DESC",
2716 Returns the Fieldmapper object representation of the requested authority records
2720 { name => 'authIds',
2721 desc => 'OpenILS authority::record_entry ids',
2725 { desc => 'The authority records',
2730 sub retrieve_isbn_object {
2735 return undef unless ($isbn);
2737 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2738 my $recs = $_storage->request(
2739 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2740 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2743 return undef unless (@$recs);
2745 return $_storage->request(
2746 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2747 { id => $recs->[0]->record }
2750 __PACKAGE__->register_method(
2751 method => 'retrieve_isbn_object',
2752 api_name => 'open-ils.supercat.isbn.object.retrieve',
2756 { desc => <<" DESC",
2757 Returns the Fieldmapper object representation of the requested bibliographic record
2766 { desc => 'The bib record',
2773 sub retrieve_metarecord_mods {
2778 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2780 # Get the metarecord in question
2783 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2786 # Now get the map of all bib records for the metarecord
2789 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2790 {metarecord => $rid}
2793 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2795 # and retrieve the lead (master) record as MODS
2797 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2798 ->run($mr->master_record);
2799 my $master_mods = $_parser->parse_string($master)->documentElement;
2800 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2801 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2803 # ... and a MODS clone to populate, with guts removed.
2804 my $mods = $_parser->parse_string($master)->documentElement;
2805 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2806 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2807 ($mods) = $mods->findnodes('//mods:mods');
2808 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2809 $mods->removeChildNodes;
2810 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2812 # Add the metarecord ID as a (locally defined) info URI
2813 my $recordInfo = $mods
2815 ->createElement("recordInfo");
2817 my $recordIdentifier = $mods
2819 ->createElement("recordIdentifier");
2821 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2826 $recordIdentifier->appendTextNode(
2827 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2830 $recordInfo->appendChild($recordIdentifier);
2831 $mods->appendChild($recordInfo);
2833 # Grab the title, author and ISBN for the master record and populate the metarecord
2834 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2837 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2838 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2839 $title = $mods->ownerDocument->importNode($title);
2840 $mods->appendChild($title);
2843 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2845 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2846 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2847 $author = $mods->ownerDocument->importNode($author);
2848 $mods->appendChild($author);
2851 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2853 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2854 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2855 $isbn = $mods->ownerDocument->importNode($isbn);
2856 $mods->appendChild($isbn);
2859 # ... and loop over the constituent records
2860 for my $map ( @$recs ) {
2864 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2865 ->run($map->source);
2867 my $part_mods = $_parser->parse_string($rec);
2868 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2869 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2870 ($part_mods) = $part_mods->findnodes('//mods:mods');
2872 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2873 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2874 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2875 $node = $mods->ownerDocument->importNode($node);
2876 $mods->appendChild( $node );
2879 my $relatedItem = $mods
2881 ->createElement("relatedItem");
2883 $relatedItem->setAttribute( type => 'constituent' );
2885 my $identifier = $mods
2887 ->createElement("identifier");
2889 $identifier->setAttribute( type => 'uri' );
2891 my $subRecordInfo = $mods
2893 ->createElement("recordInfo");
2895 my $subRecordIdentifier = $mods
2897 ->createElement("recordIdentifier");
2899 my $subid = $map->source;
2900 $subRecordIdentifier->appendTextNode(
2901 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2906 $subRecordInfo->appendChild($subRecordIdentifier);
2908 $relatedItem->appendChild( $subRecordInfo );
2910 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2911 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2912 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2913 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2914 $relatedItem->appendChild($tor) if ($tor);
2916 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2917 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2918 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2919 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2920 $relatedItem->appendChild( $part_isbn );
2923 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2927 $mods->appendChild( $relatedItem );
2931 $_storage->disconnect;
2933 return $U->entityize($mods->toString);
2936 __PACKAGE__->register_method(
2937 method => 'retrieve_metarecord_mods',
2938 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
2942 { desc => <<" DESC",
2943 Returns the MODS representation of the requested metarecord
2947 { name => 'metarecordId',
2948 desc => 'An OpenILS metabib::metarecord id',
2952 { desc => 'The metarecord in MODS',
2957 sub list_metarecord_formats {
2960 { namespace_uri => 'http://www.loc.gov/mods/',
2961 docs => 'http://www.loc.gov/mods/',
2962 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2967 for my $type ( keys %metarecord_xslt ) {
2970 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
2971 docs => $metarecord_xslt{$type}{docs},
2972 schema_location => $metarecord_xslt{$type}{schema_location},
2979 __PACKAGE__->register_method(
2980 method => 'list_metarecord_formats',
2981 api_name => 'open-ils.supercat.metarecord.formats',
2985 { desc => <<" DESC",
2986 Returns the list of valid metarecord formats that supercat understands.
2989 { desc => 'The format list',
2995 sub list_authority_formats {
2998 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
2999 docs => 'http://www.loc.gov/marcxml/',
3000 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3002 marc21 => { docs => 'http://www.loc.gov/marc/' }
3006 # for my $type ( keys %record_xslt ) {
3009 # { namespace_uri => $record_xslt{$type}{namespace_uri},
3010 # docs => $record_xslt{$type}{docs},
3011 # schema_location => $record_xslt{$type}{schema_location},
3018 __PACKAGE__->register_method(
3019 method => 'list_authority_formats',
3020 api_name => 'open-ils.supercat.authority.formats',
3024 { desc => <<" DESC",
3025 Returns the list of valid authority formats that supercat understands.
3028 { desc => 'The format list',
3033 sub list_record_formats {
3036 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3037 docs => 'http://www.loc.gov/marcxml/',
3038 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3041 { marc21 => { docs => 'http://www.loc.gov/marc/' } }
3044 for my $type ( keys %record_xslt ) {
3047 { namespace_uri => $record_xslt{$type}{namespace_uri},
3048 docs => $record_xslt{$type}{docs},
3049 schema_location => $record_xslt{$type}{schema_location},
3056 __PACKAGE__->register_method(
3057 method => 'list_record_formats',
3058 api_name => 'open-ils.supercat.record.formats',
3062 { desc => <<" DESC",
3063 Returns the list of valid record formats that supercat understands.
3066 { desc => 'The format list',
3070 __PACKAGE__->register_method(
3071 method => 'list_record_formats',
3072 api_name => 'open-ils.supercat.isbn.formats',
3076 { desc => <<" DESC",
3077 Returns the list of valid record formats that supercat understands.
3080 { desc => 'The format list',
3093 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
3094 unless (length($isbn) >= 10);
3096 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3098 # Create a storage session, since we'll be making muliple requests.
3101 # Find the record that has that ISBN.
3102 my $bibrec = $_storage->request(
3103 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
3104 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
3107 # Go away if we don't have one.
3108 return {} unless (@$bibrec);
3110 # Find the metarecord for that bib record.
3111 my $mr = $_storage->request(
3112 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3113 {source => $bibrec->[0]->record}
3116 # Find the other records for that metarecord.
3117 my $records = $_storage->request(
3118 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3119 {metarecord => $mr->[0]->metarecord}
3122 # Just to be safe. There's currently no unique constraint on sources...
3123 my %unique_recs = map { ($_->source, 1) } @$records;
3124 my @rec_list = sort keys %unique_recs;
3126 # And now fetch the ISBNs for thos records.
3130 'open-ils.cstore.direct.metabib.full_rec.search',
3131 { tag => '020', subfield => 'a', record => $_ }
3132 )->gather(1) for (@rec_list);
3134 # We're done with the storage server session.
3135 $_storage->disconnect;
3137 # Return the oISBN data structure. This will be XMLized at a higher layer.
3139 { metarecord => $mr->[0]->metarecord,
3140 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
3143 __PACKAGE__->register_method(
3145 api_name => 'open-ils.supercat.oisbn',
3149 { desc => <<" DESC",
3150 Returns the ISBN list for the metarecord of the requested isbn
3155 desc => 'An ISBN. Duh.',
3159 { desc => 'record to isbn map',
3164 sub return_bib_search_aliases {
3167 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3169 my $cmsa = $_storage->request(
3170 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
3171 { alias => { '!=' => undef } }
3175 if ($_->alias =~ /\./) {
3176 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
3177 $aliases{$qualifier}{$name}{'index'} = $_->alias;
3178 # We will add a 'title' property in a subsequent schema
3179 $aliases{$qualifier}{$name}{'title'} = $name;
3181 # au/kw/se/su/ti go into the default 'eg' qualifier
3182 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
3183 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
3190 __PACKAGE__->register_method(
3191 method => 'return_bib_search_aliases',
3192 api_name => 'open-ils.supercat.biblio.search_aliases',
3196 { desc => <<" DESC",
3197 Returns the set of qualified search aliases in the system
3201 { desc => 'Hash of qualified search aliases',
3207 package OpenILS::Application::SuperCat::unAPI;
3208 use base qw/OpenILS::Application::SuperCat/;
3211 die "dummy superclass, use a real class";
3217 return unless ($obj);
3219 $class = ref($class) || $class;
3221 if ($class eq __PACKAGE__) {
3222 return unless (ref($obj));
3223 $class .= '::' . $obj->json_hint;
3226 return bless { obj => $obj } => $class;
3231 return $self->{obj};
3234 package OpenILS::Application::SuperCat::unAPI::auri;
3235 use base qw/OpenILS::Application::SuperCat::unAPI/;
3241 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
3242 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
3243 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
3244 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3245 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
3247 if (!$args->{no_volumes}) {
3248 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
3249 $xml .= " <volumes>\n" . join(
3252 OpenILS::Application::SuperCat::unAPI
3253 ->new( $_->call_number )
3254 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
3255 } @{ $self->obj->call_number_maps }
3256 ) . " </volumes>\n";
3259 $xml .= " <volumes/>\n";
3263 $xml .= " </uri>\n";
3268 package OpenILS::Application::SuperCat::unAPI::acn;
3269 use base qw/OpenILS::Application::SuperCat::unAPI/;
3275 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
3277 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
3278 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
3279 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
3280 $xml .= 'deleted="' . $self->obj->deleted . '" ';
3281 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
3284 if (!$args->{no_copies}) {
3285 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
3286 $xml .= " <copies>\n" . join(
3289 OpenILS::Application::SuperCat::unAPI
3291 ->as_xml({ %$args, no_volume=>1 })
3292 } @{ $self->obj->copies }
3296 $xml .= " <copies/>\n";
3300 if (!$args->{no_uris}) {
3301 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3302 $xml .= " <uris>\n" . join(
3305 OpenILS::Application::SuperCat::unAPI
3307 ->as_xml({ %$args, no_volumes=>1 })
3308 } @{ $self->obj->uri_maps }
3312 $xml .= " <uris/>\n";
3317 $xml .= ' <prefix ';
3318 $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3319 $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3320 $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3321 $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3324 $xml .= ' <suffix ';
3325 $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3326 $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3327 $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3328 $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3331 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3332 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3333 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3334 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3337 unless ($args->{no_record}) {
3338 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3340 my $r_doc = $parser->parse_string($self->obj->record->marc);
3341 $r_doc->documentElement->setAttribute( id => $rec_tag );
3342 $xml .= $U->entityize($r_doc->documentElement->toString);
3345 $xml .= " </volume>\n";
3350 package OpenILS::Application::SuperCat::unAPI::ssub;
3351 use base qw/OpenILS::Application::SuperCat::unAPI/;
3357 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3359 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3360 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3361 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3362 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3365 if (!$args->{no_distributions}) {
3366 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3367 $xml .= " <distributions>\n" . join(
3370 OpenILS::Application::SuperCat::unAPI
3372 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3373 } @{ $self->obj->distributions }
3374 ) . " </distributions>\n";
3377 $xml .= " <distributions/>\n";
3381 if (!$args->{no_captions_and_patterns}) {
3382 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3383 $xml .= " <captions_and_patterns>\n" . join(
3386 OpenILS::Application::SuperCat::unAPI
3388 ->as_xml({ %$args, no_subscription=>1 })
3389 } @{ $self->obj->scaps }
3390 ) . " </captions_and_patterns>\n";
3393 $xml .= " <captions_and_patterns/>\n";
3397 if (!$args->{no_issuances}) {
3398 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3399 $xml .= " <issuances>\n" . join(
3402 OpenILS::Application::SuperCat::unAPI
3404 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3405 } @{ $self->obj->issuances }
3406 ) . " </issuances>\n";
3409 $xml .= " <issuances/>\n";
3414 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3415 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3416 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3417 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3420 unless ($args->{no_record}) {
3421 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3423 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3424 $r_doc->documentElement->setAttribute( id => $rec_tag );
3425 $xml .= $U->entityize($r_doc->documentElement->toString);
3428 $xml .= " </subscription>\n";
3433 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3434 use base qw/OpenILS::Application::SuperCat::unAPI/;
3440 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3442 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3444 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3445 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3446 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3447 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3450 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3452 $xml .= " </serial_summary>\n";
3458 package OpenILS::Application::SuperCat::unAPI::sssum;
3459 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3461 package OpenILS::Application::SuperCat::unAPI::sbsum;
3462 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3464 package OpenILS::Application::SuperCat::unAPI::sisum;
3465 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3467 package OpenILS::Application::SuperCat::unAPI::sdist;
3468 use base qw/OpenILS::Application::SuperCat::unAPI/;
3474 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3476 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3477 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3478 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3479 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3482 if (!$args->{no_distributions}) {
3483 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3484 $xml .= " <streams>\n" . join(
3487 OpenILS::Application::SuperCat::unAPI
3489 ->as_xml({ %$args, no_distribution=>1 })
3490 } @{ $self->obj->streams }
3491 ) . " </streams>\n";
3494 $xml .= " <streams/>\n";
3498 if (!$args->{no_summaries}) {
3499 $xml .= " <summaries>\n";
3503 OpenILS::Application::SuperCat::unAPI
3505 ->as_xml({ %$args, no_distribution=>1 }) : ""
3506 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3509 $xml .= " </summaries>\n";
3513 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3514 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3515 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3516 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3519 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3521 if (!$args->{no_record} && $self->obj->record_entry) {
3522 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3524 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3525 $r_doc->documentElement->setAttribute( id => $rec_tag );
3526 $xml .= $U->entityize($r_doc->documentElement->toString);
3529 $xml .= " </distribution>\n";
3534 package OpenILS::Application::SuperCat::unAPI::sstr;
3535 use base qw/OpenILS::Application::SuperCat::unAPI/;
3541 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3543 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3544 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3547 if (!$args->{no_items}) {
3548 if (ref($self->obj->items) && @{ $self->obj->items }) {
3549 $xml .= " <items>\n" . join(
3552 OpenILS::Application::SuperCat::unAPI
3554 ->as_xml({ %$args, no_stream=>1 })
3555 } @{ $self->obj->items }
3559 $xml .= " <items/>\n";
3563 #XXX routing_list_user's?
3565 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3567 $xml .= " </stream>\n";
3572 package OpenILS::Application::SuperCat::unAPI::sitem;
3573 use base qw/OpenILS::Application::SuperCat::unAPI/;
3579 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3581 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3582 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3583 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3585 if ($args->{no_issuance}) {
3586 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3587 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3592 if (ref($self->obj->notes) && $self->obj->notes) {
3593 $xml .= " <notes>\n";
3594 for my $note ( @{$self->obj->notes} ) {
3595 next unless ( $note->pub eq 't' );
3596 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3599 $xml .= " </notes>\n";
3601 $xml .= " <notes/>\n";
3604 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3605 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3606 $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});
3607 $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});
3609 $xml .= " </serial_item>\n";
3614 package OpenILS::Application::SuperCat::unAPI::sunit;
3615 use base qw/OpenILS::Application::SuperCat::unAPI/;
3621 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3622 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3624 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3625 create_date edit_date copy_number circulate deposit ref holdable deleted
3626 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3627 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3632 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3633 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3634 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3636 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3637 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3638 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3639 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3642 $xml .= " <copy_notes>\n";
3643 if (ref($self->obj->notes) && $self->obj->notes) {
3644 for my $note ( @{$self->obj->notes} ) {
3645 next unless ( $note->pub eq 't' );
3646 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3651 $xml .= " </copy_notes>\n";
3652 $xml .= " <statcats>\n";
3654 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3655 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3656 next unless ( $sce->stat_cat->opac_visible eq 't' );
3657 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3661 $xml .= " </statcats>\n";
3663 unless ($args->{no_volume}) {
3664 if (ref($self->obj->call_number)) {
3665 $xml .= OpenILS::Application::SuperCat::unAPI
3666 ->new( $self->obj->call_number )
3667 ->as_xml({ %$args, no_copies=>1 });
3669 $xml .= " <volume/>\n";
3673 $xml .= " </serial_unit>\n";
3678 package OpenILS::Application::SuperCat::unAPI::scap;
3679 use base qw/OpenILS::Application::SuperCat::unAPI/;
3685 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3686 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3688 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3689 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3690 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3693 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3694 $xml .= " </caption_and_pattern>\n";
3699 package OpenILS::Application::SuperCat::unAPI::siss;
3700 use base qw/OpenILS::Application::SuperCat::unAPI/;
3706 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3707 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3709 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3710 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3714 if (!$args->{no_items}) {
3715 if (ref($self->obj->items) && @{ $self->obj->items }) {
3716 $xml .= " <items>\n" . join(
3719 OpenILS::Application::SuperCat::unAPI
3721 ->as_xml({ %$args, no_stream=>1 })
3722 } @{ $self->obj->items }
3726 $xml .= " <items/>\n";
3730 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3731 $xml .= " </issuance>\n";
3736 package OpenILS::Application::SuperCat::unAPI::acp;
3737 use base qw/OpenILS::Application::SuperCat::unAPI/;
3739 use OpenILS::Application::AppUtils;
3740 my $U = "OpenILS::Application::AppUtils";
3746 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3747 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3749 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3750 create_date edit_date copy_number circulate deposit ref holdable deleted
3751 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3756 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3757 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3758 $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";
3760 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3761 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3762 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3763 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3766 $xml .= " <monograph_parts>\n";
3767 if (ref($self->obj->parts) && $self->obj->parts) {
3768 for my $part ( @{$self->obj->parts} ) {
3769 next if $U->is_true($part->deleted);
3770 $xml .= sprintf(' <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3775 $xml .= " </monograph_parts>\n";
3776 $xml .= " <copy_notes>\n";
3777 if (ref($self->obj->notes) && $self->obj->notes) {
3778 for my $note ( @{$self->obj->notes} ) {
3779 next unless ( $note->pub eq 't' );
3780 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3785 $xml .= " </copy_notes>\n";
3786 $xml .= " <statcats>\n";
3788 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3789 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3790 next unless ( $sce->stat_cat->opac_visible eq 't' );
3791 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3795 $xml .= " </statcats>\n";
3797 unless ($args->{no_volume}) {
3798 if (ref($self->obj->call_number)) {
3799 $xml .= OpenILS::Application::SuperCat::unAPI
3800 ->new( $self->obj->call_number )
3801 ->as_xml({ %$args, no_copies=>1 });
3803 $xml .= " <volume/>\n";
3807 $xml .= " </copy>\n";