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;
285 # undef ok, but other non numbers not ok
286 $page_size = int($page_size) if defined $page_size;
288 # Figure out how we were called and what DB procedure we'll call in turn.
289 $self->api_name =~ /\.by_(\w+)($|\.)/;
293 $self->api_name =~ /authority\.(\w+)\./;
296 my $method = "${metaaxis}_$action";
297 $method .= "_refs" if $refs;
299 # Match authority.full_rec normalization
300 # XXX don't know whether we need second arg 'subfield'?
301 $term = naco_normalize($term);
303 my $storage = create OpenSRF::AppSession("open-ils.storage");
304 my $list = $storage->request(
305 "open-ils.storage.authority.in_db.browse_or_search",
306 $method, $what, $term, $page, $page_size
320 return unless ($tree && ref($tree->$field));
322 my @things = $filter->($tree);
323 for my $v ( @{$tree->$field} ){
324 push @things, $filter->($v);
325 push @things, tree_walker($v, $field, $filter);
330 # find a label_sortkey for a call number with a label which is equal
331 # (or close to) a given label value
332 sub _label_sortkey_from_label {
333 my ($label, $_storage, $ou_ids, $cp_filter) = @_;
335 my $closest_cn = $_storage->request(
336 "open-ils.cstore.direct.asset.call_number.search.atomic",
337 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
338 owning_lib => $ou_ids,
343 order_by => { acn => "oils_text_as_bytea(label), id" }
347 return $closest_cn->[0]->label_sortkey;
349 return '~~~'; #fallback to high ascii value, we are at the end
359 my $page_size = shift || 9;
360 my $page = shift || 0;
361 my $statuses = shift || [];
362 my $copy_locations = shift || [];
364 my ($before_limit,$after_limit) = (0,0);
365 my ($before_offset,$after_offset) = (0,0);
368 $before_limit = $after_limit = int($page_size / 2);
369 $after_limit += 1 if ($page_size % 2);
371 $before_offset = $after_offset = int($page_size / 2);
372 $before_offset += 1 if ($page_size % 2);
373 $before_limit = $after_limit = $page_size;
376 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
378 my $o_search = { shortname => $ou };
379 if (!$ou || $ou eq '-') {
380 $o_search = { parent_ou => undef };
383 my $orgs = $_storage->request(
384 "open-ils.cstore.direct.actor.org_unit.search",
387 flesh_fields => { aou => [qw/children/] }
391 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
393 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
398 if (@$statuses || @$copy_locations) {
404 call_number => { '=' => { '+acn' => 'id' } },
406 ((@$statuses) ? ( status => $statuses) : ()),
407 ((@$copy_locations) ? ( location => $copy_locations) : ())
413 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
416 my $before = $_storage->request(
417 "open-ils.cstore.direct.asset.call_number.search.atomic",
418 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
419 owning_lib => \@ou_ids,
424 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
425 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
426 limit => $before_limit,
427 offset => abs($page) * $page_size - $before_offset,
430 push @list, reverse(@$before);
434 my $after = $_storage->request(
435 "open-ils.cstore.direct.asset.call_number.search.atomic",
436 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
437 owning_lib => \@ou_ids,
442 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
443 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
444 limit => $after_limit,
445 offset => abs($page) * $page_size - $after_offset,
453 __PACKAGE__->register_method(
454 method => 'cn_browse',
455 api_name => 'open-ils.supercat.call_number.browse',
460 Returns the XML representation of the requested bibliographic record's holdings
465 desc => 'The target call number label',
467 { name => 'org_unit',
468 desc => 'The org unit shortname (or "-" or undef for global) to browse',
470 { name => 'page_size',
471 desc => 'Count of call numbers to retrieve, default is 9',
474 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
476 { name => 'statuses',
477 desc => 'Array of statuses to filter copies by, optional and can be undef.',
479 { name => 'locations',
480 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
484 { desc => 'Call numbers with owning_lib and record fleshed',
495 my $limit = shift || 10;
496 my $page = shift || 0;
497 my $statuses = shift || [];
498 my $copy_locations = shift || [];
501 my $offset = abs($page) * $limit;
502 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
504 my $o_search = { shortname => $ou };
505 if (!$ou || $ou eq '-') {
506 $o_search = { parent_ou => undef };
509 my $orgs = $_storage->request(
510 "open-ils.cstore.direct.actor.org_unit.search",
513 flesh_fields => { aou => [qw/children/] }
517 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
519 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
524 if (@$statuses || @$copy_locations) {
530 call_number => { '=' => { '+acn' => 'id' } },
532 ((@$statuses) ? ( status => $statuses) : ()),
533 ((@$copy_locations) ? ( location => $copy_locations) : ())
539 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
542 my $before = $_storage->request(
543 "open-ils.cstore.direct.asset.call_number.search.atomic",
544 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
545 owning_lib => \@ou_ids,
550 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
551 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
556 push @list, reverse(@$before);
560 my $after = $_storage->request(
561 "open-ils.cstore.direct.asset.call_number.search.atomic",
562 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
563 owning_lib => \@ou_ids,
568 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
569 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
579 __PACKAGE__->register_method(
580 method => 'cn_startwith',
581 api_name => 'open-ils.supercat.call_number.startwith',
586 Returns the XML representation of the requested bibliographic record's holdings
591 desc => 'The target call number label',
593 { name => 'org_unit',
594 desc => 'The org unit shortname (or "-" or undef for global) to browse',
596 { name => 'page_size',
597 desc => 'Count of call numbers to retrieve, default is 9',
600 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
602 { name => 'statuses',
603 desc => 'Array of statuses to filter copies by, optional and can be undef.',
605 { name => 'locations',
606 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
610 { desc => 'Call numbers with owning_lib and record fleshed',
616 sub new_books_by_item {
621 my $page_size = shift || 10;
622 my $page = shift || 1;
623 my $statuses = shift || [];
624 my $copy_locations = shift || [];
626 my $offset = $page_size * ($page - 1);
628 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
631 if ($ou && $ou ne '-') {
632 my $orgs = $_storage->request(
633 "open-ils.cstore.direct.actor.org_unit.search",
634 { shortname => $ou },
636 flesh_fields => { aou => [qw/children/] }
639 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
642 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
643 my $cns = $_storage->request(
644 "open-ils.cstore.json_query.atomic",
645 { select => { acn => ['record'],
646 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
648 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
652 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
653 ((@$statuses) ? ( status => $statuses) : ()),
654 ((@$copy_locations) ? ( location => $copy_locations) : ())
656 '+acn' => { record => { '>' => 0 } },
658 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
664 return [ map { $_->{record} } @$cns ];
666 __PACKAGE__->register_method(
667 method => 'new_books_by_item',
668 api_name => 'open-ils.supercat.new_book_list',
673 Returns the XML representation of the requested bibliographic record's holdings
677 { name => 'org_unit',
678 desc => 'The org unit shortname (or "-" or undef for global) to list',
680 { name => 'page_size',
681 desc => 'Count of records to retrieve, default is 10',
684 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
686 { name => 'statuses',
687 desc => 'Array of statuses to filter copies by, optional and can be undef.',
689 { name => 'locations',
690 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
694 { desc => 'Record IDs',
703 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
705 __PACKAGE__->register_method(
706 method => 'general_browse',
707 api_name => 'open-ils.supercat.title.browse',
708 tag => 'tnf', subfield => 'a',
712 { desc => "Returns a list of the requested org-scoped record IDs held",
714 [ { name => 'value', desc => 'The target title', type => 'string' },
715 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
716 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
717 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
718 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
719 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
720 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
723 __PACKAGE__->register_method(
724 method => 'general_browse',
725 api_name => 'open-ils.supercat.author.browse',
726 tag => [qw/100 110 111/], subfield => 'a',
730 { desc => "Returns a list of the requested org-scoped record IDs held",
732 [ { name => 'value', desc => 'The target author', type => 'string' },
733 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
734 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
735 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
736 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
737 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
738 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
741 __PACKAGE__->register_method(
742 method => 'general_browse',
743 api_name => 'open-ils.supercat.subject.browse',
744 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
748 { desc => "Returns a list of the requested org-scoped record IDs held",
750 [ { name => 'value', desc => 'The target subject', type => 'string' },
751 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
752 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
753 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
754 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
755 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
756 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
759 __PACKAGE__->register_method(
760 method => 'general_browse',
761 api_name => 'open-ils.supercat.topic.browse',
762 tag => [qw/650 690/], subfield => 'a',
766 { desc => "Returns a list of the requested org-scoped record IDs held",
768 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
769 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
770 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
771 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
772 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
773 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
774 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
777 __PACKAGE__->register_method(
778 method => 'general_browse',
779 api_name => 'open-ils.supercat.series.browse',
780 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
784 { desc => "Returns a list of the requested org-scoped record IDs held",
786 [ { name => 'value', desc => 'The target series', type => 'string' },
787 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
788 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
789 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
790 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
791 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
792 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
802 my $subfield = shift;
805 my $page_size = shift || 9;
806 my $page = shift || 0;
807 my $statuses = shift || [];
808 my $copy_locations = shift || [];
810 my ($before_limit,$after_limit) = (0,0);
811 my ($before_offset,$after_offset) = (0,0);
814 $before_limit = $after_limit = int($page_size / 2);
815 $after_limit += 1 if ($page_size % 2);
817 $before_offset = $after_offset = int($page_size / 2);
818 $before_offset += 1 if ($page_size % 2);
819 $before_limit = $after_limit = $page_size;
822 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
825 if ($ou && $ou ne '-') {
826 my $orgs = $_storage->request(
827 "open-ils.cstore.direct.actor.org_unit.search",
828 { shortname => $ou },
830 flesh_fields => { aou => [qw/children/] }
833 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
836 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
841 my $before = $_storage->request(
842 "open-ils.cstore.json_query.atomic",
843 { select => { mfr => [qw/record value/] },
848 subfield => $subfield,
849 value => { '<' => lc($value) }
853 { select=> { acp => [ 'id' ] },
854 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
856 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
859 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
860 ((@$statuses) ? ( status => $statuses) : ()),
861 ((@$copy_locations) ? ( location => $copy_locations) : ())
868 { select=> { auri => [ 'id' ] },
869 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
871 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
872 '+auri' => { active => 't' }
879 order_by => { mfr => { value => 'desc' } },
880 limit => $before_limit,
881 offset => abs($page) * $page_size - $before_offset,
884 push @list, map { $_->{record} } reverse(@$before);
888 my $after = $_storage->request(
889 "open-ils.cstore.json_query.atomic",
890 { select => { mfr => [qw/record value/] },
895 subfield => $subfield,
896 value => { '>=' => lc($value) }
900 { select=> { acp => [ 'id' ] },
901 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
903 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
906 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
907 ((@$statuses) ? ( status => $statuses) : ()),
908 ((@$copy_locations) ? ( location => $copy_locations) : ())
915 { select=> { auri => [ 'id' ] },
916 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
918 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
919 '+auri' => { active => 't' }
926 order_by => { mfr => { value => 'asc' } },
927 limit => $after_limit,
928 offset => abs($page) * $page_size - $after_offset,
931 push @list, map { $_->{record} } @$after;
936 __PACKAGE__->register_method(
937 method => 'tag_sf_browse',
938 api_name => 'open-ils.supercat.tag.browse',
943 Returns a list of the requested org-scoped record IDs held
948 desc => 'The target MARC tag',
950 { name => 'subfield',
951 desc => 'The target MARC subfield',
954 desc => 'The target string',
956 { name => 'org_unit',
957 desc => 'The org unit shortname (or "-" or undef for global) to browse',
959 { name => 'page_size',
960 desc => 'Count of call numbers to retrieve, default is 9',
963 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
965 { name => 'statuses',
966 desc => 'Array of statuses to filter copies by, optional and can be undef.',
968 { name => 'locations',
969 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
973 { desc => 'Record IDs that have copies at the relevant org units',
978 sub grab_authority_browse_axes {
979 my ($self, $client, $full) = @_;
981 unless(scalar(keys(%authority_browse_axis_cache))) {
982 my $axes = new_editor->search_authority_browse_axis([
983 { code => { '<>' => undef } },
984 { flesh => 2, flesh_fields => { aba => ['fields'], acsaf => ['bib_fields','sub_entries'] } }
986 $authority_browse_axis_cache{$_->code} = $_ for (@$axes);
991 map { $authority_browse_axis_cache{$_} } sort keys %authority_browse_axis_cache
994 return [keys %authority_browse_axis_cache];
997 __PACKAGE__->register_method(
998 method => 'grab_authority_browse_axes',
999 api_name => 'open-ils.supercat.authority.browse_axis_list',
1003 { desc => "Returns a list of valid authority browse/startswith axes",
1005 { 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' }
1007 'return' => { desc => 'Axis codes or whole axes, see "full" param', type => 'array' }
1011 sub axis_authority_browse {
1016 $axis =~ s/^authority\.//;
1017 $axis =~ s/(\.refs)$//;
1020 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1023 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1024 push @tags, $f->tag;
1026 push @tags, $_->tag for @{$f->sub_entries};
1030 return authority_tag_sf_browse($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1032 __PACKAGE__->register_method(
1033 method => 'axis_authority_browse',
1034 api_name => 'open-ils.supercat.authority.browse.by_axis',
1038 { desc => "Returns a list of the requested authority record IDs held",
1040 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1041 { name => 'value', desc => 'The target value', type => 'string' },
1042 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1043 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1044 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1050 sub general_authority_browse {
1053 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
1055 __PACKAGE__->register_method(
1056 method => 'general_authority_browse',
1057 api_name => 'open-ils.supercat.authority.title.browse',
1058 tag => ['130'], subfield => 'a',
1062 { desc => "Returns a list of the requested authority record IDs held",
1064 [ { name => 'value', desc => 'The target title', type => 'string' },
1065 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1066 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1067 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1070 __PACKAGE__->register_method(
1071 method => 'general_authority_browse',
1072 api_name => 'open-ils.supercat.authority.author.browse',
1073 tag => [qw/100 110 111/], subfield => 'a',
1077 { desc => "Returns a list of the requested authority record IDs held",
1079 [ { name => 'value', desc => 'The target author', type => 'string' },
1080 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1081 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1082 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1085 __PACKAGE__->register_method(
1086 method => 'general_authority_browse',
1087 api_name => 'open-ils.supercat.authority.subject.browse',
1088 tag => [qw/148 150 151 155/], subfield => 'a',
1092 { desc => "Returns a list of the requested authority record IDs held",
1094 [ { name => 'value', desc => 'The target subject', type => 'string' },
1095 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1096 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1097 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1100 __PACKAGE__->register_method(
1101 method => 'general_authority_browse',
1102 api_name => 'open-ils.supercat.authority.topic.browse',
1103 tag => ['150'], subfield => 'a',
1107 { desc => "Returns a list of the requested authority record IDs held",
1109 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1110 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1111 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1112 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1115 __PACKAGE__->register_method(
1116 method => 'general_authority_browse',
1117 api_name => 'open-ils.supercat.authority.title.refs.browse',
1118 tag => ['130'], subfield => 'a',
1122 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1124 [ { name => 'value', desc => 'The target title', type => 'string' },
1125 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1126 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1127 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1130 __PACKAGE__->register_method(
1131 method => 'general_authority_browse',
1132 api_name => 'open-ils.supercat.authority.author.refs.browse',
1133 tag => [qw/100 110 111/], subfield => 'a',
1137 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1139 [ { name => 'value', desc => 'The target author', type => 'string' },
1140 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1141 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1142 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1145 __PACKAGE__->register_method(
1146 method => 'general_authority_browse',
1147 api_name => 'open-ils.supercat.authority.subject.refs.browse',
1148 tag => [qw/148 150 151 155/], subfield => 'a',
1152 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1154 [ { name => 'value', desc => 'The target subject', type => 'string' },
1155 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1156 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1157 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1160 __PACKAGE__->register_method(
1161 method => 'general_authority_browse',
1162 api_name => 'open-ils.supercat.authority.topic.refs.browse',
1163 tag => ['150'], subfield => 'a',
1167 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1169 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1170 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1171 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1172 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1178 sub authority_tag_sf_browse {
1183 my $subfield = shift;
1185 my $page_size = shift || 9;
1186 my $page = shift || 0;
1188 # Match authority.full_rec normalization
1189 $value = naco_normalize($value, $subfield);
1191 my ($before_limit,$after_limit) = (0,0);
1192 my ($before_offset,$after_offset) = (0,0);
1195 $before_limit = $after_limit = int($page_size / 2);
1196 $after_limit += 1 if ($page_size % 2);
1198 $before_offset = $after_offset = int($page_size / 2);
1199 $before_offset += 1 if ($page_size % 2);
1200 $before_limit = $after_limit = $page_size;
1203 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1205 # .refs variant includes 4xx and 5xx variants for see / see also
1207 foreach my $tagname (@$tag) {
1208 push(@ref_tags, $tagname);
1209 if ($self->api_name =~ /\.refs\./) {
1210 push(@ref_tags, '4' . substr($tagname, 1, 2));
1211 push(@ref_tags, '5' . substr($tagname, 1, 2));
1217 my $before = $_storage->request(
1218 "open-ils.cstore.json_query.atomic",
1219 { select => { afr => [qw/record value/] },
1221 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1222 order_by => { afr => { value => 'desc' } },
1223 limit => $before_limit,
1224 offset => abs($page) * $page_size - $before_offset,
1227 push @list, map { $_->{record} } reverse(@$before);
1231 my $after = $_storage->request(
1232 "open-ils.cstore.json_query.atomic",
1233 { select => { afr => [qw/record value/] },
1235 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1236 order_by => { afr => { value => 'asc' } },
1237 limit => $after_limit,
1238 offset => abs($page) * $page_size - $after_offset,
1241 push @list, map { $_->{record} } @$after;
1244 # If we're not pulling in see/see also references, just return the raw list
1245 if ($self->api_name !~ /\.refs\./) {
1249 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1252 foreach my $record (@list) {
1253 next if exists $seen{$record};
1254 push @retlist, int($record);
1260 __PACKAGE__->register_method(
1261 method => 'authority_tag_sf_browse',
1262 api_name => 'open-ils.supercat.authority.tag.browse',
1266 { desc => <<" DESC",
1267 Returns a list of the requested authority record IDs held
1272 desc => 'The target Authority MARC tag',
1274 { name => 'subfield',
1275 desc => 'The target Authority MARC subfield',
1278 desc => 'The target string',
1280 { name => 'page_size',
1281 desc => 'Count of call numbers to retrieve, default is 9',
1284 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1288 { desc => 'Authority Record IDs that are near the target string',
1293 sub general_startwith {
1296 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1298 __PACKAGE__->register_method(
1299 method => 'general_startwith',
1300 api_name => 'open-ils.supercat.title.startwith',
1301 tag => 'tnf', subfield => 'a',
1305 { desc => "Returns a list of the requested org-scoped record IDs held",
1307 [ { name => 'value', desc => 'The target title', type => 'string' },
1308 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1309 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1310 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1311 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1312 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1313 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1316 __PACKAGE__->register_method(
1317 method => 'general_startwith',
1318 api_name => 'open-ils.supercat.author.startwith',
1319 tag => [qw/100 110 111/], subfield => 'a',
1323 { desc => "Returns a list of the requested org-scoped record IDs held",
1325 [ { name => 'value', desc => 'The target author', type => 'string' },
1326 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1327 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1328 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1329 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1330 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1331 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1334 __PACKAGE__->register_method(
1335 method => 'general_startwith',
1336 api_name => 'open-ils.supercat.subject.startwith',
1337 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1341 { desc => "Returns a list of the requested org-scoped record IDs held",
1343 [ { name => 'value', desc => 'The target subject', type => 'string' },
1344 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1345 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1346 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1347 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1348 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1349 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1352 __PACKAGE__->register_method(
1353 method => 'general_startwith',
1354 api_name => 'open-ils.supercat.topic.startwith',
1355 tag => [qw/650 690/], subfield => 'a',
1359 { desc => "Returns a list of the requested org-scoped record IDs held",
1361 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1362 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1363 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1364 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1365 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1366 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1367 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1370 __PACKAGE__->register_method(
1371 method => 'general_startwith',
1372 api_name => 'open-ils.supercat.series.startwith',
1373 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1377 { desc => "Returns a list of the requested org-scoped record IDs held",
1379 [ { name => 'value', desc => 'The target series', type => 'string' },
1380 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1381 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1382 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1383 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1384 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1385 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1390 sub tag_sf_startwith {
1395 my $subfield = shift;
1398 my $limit = shift || 10;
1399 my $page = shift || 0;
1400 my $statuses = shift || [];
1401 my $copy_locations = shift || [];
1403 my $offset = $limit * abs($page);
1404 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1407 if ($ou && $ou ne '-') {
1408 my $orgs = $_storage->request(
1409 "open-ils.cstore.direct.actor.org_unit.search",
1410 { shortname => $ou },
1412 flesh_fields => { aou => [qw/children/] }
1415 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1418 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1423 my $before = $_storage->request(
1424 "open-ils.cstore.json_query.atomic",
1425 { select => { mfr => [qw/record value/] },
1430 subfield => $subfield,
1431 value => { '<' => lc($value) }
1435 { select=> { acp => [ 'id' ] },
1436 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1438 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1441 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1442 ((@$statuses) ? ( status => $statuses) : ()),
1443 ((@$copy_locations) ? ( location => $copy_locations) : ())
1450 { select=> { auri => [ 'id' ] },
1451 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1453 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1454 '+auri' => { active => 't' }
1461 order_by => { mfr => { value => 'desc' } },
1466 push @list, map { $_->{record} } reverse(@$before);
1470 my $after = $_storage->request(
1471 "open-ils.cstore.json_query.atomic",
1472 { select => { mfr => [qw/record value/] },
1477 subfield => $subfield,
1478 value => { '>=' => lc($value) }
1482 { select=> { acp => [ 'id' ] },
1483 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1485 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1488 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1489 ((@$statuses) ? ( status => $statuses) : ()),
1490 ((@$copy_locations) ? ( location => $copy_locations) : ())
1497 { select=> { auri => [ 'id' ] },
1498 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1500 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1501 '+auri' => { active => 't' }
1508 order_by => { mfr => { value => 'asc' } },
1513 push @list, map { $_->{record} } @$after;
1518 __PACKAGE__->register_method(
1519 method => 'tag_sf_startwith',
1520 api_name => 'open-ils.supercat.tag.startwith',
1524 { desc => <<" DESC",
1525 Returns a list of the requested org-scoped record IDs held
1530 desc => 'The target MARC tag',
1532 { name => 'subfield',
1533 desc => 'The target MARC subfield',
1536 desc => 'The target string',
1538 { name => 'org_unit',
1539 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1541 { name => 'page_size',
1542 desc => 'Count of call numbers to retrieve, default is 9',
1545 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1547 { name => 'statuses',
1548 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1550 { name => 'locations',
1551 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1555 { desc => 'Record IDs that have copies at the relevant org units',
1560 sub axis_authority_startwith {
1565 $axis =~ s/^authority\.//;
1566 $axis =~ s/(\.refs)$//;
1569 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1572 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1573 push @tags, $f->tag;
1575 push @tags, $_->tag for @{$f->sub_entries};
1579 return authority_tag_sf_startwith($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1581 __PACKAGE__->register_method(
1582 method => 'axis_authority_startwith',
1583 api_name => 'open-ils.supercat.authority.startwith.by_axis',
1587 { desc => "Returns a list of the requested authority record IDs held",
1589 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1590 { name => 'value', desc => 'The target value', type => 'string' },
1591 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1592 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1593 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1599 sub general_authority_startwith {
1602 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1604 __PACKAGE__->register_method(
1605 method => 'general_authority_startwith',
1606 api_name => 'open-ils.supercat.authority.title.startwith',
1607 tag => ['130'], subfield => 'a',
1611 { desc => "Returns a list of the requested authority record IDs held",
1613 [ { name => 'value', desc => 'The target title', type => 'string' },
1614 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1615 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1616 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1619 __PACKAGE__->register_method(
1620 method => 'general_authority_startwith',
1621 api_name => 'open-ils.supercat.authority.author.startwith',
1622 tag => [qw/100 110 111/], subfield => 'a',
1626 { desc => "Returns a list of the requested authority record IDs held",
1628 [ { name => 'value', desc => 'The target author', type => 'string' },
1629 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1630 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1631 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1634 __PACKAGE__->register_method(
1635 method => 'general_authority_startwith',
1636 api_name => 'open-ils.supercat.authority.subject.startwith',
1637 tag => [qw/148 150 151 155/], subfield => 'a',
1641 { desc => "Returns a list of the requested authority record IDs held",
1643 [ { name => 'value', desc => 'The target subject', type => 'string' },
1644 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1645 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1646 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1649 __PACKAGE__->register_method(
1650 method => 'general_authority_startwith',
1651 api_name => 'open-ils.supercat.authority.topic.startwith',
1652 tag => ['150'], subfield => 'a',
1656 { desc => "Returns a list of the requested authority record IDs held",
1658 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1659 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1660 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1661 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1664 __PACKAGE__->register_method(
1665 method => 'general_authority_startwith',
1666 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1667 tag => ['130'], subfield => 'a',
1671 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1673 [ { name => 'value', desc => 'The target title', type => 'string' },
1674 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1675 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1676 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1679 __PACKAGE__->register_method(
1680 method => 'general_authority_startwith',
1681 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1682 tag => [qw/100 110 111/], subfield => 'a',
1686 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1688 [ { name => 'value', desc => 'The target author', type => 'string' },
1689 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1690 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1691 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1694 __PACKAGE__->register_method(
1695 method => 'general_authority_startwith',
1696 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1697 tag => [qw/148 150 151 155/], subfield => 'a',
1701 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1703 [ { name => 'value', desc => 'The target subject', type => 'string' },
1704 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1705 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1706 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1709 __PACKAGE__->register_method(
1710 method => 'general_authority_startwith',
1711 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1712 tag => ['150'], subfield => 'a',
1716 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1718 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1719 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1720 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1721 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1727 sub authority_tag_sf_startwith {
1732 my $subfield = shift;
1735 my $limit = shift || 10;
1736 my $page = shift || 0;
1738 # Match authority.full_rec normalization
1739 $value = naco_normalize($value, $subfield);
1741 my $ref_limit = $limit;
1742 my $offset = $limit * abs($page);
1743 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1746 # .refs variant includes 4xx and 5xx variants for see / see also
1747 foreach my $tagname (@$tag) {
1748 push(@ref_tags, $tagname);
1749 if ($self->api_name =~ /\.refs\./) {
1750 push(@ref_tags, '4' . substr($tagname, 1, 2));
1751 push(@ref_tags, '5' . substr($tagname, 1, 2));
1758 # Don't skip the first actual page of results in descending order
1759 $offset = $offset - $limit;
1761 my $before = $_storage->request(
1762 "open-ils.cstore.json_query.atomic",
1763 { select => { afr => [qw/record value/] },
1765 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1766 order_by => { afr => { value => 'desc' } },
1767 limit => $ref_limit,
1771 push @list, map { $_->{record} } reverse(@$before);
1775 my $after = $_storage->request(
1776 "open-ils.cstore.json_query.atomic",
1777 { select => { afr => [qw/record value/] },
1779 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1780 order_by => { afr => { value => 'asc' } },
1781 limit => $ref_limit,
1785 push @list, map { $_->{record} } @$after;
1788 # If we're not pulling in see/see also references, just return the raw list
1789 if ($self->api_name !~ /\.refs\./) {
1793 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1796 foreach my $record (@list) {
1797 next if exists $seen{$record};
1798 push @retlist, int($record);
1804 __PACKAGE__->register_method(
1805 method => 'authority_tag_sf_startwith',
1806 api_name => 'open-ils.supercat.authority.tag.startwith',
1810 { desc => <<" DESC",
1811 Returns a list of the requested authority record IDs held
1816 desc => 'The target Authority MARC tag',
1818 { name => 'subfield',
1819 desc => 'The target Authority MARC subfield',
1822 desc => 'The target string',
1824 { name => 'page_size',
1825 desc => 'Count of call numbers to retrieve, default is 10',
1828 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1832 { desc => 'Authority Record IDs that are near the target string',
1838 sub holding_data_formats {
1841 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1842 docs => 'http://www.loc.gov/marcxml/',
1843 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1847 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1848 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1849 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1852 __PACKAGE__->register_method(
1853 method => 'retrieve_uri',
1854 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1858 { desc => <<" DESC",
1859 Returns a fleshed call number object
1864 desc => 'An OpenILS asset::uri id',
1868 { desc => 'fleshed uri',
1876 my $args = shift || {};
1878 return OpenILS::Application::SuperCat::unAPI
1879 ->new(OpenSRF::AppSession
1880 ->create( 'open-ils.cstore' )
1882 "open-ils.cstore.direct.asset.uri.retrieve",
1886 auri => [qw/call_number_maps/],
1887 auricnm => [qw/call_number/],
1888 acn => [qw/owning_lib record prefix suffix/],
1895 __PACKAGE__->register_method(
1896 method => 'retrieve_copy',
1897 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1901 { desc => <<" DESC",
1902 Returns a fleshed call number object
1907 desc => 'An OpenILS asset::copy id',
1911 { desc => 'fleshed copy',
1919 my $args = shift || {};
1921 return OpenILS::Application::SuperCat::unAPI
1922 ->new(OpenSRF::AppSession
1923 ->create( 'open-ils.cstore' )
1925 "open-ils.cstore.direct.asset.copy.retrieve",
1929 acn => [qw/owning_lib record prefix suffix/],
1930 acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
1937 __PACKAGE__->register_method(
1938 method => 'retrieve_callnumber',
1939 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
1944 { desc => <<" DESC",
1945 Returns a fleshed call number object
1950 desc => 'An OpenILS asset::call_number id',
1954 { desc => 'call number with copies',
1958 sub retrieve_callnumber {
1962 my $args = shift || {};
1964 return OpenILS::Application::SuperCat::unAPI
1965 ->new(OpenSRF::AppSession
1966 ->create( 'open-ils.cstore' )
1968 "open-ils.cstore.direct.asset.call_number.retrieve",
1972 acn => [qw/owning_lib record copies uri_maps prefix suffix/],
1973 auricnm => [qw/uri/],
1974 acp => [qw/location status circ_lib stat_cat_entries notes parts/],
1982 __PACKAGE__->register_method(
1983 method => 'basic_record_holdings',
1984 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
1989 { desc => <<" DESC",
1990 Returns a basic hash representation of the requested bibliographic record's holdings
1995 desc => 'An OpenILS biblio::record_entry id',
1999 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
2003 sub basic_record_holdings {
2009 # holdings hold an array of call numbers, which hold an array of copies
2010 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
2013 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2015 my $tree = $_storage->request(
2016 "open-ils.cstore.direct.biblio.record_entry.retrieve",
2020 bre => [qw/call_numbers/],
2021 acn => [qw/copies owning_lib prefix suffix/],
2022 acp => [qw/location status circ_lib parts/],
2027 my $o_search = { shortname => uc($ou) };
2028 if (!$ou || $ou eq '-') {
2029 $o_search = { parent_ou => undef };
2032 my $orgs = $_storage->request(
2033 "open-ils.cstore.direct.actor.org_unit.search",
2036 flesh_fields => { aou => [qw/children/] }
2040 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
2042 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2044 for my $cn (@{$tree->call_numbers}) {
2045 next unless ( $cn->deleted eq 'f' || $cn->deleted == 0 );
2048 for my $c (@{$cn->copies}) {
2049 next unless grep {$c->circ_lib->id == $_} @ou_ids;
2050 next unless _cp_is_visible($cn, $c);
2056 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
2058 for my $cp (@{$cn->copies}) {
2060 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
2061 next unless _cp_is_visible($cn, $cp);
2063 push @{$holdings{$cn->label}{'copies'}}, {
2064 barcode => $cp->barcode,
2065 status => $cp->status->name,
2066 location => $cp->location->name,
2067 circlib => $cp->circ_lib->shortname
2076 sub _cp_is_visible {
2081 if ( ($cp->deleted eq 'f' || $cp->deleted == 0) &&
2082 $cp->location->opac_visible eq 't' &&
2083 $cp->status->opac_visible eq 't' &&
2084 $cp->opac_visible eq 't' &&
2085 $cp->circ_lib->opac_visible eq 't' &&
2086 $cn->owning_lib->opac_visible eq 't'
2094 #__PACKAGE__->register_method(
2095 # method => 'new_record_holdings',
2096 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2101 # { desc => <<" DESC",
2102 #Returns the XML representation of the requested bibliographic record's holdings
2106 # { name => 'bibId',
2107 # desc => 'An OpenILS biblio::record_entry id',
2108 # type => 'number' },
2111 # { desc => 'Stream of bib record holdings hierarchy in XML',
2112 # type => 'string' }
2117 sub new_record_holdings {
2126 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
2127 my $limit = $$paging[0];
2128 my $offset = $$paging[1] || 0;
2130 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2131 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
2133 my $o_search = { shortname => uc($ou) };
2134 if (!$ou || $ou eq '-') {
2135 $o_search = { parent_ou => undef };
2138 my $one_org = $_storage->request(
2139 "open-ils.cstore.direct.actor.org_unit.search",
2143 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
2144 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
2146 my $orgs = $_storage->request(
2147 'open-ils.cstore.json_query.atomic',
2148 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
2152 my @ou_ids = map { $_->{id} } @$orgs;
2154 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2156 my %subselect = ( '-or' => [
2157 { owning_lib => \@ou_ids },
2161 call_number => { '=' => {'+acn'=>'id'} },
2163 circ_lib => \@ou_ids
2170 # we are dealing with -full or -uris, so we need to flesh things out
2173 # either way we're going to need uris
2174 # get all the uris up the tree (see also ba47ecc6196)
2176 my $uri_orgs = $_storage->request(
2177 'open-ils.cstore.json_query.atomic',
2178 { from => [ 'actor.org_unit_ancestors', $one_org->id ] }
2181 my @uri_ou_ids = map { $_->{id} } @$uri_orgs;
2183 # we have a -uris, just get the uris
2186 owning_lib => \@uri_ou_ids,
2189 from => { auricnm => 'auri' },
2191 call_number => { '=' => {'+acn'=>'id'} },
2192 '+auri' => { active => 't' }
2196 # we have a -full, get all the things
2197 } elsif ($flesh == 1) {
2198 %subselect = ( '-or' => [
2199 { owning_lib => \@ou_ids },
2203 call_number => { '=' => {'+acn'=>'id'} },
2205 circ_lib => \@ou_ids
2211 { owning_lib => \@uri_ou_ids },
2213 from => { auricnm => 'auri' },
2215 call_number => { '=' => {'+acn'=>'id'} },
2216 '+auri' => { active => 't' }
2225 my $cns = $_storage->request(
2226 "open-ils.cstore.direct.asset.call_number.search.atomic",
2233 acn => [qw/copies owning_lib uri_maps prefix suffix/],
2234 auricnm => [qw/uri/],
2235 acp => [qw/circ_lib location status stat_cat_entries notes parts/],
2236 asce => [qw/stat_cat/],
2238 ( $limit > -1 ? ( limit => $limit ) : () ),
2239 ( $offset ? ( offset => $offset ) : () ),
2240 order_by => { acn => { label_sortkey => {} } }
2244 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2248 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
2250 my $copy_counts = $count_req->gather(1);
2251 my $staff_copy_counts = $staff_count_req->gather(1);
2253 for my $c (@$copy_counts) {
2254 $$c{transcendant} ||= 0;
2255 my $out = "<count type='public'";
2256 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2257 $client->respond("$out/>\n")
2260 for my $c (@$staff_copy_counts) {
2261 $$c{transcendant} ||= 0;
2262 my $out = "<count type='staff'";
2263 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2264 $client->respond("$out/>\n")
2267 $client->respond("</counts><volumes>\n");
2269 for my $cn (@$cns) {
2270 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2272 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2273 # In the context of BibTemplate, copies aren't necessary because we pull those
2274 # in a separate call
2276 OpenILS::Application::SuperCat::unAPI::acn
2278 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2282 $client->respond("</volumes><subscriptions>\n");
2284 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2286 %subselect = ( '-or' => [
2287 { owning_lib => \@ou_ids },
2290 where => { holding_lib => \@ou_ids },
2296 my $ssubs = $_storage->request(
2297 "open-ils.cstore.direct.serial.subscription.search.atomic",
2298 { record_entry => $bib,
2303 ssub => [qw/distributions issuances scaps owning_lib/],
2304 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2305 sstr => [qw/items/],
2306 sitem => [qw/notes unit/],
2307 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2308 acn => [qw/owning_lib prefix suffix/],
2310 ( $limit > -1 ? ( limit => $limit ) : () ),
2311 ( $offset ? ( offset => $offset ) : () ),
2323 date_expected => {},
2330 for my $ssub (@$ssubs) {
2331 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2333 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2334 # In the context of BibTemplate, copies aren't necessary because we pull those
2335 # in a separate call
2337 OpenILS::Application::SuperCat::unAPI::ssub
2339 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2344 return "</subscriptions></holdings>\n";
2346 __PACKAGE__->register_method(
2347 method => 'new_record_holdings',
2348 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2353 { desc => <<" DESC",
2354 Returns the XML representation of the requested bibliographic record's holdings
2359 desc => 'An OpenILS biblio::record_entry ID',
2361 { name => 'orgUnit',
2362 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2365 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2367 { name => 'hideCopies',
2368 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2369 type => 'boolean' },
2371 desc => 'Arry of limit and offset for holdings paging',
2375 { desc => 'Stream of bib record holdings hierarchy in XML',
2385 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2387 my $recs = $_storage->request(
2388 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2389 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2392 return undef unless (@$recs);
2394 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2396 __PACKAGE__->register_method(
2397 method => 'isbn_holdings',
2398 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2402 { desc => <<" DESC",
2403 Returns the XML representation of the requested bibliographic record's holdings
2412 { desc => 'The bib record holdings hierarchy in XML',
2420 return '' unless $text;
2421 $text =~ s/&/&/gsom;
2422 $text =~ s/</</gsom;
2423 $text =~ s/>/>/gsom;
2424 $text =~ s/"/"/gsom;
2425 $text =~ s/'/'/gsom;
2429 sub recent_changes {
2432 my $when = shift || '1-01-01';
2435 my $type = 'biblio';
2438 if ($self->api_name =~ /authority/o) {
2439 $type = 'authority';
2443 my $axis = 'create_date';
2444 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2446 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2448 return $_storage->request(
2449 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2450 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2451 { order_by => { $hint => "$axis desc" }, limit => $limit }
2455 for my $t ( qw/biblio authority/ ) {
2456 for my $a ( qw/import edit/ ) {
2458 __PACKAGE__->register_method(
2459 method => 'recent_changes',
2460 api_name => "open-ils.supercat.$t.record.$a.recent",
2464 { desc => "Returns a list of recently ${a}ed $t records",
2468 desc => "Date to start looking for ${a}ed records",
2469 default => '1-01-01',
2473 desc => "Maximum count to retrieve",
2477 { desc => "An id list of $t records",
2485 sub retrieve_authority_marcxml {
2490 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2492 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2493 return $U->entityize( $record->marc ) if ($record);
2497 __PACKAGE__->register_method(
2498 method => 'retrieve_authority_marcxml',
2499 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2503 { desc => <<" DESC",
2504 Returns the MARCXML representation of the requested authority record
2508 { name => 'authorityId',
2509 desc => 'An OpenILS authority::record_entry id',
2513 { desc => 'The authority record in MARCXML',
2518 sub retrieve_record_marcxml {
2523 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2525 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2526 return $U->entityize( $record->marc ) if ($record);
2530 __PACKAGE__->register_method(
2531 method => 'retrieve_record_marcxml',
2532 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2536 { desc => <<" DESC",
2537 Returns the MARCXML representation of the requested bibliographic record
2542 desc => 'An OpenILS biblio::record_entry id',
2546 { desc => 'The bib record in MARCXML',
2551 sub retrieve_isbn_marcxml {
2556 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2558 my $recs = $_storage->request(
2559 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2560 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2563 return undef unless (@$recs);
2565 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2566 return $U->entityize( $record->marc ) if ($record);
2570 __PACKAGE__->register_method(
2571 method => 'retrieve_isbn_marcxml',
2572 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2576 { desc => <<" DESC",
2577 Returns the MARCXML representation of the requested ISBN
2582 desc => 'An ... um ... ISBN',
2586 { desc => 'The bib record in MARCXML',
2591 sub retrieve_record_transform {
2596 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2598 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2599 #$_storage->connect;
2601 my $record = $_storage->request(
2602 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2606 return undef unless ($record);
2608 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2611 sub retrieve_isbn_transform {
2616 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2618 my $recs = $_storage->request(
2619 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2620 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2623 return undef unless (@$recs);
2625 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2627 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2629 return undef unless ($record);
2631 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2634 sub retrieve_record_objects {
2639 my $type = 'biblio';
2641 if ($self->api_name =~ /authority/) {
2642 $type = 'authority';
2645 $ids = [$ids] unless (ref $ids);
2646 $ids = [grep {$_} @$ids];
2648 return [] unless (@$ids);
2650 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2651 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2653 __PACKAGE__->register_method(
2654 method => 'retrieve_record_objects',
2655 api_name => 'open-ils.supercat.record.object.retrieve',
2659 { desc => <<" DESC",
2660 Returns the Fieldmapper object representation of the requested bibliographic records
2665 desc => 'OpenILS biblio::record_entry ids',
2669 { desc => 'The bib records',
2674 __PACKAGE__->register_method(
2675 method => 'retrieve_record_objects',
2676 api_name => 'open-ils.supercat.authority.object.retrieve',
2680 { desc => <<" DESC",
2681 Returns the Fieldmapper object representation of the requested authority records
2685 { name => 'authIds',
2686 desc => 'OpenILS authority::record_entry ids',
2690 { desc => 'The authority records',
2695 sub retrieve_isbn_object {
2700 return undef unless ($isbn);
2702 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2703 my $recs = $_storage->request(
2704 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2705 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2708 return undef unless (@$recs);
2710 return $_storage->request(
2711 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2712 { id => $recs->[0]->record }
2715 __PACKAGE__->register_method(
2716 method => 'retrieve_isbn_object',
2717 api_name => 'open-ils.supercat.isbn.object.retrieve',
2721 { desc => <<" DESC",
2722 Returns the Fieldmapper object representation of the requested bibliographic record
2731 { desc => 'The bib record',
2738 sub retrieve_metarecord_mods {
2743 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2745 # Get the metarecord in question
2748 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2751 # Now get the map of all bib records for the metarecord
2754 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2755 {metarecord => $rid}
2758 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2760 # and retrieve the lead (master) record as MODS
2762 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2763 ->run($mr->master_record);
2764 my $master_mods = $_parser->parse_string($master)->documentElement;
2765 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2766 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2768 # ... and a MODS clone to populate, with guts removed.
2769 my $mods = $_parser->parse_string($master)->documentElement;
2770 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2771 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2772 ($mods) = $mods->findnodes('//mods:mods');
2773 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2774 $mods->removeChildNodes;
2775 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2777 # Add the metarecord ID as a (locally defined) info URI
2778 my $recordInfo = $mods
2780 ->createElement("recordInfo");
2782 my $recordIdentifier = $mods
2784 ->createElement("recordIdentifier");
2786 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2791 $recordIdentifier->appendTextNode(
2792 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2795 $recordInfo->appendChild($recordIdentifier);
2796 $mods->appendChild($recordInfo);
2798 # Grab the title, author and ISBN for the master record and populate the metarecord
2799 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2802 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2803 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2804 $title = $mods->ownerDocument->importNode($title);
2805 $mods->appendChild($title);
2808 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2810 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2811 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2812 $author = $mods->ownerDocument->importNode($author);
2813 $mods->appendChild($author);
2816 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2818 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2819 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2820 $isbn = $mods->ownerDocument->importNode($isbn);
2821 $mods->appendChild($isbn);
2824 # ... and loop over the constituent records
2825 for my $map ( @$recs ) {
2829 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2830 ->run($map->source);
2832 my $part_mods = $_parser->parse_string($rec);
2833 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2834 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2835 ($part_mods) = $part_mods->findnodes('//mods:mods');
2837 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2838 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2839 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2840 $node = $mods->ownerDocument->importNode($node);
2841 $mods->appendChild( $node );
2844 my $relatedItem = $mods
2846 ->createElement("relatedItem");
2848 $relatedItem->setAttribute( type => 'constituent' );
2850 my $identifier = $mods
2852 ->createElement("identifier");
2854 $identifier->setAttribute( type => 'uri' );
2856 my $subRecordInfo = $mods
2858 ->createElement("recordInfo");
2860 my $subRecordIdentifier = $mods
2862 ->createElement("recordIdentifier");
2864 my $subid = $map->source;
2865 $subRecordIdentifier->appendTextNode(
2866 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2871 $subRecordInfo->appendChild($subRecordIdentifier);
2873 $relatedItem->appendChild( $subRecordInfo );
2875 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2876 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2877 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2878 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2879 $relatedItem->appendChild($tor) if ($tor);
2881 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2882 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2883 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2884 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2885 $relatedItem->appendChild( $part_isbn );
2888 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2892 $mods->appendChild( $relatedItem );
2896 $_storage->disconnect;
2898 return $U->entityize($mods->toString);
2901 __PACKAGE__->register_method(
2902 method => 'retrieve_metarecord_mods',
2903 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
2907 { desc => <<" DESC",
2908 Returns the MODS representation of the requested metarecord
2912 { name => 'metarecordId',
2913 desc => 'An OpenILS metabib::metarecord id',
2917 { desc => 'The metarecord in MODS',
2922 sub list_metarecord_formats {
2925 { namespace_uri => 'http://www.loc.gov/mods/',
2926 docs => 'http://www.loc.gov/mods/',
2927 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2932 for my $type ( keys %metarecord_xslt ) {
2935 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
2936 docs => $metarecord_xslt{$type}{docs},
2937 schema_location => $metarecord_xslt{$type}{schema_location},
2944 __PACKAGE__->register_method(
2945 method => 'list_metarecord_formats',
2946 api_name => 'open-ils.supercat.metarecord.formats',
2950 { desc => <<" DESC",
2951 Returns the list of valid metarecord formats that supercat understands.
2954 { desc => 'The format list',
2960 sub list_authority_formats {
2963 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
2964 docs => 'http://www.loc.gov/marcxml/',
2965 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2970 # for my $type ( keys %record_xslt ) {
2973 # { namespace_uri => $record_xslt{$type}{namespace_uri},
2974 # docs => $record_xslt{$type}{docs},
2975 # schema_location => $record_xslt{$type}{schema_location},
2982 __PACKAGE__->register_method(
2983 method => 'list_authority_formats',
2984 api_name => 'open-ils.supercat.authority.formats',
2988 { desc => <<" DESC",
2989 Returns the list of valid authority formats that supercat understands.
2992 { desc => 'The format list',
2997 sub list_record_formats {
3000 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3001 docs => 'http://www.loc.gov/marcxml/',
3002 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3007 for my $type ( keys %record_xslt ) {
3010 { namespace_uri => $record_xslt{$type}{namespace_uri},
3011 docs => $record_xslt{$type}{docs},
3012 schema_location => $record_xslt{$type}{schema_location},
3019 __PACKAGE__->register_method(
3020 method => 'list_record_formats',
3021 api_name => 'open-ils.supercat.record.formats',
3025 { desc => <<" DESC",
3026 Returns the list of valid record formats that supercat understands.
3029 { desc => 'The format list',
3033 __PACKAGE__->register_method(
3034 method => 'list_record_formats',
3035 api_name => 'open-ils.supercat.isbn.formats',
3039 { desc => <<" DESC",
3040 Returns the list of valid record formats that supercat understands.
3043 { desc => 'The format list',
3056 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
3057 unless (length($isbn) >= 10);
3059 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3061 # Create a storage session, since we'll be making muliple requests.
3064 # Find the record that has that ISBN.
3065 my $bibrec = $_storage->request(
3066 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
3067 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
3070 # Go away if we don't have one.
3071 return {} unless (@$bibrec);
3073 # Find the metarecord for that bib record.
3074 my $mr = $_storage->request(
3075 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3076 {source => $bibrec->[0]->record}
3079 # Find the other records for that metarecord.
3080 my $records = $_storage->request(
3081 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3082 {metarecord => $mr->[0]->metarecord}
3085 # Just to be safe. There's currently no unique constraint on sources...
3086 my %unique_recs = map { ($_->source, 1) } @$records;
3087 my @rec_list = sort keys %unique_recs;
3089 # And now fetch the ISBNs for thos records.
3093 'open-ils.cstore.direct.metabib.full_rec.search',
3094 { tag => '020', subfield => 'a', record => $_ }
3095 )->gather(1) for (@rec_list);
3097 # We're done with the storage server session.
3098 $_storage->disconnect;
3100 # Return the oISBN data structure. This will be XMLized at a higher layer.
3102 { metarecord => $mr->[0]->metarecord,
3103 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
3106 __PACKAGE__->register_method(
3108 api_name => 'open-ils.supercat.oisbn',
3112 { desc => <<" DESC",
3113 Returns the ISBN list for the metarecord of the requested isbn
3118 desc => 'An ISBN. Duh.',
3122 { desc => 'record to isbn map',
3127 sub return_bib_search_aliases {
3130 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3132 my $cmsa = $_storage->request(
3133 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
3134 { alias => { '!=' => undef } }
3138 if ($_->alias =~ /\./) {
3139 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
3140 $aliases{$qualifier}{$name}{'index'} = $_->alias;
3141 # We will add a 'title' property in a subsequent schema
3142 $aliases{$qualifier}{$name}{'title'} = $name;
3144 # au/kw/se/su/ti go into the default 'eg' qualifier
3145 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
3146 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
3153 __PACKAGE__->register_method(
3154 method => 'return_bib_search_aliases',
3155 api_name => 'open-ils.supercat.biblio.search_aliases',
3159 { desc => <<" DESC",
3160 Returns the set of qualified search aliases in the system
3164 { desc => 'Hash of qualified search aliases',
3170 package OpenILS::Application::SuperCat::unAPI;
3171 use base qw/OpenILS::Application::SuperCat/;
3174 die "dummy superclass, use a real class";
3180 return unless ($obj);
3182 $class = ref($class) || $class;
3184 if ($class eq __PACKAGE__) {
3185 return unless (ref($obj));
3186 $class .= '::' . $obj->json_hint;
3189 return bless { obj => $obj } => $class;
3194 return $self->{obj};
3197 package OpenILS::Application::SuperCat::unAPI::auri;
3198 use base qw/OpenILS::Application::SuperCat::unAPI/;
3204 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
3205 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
3206 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
3207 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3208 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
3210 if (!$args->{no_volumes}) {
3211 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
3212 $xml .= " <volumes>\n" . join(
3215 OpenILS::Application::SuperCat::unAPI
3216 ->new( $_->call_number )
3217 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
3218 } @{ $self->obj->call_number_maps }
3219 ) . " </volumes>\n";
3222 $xml .= " <volumes/>\n";
3226 $xml .= " </uri>\n";
3231 package OpenILS::Application::SuperCat::unAPI::acn;
3232 use base qw/OpenILS::Application::SuperCat::unAPI/;
3238 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
3240 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
3241 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
3242 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
3243 $xml .= 'deleted="' . $self->obj->deleted . '" ';
3244 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
3247 if (!$args->{no_copies}) {
3248 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
3249 $xml .= " <copies>\n" . join(
3252 OpenILS::Application::SuperCat::unAPI
3254 ->as_xml({ %$args, no_volume=>1 })
3255 } @{ $self->obj->copies }
3259 $xml .= " <copies/>\n";
3263 if (!$args->{no_uris}) {
3264 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3265 $xml .= " <uris>\n" . join(
3268 OpenILS::Application::SuperCat::unAPI
3270 ->as_xml({ %$args, no_volumes=>1 })
3271 } @{ $self->obj->uri_maps }
3275 $xml .= " <uris/>\n";
3280 $xml .= ' <prefix ';
3281 $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3282 $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3283 $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3284 $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3287 $xml .= ' <suffix ';
3288 $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3289 $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3290 $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3291 $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3294 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3295 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3296 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3297 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3300 unless ($args->{no_record}) {
3301 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3303 my $r_doc = $parser->parse_string($self->obj->record->marc);
3304 $r_doc->documentElement->setAttribute( id => $rec_tag );
3305 $xml .= $U->entityize($r_doc->documentElement->toString);
3308 $xml .= " </volume>\n";
3313 package OpenILS::Application::SuperCat::unAPI::ssub;
3314 use base qw/OpenILS::Application::SuperCat::unAPI/;
3320 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3322 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3323 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3324 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3325 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3328 if (!$args->{no_distributions}) {
3329 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3330 $xml .= " <distributions>\n" . join(
3333 OpenILS::Application::SuperCat::unAPI
3335 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3336 } @{ $self->obj->distributions }
3337 ) . " </distributions>\n";
3340 $xml .= " <distributions/>\n";
3344 if (!$args->{no_captions_and_patterns}) {
3345 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3346 $xml .= " <captions_and_patterns>\n" . join(
3349 OpenILS::Application::SuperCat::unAPI
3351 ->as_xml({ %$args, no_subscription=>1 })
3352 } @{ $self->obj->scaps }
3353 ) . " </captions_and_patterns>\n";
3356 $xml .= " <captions_and_patterns/>\n";
3360 if (!$args->{no_issuances}) {
3361 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3362 $xml .= " <issuances>\n" . join(
3365 OpenILS::Application::SuperCat::unAPI
3367 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3368 } @{ $self->obj->issuances }
3369 ) . " </issuances>\n";
3372 $xml .= " <issuances/>\n";
3377 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3378 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3379 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3380 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3383 unless ($args->{no_record}) {
3384 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3386 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3387 $r_doc->documentElement->setAttribute( id => $rec_tag );
3388 $xml .= $U->entityize($r_doc->documentElement->toString);
3391 $xml .= " </subscription>\n";
3396 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3397 use base qw/OpenILS::Application::SuperCat::unAPI/;
3403 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3405 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3407 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3408 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3409 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3410 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3413 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3415 $xml .= " </serial_summary>\n";
3421 package OpenILS::Application::SuperCat::unAPI::sssum;
3422 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3424 package OpenILS::Application::SuperCat::unAPI::sbsum;
3425 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3427 package OpenILS::Application::SuperCat::unAPI::sisum;
3428 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3430 package OpenILS::Application::SuperCat::unAPI::sdist;
3431 use base qw/OpenILS::Application::SuperCat::unAPI/;
3437 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3439 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3440 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3441 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3442 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3445 if (!$args->{no_distributions}) {
3446 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3447 $xml .= " <streams>\n" . join(
3450 OpenILS::Application::SuperCat::unAPI
3452 ->as_xml({ %$args, no_distribution=>1 })
3453 } @{ $self->obj->streams }
3454 ) . " </streams>\n";
3457 $xml .= " <streams/>\n";
3461 if (!$args->{no_summaries}) {
3462 $xml .= " <summaries>\n";
3466 OpenILS::Application::SuperCat::unAPI
3468 ->as_xml({ %$args, no_distribution=>1 }) : ""
3469 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3472 $xml .= " </summaries>\n";
3476 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3477 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3478 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3479 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3482 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3484 if (!$args->{no_record} && $self->obj->record_entry) {
3485 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3487 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3488 $r_doc->documentElement->setAttribute( id => $rec_tag );
3489 $xml .= $U->entityize($r_doc->documentElement->toString);
3492 $xml .= " </distribution>\n";
3497 package OpenILS::Application::SuperCat::unAPI::sstr;
3498 use base qw/OpenILS::Application::SuperCat::unAPI/;
3504 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3506 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3507 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3510 if (!$args->{no_items}) {
3511 if (ref($self->obj->items) && @{ $self->obj->items }) {
3512 $xml .= " <items>\n" . join(
3515 OpenILS::Application::SuperCat::unAPI
3517 ->as_xml({ %$args, no_stream=>1 })
3518 } @{ $self->obj->items }
3522 $xml .= " <items/>\n";
3526 #XXX routing_list_user's?
3528 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3530 $xml .= " </stream>\n";
3535 package OpenILS::Application::SuperCat::unAPI::sitem;
3536 use base qw/OpenILS::Application::SuperCat::unAPI/;
3542 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3544 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3545 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3546 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3548 if ($args->{no_issuance}) {
3549 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3550 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3555 if (ref($self->obj->notes) && $self->obj->notes) {
3556 $xml .= " <notes>\n";
3557 for my $note ( @{$self->obj->notes} ) {
3558 next unless ( $note->pub eq 't' );
3559 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3562 $xml .= " </notes>\n";
3564 $xml .= " <notes/>\n";
3567 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3568 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3569 $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});
3570 $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});
3572 $xml .= " </serial_item>\n";
3577 package OpenILS::Application::SuperCat::unAPI::sunit;
3578 use base qw/OpenILS::Application::SuperCat::unAPI/;
3584 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3585 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3587 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3588 create_date edit_date copy_number circulate deposit ref holdable deleted
3589 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3590 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3595 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3596 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3597 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3599 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3600 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3601 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3602 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3605 $xml .= " <copy_notes>\n";
3606 if (ref($self->obj->notes) && $self->obj->notes) {
3607 for my $note ( @{$self->obj->notes} ) {
3608 next unless ( $note->pub eq 't' );
3609 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3614 $xml .= " </copy_notes>\n";
3615 $xml .= " <statcats>\n";
3617 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3618 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3619 next unless ( $sce->stat_cat->opac_visible eq 't' );
3620 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3624 $xml .= " </statcats>\n";
3626 unless ($args->{no_volume}) {
3627 if (ref($self->obj->call_number)) {
3628 $xml .= OpenILS::Application::SuperCat::unAPI
3629 ->new( $self->obj->call_number )
3630 ->as_xml({ %$args, no_copies=>1 });
3632 $xml .= " <volume/>\n";
3636 $xml .= " </serial_unit>\n";
3641 package OpenILS::Application::SuperCat::unAPI::scap;
3642 use base qw/OpenILS::Application::SuperCat::unAPI/;
3648 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3649 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3651 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3652 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3653 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3656 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3657 $xml .= " </caption_and_pattern>\n";
3662 package OpenILS::Application::SuperCat::unAPI::siss;
3663 use base qw/OpenILS::Application::SuperCat::unAPI/;
3669 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3670 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3672 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3673 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3677 if (!$args->{no_items}) {
3678 if (ref($self->obj->items) && @{ $self->obj->items }) {
3679 $xml .= " <items>\n" . join(
3682 OpenILS::Application::SuperCat::unAPI
3684 ->as_xml({ %$args, no_stream=>1 })
3685 } @{ $self->obj->items }
3689 $xml .= " <items/>\n";
3693 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3694 $xml .= " </issuance>\n";
3699 package OpenILS::Application::SuperCat::unAPI::acp;
3700 use base qw/OpenILS::Application::SuperCat::unAPI/;
3706 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3707 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3709 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3710 create_date edit_date copy_number circulate deposit ref holdable deleted
3711 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3716 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3717 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3718 $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";
3720 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3721 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3722 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3723 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3726 $xml .= " <monograph_parts>\n";
3727 if (ref($self->obj->parts) && $self->obj->parts) {
3728 for my $part ( @{$self->obj->parts} ) {
3729 $xml .= sprintf(' <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3734 $xml .= " </monograph_parts>\n";
3735 $xml .= " <copy_notes>\n";
3736 if (ref($self->obj->notes) && $self->obj->notes) {
3737 for my $note ( @{$self->obj->notes} ) {
3738 next unless ( $note->pub eq 't' );
3739 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3744 $xml .= " </copy_notes>\n";
3745 $xml .= " <statcats>\n";
3747 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3748 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3749 next unless ( $sce->stat_cat->opac_visible eq 't' );
3750 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3754 $xml .= " </statcats>\n";
3756 unless ($args->{no_volume}) {
3757 if (ref($self->obj->call_number)) {
3758 $xml .= OpenILS::Application::SuperCat::unAPI
3759 ->new( $self->obj->call_number )
3760 ->as_xml({ %$args, no_copies=>1 });
3762 $xml .= " <volume/>\n";
3766 $xml .= " </copy>\n";