1 # We'll be working with XML, so...
4 use Unicode::Normalize;
6 # ... and this has some handy common methods
7 use OpenILS::Application::AppUtils;
9 my $parser = new XML::LibXML;
10 my $U = 'OpenILS::Application::AppUtils';
13 package OpenILS::Application::SuperCat;
17 use OpenILS::Utils::Normalize qw( naco_normalize );
19 # All OpenSRF applications must be based on OpenSRF::Application or
20 # a subclass thereof. Makes sense, eh?
21 use OpenILS::Application;
22 use base qw/OpenILS::Application/;
24 # This is the client class, used for connecting to open-ils.storage
25 use OpenSRF::AppSession;
27 # This is an extension of Error.pm that supplies some error types to throw
28 use OpenSRF::EX qw(:try);
30 # This is a helper class for querying the OpenSRF Settings application ...
31 use OpenSRF::Utils::SettingsClient;
33 # ... and here we have the built in logging helper ...
34 use OpenSRF::Utils::Logger qw($logger);
36 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
37 use OpenILS::Utils::Fieldmapper;
39 use OpenILS::Utils::CStoreEditor q/:funcs/;
40 use OpenILS::Utils::TagURI;
49 %authority_browse_axis_cache,
53 # we need an XML parser
54 $_parser = new XML::LibXML;
57 $_xslt = new XML::LibXSLT;
59 # parse the MODS xslt ...
60 my $mods33_xslt = $_parser->parse_file(
61 OpenSRF::Utils::SettingsClient
63 ->config_value( dirs => 'xsl' ).
64 "/MARC21slim2MODS33.xsl"
66 # and stash a transformer
67 $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
68 $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
69 $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
70 $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
72 # parse the MODS xslt ...
73 my $mods32_xslt = $_parser->parse_file(
74 OpenSRF::Utils::SettingsClient
76 ->config_value( dirs => 'xsl' ).
77 "/MARC21slim2MODS32.xsl"
79 # and stash a transformer
80 $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
81 $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
82 $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
83 $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
85 # parse the MODS xslt ...
86 my $mods3_xslt = $_parser->parse_file(
87 OpenSRF::Utils::SettingsClient
89 ->config_value( dirs => 'xsl' ).
90 "/MARC21slim2MODS3.xsl"
92 # and stash a transformer
93 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
94 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
95 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
96 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
98 # parse the MODS xslt ...
99 my $mods_xslt = $_parser->parse_file(
100 OpenSRF::Utils::SettingsClient
102 ->config_value( dirs => 'xsl' ).
103 "/MARC21slim2MODS.xsl"
105 # and stash a transformer
106 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
107 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
108 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
109 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
111 # parse the ATOM entry xslt ...
112 my $atom_xslt = $_parser->parse_file(
113 OpenSRF::Utils::SettingsClient
115 ->config_value( dirs => 'xsl' ).
116 "/MARC21slim2ATOM.xsl"
118 # and stash a transformer
119 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
120 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
121 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
123 # parse the RDFDC xslt ...
124 my $rdf_dc_xslt = $_parser->parse_file(
125 OpenSRF::Utils::SettingsClient
127 ->config_value( dirs => 'xsl' ).
128 "/MARC21slim2RDFDC.xsl"
130 # and stash a transformer
131 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
132 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
133 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
135 # parse the SRWDC xslt ...
136 my $srw_dc_xslt = $_parser->parse_file(
137 OpenSRF::Utils::SettingsClient
139 ->config_value( dirs => 'xsl' ).
140 "/MARC21slim2SRWDC.xsl"
142 # and stash a transformer
143 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
144 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
145 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
147 # parse the OAIDC xslt ...
148 my $oai_dc_xslt = $_parser->parse_file(
149 OpenSRF::Utils::SettingsClient
151 ->config_value( dirs => 'xsl' ).
152 "/MARC21slim2OAIDC.xsl"
154 # and stash a transformer
155 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
156 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
157 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
159 # parse the RSS xslt ...
160 my $rss_xslt = $_parser->parse_file(
161 OpenSRF::Utils::SettingsClient
163 ->config_value( dirs => 'xsl' ).
164 "/MARC21slim2RSS2.xsl"
166 # and stash a transformer
167 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
169 # parse the FGDC xslt ...
170 my $fgdc_xslt = $_parser->parse_file(
171 OpenSRF::Utils::SettingsClient
173 ->config_value( dirs => 'xsl' ).
174 "/MARC21slim2FGDC.xsl"
176 # and stash a transformer
177 $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
178 $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
179 $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
181 register_record_transforms();
183 register_new_authorities_methods();
188 sub register_record_transforms {
189 for my $type ( keys %record_xslt ) {
190 __PACKAGE__->register_method(
191 method => 'retrieve_record_transform',
192 api_name => "open-ils.supercat.record.$type.retrieve",
196 { desc => "Returns the \U$type\E representation ".
197 "of the requested bibliographic record",
201 desc => 'An OpenILS biblio::record_entry id',
205 { desc => "The bib record in \U$type\E",
210 __PACKAGE__->register_method(
211 method => 'retrieve_isbn_transform',
212 api_name => "open-ils.supercat.isbn.$type.retrieve",
216 { desc => "Returns the \U$type\E representation ".
217 "of the requested bibliographic record",
225 { desc => "The bib record in \U$type\E",
232 sub register_new_authorities_methods {
233 my %register_args = (
234 method => "generic_new_authorities_method",
238 desc => q/Generated method/,
241 desc => "An axis, an authority tag number, or a bibliographic tag number, depending on invocation",
244 desc => "A search term",
247 desc => "zero-based page number of results",
249 {name => "page size",
250 desc => "number of results per page",
254 desc => "A list of authority record IDs", type => "array"
259 foreach my $how (qw/axis atag btag/) {
260 foreach my $action (qw/browse_center browse_top
261 search_rank search_heading/) {
263 $register_args{api_name} =
264 "open-ils.supercat.authority.$action.by_$how";
265 __PACKAGE__->register_method(%register_args);
267 $register_args{api_name} =
268 "open-ils.supercat.authority.$action.by_$how.refs";
269 __PACKAGE__->register_method(%register_args);
275 sub generic_new_authorities_method {
279 # We want to be extra careful with these arguments, since the next
280 # thing we're doing with them is passing them to a DB procedure.
283 my $page = int(shift || 0);
284 my $page_size = shift;
285 my $thesauruses = shift;
287 # undef ok, but other non numbers not ok
288 $page_size = int($page_size) if defined $page_size;
290 # Figure out how we were called and what DB procedure we'll call in turn.
291 $self->api_name =~ /\.by_(\w+)($|\.)/;
295 $self->api_name =~ /authority\.(\w+)\./;
298 my $method = "${metaaxis}_$action";
299 $method .= "_refs" if $refs;
301 # Match authority.full_rec normalization
302 # XXX don't know whether we need second arg 'subfield'?
303 $term = naco_normalize($term);
305 my $storage = create OpenSRF::AppSession("open-ils.storage");
306 my $list = $storage->request(
307 "open-ils.storage.authority.in_db.browse_or_search",
308 $method, $what, $term, $page, $page_size, $thesauruses
322 return unless ($tree && ref($tree->$field));
324 my @things = $filter->($tree);
325 for my $v ( @{$tree->$field} ){
326 push @things, $filter->($v);
327 push @things, tree_walker($v, $field, $filter);
332 # find a label_sortkey for a call number with a label which is equal
333 # (or close to) a given label value
334 sub _label_sortkey_from_label {
335 my ($label, $_storage, $ou_ids, $cp_filter) = @_;
337 my $closest_cn = $_storage->request(
338 "open-ils.cstore.direct.asset.call_number.search.atomic",
339 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
340 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
345 flesh_fields => { acn => [qw/label_class/] },
347 order_by => { acn => "oils_text_as_bytea(label), id" }
351 if ($closest_cn->[0]->label eq $label) {
352 # we found an exact match stop here
353 return $closest_cn->[0]->label_sortkey;
355 # we got as close as we could by label alone, let's try to
356 # normalize and get closer
357 $closest_cn = $_storage->request(
358 "open-ils.cstore.direct.asset.call_number.search.atomic",
360 => { ">=" => [$closest_cn->[0]->label_class->normalizer, $label] },
361 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
366 order_by => { acn => "label_sortkey, id" }
370 return $closest_cn->[0]->label_sortkey;
375 return '~~~'; #fallback to high ascii value, we are at the end of the range
384 my $page_size = shift || 9;
385 my $page = shift || 0;
386 my $statuses = shift || [];
387 my $copy_locations = shift || [];
389 my ($before_limit,$after_limit) = (0,0);
390 my ($before_offset,$after_offset) = (0,0);
393 $before_limit = $after_limit = int($page_size / 2);
394 $after_limit += 1 if ($page_size % 2);
396 $before_offset = $after_offset = int($page_size / 2);
397 $before_offset += 1 if ($page_size % 2);
398 $before_limit = $after_limit = $page_size;
401 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
404 # if OU is not specified, or if '-' is specified, do not enter the block
405 unless (!$ou || $ou eq '-') {
406 # check if the shortname of the top org_unit is specified
407 my $top_org = $_storage->request(
408 "open-ils.cstore.direct.actor.org_unit.search",
409 { parent_ou => undef }
412 if ($ou eq $top_org->shortname) {
413 $logger->debug("Searching for CNs at top org $ou");
415 my $o_search = { shortname => $ou };
417 my $orgs = $_storage->request(
418 "open-ils.cstore.direct.actor.org_unit.search",
421 flesh_fields => { aou => [qw/children/] }
425 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
427 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
434 if (@$statuses || @$copy_locations) {
440 call_number => { '=' => { '+acn' => 'id' } },
442 ((@$statuses) ? ( status => $statuses) : ()),
443 ((@$copy_locations) ? ( location => $copy_locations) : ())
449 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
452 my $before = $_storage->request(
453 "open-ils.cstore.direct.asset.call_number.search.atomic",
454 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
455 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
460 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
461 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
462 limit => $before_limit,
463 offset => abs($page) * $page_size - $before_offset,
466 push @list, reverse(@$before);
470 my $after = $_storage->request(
471 "open-ils.cstore.direct.asset.call_number.search.atomic",
472 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
473 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
478 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
479 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
480 limit => $after_limit,
481 offset => abs($page) * $page_size - $after_offset,
489 __PACKAGE__->register_method(
490 method => 'cn_browse',
491 api_name => 'open-ils.supercat.call_number.browse',
496 Returns the XML representation of the requested bibliographic record's holdings
501 desc => 'The target call number label',
503 { name => 'org_unit',
504 desc => 'The org unit shortname (or "-" or undef for global) to browse',
506 { name => 'page_size',
507 desc => 'Count of call numbers to retrieve, default is 9',
510 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
512 { name => 'statuses',
513 desc => 'Array of statuses to filter copies by, optional and can be undef.',
515 { name => 'locations',
516 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
520 { desc => 'Call numbers with owning_lib and record fleshed',
531 my $limit = shift || 10;
532 my $page = shift || 0;
533 my $statuses = shift || [];
534 my $copy_locations = shift || [];
537 my $offset = abs($page) * $limit;
538 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
540 my $o_search = { shortname => $ou };
541 if (!$ou || $ou eq '-') {
542 $o_search = { parent_ou => undef };
545 my $orgs = $_storage->request(
546 "open-ils.cstore.direct.actor.org_unit.search",
549 flesh_fields => { aou => [qw/children/] }
553 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
555 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
560 if (@$statuses || @$copy_locations) {
566 call_number => { '=' => { '+acn' => 'id' } },
568 ((@$statuses) ? ( status => $statuses) : ()),
569 ((@$copy_locations) ? ( location => $copy_locations) : ())
575 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
578 my $before = $_storage->request(
579 "open-ils.cstore.direct.asset.call_number.search.atomic",
580 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
581 owning_lib => \@ou_ids,
586 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
587 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
592 push @list, reverse(@$before);
596 my $after = $_storage->request(
597 "open-ils.cstore.direct.asset.call_number.search.atomic",
598 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
599 owning_lib => \@ou_ids,
604 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
605 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
615 __PACKAGE__->register_method(
616 method => 'cn_startwith',
617 api_name => 'open-ils.supercat.call_number.startwith',
622 Returns the XML representation of the requested bibliographic record's holdings
627 desc => 'The target call number label',
629 { name => 'org_unit',
630 desc => 'The org unit shortname (or "-" or undef for global) to browse',
632 { name => 'page_size',
633 desc => 'Count of call numbers to retrieve, default is 9',
636 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
638 { name => 'statuses',
639 desc => 'Array of statuses to filter copies by, optional and can be undef.',
641 { name => 'locations',
642 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
646 { desc => 'Call numbers with owning_lib and record fleshed',
652 sub new_books_by_item {
657 my $page_size = shift || 10;
658 my $page = shift || 1;
659 my $statuses = shift || [];
660 my $copy_locations = shift || [];
662 my $offset = $page_size * ($page - 1);
664 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
667 if ($ou && $ou ne '-') {
668 my $orgs = $_storage->request(
669 "open-ils.cstore.direct.actor.org_unit.search",
670 { shortname => $ou },
672 flesh_fields => { aou => [qw/children/] }
675 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
678 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
679 my $cns = $_storage->request(
680 "open-ils.cstore.json_query.atomic",
681 { select => { acn => ['record'],
682 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
684 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
688 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
689 ((@$statuses) ? ( status => $statuses) : ()),
690 ((@$copy_locations) ? ( location => $copy_locations) : ())
692 '+acn' => { record => { '>' => 0 } },
694 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
700 return [ map { $_->{record} } @$cns ];
702 __PACKAGE__->register_method(
703 method => 'new_books_by_item',
704 api_name => 'open-ils.supercat.new_book_list',
709 Returns the XML representation of the requested bibliographic record's holdings
713 { name => 'org_unit',
714 desc => 'The org unit shortname (or "-" or undef for global) to list',
716 { name => 'page_size',
717 desc => 'Count of records to retrieve, default is 10',
720 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
722 { name => 'statuses',
723 desc => 'Array of statuses to filter copies by, optional and can be undef.',
725 { name => 'locations',
726 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
730 { desc => 'Record IDs',
740 my $format = shift || 'xml';
742 $u2 = OpenILS::Utils::TagURI->new($u2);
743 return '' unless $u2;
745 # Use pathinfo on acp as a lookup type specifier.
746 if ($u2->classname eq 'acp' and $u2->pathinfo =~ /\bbarcode\b/) {
747 my( $copy, $evt ) = $U->fetch_copy_by_barcode( $u2->id );
748 $u2->id( $copy->id );
751 return OpenSRF::AppSession->create('open-ils.cstore')->request(
752 "open-ils.cstore.json_query.atomic",
754 [ 'unapi.' . $u2->classname,
758 '{' . ( $u2->includes ? join( ',', keys %{ $u2->includes } ) : '' ) . '}',
759 $u2->location || undef,
763 )->gather(1)->[0]{'unapi.'. $u2->classname};
765 __PACKAGE__->register_method(
767 api_name => 'open-ils.supercat.u2',
772 Returns the XML representation of the requested object
777 desc => 'The U2 Tag URI (OpenILS::Utils::TagURI)',
780 desc => 'For bre and bre feeds, the xml transform format',
784 { desc => 'XML (or transformed) object data',
793 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
795 __PACKAGE__->register_method(
796 method => 'general_browse',
797 api_name => 'open-ils.supercat.title.browse',
798 tag => 'tnf', subfield => 'a',
802 { desc => "Returns a list of the requested org-scoped record IDs held",
804 [ { name => 'value', desc => 'The target title', type => 'string' },
805 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
806 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
807 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
808 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
809 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
810 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
813 __PACKAGE__->register_method(
814 method => 'general_browse',
815 api_name => 'open-ils.supercat.author.browse',
816 tag => [qw/100 110 111/], subfield => 'a',
820 { desc => "Returns a list of the requested org-scoped record IDs held",
822 [ { name => 'value', desc => 'The target author', type => 'string' },
823 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
824 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
825 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
826 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
827 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
828 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
831 __PACKAGE__->register_method(
832 method => 'general_browse',
833 api_name => 'open-ils.supercat.subject.browse',
834 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
838 { desc => "Returns a list of the requested org-scoped record IDs held",
840 [ { name => 'value', desc => 'The target subject', type => 'string' },
841 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
842 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
843 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
844 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
845 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
846 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
849 __PACKAGE__->register_method(
850 method => 'general_browse',
851 api_name => 'open-ils.supercat.topic.browse',
852 tag => [qw/650 690/], subfield => 'a',
856 { desc => "Returns a list of the requested org-scoped record IDs held",
858 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
859 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
860 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
861 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
862 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
863 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
864 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
867 __PACKAGE__->register_method(
868 method => 'general_browse',
869 api_name => 'open-ils.supercat.series.browse',
870 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
874 { desc => "Returns a list of the requested org-scoped record IDs held",
876 [ { name => 'value', desc => 'The target series', type => 'string' },
877 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
878 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
879 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
880 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
881 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
882 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
892 my $subfield = shift;
895 my $page_size = shift || 9;
896 my $page = shift || 0;
897 my $statuses = shift || [];
898 my $copy_locations = shift || [];
900 my ($before_limit,$after_limit) = (0,0);
901 my ($before_offset,$after_offset) = (0,0);
904 $before_limit = $after_limit = int($page_size / 2);
905 $after_limit += 1 if ($page_size % 2);
907 $before_offset = $after_offset = int($page_size / 2);
908 $before_offset += 1 if ($page_size % 2);
909 $before_limit = $after_limit = $page_size;
912 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
915 if ($ou && $ou ne '-') {
916 my $orgs = $_storage->request(
917 "open-ils.cstore.direct.actor.org_unit.search",
918 { shortname => $ou },
920 flesh_fields => { aou => [qw/children/] }
923 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
926 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
931 my $before = $_storage->request(
932 "open-ils.cstore.json_query.atomic",
933 { select => { mfr => [qw/record value/] },
938 subfield => $subfield,
939 value => { '<' => lc($value) }
943 { select=> { acp => [ 'id' ] },
944 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
946 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
949 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
950 ((@$statuses) ? ( status => $statuses) : ()),
951 ((@$copy_locations) ? ( location => $copy_locations) : ())
958 { select=> { auri => [ 'id' ] },
959 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
961 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
962 '+auri' => { active => 't' }
969 order_by => { mfr => { value => 'desc' } },
970 limit => $before_limit,
971 offset => abs($page) * $page_size - $before_offset,
974 push @list, map { $_->{record} } reverse(@$before);
978 my $after = $_storage->request(
979 "open-ils.cstore.json_query.atomic",
980 { select => { mfr => [qw/record value/] },
985 subfield => $subfield,
986 value => { '>=' => lc($value) }
990 { select=> { acp => [ 'id' ] },
991 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
993 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
996 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
997 ((@$statuses) ? ( status => $statuses) : ()),
998 ((@$copy_locations) ? ( location => $copy_locations) : ())
1005 { select=> { auri => [ 'id' ] },
1006 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1008 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1009 '+auri' => { active => 't' }
1016 order_by => { mfr => { value => 'asc' } },
1017 limit => $after_limit,
1018 offset => abs($page) * $page_size - $after_offset,
1021 push @list, map { $_->{record} } @$after;
1026 __PACKAGE__->register_method(
1027 method => 'tag_sf_browse',
1028 api_name => 'open-ils.supercat.tag.browse',
1032 { desc => <<" DESC",
1033 Returns a list of the requested org-scoped record IDs held
1038 desc => 'The target MARC tag',
1040 { name => 'subfield',
1041 desc => 'The target MARC subfield',
1044 desc => 'The target string',
1046 { name => 'org_unit',
1047 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1049 { name => 'page_size',
1050 desc => 'Count of call numbers to retrieve, default is 9',
1053 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1055 { name => 'statuses',
1056 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1058 { name => 'locations',
1059 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1063 { desc => 'Record IDs that have copies at the relevant org units',
1068 sub grab_authority_browse_axes {
1069 my ($self, $client, $full) = @_;
1071 unless(scalar(keys(%authority_browse_axis_cache))) {
1072 my $axes = new_editor->search_authority_browse_axis([
1073 { code => { '<>' => undef } },
1074 { flesh => 2, flesh_fields => { aba => ['fields'], acsaf => ['bib_fields','sub_entries'] } }
1076 $authority_browse_axis_cache{$_->code} = $_ for (@$axes);
1081 map { $authority_browse_axis_cache{$_} } sort keys %authority_browse_axis_cache
1084 return [keys %authority_browse_axis_cache];
1087 __PACKAGE__->register_method(
1088 method => 'grab_authority_browse_axes',
1089 api_name => 'open-ils.supercat.authority.browse_axis_list',
1093 { desc => "Returns a list of valid authority browse/startswith axes",
1095 { 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' }
1097 'return' => { desc => 'Axis codes or whole axes, see "full" param', type => 'array' }
1101 sub axis_authority_browse {
1106 $axis =~ s/^authority\.//;
1107 $axis =~ s/(\.refs)$//;
1110 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1113 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1114 push @tags, $f->tag;
1116 push @tags, $_->tag for @{$f->sub_entries};
1120 return authority_tag_sf_browse($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1122 __PACKAGE__->register_method(
1123 method => 'axis_authority_browse',
1124 api_name => 'open-ils.supercat.authority.browse.by_axis',
1128 { desc => "Returns a list of the requested authority record IDs held",
1130 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1131 { name => 'value', desc => 'The target value', type => 'string' },
1132 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1133 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1134 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1140 sub general_authority_browse {
1143 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
1145 __PACKAGE__->register_method(
1146 method => 'general_authority_browse',
1147 api_name => 'open-ils.supercat.authority.title.browse',
1148 tag => ['130'], subfield => 'a',
1152 { desc => "Returns a list of the requested authority record IDs held",
1154 [ { name => 'value', desc => 'The target title', 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.author.browse',
1163 tag => [qw/100 110 111/], subfield => 'a',
1167 { desc => "Returns a list of the requested authority record IDs held",
1169 [ { name => 'value', desc => 'The target author', 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' }
1175 __PACKAGE__->register_method(
1176 method => 'general_authority_browse',
1177 api_name => 'open-ils.supercat.authority.subject.browse',
1178 tag => [qw/148 150 151 155/], subfield => 'a',
1182 { desc => "Returns a list of the requested authority record IDs held",
1184 [ { name => 'value', desc => 'The target subject', type => 'string' },
1185 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1186 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1187 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1190 __PACKAGE__->register_method(
1191 method => 'general_authority_browse',
1192 api_name => 'open-ils.supercat.authority.topic.browse',
1193 tag => ['150'], subfield => 'a',
1197 { desc => "Returns a list of the requested authority record IDs held",
1199 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1200 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1201 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1202 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1205 __PACKAGE__->register_method(
1206 method => 'general_authority_browse',
1207 api_name => 'open-ils.supercat.authority.title.refs.browse',
1208 tag => ['130'], subfield => 'a',
1212 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1214 [ { name => 'value', desc => 'The target title', type => 'string' },
1215 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1216 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1217 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1220 __PACKAGE__->register_method(
1221 method => 'general_authority_browse',
1222 api_name => 'open-ils.supercat.authority.author.refs.browse',
1223 tag => [qw/100 110 111/], subfield => 'a',
1227 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1229 [ { name => 'value', desc => 'The target author', type => 'string' },
1230 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1231 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1232 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1235 __PACKAGE__->register_method(
1236 method => 'general_authority_browse',
1237 api_name => 'open-ils.supercat.authority.subject.refs.browse',
1238 tag => [qw/148 150 151 155/], subfield => 'a',
1242 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1244 [ { name => 'value', desc => 'The target subject', type => 'string' },
1245 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1246 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1247 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1250 __PACKAGE__->register_method(
1251 method => 'general_authority_browse',
1252 api_name => 'open-ils.supercat.authority.topic.refs.browse',
1253 tag => ['150'], subfield => 'a',
1257 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1259 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1260 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1261 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1262 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1268 sub authority_tag_sf_browse {
1273 my $subfield = shift;
1275 my $page_size = shift || 9;
1276 my $page = shift || 0;
1278 # Match authority.full_rec normalization
1279 $value = naco_normalize($value, $subfield);
1281 my ($before_limit,$after_limit) = (0,0);
1282 my ($before_offset,$after_offset) = (0,0);
1285 $before_limit = $after_limit = int($page_size / 2);
1286 $after_limit += 1 if ($page_size % 2);
1288 $before_offset = $after_offset = int($page_size / 2);
1289 $before_offset += 1 if ($page_size % 2);
1290 $before_limit = $after_limit = $page_size;
1293 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1295 # .refs variant includes 4xx and 5xx variants for see / see also
1297 foreach my $tagname (@$tag) {
1298 push(@ref_tags, $tagname);
1299 if ($self->api_name =~ /\.refs\./) {
1300 push(@ref_tags, '4' . substr($tagname, 1, 2));
1301 push(@ref_tags, '5' . substr($tagname, 1, 2));
1307 my $before = $_storage->request(
1308 "open-ils.cstore.json_query.atomic",
1309 { select => { afr => [qw/record value/] },
1311 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1312 order_by => { afr => { value => 'desc' } },
1313 limit => $before_limit,
1314 offset => abs($page) * $page_size - $before_offset,
1317 push @list, map { $_->{record} } reverse(@$before);
1321 my $after = $_storage->request(
1322 "open-ils.cstore.json_query.atomic",
1323 { select => { afr => [qw/record value/] },
1325 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1326 order_by => { afr => { value => 'asc' } },
1327 limit => $after_limit,
1328 offset => abs($page) * $page_size - $after_offset,
1331 push @list, map { $_->{record} } @$after;
1334 # If we're not pulling in see/see also references, just return the raw list
1335 if ($self->api_name !~ /\.refs\./) {
1339 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1342 foreach my $record (@list) {
1343 next if exists $seen{$record};
1344 push @retlist, int($record);
1350 __PACKAGE__->register_method(
1351 method => 'authority_tag_sf_browse',
1352 api_name => 'open-ils.supercat.authority.tag.browse',
1356 { desc => <<" DESC",
1357 Returns a list of the requested authority record IDs held
1362 desc => 'The target Authority MARC tag',
1364 { name => 'subfield',
1365 desc => 'The target Authority MARC subfield',
1368 desc => 'The target string',
1370 { name => 'page_size',
1371 desc => 'Count of call numbers to retrieve, default is 9',
1374 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1378 { desc => 'Authority Record IDs that are near the target string',
1383 sub general_startwith {
1386 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1388 __PACKAGE__->register_method(
1389 method => 'general_startwith',
1390 api_name => 'open-ils.supercat.title.startwith',
1391 tag => 'tnf', subfield => 'a',
1395 { desc => "Returns a list of the requested org-scoped record IDs held",
1397 [ { name => 'value', desc => 'The target title', type => 'string' },
1398 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1399 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1400 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1401 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1402 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1403 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1406 __PACKAGE__->register_method(
1407 method => 'general_startwith',
1408 api_name => 'open-ils.supercat.author.startwith',
1409 tag => [qw/100 110 111/], subfield => 'a',
1413 { desc => "Returns a list of the requested org-scoped record IDs held",
1415 [ { name => 'value', desc => 'The target author', type => 'string' },
1416 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1417 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1418 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1419 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1420 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1421 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1424 __PACKAGE__->register_method(
1425 method => 'general_startwith',
1426 api_name => 'open-ils.supercat.subject.startwith',
1427 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1431 { desc => "Returns a list of the requested org-scoped record IDs held",
1433 [ { name => 'value', desc => 'The target subject', type => 'string' },
1434 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1435 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1436 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1437 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1438 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1439 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1442 __PACKAGE__->register_method(
1443 method => 'general_startwith',
1444 api_name => 'open-ils.supercat.topic.startwith',
1445 tag => [qw/650 690/], subfield => 'a',
1449 { desc => "Returns a list of the requested org-scoped record IDs held",
1451 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1452 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1453 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1454 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1455 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1456 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1457 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1460 __PACKAGE__->register_method(
1461 method => 'general_startwith',
1462 api_name => 'open-ils.supercat.series.startwith',
1463 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1467 { desc => "Returns a list of the requested org-scoped record IDs held",
1469 [ { name => 'value', desc => 'The target series', type => 'string' },
1470 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1471 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1472 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1473 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1474 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1475 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1480 sub tag_sf_startwith {
1485 my $subfield = shift;
1488 my $limit = shift || 10;
1489 my $page = shift || 0;
1490 my $statuses = shift || [];
1491 my $copy_locations = shift || [];
1493 my $offset = $limit * abs($page);
1494 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1497 if ($ou && $ou ne '-') {
1498 my $orgs = $_storage->request(
1499 "open-ils.cstore.direct.actor.org_unit.search",
1500 { shortname => $ou },
1502 flesh_fields => { aou => [qw/children/] }
1505 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1508 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1513 my $before = $_storage->request(
1514 "open-ils.cstore.json_query.atomic",
1515 { select => { mfr => [qw/record value/] },
1520 subfield => $subfield,
1521 value => { '<' => lc($value) }
1525 { select=> { acp => [ 'id' ] },
1526 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1528 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1531 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1532 ((@$statuses) ? ( status => $statuses) : ()),
1533 ((@$copy_locations) ? ( location => $copy_locations) : ())
1540 { select=> { auri => [ 'id' ] },
1541 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1543 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1544 '+auri' => { active => 't' }
1551 order_by => { mfr => { value => 'desc' } },
1556 push @list, map { $_->{record} } reverse(@$before);
1560 my $after = $_storage->request(
1561 "open-ils.cstore.json_query.atomic",
1562 { select => { mfr => [qw/record value/] },
1567 subfield => $subfield,
1568 value => { '>=' => lc($value) }
1572 { select=> { acp => [ 'id' ] },
1573 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1575 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1578 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1579 ((@$statuses) ? ( status => $statuses) : ()),
1580 ((@$copy_locations) ? ( location => $copy_locations) : ())
1587 { select=> { auri => [ 'id' ] },
1588 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1590 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1591 '+auri' => { active => 't' }
1598 order_by => { mfr => { value => 'asc' } },
1603 push @list, map { $_->{record} } @$after;
1608 __PACKAGE__->register_method(
1609 method => 'tag_sf_startwith',
1610 api_name => 'open-ils.supercat.tag.startwith',
1614 { desc => <<" DESC",
1615 Returns a list of the requested org-scoped record IDs held
1620 desc => 'The target MARC tag',
1622 { name => 'subfield',
1623 desc => 'The target MARC subfield',
1626 desc => 'The target string',
1628 { name => 'org_unit',
1629 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1631 { name => 'page_size',
1632 desc => 'Count of call numbers to retrieve, default is 9',
1635 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1637 { name => 'statuses',
1638 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1640 { name => 'locations',
1641 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1645 { desc => 'Record IDs that have copies at the relevant org units',
1650 sub axis_authority_startwith {
1655 $axis =~ s/^authority\.//;
1656 $axis =~ s/(\.refs)$//;
1659 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1662 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1663 push @tags, $f->tag;
1665 push @tags, $_->tag for @{$f->sub_entries};
1669 return authority_tag_sf_startwith($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1671 __PACKAGE__->register_method(
1672 method => 'axis_authority_startwith',
1673 api_name => 'open-ils.supercat.authority.startwith.by_axis',
1677 { desc => "Returns a list of the requested authority record IDs held",
1679 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1680 { name => 'value', desc => 'The target value', type => 'string' },
1681 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1682 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1683 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1689 sub general_authority_startwith {
1692 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1694 __PACKAGE__->register_method(
1695 method => 'general_authority_startwith',
1696 api_name => 'open-ils.supercat.authority.title.startwith',
1697 tag => ['130'], subfield => 'a',
1701 { desc => "Returns a list of the requested authority record IDs held",
1703 [ { name => 'value', desc => 'The target title', 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.author.startwith',
1712 tag => [qw/100 110 111/], subfield => 'a',
1716 { desc => "Returns a list of the requested authority record IDs held",
1718 [ { name => 'value', desc => 'The target author', 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' }
1724 __PACKAGE__->register_method(
1725 method => 'general_authority_startwith',
1726 api_name => 'open-ils.supercat.authority.subject.startwith',
1727 tag => [qw/148 150 151 155/], subfield => 'a',
1731 { desc => "Returns a list of the requested authority record IDs held",
1733 [ { name => 'value', desc => 'The target subject', type => 'string' },
1734 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1735 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1736 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1739 __PACKAGE__->register_method(
1740 method => 'general_authority_startwith',
1741 api_name => 'open-ils.supercat.authority.topic.startwith',
1742 tag => ['150'], subfield => 'a',
1746 { desc => "Returns a list of the requested authority record IDs held",
1748 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1749 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1750 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1751 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1754 __PACKAGE__->register_method(
1755 method => 'general_authority_startwith',
1756 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1757 tag => ['130'], subfield => 'a',
1761 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1763 [ { name => 'value', desc => 'The target title', type => 'string' },
1764 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1765 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1766 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1769 __PACKAGE__->register_method(
1770 method => 'general_authority_startwith',
1771 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1772 tag => [qw/100 110 111/], subfield => 'a',
1776 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1778 [ { name => 'value', desc => 'The target author', type => 'string' },
1779 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1780 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1781 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1784 __PACKAGE__->register_method(
1785 method => 'general_authority_startwith',
1786 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1787 tag => [qw/148 150 151 155/], subfield => 'a',
1791 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1793 [ { name => 'value', desc => 'The target subject', type => 'string' },
1794 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1795 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1796 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1799 __PACKAGE__->register_method(
1800 method => 'general_authority_startwith',
1801 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1802 tag => ['150'], subfield => 'a',
1806 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1808 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1809 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1810 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1811 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1817 sub authority_tag_sf_startwith {
1822 my $subfield = shift;
1825 my $limit = shift || 10;
1826 my $page = shift || 0;
1828 # Match authority.full_rec normalization
1829 $value = naco_normalize($value, $subfield);
1831 my $ref_limit = $limit;
1832 my $offset = $limit * abs($page);
1833 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1836 # .refs variant includes 4xx and 5xx variants for see / see also
1837 foreach my $tagname (@$tag) {
1838 push(@ref_tags, $tagname);
1839 if ($self->api_name =~ /\.refs\./) {
1840 push(@ref_tags, '4' . substr($tagname, 1, 2));
1841 push(@ref_tags, '5' . substr($tagname, 1, 2));
1848 # Don't skip the first actual page of results in descending order
1849 $offset = $offset - $limit;
1851 my $before = $_storage->request(
1852 "open-ils.cstore.json_query.atomic",
1853 { select => { afr => [qw/record value/] },
1855 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1856 order_by => { afr => { value => 'desc' } },
1857 limit => $ref_limit,
1861 push @list, map { $_->{record} } reverse(@$before);
1865 my $after = $_storage->request(
1866 "open-ils.cstore.json_query.atomic",
1867 { select => { afr => [qw/record value/] },
1869 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1870 order_by => { afr => { value => 'asc' } },
1871 limit => $ref_limit,
1875 push @list, map { $_->{record} } @$after;
1878 # If we're not pulling in see/see also references, just return the raw list
1879 if ($self->api_name !~ /\.refs\./) {
1883 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1886 foreach my $record (@list) {
1887 next if exists $seen{$record};
1888 push @retlist, int($record);
1894 __PACKAGE__->register_method(
1895 method => 'authority_tag_sf_startwith',
1896 api_name => 'open-ils.supercat.authority.tag.startwith',
1900 { desc => <<" DESC",
1901 Returns a list of the requested authority record IDs held
1906 desc => 'The target Authority MARC tag',
1908 { name => 'subfield',
1909 desc => 'The target Authority MARC subfield',
1912 desc => 'The target string',
1914 { name => 'page_size',
1915 desc => 'Count of call numbers to retrieve, default is 10',
1918 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1922 { desc => 'Authority Record IDs that are near the target string',
1928 sub holding_data_formats {
1931 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1932 docs => 'http://www.loc.gov/marcxml/',
1933 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1937 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1938 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1939 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1942 __PACKAGE__->register_method(
1943 method => 'retrieve_uri',
1944 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1948 { desc => <<" DESC",
1949 Returns a fleshed call number object
1954 desc => 'An OpenILS asset::uri id',
1958 { desc => 'fleshed uri',
1966 my $args = shift || {};
1968 return OpenILS::Application::SuperCat::unAPI
1969 ->new(OpenSRF::AppSession
1970 ->create( 'open-ils.cstore' )
1972 "open-ils.cstore.direct.asset.uri.retrieve",
1976 auri => [qw/call_number_maps/],
1977 auricnm => [qw/call_number/],
1978 acn => [qw/owning_lib record prefix suffix/],
1985 __PACKAGE__->register_method(
1986 method => 'retrieve_copy',
1987 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1991 { desc => <<" DESC",
1992 Returns a fleshed call number object
1997 desc => 'An OpenILS asset::copy id',
2001 { desc => 'fleshed copy',
2009 my $args = shift || {};
2011 return OpenILS::Application::SuperCat::unAPI
2012 ->new(OpenSRF::AppSession
2013 ->create( 'open-ils.cstore' )
2015 "open-ils.cstore.direct.asset.copy.retrieve",
2019 acn => [qw/owning_lib record prefix suffix/],
2020 acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
2027 __PACKAGE__->register_method(
2028 method => 'retrieve_callnumber',
2029 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
2034 { desc => <<" DESC",
2035 Returns a fleshed call number object
2040 desc => 'An OpenILS asset::call_number id',
2044 { desc => 'call number with copies',
2048 sub retrieve_callnumber {
2052 my $args = shift || {};
2054 return OpenILS::Application::SuperCat::unAPI
2055 ->new(OpenSRF::AppSession
2056 ->create( 'open-ils.cstore' )
2058 "open-ils.cstore.direct.asset.call_number.retrieve",
2062 acn => [qw/owning_lib record copies uri_maps prefix suffix/],
2063 auricnm => [qw/uri/],
2064 acp => [qw/location status circ_lib stat_cat_entries notes parts/],
2072 __PACKAGE__->register_method(
2073 method => 'basic_record_holdings',
2074 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
2079 { desc => <<" DESC",
2080 Returns a basic hash representation of the requested bibliographic record's holdings
2085 desc => 'An OpenILS biblio::record_entry id',
2089 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
2093 sub basic_record_holdings {
2099 # holdings hold an array of call numbers, which hold an array of copies
2100 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
2103 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2105 my $tree = $_storage->request(
2106 "open-ils.cstore.direct.biblio.record_entry.retrieve",
2110 bre => [qw/call_numbers/],
2111 acn => [qw/copies owning_lib prefix suffix/],
2112 acp => [qw/location status circ_lib parts/],
2117 my $o_search = { shortname => uc($ou) };
2118 if (!$ou || $ou eq '-') {
2119 $o_search = { parent_ou => undef };
2122 my $orgs = $_storage->request(
2123 "open-ils.cstore.direct.actor.org_unit.search",
2126 flesh_fields => { aou => [qw/children/] }
2130 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
2132 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2134 for my $cn (@{$tree->call_numbers}) {
2135 next unless ( $cn->deleted eq 'f' || !$cn->deleted );
2138 for my $c (@{$cn->copies}) {
2139 next unless grep {$c->circ_lib->id == $_} @ou_ids;
2140 next unless _cp_is_visible($cn, $c);
2146 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
2148 for my $cp (@{$cn->copies}) {
2150 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
2151 next unless _cp_is_visible($cn, $cp);
2153 push @{$holdings{$cn->label}{'copies'}}, {
2154 barcode => $cp->barcode,
2155 status => $cp->status->name,
2156 location => $cp->location->name,
2157 circlib => $cp->circ_lib->shortname
2166 sub _cp_is_visible {
2171 if ( ($cp->deleted eq 'f' || !$cp->deleted) &&
2172 $cp->location->opac_visible eq 't' &&
2173 $cp->status->opac_visible eq 't' &&
2174 $cp->opac_visible eq 't' &&
2175 $cp->circ_lib->opac_visible eq 't' &&
2176 $cn->owning_lib->opac_visible eq 't'
2184 #__PACKAGE__->register_method(
2185 # method => 'new_record_holdings',
2186 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2191 # { desc => <<" DESC",
2192 #Returns the XML representation of the requested bibliographic record's holdings
2196 # { name => 'bibId',
2197 # desc => 'An OpenILS biblio::record_entry id',
2198 # type => 'number' },
2201 # { desc => 'Stream of bib record holdings hierarchy in XML',
2202 # type => 'string' }
2207 sub new_record_holdings {
2216 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
2217 my $limit = $$paging[0];
2218 my $offset = $$paging[1] || 0;
2220 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2221 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
2223 my $o_search = { shortname => uc($ou) };
2224 if (!$ou || $ou eq '-') {
2225 $o_search = { parent_ou => undef };
2228 my $one_org = $_storage->request(
2229 "open-ils.cstore.direct.actor.org_unit.search",
2233 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
2234 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
2236 my $orgs = $_storage->request(
2237 'open-ils.cstore.json_query.atomic',
2238 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
2242 my @ou_ids = map { $_->{id} } @$orgs;
2244 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2246 my %subselect = ( '-or' => [
2247 { owning_lib => \@ou_ids },
2251 call_number => { '=' => {'+acn'=>'id'} },
2253 circ_lib => \@ou_ids
2260 # we are dealing with -full or -uris, so we need to flesh things out
2263 # either way we're going to need uris
2264 # get all the uris up the tree (see also ba47ecc6196)
2266 my $uri_orgs = $_storage->request(
2267 'open-ils.cstore.json_query.atomic',
2268 { from => [ 'actor.org_unit_ancestors', $one_org->id ] }
2271 my @uri_ou_ids = map { $_->{id} } @$uri_orgs;
2273 # we have a -uris, just get the uris
2276 owning_lib => \@uri_ou_ids,
2279 from => { auricnm => 'auri' },
2281 call_number => { '=' => {'+acn'=>'id'} },
2282 '+auri' => { active => 't' }
2286 # we have a -full, get all the things
2287 } elsif ($flesh == 1) {
2288 %subselect = ( '-or' => [
2289 { owning_lib => \@ou_ids },
2293 call_number => { '=' => {'+acn'=>'id'} },
2295 circ_lib => \@ou_ids
2301 { owning_lib => \@uri_ou_ids },
2303 from => { auricnm => 'auri' },
2305 call_number => { '=' => {'+acn'=>'id'} },
2306 '+auri' => { active => 't' }
2315 my $cns = $_storage->request(
2316 "open-ils.cstore.direct.asset.call_number.search.atomic",
2323 acn => [qw/copies owning_lib uri_maps prefix suffix/],
2324 auricnm => [qw/uri/],
2325 acp => [qw/circ_lib location status stat_cat_entries notes parts/],
2326 asce => [qw/stat_cat/],
2328 ( $limit > -1 ? ( limit => $limit ) : () ),
2329 ( $offset ? ( offset => $offset ) : () ),
2330 order_by => { acn => { label_sortkey => {} } }
2334 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2338 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
2340 my $copy_counts = $count_req->gather(1);
2341 my $staff_copy_counts = $staff_count_req->gather(1);
2343 for my $c (@$copy_counts) {
2344 $$c{transcendant} ||= 0;
2345 my $out = "<count type='public'";
2346 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2347 $client->respond("$out/>\n")
2350 for my $c (@$staff_copy_counts) {
2351 $$c{transcendant} ||= 0;
2352 my $out = "<count type='staff'";
2353 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2354 $client->respond("$out/>\n")
2357 $client->respond("</counts><volumes>\n");
2359 for my $cn (@$cns) {
2360 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2362 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2363 # In the context of BibTemplate, copies aren't necessary because we pull those
2364 # in a separate call
2366 OpenILS::Application::SuperCat::unAPI::acn
2368 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2372 $client->respond("</volumes><subscriptions>\n");
2374 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2376 %subselect = ( '-or' => [
2377 { owning_lib => \@ou_ids },
2380 where => { holding_lib => \@ou_ids },
2386 my $ssubs = $_storage->request(
2387 "open-ils.cstore.direct.serial.subscription.search.atomic",
2388 { record_entry => $bib,
2393 ssub => [qw/distributions issuances scaps owning_lib/],
2394 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2395 sstr => [qw/items/],
2396 sitem => [qw/notes unit/],
2397 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2398 acn => [qw/owning_lib prefix suffix/],
2400 ( $limit > -1 ? ( limit => $limit ) : () ),
2401 ( $offset ? ( offset => $offset ) : () ),
2413 date_expected => {},
2420 for my $ssub (@$ssubs) {
2421 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2423 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2424 # In the context of BibTemplate, copies aren't necessary because we pull those
2425 # in a separate call
2427 OpenILS::Application::SuperCat::unAPI::ssub
2429 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2434 return "</subscriptions></holdings>\n";
2436 __PACKAGE__->register_method(
2437 method => 'new_record_holdings',
2438 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2443 { desc => <<" DESC",
2444 Returns the XML representation of the requested bibliographic record's holdings
2449 desc => 'An OpenILS biblio::record_entry ID',
2451 { name => 'orgUnit',
2452 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2455 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2457 { name => 'hideCopies',
2458 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2459 type => 'boolean' },
2461 desc => 'Arry of limit and offset for holdings paging',
2465 { desc => 'Stream of bib record holdings hierarchy in XML',
2475 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2477 my $recs = $_storage->request(
2478 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2479 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2482 return undef unless (@$recs);
2484 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2486 __PACKAGE__->register_method(
2487 method => 'isbn_holdings',
2488 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2492 { desc => <<" DESC",
2493 Returns the XML representation of the requested bibliographic record's holdings
2502 { desc => 'The bib record holdings hierarchy in XML',
2510 return '' unless $text;
2511 $text =~ s/&/&/gsom;
2512 $text =~ s/</</gsom;
2513 $text =~ s/>/>/gsom;
2514 $text =~ s/"/"/gsom;
2515 $text =~ s/'/'/gsom;
2519 sub recent_changes {
2522 my $when = shift || '1-01-01';
2525 my $type = 'biblio';
2528 if ($self->api_name =~ /authority/o) {
2529 $type = 'authority';
2533 my $axis = 'create_date';
2534 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2536 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2538 return $_storage->request(
2539 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2540 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2541 { order_by => { $hint => "$axis desc" }, limit => $limit }
2545 for my $t ( qw/biblio authority/ ) {
2546 for my $a ( qw/import edit/ ) {
2548 __PACKAGE__->register_method(
2549 method => 'recent_changes',
2550 api_name => "open-ils.supercat.$t.record.$a.recent",
2554 { desc => "Returns a list of recently ${a}ed $t records",
2558 desc => "Date to start looking for ${a}ed records",
2559 default => '1-01-01',
2563 desc => "Maximum count to retrieve",
2567 { desc => "An id list of $t records",
2575 sub retrieve_authority_marcxml {
2580 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2582 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2583 return $U->entityize( $record->marc ) if ($record);
2587 __PACKAGE__->register_method(
2588 method => 'retrieve_authority_marcxml',
2589 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2593 { desc => <<" DESC",
2594 Returns the MARCXML representation of the requested authority record
2598 { name => 'authorityId',
2599 desc => 'An OpenILS authority::record_entry id',
2603 { desc => 'The authority record in MARCXML',
2608 sub retrieve_record_marcxml {
2613 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2615 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2616 return $U->entityize( $record->marc ) if ($record);
2620 __PACKAGE__->register_method(
2621 method => 'retrieve_record_marcxml',
2622 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2626 { desc => <<" DESC",
2627 Returns the MARCXML representation of the requested bibliographic record
2632 desc => 'An OpenILS biblio::record_entry id',
2636 { desc => 'The bib record in MARCXML',
2641 sub retrieve_isbn_marcxml {
2646 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2648 my $recs = $_storage->request(
2649 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2650 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2653 return undef unless (@$recs);
2655 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2656 return $U->entityize( $record->marc ) if ($record);
2660 __PACKAGE__->register_method(
2661 method => 'retrieve_isbn_marcxml',
2662 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2666 { desc => <<" DESC",
2667 Returns the MARCXML representation of the requested ISBN
2672 desc => 'An ... um ... ISBN',
2676 { desc => 'The bib record in MARCXML',
2681 sub retrieve_record_transform {
2686 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2688 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2689 #$_storage->connect;
2691 my $record = $_storage->request(
2692 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2696 return undef unless ($record);
2698 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2701 sub retrieve_isbn_transform {
2706 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2708 my $recs = $_storage->request(
2709 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2710 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2713 return undef unless (@$recs);
2715 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2717 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2719 return undef unless ($record);
2721 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2724 sub retrieve_record_objects {
2729 my $type = 'biblio';
2731 if ($self->api_name =~ /authority/) {
2732 $type = 'authority';
2735 $ids = [$ids] unless (ref $ids);
2736 $ids = [grep {$_} @$ids];
2738 return [] unless (@$ids);
2740 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2741 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2743 __PACKAGE__->register_method(
2744 method => 'retrieve_record_objects',
2745 api_name => 'open-ils.supercat.record.object.retrieve',
2749 { desc => <<" DESC",
2750 Returns the Fieldmapper object representation of the requested bibliographic records
2755 desc => 'OpenILS biblio::record_entry ids',
2759 { desc => 'The bib records',
2764 __PACKAGE__->register_method(
2765 method => 'retrieve_record_objects',
2766 api_name => 'open-ils.supercat.authority.object.retrieve',
2770 { desc => <<" DESC",
2771 Returns the Fieldmapper object representation of the requested authority records
2775 { name => 'authIds',
2776 desc => 'OpenILS authority::record_entry ids',
2780 { desc => 'The authority records',
2785 sub retrieve_isbn_object {
2790 return undef unless ($isbn);
2792 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2793 my $recs = $_storage->request(
2794 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2795 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2798 return undef unless (@$recs);
2800 return $_storage->request(
2801 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2802 { id => $recs->[0]->record }
2805 __PACKAGE__->register_method(
2806 method => 'retrieve_isbn_object',
2807 api_name => 'open-ils.supercat.isbn.object.retrieve',
2811 { desc => <<" DESC",
2812 Returns the Fieldmapper object representation of the requested bibliographic record
2821 { desc => 'The bib record',
2828 sub retrieve_metarecord_mods {
2833 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2835 # Get the metarecord in question
2838 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2841 # Now get the map of all bib records for the metarecord
2844 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2845 {metarecord => $rid}
2848 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2850 # and retrieve the lead (master) record as MODS
2852 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2853 ->run($mr->master_record);
2854 my $master_mods = $_parser->parse_string($master)->documentElement;
2855 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2856 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2858 # ... and a MODS clone to populate, with guts removed.
2859 my $mods = $_parser->parse_string($master)->documentElement;
2860 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2861 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2862 ($mods) = $mods->findnodes('//mods:mods');
2863 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2864 $mods->removeChildNodes;
2865 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2867 # Add the metarecord ID as a (locally defined) info URI
2868 my $recordInfo = $mods
2870 ->createElement("recordInfo");
2872 my $recordIdentifier = $mods
2874 ->createElement("recordIdentifier");
2876 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2881 $recordIdentifier->appendTextNode(
2882 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2885 $recordInfo->appendChild($recordIdentifier);
2886 $mods->appendChild($recordInfo);
2888 # Grab the title, author and ISBN for the master record and populate the metarecord
2889 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2892 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2893 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2894 $title = $mods->ownerDocument->importNode($title);
2895 $mods->appendChild($title);
2898 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2900 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2901 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2902 $author = $mods->ownerDocument->importNode($author);
2903 $mods->appendChild($author);
2906 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2908 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2909 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2910 $isbn = $mods->ownerDocument->importNode($isbn);
2911 $mods->appendChild($isbn);
2914 # ... and loop over the constituent records
2915 for my $map ( @$recs ) {
2919 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2920 ->run($map->source);
2922 my $part_mods = $_parser->parse_string($rec);
2923 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2924 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2925 ($part_mods) = $part_mods->findnodes('//mods:mods');
2927 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2928 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2929 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2930 $node = $mods->ownerDocument->importNode($node);
2931 $mods->appendChild( $node );
2934 my $relatedItem = $mods
2936 ->createElement("relatedItem");
2938 $relatedItem->setAttribute( type => 'constituent' );
2940 my $identifier = $mods
2942 ->createElement("identifier");
2944 $identifier->setAttribute( type => 'uri' );
2946 my $subRecordInfo = $mods
2948 ->createElement("recordInfo");
2950 my $subRecordIdentifier = $mods
2952 ->createElement("recordIdentifier");
2954 my $subid = $map->source;
2955 $subRecordIdentifier->appendTextNode(
2956 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2961 $subRecordInfo->appendChild($subRecordIdentifier);
2963 $relatedItem->appendChild( $subRecordInfo );
2965 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2966 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2967 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2968 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2969 $relatedItem->appendChild($tor) if ($tor);
2971 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2972 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2973 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2974 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2975 $relatedItem->appendChild( $part_isbn );
2978 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2982 $mods->appendChild( $relatedItem );
2986 $_storage->disconnect;
2988 return $U->entityize($mods->toString);
2991 __PACKAGE__->register_method(
2992 method => 'retrieve_metarecord_mods',
2993 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
2997 { desc => <<" DESC",
2998 Returns the MODS representation of the requested metarecord
3002 { name => 'metarecordId',
3003 desc => 'An OpenILS metabib::metarecord id',
3007 { desc => 'The metarecord in MODS',
3012 sub list_metarecord_formats {
3015 { namespace_uri => 'http://www.loc.gov/mods/',
3016 docs => 'http://www.loc.gov/mods/',
3017 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
3022 for my $type ( keys %metarecord_xslt ) {
3025 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
3026 docs => $metarecord_xslt{$type}{docs},
3027 schema_location => $metarecord_xslt{$type}{schema_location},
3034 __PACKAGE__->register_method(
3035 method => 'list_metarecord_formats',
3036 api_name => 'open-ils.supercat.metarecord.formats',
3040 { desc => <<" DESC",
3041 Returns the list of valid metarecord formats that supercat understands.
3044 { desc => 'The format list',
3050 sub list_authority_formats {
3053 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3054 docs => 'http://www.loc.gov/marcxml/',
3055 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3057 marc21 => { docs => 'http://www.loc.gov/marc/' }
3061 # for my $type ( keys %record_xslt ) {
3064 # { namespace_uri => $record_xslt{$type}{namespace_uri},
3065 # docs => $record_xslt{$type}{docs},
3066 # schema_location => $record_xslt{$type}{schema_location},
3073 __PACKAGE__->register_method(
3074 method => 'list_authority_formats',
3075 api_name => 'open-ils.supercat.authority.formats',
3079 { desc => <<" DESC",
3080 Returns the list of valid authority formats that supercat understands.
3083 { desc => 'The format list',
3088 sub list_record_formats {
3091 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3092 docs => 'http://www.loc.gov/marcxml/',
3093 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3096 { marc21 => { docs => 'http://www.loc.gov/marc/' } }
3099 for my $type ( keys %record_xslt ) {
3102 { namespace_uri => $record_xslt{$type}{namespace_uri},
3103 docs => $record_xslt{$type}{docs},
3104 schema_location => $record_xslt{$type}{schema_location},
3111 __PACKAGE__->register_method(
3112 method => 'list_record_formats',
3113 api_name => 'open-ils.supercat.record.formats',
3117 { desc => <<" DESC",
3118 Returns the list of valid record formats that supercat understands.
3121 { desc => 'The format list',
3125 __PACKAGE__->register_method(
3126 method => 'list_record_formats',
3127 api_name => 'open-ils.supercat.isbn.formats',
3131 { desc => <<" DESC",
3132 Returns the list of valid record formats that supercat understands.
3135 { desc => 'The format list',
3148 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
3149 unless (length($isbn) >= 10);
3151 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3153 # Create a storage session, since we'll be making muliple requests.
3156 # Find the record that has that ISBN.
3157 my $bibrec = $_storage->request(
3158 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
3159 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
3162 # Go away if we don't have one.
3163 return {} unless (@$bibrec);
3165 # Find the metarecord for that bib record.
3166 my $mr = $_storage->request(
3167 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3168 {source => $bibrec->[0]->record}
3171 # Find the other records for that metarecord.
3172 my $records = $_storage->request(
3173 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3174 {metarecord => $mr->[0]->metarecord}
3177 # Just to be safe. There's currently no unique constraint on sources...
3178 my %unique_recs = map { ($_->source, 1) } @$records;
3179 my @rec_list = sort keys %unique_recs;
3181 # And now fetch the ISBNs for thos records.
3185 'open-ils.cstore.direct.metabib.full_rec.search',
3186 { tag => '020', subfield => 'a', record => $_ }
3187 )->gather(1) for (@rec_list);
3189 # We're done with the storage server session.
3190 $_storage->disconnect;
3192 # Return the oISBN data structure. This will be XMLized at a higher layer.
3194 { metarecord => $mr->[0]->metarecord,
3195 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
3198 __PACKAGE__->register_method(
3200 api_name => 'open-ils.supercat.oisbn',
3204 { desc => <<" DESC",
3205 Returns the ISBN list for the metarecord of the requested isbn
3210 desc => 'An ISBN. Duh.',
3214 { desc => 'record to isbn map',
3219 sub return_bib_search_aliases {
3222 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3224 my $cmsa = $_storage->request(
3225 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
3226 { alias => { '!=' => undef } }
3230 if ($_->alias =~ /\./) {
3231 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
3232 $aliases{$qualifier}{$name}{'index'} = $_->alias;
3233 # We will add a 'title' property in a subsequent schema
3234 $aliases{$qualifier}{$name}{'title'} = $name;
3236 # au/kw/se/su/ti go into the default 'eg' qualifier
3237 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
3238 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
3245 __PACKAGE__->register_method(
3246 method => 'return_bib_search_aliases',
3247 api_name => 'open-ils.supercat.biblio.search_aliases',
3251 { desc => <<" DESC",
3252 Returns the set of qualified search aliases in the system
3256 { desc => 'Hash of qualified search aliases',
3262 package OpenILS::Application::SuperCat::unAPI;
3263 use base qw/OpenILS::Application::SuperCat/;
3266 die "dummy superclass, use a real class";
3272 return unless ($obj);
3274 $class = ref($class) || $class;
3276 if ($class eq __PACKAGE__) {
3277 return unless (ref($obj));
3278 $class .= '::' . $obj->json_hint;
3281 return bless { obj => $obj } => $class;
3286 return $self->{obj};
3289 package OpenILS::Application::SuperCat::unAPI::auri;
3290 use base qw/OpenILS::Application::SuperCat::unAPI/;
3296 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
3297 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
3298 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
3299 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3300 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
3302 if (!$args->{no_volumes}) {
3303 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
3304 $xml .= " <volumes>\n" . join(
3307 OpenILS::Application::SuperCat::unAPI
3308 ->new( $_->call_number )
3309 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
3310 } @{ $self->obj->call_number_maps }
3311 ) . " </volumes>\n";
3314 $xml .= " <volumes/>\n";
3318 $xml .= " </uri>\n";
3323 package OpenILS::Application::SuperCat::unAPI::acn;
3324 use base qw/OpenILS::Application::SuperCat::unAPI/;
3330 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
3332 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
3333 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
3334 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
3335 $xml .= 'deleted="' . $self->obj->deleted . '" ';
3336 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
3339 if (!$args->{no_copies}) {
3340 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
3341 $xml .= " <copies>\n" . join(
3344 OpenILS::Application::SuperCat::unAPI
3346 ->as_xml({ %$args, no_volume=>1 })
3347 } @{ $self->obj->copies }
3351 $xml .= " <copies/>\n";
3355 if (!$args->{no_uris}) {
3356 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3357 $xml .= " <uris>\n" . join(
3360 OpenILS::Application::SuperCat::unAPI
3362 ->as_xml({ %$args, no_volumes=>1 })
3363 } @{ $self->obj->uri_maps }
3367 $xml .= " <uris/>\n";
3372 $xml .= ' <prefix ';
3373 $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3374 $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3375 $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3376 $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3379 $xml .= ' <suffix ';
3380 $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3381 $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3382 $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3383 $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3386 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3387 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3388 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3389 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3392 unless ($args->{no_record}) {
3393 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3395 my $r_doc = $parser->parse_string($self->obj->record->marc);
3396 $r_doc->documentElement->setAttribute( id => $rec_tag );
3397 $xml .= $U->entityize($r_doc->documentElement->toString);
3400 $xml .= " </volume>\n";
3405 package OpenILS::Application::SuperCat::unAPI::ssub;
3406 use base qw/OpenILS::Application::SuperCat::unAPI/;
3412 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3414 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3415 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3416 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3417 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3420 if (!$args->{no_distributions}) {
3421 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3422 $xml .= " <distributions>\n" . join(
3425 OpenILS::Application::SuperCat::unAPI
3427 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3428 } @{ $self->obj->distributions }
3429 ) . " </distributions>\n";
3432 $xml .= " <distributions/>\n";
3436 if (!$args->{no_captions_and_patterns}) {
3437 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3438 $xml .= " <captions_and_patterns>\n" . join(
3441 OpenILS::Application::SuperCat::unAPI
3443 ->as_xml({ %$args, no_subscription=>1 })
3444 } @{ $self->obj->scaps }
3445 ) . " </captions_and_patterns>\n";
3448 $xml .= " <captions_and_patterns/>\n";
3452 if (!$args->{no_issuances}) {
3453 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3454 $xml .= " <issuances>\n" . join(
3457 OpenILS::Application::SuperCat::unAPI
3459 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3460 } @{ $self->obj->issuances }
3461 ) . " </issuances>\n";
3464 $xml .= " <issuances/>\n";
3469 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3470 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3471 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3472 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3475 unless ($args->{no_record}) {
3476 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3478 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3479 $r_doc->documentElement->setAttribute( id => $rec_tag );
3480 $xml .= $U->entityize($r_doc->documentElement->toString);
3483 $xml .= " </subscription>\n";
3488 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3489 use base qw/OpenILS::Application::SuperCat::unAPI/;
3495 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3497 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3499 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3500 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3501 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3502 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3505 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3507 $xml .= " </serial_summary>\n";
3513 package OpenILS::Application::SuperCat::unAPI::sssum;
3514 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3516 package OpenILS::Application::SuperCat::unAPI::sbsum;
3517 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3519 package OpenILS::Application::SuperCat::unAPI::sisum;
3520 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3522 package OpenILS::Application::SuperCat::unAPI::sdist;
3523 use base qw/OpenILS::Application::SuperCat::unAPI/;
3529 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3531 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3532 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3533 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3534 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3537 if (!$args->{no_distributions}) {
3538 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3539 $xml .= " <streams>\n" . join(
3542 OpenILS::Application::SuperCat::unAPI
3544 ->as_xml({ %$args, no_distribution=>1 })
3545 } @{ $self->obj->streams }
3546 ) . " </streams>\n";
3549 $xml .= " <streams/>\n";
3553 if (!$args->{no_summaries}) {
3554 $xml .= " <summaries>\n";
3558 OpenILS::Application::SuperCat::unAPI
3560 ->as_xml({ %$args, no_distribution=>1 }) : ""
3561 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3564 $xml .= " </summaries>\n";
3568 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3569 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3570 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3571 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3574 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3576 if (!$args->{no_record} && $self->obj->record_entry) {
3577 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3579 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3580 $r_doc->documentElement->setAttribute( id => $rec_tag );
3581 $xml .= $U->entityize($r_doc->documentElement->toString);
3584 $xml .= " </distribution>\n";
3589 package OpenILS::Application::SuperCat::unAPI::sstr;
3590 use base qw/OpenILS::Application::SuperCat::unAPI/;
3596 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3598 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3599 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3602 if (!$args->{no_items}) {
3603 if (ref($self->obj->items) && @{ $self->obj->items }) {
3604 $xml .= " <items>\n" . join(
3607 OpenILS::Application::SuperCat::unAPI
3609 ->as_xml({ %$args, no_stream=>1 })
3610 } @{ $self->obj->items }
3614 $xml .= " <items/>\n";
3618 #XXX routing_list_user's?
3620 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3622 $xml .= " </stream>\n";
3627 package OpenILS::Application::SuperCat::unAPI::sitem;
3628 use base qw/OpenILS::Application::SuperCat::unAPI/;
3634 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3636 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3637 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3638 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3640 if ($args->{no_issuance}) {
3641 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3642 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3647 if (ref($self->obj->notes) && $self->obj->notes) {
3648 $xml .= " <notes>\n";
3649 for my $note ( @{$self->obj->notes} ) {
3650 next unless ( $note->pub eq 't' );
3651 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3654 $xml .= " </notes>\n";
3656 $xml .= " <notes/>\n";
3659 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3660 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3661 $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});
3662 $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});
3664 $xml .= " </serial_item>\n";
3669 package OpenILS::Application::SuperCat::unAPI::sunit;
3670 use base qw/OpenILS::Application::SuperCat::unAPI/;
3676 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3677 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3679 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3680 create_date edit_date copy_number circulate deposit ref holdable deleted
3681 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3682 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3687 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3688 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3689 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3691 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3692 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3693 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3694 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3697 $xml .= " <copy_notes>\n";
3698 if (ref($self->obj->notes) && $self->obj->notes) {
3699 for my $note ( @{$self->obj->notes} ) {
3700 next unless ( $note->pub eq 't' );
3701 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3706 $xml .= " </copy_notes>\n";
3707 $xml .= " <statcats>\n";
3709 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3710 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3711 next unless ( $sce->stat_cat->opac_visible eq 't' );
3712 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3716 $xml .= " </statcats>\n";
3718 unless ($args->{no_volume}) {
3719 if (ref($self->obj->call_number)) {
3720 $xml .= OpenILS::Application::SuperCat::unAPI
3721 ->new( $self->obj->call_number )
3722 ->as_xml({ %$args, no_copies=>1 });
3724 $xml .= " <volume/>\n";
3728 $xml .= " </serial_unit>\n";
3733 package OpenILS::Application::SuperCat::unAPI::scap;
3734 use base qw/OpenILS::Application::SuperCat::unAPI/;
3740 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3741 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3743 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3744 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3745 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3748 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3749 $xml .= " </caption_and_pattern>\n";
3754 package OpenILS::Application::SuperCat::unAPI::siss;
3755 use base qw/OpenILS::Application::SuperCat::unAPI/;
3761 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3762 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3764 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3765 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3769 if (!$args->{no_items}) {
3770 if (ref($self->obj->items) && @{ $self->obj->items }) {
3771 $xml .= " <items>\n" . join(
3774 OpenILS::Application::SuperCat::unAPI
3776 ->as_xml({ %$args, no_stream=>1 })
3777 } @{ $self->obj->items }
3781 $xml .= " <items/>\n";
3785 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3786 $xml .= " </issuance>\n";
3791 package OpenILS::Application::SuperCat::unAPI::acp;
3792 use base qw/OpenILS::Application::SuperCat::unAPI/;
3794 use OpenILS::Application::AppUtils;
3795 my $U = "OpenILS::Application::AppUtils";
3801 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3802 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3804 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3805 create_date edit_date copy_number circulate deposit ref holdable deleted
3806 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3811 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3812 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3813 $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";
3815 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3816 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3817 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3818 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3821 $xml .= " <monograph_parts>\n";
3822 if (ref($self->obj->parts) && $self->obj->parts) {
3823 for my $part ( @{$self->obj->parts} ) {
3824 next if $U->is_true($part->deleted);
3825 $xml .= sprintf(' <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3830 $xml .= " </monograph_parts>\n";
3831 $xml .= " <copy_notes>\n";
3832 if (ref($self->obj->notes) && $self->obj->notes) {
3833 for my $note ( @{$self->obj->notes} ) {
3834 next unless ( $note->pub eq 't' );
3835 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3840 $xml .= " </copy_notes>\n";
3841 $xml .= " <statcats>\n";
3843 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3844 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3845 next unless ( $sce->stat_cat->opac_visible eq 't' );
3846 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3850 $xml .= " </statcats>\n";
3852 unless ($args->{no_volume}) {
3853 if (ref($self->obj->call_number)) {
3854 $xml .= OpenILS::Application::SuperCat::unAPI
3855 ->new( $self->obj->call_number )
3856 ->as_xml({ %$args, no_copies=>1 });
3858 $xml .= " <volume/>\n";
3862 $xml .= " </copy>\n";